Mozaic: Scripting Tips & Tricks

From -ki

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, an incomming ‚NoteOn Vel 0‘ command will be converted to ‚NoteOff Vel 0‘

From -ki

Each variable in Mozaic is in fact an array with a maximum of 1024 elements. Scripting often needs two dimensional arrays, but the variables only offer one dimensional access.

Its is very easy to 'fold' two or more dimensions into a normal array if the row length is fixed:

  xMax = 16

  x = 2
  y = 5
  value = 42

  // write the value to position x,y : 
  twoDimStorage[ x + xMax *y] = value

Retrieval of values is done using the same folding:

  
  // retrieve value from 5,3
  x = 5
  y = 3
  value =   twoDimStorage[ x + xMax *y ]

Mozaics array length is limited to 1024, so 32×32 just fits - but 128×16 (often needed in midi programming) doesn‘t.


There is a single two dimensional array in Mozaic that fits this dimensions, the note state array. Since it is special, there are separate functions:

  ResetNoteState initValue         // Fills all 16x128 elements with the given value
  
  SetNoteState row, column, value  // Sets a value
  value = GetNoteState row, colum  // Retrieves the value

There is only one array of this kind, but sometimes you Need to store multiple values. In such a case, its possible to fold these values into a single value - see the tricks below:


From -ki

The above folding can be expanded to multiple dimension, here an example for a 3 dimensional array folded into one dimensions:

  array[x + xMax * y + xMax*yMax *z] = value

Since Mozaic arrays are limited to 1024 elements, xMax * yMax * zMax needs to be less or equal 1024.

From -ki

It is possible to store two positive values in a single variable if their product is less than 16.777.216 and they both have a known maximum value.

  maxA = 1000  // Maximum value for A plus one
  valA = 723   // allowed range 0 .. 999
  varB = 124   // allowed range 0 .. ~16000
  
  combinedValue = valA + (valB * maxA) 

For single-byte MIDI values, use 128 for maxA

To later extract the values from their packed format use

  valA = combinedValue % maxA
  valB = Div combinedValue, maxA


Storing of 3 unsigned bytes (J,K & L, each with the range of 0-255) into a single 24bit integer can be done by using the packing formular:

combinedValue = (J * 0x10000) + (K * 0x100) + (L)

This would pack the bytes 0xAA, 0xBB, 0xCC into the combined value 0xAABBCC


To unpack, you mask off the parts you don't want and then divide to shift things over. Mozaic has an Integer AND that you can use for masking things. Its operator is &.

To mask, use zero in the bytes you don't want and FF in the byte you want. For example, to turn 0xAACC into 0xAA00, do 0xAACC & 0xFF00. Now we can divide by 0x100 to shift our byte to the right two place. 0xAA00/0x100 = 0xAA

Dividing the masked numbers by 0x10000 will shift hex numbers four digits to the right and dividing by 0x100 will shift them two. So, 0xAA0000/0x10000 = 0xAA

If you want the middle byte of 0xAABBCC, do this 0xAABBCC & 0x00FF00, you end up with 0xBB00 divide the result by 0x100 to get 0xBB

You don't need the leading zeros, but I think it makes the code more understandable.

J = (combinedValue & 0xFF0000) / 0x10000 
K = (combinedValue & 0x00FF00) / 0x100
L = (combinedValue & 0x0000FF)


From -ki

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:},f,{.},r1,r2


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


From -ki

  • Its helpfull to add the version number right into the title label.
    And also add this version number to the script title and patchname if uploaded / updated on PatchStorage so that script-users can notice the version change.
  • Add a short name to set the icon name. This helps when a project contains multiple Mozaic instances with different scripts
  • Visually unclutter the scripts gui
    • Clear all unused title and knob labels by setting it to empty { }
    • Position all knobs to 0
    • Position the XY to 0,0
@OnLoad
  ShowLayout 0
  
  SetShortName {DUMMY}
  
  LabelPads { }
  LabelKnobs {My Dummy Script     v0.1}
  LabelXY { }
  
  for _knob = 0 to 9
    LabelKnob _knob, { }
    SetKnobValue _knob,0
  endfor
  SetXYValues 0,0
@End


From -ki

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/session and restores them when reloading:

  • 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


From wim

@OnLoad
  FillArray downStart,0,16
  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


From wim

@OnLoad
  shiftStart = 0
  pressTime  = 250
@End

@OnShiftDown
  shiftStart = SystemTime
@End

@OnShiftUp
  if SystemTime - shiftStart < pressTime
    Log {Short tap SHIFT}
  else
    Log {Long  tap SHIFT}
  endif
@End


From -ki

You can force multiline pad labels by adding space-filled substrings.

  • There must be enough spaces to fill the rest of the current line
  • Too many spaces don't hinder, since mozaic will discard them infront of a new output line - this helps if the text infront of a line break has variable length
  • You can't force an empty line between two lines
  • One can use 14 dashes to split the pad into two labels. Don't forget the space infront and behind the dashes, its needed for line-break
@OnLoad
  LabelPad 0,{Line 1},{                     },{Line 2}
  LabelPad 1,{Upper},{ -------------- },{Lower}
@End


From -ki

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   = [9,11,0,2,4,5,7]

  for i = 0 to 15
    row = 1 + (i>7)
    id  = i%8
    if (id < 7)
      LabelPad i, {Pad },(NoteName ABCDEFG[id],NO),{     },{Row },row
    endif
  endfor
@End


From -ki

@Description
My Sample Plugin

This description will be shown on the HELP page. Longer
texts are possible, as the description box is scrollable.
@End

@OnLoad
  mainLayout = 0
  showsHelp = NO

  ShowLayout mainLayout
  LabelKnobs {My Plugin                  SHIFT to help}
@End

@OnShiftDown
  if showsHelp  
    ShowLayout mainLayout
    LabelKnobs {My Plugin                  SHIFT to help}
    
  else
    ShowLayout 4
    LabelKnobs {SHIFT to main}
    
  endif
  showsHelp = not showsHelp
@End


From -ki

This script snippet is only applicable for the 16 pads layout (2) as this the only layout where the knobs 0-3 are at the same screen position as on the HELP page.

@Description
My Sample Plugin
This description will be shown on the HELP page. 
@End

@OnLoad
  ShowLayout 2
  SetKnobValue 3,0
  LabelKnob 3,{HELP}
@End

@OnKnobChange
  if LastKnob = 3
    ShowLayout 2 + 2* ( (GetKnobValue LastKnob ) >= 64)   
  endif
@End


From -ki

The value returned from GetKnobValue is a floating point number, usually it will contain non-zero decimal places when the knob is turned manually. If double tapped the knob-value is set to 64 exactly. 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,48 + 32*isOn
    else
      isOn = _val > 64  
    endif
    
    if isOn
      LabelKnob TOGGLE_KNOB,  {ON}
    else
      LabelKnob TOGGLE_KNOB,  {OFF}
    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,48 + 32*isOn
  if isOn
    LabelKnob TOGGLE_KNOB,  {ON}
  else
    LabelKnob TOGGLE_KNOB,  {OFF}
  endif
@End

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

From -ki

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 Mozaic function:

 CopyArray source, dest, size


Remove an entry of an array (shift left)

@OnLoad
  for i=0 to 6
    a[i] = i
  endfor
  Log {Before },a[0],{ },a[1],{ },a[2],{ },a[3],{ },a[4],{ },a[5],{ },a[6]

  // Remove entry a[2] by left shift of the following entries
  CopyArray a[3], a[2], 6
  
  Log {After  },a[0],{ },a[1],{ },a[2],{ },a[3],{ },a[4],{ },a[5],{ },a[6]
@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  },a[0],{ },a[1],{ },a[2],{ },a[3],{ },a[4],{ },a[5],{ },a[6]

  // Insert a new a[2]
  CopyArray a[2], _tmp, 6
  CopyArray _tmp, a[3], 6  
  a[2] = 9
  
  Log {After   },a[0],{ },a[1],{ },a[2],{ },a[3],{ },a[4],{ },a[5],{ },a[6]
@End

From -ki

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  is a boolean with the values YES/NO or TRUE/FALSE

  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>=8)


From -ki

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, but one has to compensate for the pre-increment.

  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, idx needs to start at -1 since it will be incremented to zero on first usage


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


From wim

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


From -ki

On PatchStorage you can find several 'Include Snippets' that cover specific programming problems and that are intended to be appended your own scipt code.

The snippets can be seen as 'well tested library functions'. For the current available include snippets, you copy their main code to the end of you script and call one or two initialization event functions in your @OnLoad. 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 'developer-friendly' as possible and also did extensive testing on each of them. The documentation wiki pages show the snippets source at the bottom since its easier to read on the wiki than inside Mozaic.

PatchStorage Wiki OnLoad Other Callbacks Info
Pad & Shift Manager Documentation optional vars
one call
- 2 Detect single-/double-/triple-tap and hold interactions for pads or shift
Active Notes Tracker Documentation optional var
one call
note-on / note-off
events
- Manages a list of active notes, velocities and durations for each channel
Migration Manager Documentation two calls - 4 Migrate script parameters between different script versions
  • mozaic_tips_and_tricks.txt
  • Last modified: 2024/04/23 19:07
  • by _ki