Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
mozaic_tips_and_tricks [2020/01/12 21:16] – [Store two positive Values into a single Variable] _ki | mozaic_tips_and_tricks [2021/07/01 21:41] (current) – Added „ About State-Saving“ section _ki | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Mozaic: Scripting Tips & Tricks ====== | ====== Mozaic: Scripting Tips & Tricks ====== | ||
+ | ~~NOTOC~~ {{tag> | ||
This wiki page contains programming tips & tricks for [[mozaic_plugin_engine|Mozaic]] | This wiki page contains programming tips & tricks for [[mozaic_plugin_engine|Mozaic]] | ||
+ | * [[#NoteOn Velocity 0 Special Case]] | ||
+ | * [[#Two dimensional Arrays]] | ||
+ | * [[#Multi dimensional Arrays]] | ||
+ | * [[#Store two positive Values into a single Variable]] | ||
+ | * [[#Store three unsigned Bytes into a single Variable]] | ||
+ | * [[#Output Fixed Point Values in Labels]] | ||
+ | * [[#Some Best Practice Tips]] | ||
+ | * [[#About State-Saving]] | ||
+ | * [[#Detect Long or Short Pad Taps]] | ||
+ | * [[#Detect Long or Short SHIFT Button Taps]] | ||
+ | * [[# | ||
+ | * [[#Dynamic Letters for Labeling]] | ||
+ | * [[#Use the SHIFT Button to toggle to HELP View]] | ||
+ | * [[#Use a Knob to toggle 16 Pads View to HELP View]] | ||
+ | * [[#Knob double-tap Support]] | ||
+ | * [[#Remove or add an entry inside an array]] | ||
+ | * [[#Using Logic Operators in Expressions instead of IF cases]] | ||
+ | * [[#Using Inc and Dec in Expressions]] | ||
+ | * [[# | ||
+ | * [[#Include Snippets]] | ||
+ | |||
+ | ===== NoteOn Velocity 0 Special Case===== | ||
+ | < | ||
+ | The Midi Spec allows to use a NoteOn command with velocity zero instead of a NoteOff command. If this combination is received by Mozaic, this midi command is automatically converted to
NoteOff vel 0 - even for the @OnMidiInput event and when checking MidiByte1. | ||
+ | |||
+ | Therefore scripts don‘t need to check for this ‚NoteOn Vel 0‘ case, as that special case is handled by Mozaic. Even when using MidiThrough, | ||
- | * [[mozaic_tips_and_tricks# | ||
- | * [[mozaic_tips_and_tricks# | ||
- | * [[mozaic_tips_and_tricks# | ||
- | * [[mozaic_tips_and_tricks# | ||
- | * [[mozaic_tips_and_tricks# | ||
- | * [[mozaic_tips_and_tricks# | ||
- | * [[mozaic_tips_and_tricks# | ||
- | * [[mozaic_tips_and_tricks# | ||
===== Two dimensional Arrays ===== | ===== Two dimensional Arrays ===== | ||
Line 70: | Line 89: | ||
< | < | ||
- | | + | |
- | valA = 723 // allowed range 0 .. ~16000 | + | valA = 723 // allowed range 0 .. 999 |
- | varB = 124 // allowed range 0 .. 999 | + | varB = 124 // allowed range 0 .. ~16000 |
| | ||
- | combinedValue = valA * maxB+ valB | + | combinedValue = valA + (valB * maxA) |
</ | </ | ||
+ | |||
+ | For single-byte MIDI values, use 128 for maxA | ||
To later extract the values from their packed format use | To later extract the values from their packed format use | ||
< | < | ||
- | valA = RoundDown | + | valA = combinedValue |
- | valB = combinedValue | + | valB = Div combinedValue, maxA |
</ | </ | ||
\\ | \\ | ||
Line 110: | Line 131: | ||
</ | </ | ||
+ | |||
+ | \\ | ||
+ | ===== Output Fixed Point Values in Labels ===== | ||
+ | < | ||
+ | When outputting floating point values into labels (knobs, pads, titles) Moazic generates either a 4 digit fraction string | ||
+ | like 42.5000 (if a remainder present) or an integer value without fraction. | ||
+ | |||
+ | \\ | ||
+ | To always output a positive float with two digits (even when integer), one needs to split the input value and output the computed integer parts individually: | ||
+ | < | ||
+ | n = Round value * 100 | ||
+ | f = Div n, 100 | ||
+ | d = n-f*100 | ||
+ | r1 = Div d, 10 | ||
+ | r2 = d % 10 | ||
+ | LabelKnob 0, {B: | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | To always output a negative and positive float with a single digit, the split needs to work on the absolute value | ||
+ | and a conditional statement is used to output both variants: | ||
+ | < | ||
+ | n = Round Abs(value * 10) | ||
+ | f = Div n, 10 | ||
+ | r = (n-f*10) % 10 | ||
+ | if value >=0 | ||
+ | LabelKnob 1, {N: +},f,{.},r | ||
+ | else | ||
+ | LabelKnob 1, {N: },f,{.},r | ||
+ | endif | ||
+ | </ | ||
\\ | \\ | ||
Line 137: | Line 189: | ||
@End | @End | ||
</ | </ | ||
+ | |||
+ | \\ | ||
+ | ===== About State-Saving ===== | ||
+ | < | ||
+ | All assigned variables of a script are automatically included in a saved preset and restored when reloading. To not overwrite them, use the | ||
+ | < | ||
+ | if Unassigned channel | ||
+ | channel = 10 | ||
+ | bank = 1 | ||
+ | .... | ||
+ | endif | ||
+ | </ | ||
+ | construct in the scripts **OnLoad** that only set the default value if a script is freshly run using ‚Upload‘. If reloaded (and therefor some value is already assigned to ‚channel‘ ) the initialization block is skipped. | ||
+ | |||
+ | |||
+ | Mozaic stores some more more information in the presets/ | ||
+ | * Layout | ||
+ | * Pad labels, color, latch-state | ||
+ | * Knob labels and position | ||
+ | * XY position | ||
+ | * Title label above pads, knobs, XY | ||
+ | * Rootnote and scale | ||
+ | |||
+ | Things **not** restored during session reload: | ||
+ | * ShortName | ||
+ | * Timer state and settings | ||
+ | * LFO settings | ||
+ | |||
+ | \\ | ||
+ | ===== Detect Long or Short Pad Taps ===== | ||
+ | < | ||
+ | |||
+ | < | ||
+ | @OnLoad | ||
+ | FillArray downStart, | ||
+ | pressTime = 250 | ||
+ | @End | ||
+ | |||
+ | @OnPadDown | ||
+ | downStart[LastPad] = SystemTime | ||
+ | @End | ||
+ | |||
+ | @OnPadUp | ||
+ | pad = LastPad | ||
+ | if SystemTime - downStart[pad] < pressTime | ||
+ | Log {Short tap pad }, pad | ||
+ | else | ||
+ | Log {Long tap pad }, pad | ||
+ | endif | ||
+ | @End | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | |||
+ | ===== Detect Long or Short SHIFT Button Taps ===== | ||
+ | < | ||
+ | |||
+ | < | ||
+ | @OnLoad | ||
+ | shiftStart = 0 | ||
+ | pressTime | ||
+ | @End | ||
+ | |||
+ | @OnShiftDown | ||
+ | shiftStart = SystemTime | ||
+ | @End | ||
+ | |||
+ | @OnShiftUp | ||
+ | if SystemTime - shiftStart < pressTime | ||
+ | Log {Short tap SHIFT} | ||
+ | else | ||
+ | Log {Long tap SHIFT} | ||
+ | endif | ||
+ | @End | ||
+ | </ | ||
\\ | \\ | ||
Line 151: | Line 278: | ||
LabelPad 0,{Line 1},{ | LabelPad 0,{Line 1},{ | ||
LabelPad 1,{Upper},{ -------------- },{Lower} | LabelPad 1,{Upper},{ -------------- },{Lower} | ||
+ | @End | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | ===== Dynamic Letters for Labeling ===== | ||
+ | < | ||
+ | The NoteName function of Moazic can be used to dynamically output the letters A-G inside the label string definitions of Knobs, Pads or Logs. This allows to construct labels like ‚Bank A‘ to ‚Bank D‘, or ‚Preset A-1‘ to ‚Preset F-8‘. | ||
+ | |||
+ | < | ||
+ | @OnLoad | ||
+ | ShowLayout 2 | ||
+ | ABCDEFG | ||
+ | |||
+ | for i = 0 to 15 | ||
+ | row = 1 + (i>7) | ||
+ | id = i%8 | ||
+ | if (id < 7) | ||
+ | LabelPad i, {Pad },(NoteName ABCDEFG[id], | ||
+ | endif | ||
+ | endfor | ||
@End | @End | ||
</ | </ | ||
Line 212: | Line 359: | ||
@End | @End | ||
</ | </ | ||
- | {{tag> | + | |
+ | \\ | ||
+ | ===== Knob double-tap Support ===== | ||
+ | < | ||
+ | |||
+ | The value returned from // | ||
+ | This tip exploits the fact, that is very unlikely to manually dial in exactly 64.0000 - if such a knob value is returned, then the knob has probably been double-tapped. | ||
+ | |||
+ | |||
+ | If a knob is only used to toggle between two states like a switch (like the variable //isOn// in the example), the code for double-tap detection would look like: | ||
+ | < | ||
+ | @OnKnobChange | ||
+ | _knob = LastKnob | ||
+ | _val = GetKnobValue _knob | ||
+ | |||
+ | if _knob = TOGGLE_KNOB | ||
+ | if _val = 64 | ||
+ | isOn = not isOn | ||
+ | SetKnobValue TOGGLE_KNOB, | ||
+ | else | ||
+ | isOn = _val > 64 | ||
+ | endif | ||
+ | |||
+ | if isOn | ||
+ | LabelKnob TOGGLE_KNOB, | ||
+ | else | ||
+ | LabelKnob TOGGLE_KNOB, | ||
+ | endif | ||
+ | endif | ||
+ | @End | ||
+ | |||
+ | @OnLoad | ||
+ | ShowLayout 2 | ||
+ | LabelPads {Knob Double-Tap Demo } | ||
+ | LabelKnobs {Toggle with Double-Tap} | ||
+ | for _knob = 0 to 3 | ||
+ | SetKnobValue _knob,0 | ||
+ | LabelKnob _knob, | ||
+ | endfor | ||
+ | |||
+ | if Unassigned isOn | ||
+ | isOn = NO | ||
+ | endif | ||
+ | |||
+ | TOGGLE_KNOB = 1 | ||
+ | |||
+ | SetKnobValue TOGGLE_KNOB, | ||
+ | if isOn | ||
+ | LabelKnob TOGGLE_KNOB, | ||
+ | else | ||
+ | LabelKnob TOGGLE_KNOB, | ||
+ | endif | ||
+ | @End | ||
+ | </code> | ||
+ | The script snippet also features | ||
+ | * State saving of the toggle variable isOn | ||
+ | * Conditional expressions: | ||
+ | * //48 + 32*isOn// is either 48 or 80 depending on the state of //isOn// | ||
+ | * //isOn = _val > 64// assigns either 0 or 1 to //isOn// depending on _val | ||
+ | |||
+ | |||
+ | \\ | ||
+ | ===== Remove or add an entry inside an array ===== | ||
+ | < | ||
+ | Instead of running through an array with a FOR loop to shift entries to either insert or remove an intermedeate enry, it is way faster to use the CopyArray | ||
+ | |||
+ | < | ||
+ | |||
+ | \\ | ||
+ | === Remove an entry of an array (shift left) === | ||
+ | |||
+ | < | ||
+ | @OnLoad | ||
+ | for i=0 to 6 | ||
+ | a[i] = i | ||
+ | endfor | ||
+ | Log {Before | ||
+ | |||
+ | // Remove entry a[2] by left shift of the following entries | ||
+ | CopyArray a[3], a[2], 6 | ||
+ | |||
+ | Log {After | ||
+ | @End | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | === Insert an entry into array (shift right) === | ||
+ | |||
+ | Due to the inner working of CopyArray, the above trick doesn‘t work for right shifts - but one can use a temporary array to be more efficient than iterating over the array: | ||
+ | |||
+ | < | ||
+ | @OnLoad | ||
+ | for i=0 to 6 | ||
+ | a[i] = i | ||
+ | endfor | ||
+ | Log {Before | ||
+ | |||
+ | // Insert a new a[2] | ||
+ | CopyArray a[2], _tmp, 6 | ||
+ | CopyArray _tmp, a[3], 6 | ||
+ | a[2] = 9 | ||
+ | |||
+ | Log {After | ||
+ | @End | ||
+ | </ | ||
+ | ===== Using Logic Operators in Expressions instead of IF cases ===== | ||
+ | < | ||
+ | |||
+ | Boolean expressions compute to either 1 or 0, which are the internal values for TRUE/FALSE or YES/NO. | ||
+ | Using this behavior in expressions allows to get rid of some IFs, but on the other hand makes the script a bit more complicated to read and understand for novices. | ||
+ | |||
+ | Instead of the lengthy | ||
+ | < | ||
+ | // showHelp | ||
+ | |||
+ | If showHelp | ||
+ | ShowLayout 4 | ||
+ | Else | ||
+ | ShowLayout 2 | ||
+ | Endif | ||
+ | </ | ||
+ | |||
+ | On can write | ||
+ | < | ||
+ | ShowLayout 2 + 2*showHelp | ||
+ | </ | ||
+ | |||
+ | . | ||
+ | |||
+ | Here second example where in the 22 knob layout the left most 8 knobs of both rows are used as channel knobs for channels 1 to 16. Since there are 11 knobs per row, the channel computed for the lower row needs to be adjusted: | ||
+ | < | ||
+ | chan = LastKnob + 1 | ||
+ | If LastKnob >= 8 | ||
+ | chan = chan - 3 | ||
+ | Endif | ||
+ | </ | ||
+ | |||
+ | This can be written shorter as: | ||
+ | < | ||
+ | chan = LastKnob +1 - 3*(LastKnob> | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | ===== Using Inc and Dec in Expressions ===== | ||
+ | < | ||
+ | |||
+ | TheOriginalPaulB discovered that Inc and Dec are working as functions. Both Inc and Dec are ‚pre-increment‘ operations like ++var in C, as the function returns the already incremented value. This allows for several interesting language constructs: | ||
+ | |||
+ | In case of a ring buffer index, one could use | ||
+ | < | ||
+ | index = (Inc index) % 64 | ||
+ | </ | ||
+ | instead of the longer construct | ||
+ | |||
+ | < | ||
+ | Inc index | ||
+ | index = index % 64 | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | Sometimes this trick is also applicable in array initialization, | ||
+ | < | ||
+ | idx = -1 | ||
+ | for i = 10 to 19 | ||
+ | array[Inc idx] = 2*i | ||
+ | endfor | ||
+ | </ | ||
+ | The example fills array[0] to array[9] with the values 20 to 38. To compensate for pre-increment, | ||
+ | |||
+ | \\ | ||
+ | It is also possible to construct a post-increment operation by using the inc in the expression itself, but discarding it using multiplication with 0: | ||
+ | |||
+ | < | ||
+ | idx = 0 | ||
+ | for i = 10 to 19 | ||
+ | array[idx] = 2*i + 0*(Inc idx) | ||
+ | endfor | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | ===== Calculate Standard Chords from a Root Note ==== | ||
+ | < | ||
+ | |||
+ | This is a compact way to build standard chords from just a root note. It turns out that for standard western scales such as Major, Minor, Dorian, Phrygian, etc, normal chord notes can be found by starting at the root and counting up in steps of three semitones then quantizing up to the next scale tone. Repeat this for the number of notes you want in the chord (triad (3), seventh (4), ninth (5), eleventh (6), 13th (7). | ||
+ | |||
+ | Here’s a code snippet that efficiently creates a chord of “notes” degree from an incoming MIDI Note. Code to do something with that array would obviously have to be added. | ||
+ | |||
+ | < | ||
+ | @OnMIDINote | ||
+ | //set “notes” to the number of notes wanted in the chord before this event triggers! | ||
+ | chordNotes[0] = ScaleQuantize MIDINote | ||
+ | for i = 1 to (notes - 1) | ||
+ | chordNotes[i] = ScaleQuantize chordNotes[i-1] + 3 | ||
+ | endfor | ||
+ | @End | ||
+ | </ | ||
+ | |||
+ | |||
+ | \\ | ||
+ | ===== Include Snippets ===== | ||
+ | < | ||
+ | |||
+ | On PatchStorage you can find several ' | ||
+ | |||
+ | The snippets can be seen as 'well tested library functions' | ||
+ | The snippet in turn will later call event that you need to define in your code (callbacks). | ||
+ | All of the current available snippets need a timer event to do their work, the snippets are coded in a way that allows to also to work with a timer event that you defined in your script. | ||
+ | |||
+ | I tried to make them as ' | ||
+ | |||
+ | ^PatchStorage | ||
+ | | [[https:// | ||
+ | | [[https:// | ||
+ | | [[https:// |