Harmony and sequencing
Scales, chords, arps, and Euclidean rhythms, all allocation-free.The harmony toolkit ships in @msh/device-sdk/device/assembly. Everything here is real-time
safe with no allocation, so it runs in process.
Scales
60+ scales in the ScaleType catalog (the first 21 match the host's mesh scales).
SCALE_NAMES and ROOT_NAMES give labels for selects.
| Function | Does |
|---|---|
foldToScale(note, root, scaleId) | Snap a note to the nearest scale member (ties up). |
foldToMask(note, mask) | Same, against a raw 12-bit mask. |
transposeInScale(note, steps, root, scaleId) | Move by scale degrees, wrapping octaves. |
degreeOf(note, root, scaleId) | Scale-degree index of a note. |
scaleDegreeToNote(degree, octave, root, scaleId) | MIDI note for a degree. |
scaleMask(scaleId, root) | 12-bit in-scale mask. |
Or hold a Scale and re-root it to the project key per block (allocation-free):
let scale: Scale;
prepare(): void { scale = new Scale(ScaleType.Dorian, 0); }
// per block:
scale.setMask(ctx.meshScaleMask(), ctx.meshKey());
const snapped = scale.fold(note);Scale methods: setScale, setRoot, setMask, mask, fold, transpose,
degree, contains, note, intervalAt, degreeCount. Build one from the project
scale with ctx.meshScale() / ctx.globalScale().
Chords
60+ qualities in ChordQuality (CHORD_QUALITY_NAMES for labels). Chords write into
a StaticArray<i32> you own (size 8 covers the largest).
| Function | Does |
|---|---|
buildChord(rootNote, quality, out) | Write a chord's notes into out; returns the count. |
invert(out, count, n) | Raise the lowest n voices an octave, in place. |
dropVoicing(out, count, n) | Drop the n-th voice from the top an octave, in place. |
diatonicTriad(degree, octave, root, scaleId, out) | 3-note stack of thirds within a scale. |
diatonicSeventh(...) | 4-note version. |
Interval constants (MAJOR_THIRD, PERFECT_FIFTH, MINOR_SEVENTH, ...) read better
than raw semitones.
Rhythm and sequencing
| Tool | Does |
|---|---|
euclid(pulses, steps, rotation, out) | Fill a StaticArray<bool> with a Euclidean pattern. Re-bake only on a param change. |
Clock(beatsPerStep) | Tempo-synced step clock. update(ctx.ppqPosition()) is true on each new step; step(), phase(). |
Arpeggiator(maxNotes) | noteOn/noteOff to collect held notes, setPattern(ArpPattern.UpDown), setOctaves, next() per step. |
MonoNoteStack(maxNotes) | Mono voice priority: current(NotePriority.Highest | Lowest | Last). |
ArpPattern: Up, Down, UpDown, DownUp, Converge, Diverge, Random, AsPlayed.
Tempo sync
| Function | Returns |
|---|---|
noteToFreq(note) / freqToNote(hz) | Pitch conversion. |
syncToHz(beats, bpm) | A note division as a rate in Hz (tempo-synced LFO). |
syncToSamples(beats, bpm, sampleRate) | The same in samples. |
beatsToSeconds(beats, bpm) | Quarter notes to seconds. |
Beat constants: BEATS_WHOLE, BEATS_HALF, BEATS_QUARTER, BEATS_EIGHTH,
BEATS_SIXTEENTH, BEATS_THIRTYSECOND, times DOTTED or TRIPLET.
Param factories
ParamDef.scaleSelect(id, name, short), ParamDef.rootSelect(...),
ParamDef.chordSelect(...) build selects wired to these catalogs. The value is the
catalog index, so read it straight into ScaleType / ChordQuality. Pair with
m.addSetting(...).
How-to: Play in key, Build chords, Sequence and arp.