Compare commits

...

3 Commits

  1. 18
      README.md
  2. 115
      docs/tc.preset.maxref.xml
  3. 126
      tc.preset.js
  4. 2746
      tc.preset.maxhelp
  5. 83
      tc.preset_demo.maxpat

18
README.md

@ -3,9 +3,10 @@
A [jsui] replacement for the [preset] object in Cycling'74 Max.
## Features
- Same click + modifier key behavior as the vanilla object to store and delete presets
- Drag and drop presets to re-organize
- Display presets as a grid or a list
- Scrollable (requires Max 8.6.2)
- Scrollable list layout (requires Max 8.6.2)
- Shows active preset even if recalled directly from pattrstorage
- Shows previously active preset, with the ability to ignore preset 0 if it being used as an intermediary step
- Shows presets being interpolated (using recall or recallmulti)
@ -14,24 +15,27 @@ A [jsui] replacement for the [preset] object in Cycling'74 Max.
- Ability to rewrite json file automatically every time a preset is stored/moved/deleted/renamed/(un)locked
- Helps keeping in sync a umenu with the list of stored slotstlet
- More look customization
- Same click + modifier key behavior as the vanilla object to store or delete presets
- Dynamically adapts to resize both in Edit and Presentation mode
- Select mode: simple click selects the slot, double click recalls it (allows for organizing presets without recalling them)
- Color mode: ability to color sstored presets with 6 customizable colors (currently colors are only assigned automatically depending on slot number)
## How to use
- Place `tc.preset.js` in the same directory as your patch, or somewhere in the Max search path
- Create a [`jsui @filename tc.preset.js`]. You can either add `@jsarguments` followed by the name of the pattrstorage you want to communicate with, or set that later by sending a `pattrstorage` (followed by the pattrstorage name) message to the [jsui].
- Create a [`jsui @filename tc.preset.js`].
- Connect the [pattrstorage] outlet to the [jsui] inlet
- Set the jsui attribute named "pattrstorage" to the name of the pattrstorage you just connected (or send a message like "pattrstorage my_pattrstorage_name")
## Limitations
- Resize doesn't work in Presentation mode (jsui limitation)
- Due to the way [pattrstorage] works, some pattrstorage-specific messages should be sent to the [jsui] instead of the [pattrstorage]. Some to both:
- `recall`, `delete`: send to [pattrstorage] only
- `recall`: send to [pattrstorage] only
- `recallmulti`, `slotname`: send to [pattrstorage] first (for better timing), then to the [jsui]
- `store`: send to [jsui] only
- Some messages to pattrstorage causes the jsui to be out of sync (`clear`, `insert`, `lockall`, `read`, `readagain`, `remove`, `renumber`). If you use any of these messages, make sure to then send a `resync` to the jsui.
- The js program send a lot of message to the [pattrstorage] (using `maxobj.message()`syntax, so without patch cord), which in return send (using a patch cord) a lot of messages required for the [jsui] to stay in sync. Using one of the above messages incorrectly, or sending `getslotlist`, `getslotnamelist`, or any message that will impact the presets might cause the [pattrstorage] to get out of sync. In case something like that happens, you can send the `resync` message to the [jsui].
## Desired features (for someday)
- Select mode: simple click selects the slot, double click recalls it
## Desired features (for someday, if ever)
- No need for a patch cord (programmatically create a [send]/[receive] pair?)
- Ability to lock/unlock and rename directly in the jsui without the need of external objects
- Ability to target a [pattrstorage] in a different patcher level

115
docs/tc.preset.maxref.xml

@ -0,0 +1,115 @@
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<c74object name='tc.preset' category='jsui, preset, pattrstorage'>
<digest>
A jsui replacement for the preset object.
</digest>
<description>
tc.preset is a jsui clone of the preset object, but loaded with more features, such as preset organization through drag and drop, display as a scrollable list, auto-rewrite the saved JSON/XML file after any change.
Contrary to the preset object, tc.preset has to be used in conjonction with a pattrstorage object.<modification class=""></modification>
</description>
<!--METADATA-->
<metadatalist>
<metadata name='author'>Théophile Clet</metadata>
<metadata name='tag'>preset</metadata>
<metadata name='tag'>pattrstorage</metadata>
<metadata name='tag'>jsui</metadata>
</metadatalist>
<!--ARGUMENTS-->
<!-- <objarglist>
<objarg name='pattrstorage' optional='0' type='symbol'>
<digest>Name of the pattrstorage to control</digest>
<description>Name of the pattrstorage this object should be linked to.</description>
</objarg>
</objarglist> -->
<!--MESSAGES-->
<methodlist>
<method name="int">
<digest>Recall numbered preset</digest>
<description>Sending any integer will trigger the preset of the same number</description>
</method>
<method name="float">
<digest>Interpolate between presets</digest>
<description>Recalls the data from the preset specified by float. If the number falls between two whole numbers (e.g. 1.5), the pattrstorage object will interpolate between the data stored in the preset corresponding to the integer portion of the float and the data stored at the preset numbered one higher (e.g. 1.5 will cause pattrstorage to interpolate 50% between presets 1 and 2). See the interp message for more information about interpolation modes.</description>
</method>
<method name="bang">
<digest>Recall last recalled preset</digest>
<description>When a bang is received, the last triggered preset is triggered again.</description>
</method>
<!-- <method name="list">
<digest>Function depends on inlet</digest>
<description>Function depends on inlet</description>
</method> -->
<method name="pattrstorage">
<digest>Link to named pattrstorage object</digest>
<description>The word 'pattrstorage' followed by the name of an existing pattrstorage links the jsui to that pattrstorage.
Make sure that pattrstorage outlet is connected to the jsui left inlet prior to send the message.
If the word `pattrstorage`is sent alone, the jsui is unlinked from any pattrstorage.
</description>
</method>
<method name="resync">
<digest>Resync the jsui to the pattrstorage</digest>
<description>The word 'resync' will repopulate the jsui with the current preset list from the pattrstorage. It is usefull in case you add/remove/edit presets without using the jsui.
</description>
</method>
<method name="setlock">
<digest>Lock or unlock selected preset</digest>
<description>The word 'setlock' followed by a 0 or a 1 respectively unlocks or locks the currently selected preset in the jsui.
Note that if select_mode is set to 1, the selected preset is not necessarily the last recalled one.
</description>
</method>
<method name="setslotname">
<digest>Set the name of the selected preset</digest>
<description>The word 'setslotname' followed by a symbol sets that symbol as the name of the currently selected preset in the jsui.
Note that if select_mode is set to 1, the selected preset is not necessarily the last recalled one.
</description>
</method>
<method name="text">
<digest>Set the name of the selected preset</digest>
<description>Same as setslotname. Allows to connect the leftmost outlet of a textedit to the jsui and use it as an interface to rename the selected presets.
</description>
</method>
</methodlist>
<!--ATTRIBUTES-->
<attributelist>
<attribute name='active_slot_color' get='1' set='1' type='list' size='4' >
<digest>Active slot color</digest>
<description>Color of the last recalled preset</description>
</attribute>
<attribute name='autowriteagain' get='1' set='1' type='int' size='1' >
<digest>Automatic writeagain</digest>
<description>When set to 1, the jsui will automatically send a "writeagain" message to its linked pattrstorage anytime a preset have been stored, moved, renamed, (un)locked or deleted, saving any change into the preset file immediately.</description>
</attribute>
<attribute name='bubblesize' get='1' set='1' type='float' size='1' >
<digest>Slot size</digest>
<description>Size of the preset slots</description>
</attribute>
</attributelist>
<!--RELATED-->
<seealsolist>
<seealso name='pattrstorage' />
<seealso name='preset' />
<seealso name='jsui' />
</seealsolist>
</c74object>

126
tc.preset.js

@ -106,18 +106,26 @@ var is_interpolating = 0;
var is_dragging = 0; // Drag flag
var drag_slot = -1; // Stores the slot that's being dragged
if (jsarguments.length>1) {
var has_loaded = false;
// Allows for dynamic resizing even in presentation mode (addressing the limitation of onresize())
var pres_rect = new MaxobjListener(this.box,"presentation_rect",get_prect);
function get_prect(prect) {
onresize(prect.value[2], prect.value[3])
}
if (jsarguments.length>1) { // Depreciated, use "pattrstorage" attribute instead of jsarguments.
pattrstorage_name = jsarguments[1];
}
function loadbang() {
has_loaded = true;
post("loadbang\n");
outlet(2, "set");
find_pattrstorage(pattrstorage_name);
calc_rows_columns();
}
// loadbang();
function calc_rows_columns() {
half_margin = margin / 2;
half_spacing = spacing / 2;
@ -246,6 +254,7 @@ function draw_text_bubble(x, y, w, h, text, cont) {
cont.move_to(txt_pos_x, txt_pos_y);
cont.show_text(text.toString());
}
draw_text_bubble.local = 1;
function format_slot_name(id) {
var text = id;
@ -264,7 +273,7 @@ format_slot_name.local = 1;
function paint_base() {
// We draw all slots (empty and stored ones) so we don't have to for every redraw
// post("paint_base\n");
is_painting_base = 1;
// Background
@ -305,6 +314,12 @@ paint_base.local = 1;
function paint()
{
// post("redraw\n");
// Handling Presentation mode enable/disable
var cur_size = mgraphics.size;
if (cur_size[0] != ui_width || cur_size[1] != ui_height) {
onresize(cur_size[0], cur_size[1]);
} else {
with (mgraphics) {
select_font_face(font_name);
set_font_size(font_size);
@ -349,7 +364,7 @@ function paint()
}
// Selected slot
if (select_mode && selected_slot > 0 && selected_slot <= slots_count_display) {
if (selected_slot > 0 && selected_slot <= slots_count_display) {
set_source_rgba(active_slot_color);
set_line_width(1);
draw_slot_bubble(slots[selected_slot][0] - 0.5, slots[selected_slot][1] - 0.5, slot_size + 1, slot_size + 1);
@ -466,6 +481,7 @@ function paint()
}
}
}
}
paint.local = 1;
@ -529,6 +545,8 @@ function anything() {
slots[v][6] = -1;
if (active_slot == v) {
active_slot = 0;
} else if (previous_active_slot == v) {
previous_active_slot = 0;
}
// to_pattrstorage("getslotname", v);
@ -538,6 +556,11 @@ function anything() {
set_active_slot(active_slot);
if (!is_dragging) {
outlet(0, "delete", v);
if (selected_slot == v) {
selected_slot == 0
outlet(2, 'set');
outlet(3, 'set', 0);
}
}
trigger_writeagain();
}
@ -545,8 +568,7 @@ function anything() {
}
}
function bang()
{
function bang() {
to_pattrstorage("recall", active_slot);
}
@ -554,14 +576,17 @@ function msg_int(v) {
to_pattrstorage("recall", v);
}
function msg_float(v)
{
function msg_float(v) {
var s = Math.floor(v);
var i = v % 1;
to_pattrstorage("recall", s, s+1, i);
}
function pattrstorage(v){
function init() {
loadbang();
}
function pattrstorage(v) {
find_pattrstorage(v);
paint_base();
}
@ -756,6 +781,9 @@ function lockedslots() {
if (locked_slots.length) {
for (var i = 0; i < locked_slots.length; i++) {
slots[locked_slots[i]][5] = 1;
if (locked_slots[i] == selected_slot) {
select(selected_slot);
}
}
}
}
@ -780,6 +808,7 @@ function read() {
}
function resync() {
set_active_slot(0);
calc_rows_columns();
}
@ -794,6 +823,9 @@ function find_pattrstorage(name) {
to_pattrstorage("getlockedslots");
} else {
pattrstorage_name = null;
active_slot = 0;
previous_active_slot = 0;
selected_slot = 0;
slots_clear();
// error("Pattrstorage", name, "doesn't exist.\n");
}
@ -882,15 +914,22 @@ trigger_writeagain.local = 1;
// MOUSE EVENTS
function onidle(x,y,but,cmd,shift,capslock,option,ctrl)
{
if (last_x != x || last_y != y - y_offset|| shift_hold != shift || option_hold != option) {
var redraw_flag = false;
if (last_x != x || last_y != y - y_offset) {
last_x = x;
last_y = y - y_offset;
shift_hold = shift;
option_hold = option;
var cur = get_slot_index(x, y - y_offset);
if (cur != last_hovered) {
last_hovered = cur;
redraw_flag = true;
}
}
if (shift_hold != shift || option_hold != option) {
shift_hold = shift;
option_hold = option;
redraw_flag = true;
}
if (redraw_flag) {
mgraphics.redraw();
}
}
@ -939,28 +978,6 @@ function ondblclick(x,y,but,cmd,shift,capslock,option,ctrl)
{
if (last_hovered > -1 && pattrstorage_name != null && filled_slots.indexOf(last_hovered) > -1) {
to_pattrstorage("recall", last_hovered);
// var output = "recall";
// if (select_mode) {
// output = "select";
// }
// if (shift) {
// output = "store";
// if (option) {
// output = "delete";
// }
// } else if (slots[last_hovered][4] == null) {
// return;
// }
// if (output == "store") {
// store(last_hovered);
// } else {
// if (output == "select") {
// select(last_hovered);
// // mgraphics.redraw();
// } else {
// to_pattrstorage(output, last_hovered);
// }
// }
}
last_x = x;
@ -1075,14 +1092,32 @@ function onresize(w,h)
}
onresize.local = 1;
// function ondblclick(x,y,but,cmd,shift,capslock,option,ctrl)
// {
// last_x = x;
// last_y = y;
// }
// ondblclick.local = 1;
// ATTRIBUTES DECLARATION
declareattribute("pattrstorage", "getpattrstorage", "setpattrstorage", 1);
function getpattrstorage() {
if (pattrstorage_name == null) {
return
} else {
return pattrstorage_name;
}
}
function setpattrstorage(v){
// This method is called for the first time when the patch is loading, before the loadbang (not all objects are instanciated yet)
// With v being the value stored whithin the patcher
if (v == null) {
pattrstorage_name = null;
pattrstorage_obj = null;
} else {
pattrstorage_name = arrayfromargs(arguments)[0];
}
// If the loadbang already occured once, we need to retrigger here
if (has_loaded) {
find_pattrstorage(pattrstorage_name);
loadbang();
}
// Otherwise, we just wait for the patch to call loadbang automatically at the end of its startup routine.
}
declareattribute("bubblesize", "getslotsize", "setslotsize", 1);
function getslotsize() {
return slot_size;
@ -1467,3 +1502,10 @@ function setcolor6(){
error('color_6: wrong number of arguments\n');
}
}
// UTILITY
function post_keys(obj) {
post('Keys of obj: ', obj, '\n');
post(Object.keys(obj));
post('\n');
}

2746
tc.preset.maxhelp

File diff suppressed because it is too large Load Diff

83
tc.preset_demo.maxpat

@ -4,13 +4,13 @@
"appversion" : {
"major" : 8,
"minor" : 6,
"revision" : 2,
"revision" : 3,
"architecture" : "x64",
"modernui" : 1
}
,
"classnamespace" : "box",
"rect" : [ 34.0, 100.0, 854.0, 848.0 ],
"rect" : [ 310.0, 100.0, 854.0, 848.0 ],
"bglocked" : 0,
"openinpresentation" : 0,
"default_fontsize" : 12.0,
@ -39,6 +39,42 @@
"subpatcher_template" : "",
"assistshowspatchername" : 0,
"boxes" : [ {
"box" : {
"id" : "obj-31",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 245.5, 565.0, 89.0, 22.0 ],
"text" : "storagewindow"
}
}
, {
"box" : {
"id" : "obj-25",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 422.0, 598.0, 45.0, 22.0 ],
"text" : "store 3"
}
}
, {
"box" : {
"id" : "obj-14",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 360.5, 586.0, 29.5, 22.0 ],
"text" : "init"
}
}
, {
"box" : {
"id" : "obj-128",
"items" : [ "Grid", ",", "List" ],
@ -69,7 +105,7 @@
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 959.0, 908.0, 131.0, 33.0 ],
"patching_rect" : [ 959.0, 908.0, 133.0, 33.0 ],
"text" : "Set a given color (from 1 to 6)"
}
@ -82,7 +118,6 @@
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 830.0, 927.0, 123.0, 22.0 ],
"presentation_linecount" : 2,
"text" : "color_wheel 3 0 1 1 1"
}
@ -129,7 +164,7 @@
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 928.0, 780.0, 150.0, 87.0 ],
"patching_rect" : [ 928.0, 780.0, 151.0, 87.0 ],
"text" : "When color mode is set to cycle, stored presets are shown in colors from color_1 to color_6, cyclically depending on their slot number."
}
@ -303,7 +338,7 @@
"appversion" : {
"major" : 8,
"minor" : 6,
"revision" : 2,
"revision" : 3,
"architecture" : "x64",
"modernui" : 1
}
@ -967,7 +1002,7 @@
, {
"box" : {
"id" : "obj-8",
"items" : [ "0 (tmp)", ",", "1 <(unnamed)>", ",", "2 <(unnamed)>", ",", "3 <(unnamed)>", ",", "4 <(unnamed)>", ",", "5 <(unnamed)>", ",", "6 <(unnamed)>", ",", "7 onze", ",", "8 up", ",", "9 <(unnamed)>", ",", "10 oui", ",", "11 <(unnamed)>", ",", "12 <(unnamed)>", ",", "13 <(unnamed)>", ",", "14 <(unnamed)>", ",", "15 <(unnamed)>" ],
"items" : [ "6 <(unnamed)>", ",", "19 <(unnamed)>", ",", "39 <(unnamed)>" ],
"maxclass" : "umenu",
"numinlets" : 1,
"numoutlets" : 3,
@ -987,7 +1022,7 @@
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 72.0, 139.0, 507.0, 87.0 ],
"text" : "Pattrstorage messages that doesn't trigger output and need to be sent to the jsui instead:\n- store (send to jsui only)\n- recallmulti (send to pattrstorage first for timing accuracy, then to jsui)\n- slotname (send \"setslotname\" or \"text\" to jsui instead, will rename active slot)\n- lock (or setlock to lock/unlock of the active slot)\n- some other. I've not tested them all. "
"text" : "Pattrstorage messages that doesn't trigger output and need to be sent to the jsui instead:\n- store (send to jsui only)\n- recallmulti (send to pattrstorage first for timing accuracy, then to jsui)\n- slotname (send \"setslotname\" or \"text\" to jsui instead, will rename selected slot)\n- lock (or setlock to lock/unlock of the active slot)\n- some other. I've not tested them all. "
}
}
@ -1056,18 +1091,17 @@
, {
"box" : {
"border" : 0,
"embedstate" : [ [ "autowriteagain", 0 ], [ "active_slot_color", 0.808, 0.898, 0.91, 1 ], [ "color_4", 0.367, 0.542, 0.712, 1 ], [ "slot_round", 0 ], [ "stored_slot_color", 0.502, 0.502, 0.502, 1 ], [ "layout", 0 ], [ "select_mode", 0 ], [ "color_5", 0.283, 0.606, 0.559, 1 ], [ "text_color", 0.129, 0.129, 0.129, 1 ], [ "color_mode", 0 ], [ "color_1", 0.743, 0.41, 0.501, 1 ], [ "bubblesize", 20 ], [ "interp_slot_color", 1, 1, 1, 0.8 ], [ "margin", 4 ], [ "color_6", 0.316, 0.616, 0.377, 1 ], [ "fontsize", 14 ], [ "scrollable", 1 ], [ "empty_slot_color", 0.349, 0.349, 0.349, 1 ], [ "color_2", 0.679, 0.405, 0.669, 1 ], [ "fontname", "Arial" ], [ "ignoreslotzero", 1 ], [ "spacing", 4 ], [ "text_bg_color", 1, 1, 1, 0.5 ], [ "min_rows", 50 ], [ "bgcolor", 0.2, 0.2, 0.2, 1 ], [ "color_3", 0.527, 0.459, 0.756, 1 ], [ "displayinterp", 1 ] ],
"embedstate" : [ [ "layout", 0 ], [ "color_6", 0.316, 0.616, 0.377, 1 ], [ "text_color", 0.129, 0.129, 0.129, 1 ], [ "ignoreslotzero", 1 ], [ "min_rows", 50 ], [ "bgcolor", 0.2, 0.2, 0.2, 1 ], [ "spacing", 4 ], [ "empty_slot_color", 0.349, 0.349, 0.349, 1 ], [ "displayinterp", 1 ], [ "color_1", 0.743, 0.41, 0.501, 1 ], [ "text_bg_color", 1, 1, 1, 0.5 ], [ "slot_round", 0 ], [ "color_2", 0.679, 0.405, 0.669, 1 ], [ "color_4", 0.367, 0.542, 0.712, 1 ], [ "autowriteagain", 0 ], [ "color_3", 0.527, 0.459, 0.756, 1 ], [ "active_slot_color", 0.808, 0.898, 0.91, 1 ], [ "color_mode", 0 ], [ "margin", 4 ], [ "interp_slot_color", 1, 1, 1, 0.8 ], [ "fontsize", 14 ], [ "select_mode", 0 ], [ "color_5", 0.283, 0.606, 0.559, 1 ], [ "bubblesize", 20 ], [ "stored_slot_color", 0.502, 0.502, 0.502, 1 ], [ "scrollable", 1 ], [ "fontname", "Arial" ], [ "pattrstorage", "test" ] ],
"filename" : "tc.preset.js",
"id" : "obj-10",
"jsarguments" : [ "test" ],
"maxclass" : "jsui",
"numinlets" : 1,
"numoutlets" : 4,
"outlettype" : [ "", "", "", "" ],
"parameter_enable" : 0,
"patching_rect" : [ 72.0, 637.0, 364.0, 124.0 ],
"patching_rect" : [ 72.0, 636.0, 364.0, 125.0 ],
"presentation" : 1,
"presentation_rect" : [ 1.0, 103.0, 269.0, 77.0 ]
"presentation_rect" : [ 1.0, 103.0, 293.0, 149.0 ]
}
}
@ -1125,10 +1159,10 @@
"outlettype" : [ "" ],
"patching_rect" : [ 72.0, 599.0, 176.0, 22.0 ],
"saved_object_attributes" : {
"client_rect" : [ 27, 158, 514, 944 ],
"client_rect" : [ 1450, 76, 2131, 1323 ],
"parameter_enable" : 0,
"parameter_mappable" : 0,
"storage_rect" : [ 25, 107, 816, 991 ]
"storage_rect" : [ 583, 69, 1034, 197 ]
}
,
"text" : "pattrstorage test @savemode 0",
@ -1334,6 +1368,13 @@
"source" : [ "obj-131", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-10", 0 ],
"source" : [ "obj-14", 0 ]
}
}
, {
"patchline" : {
@ -1387,6 +1428,13 @@
"source" : [ "obj-24", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-10", 0 ],
"source" : [ "obj-25", 0 ]
}
}
, {
"patchline" : {
@ -1408,6 +1456,13 @@
"source" : [ "obj-3", 1 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-1", 0 ],
"source" : [ "obj-31", 0 ]
}
}
, {
"patchline" : {

Loading…
Cancel
Save