Play in key
Snap notes to the project key and scale.To keep emitted notes in key, read the mesh scale off the transport and fold each note
onto it. Msh reports the project key as a root pitch class (ctx.meshKey()) plus a
12-bit in-scale mask (ctx.meshScaleMask()).
Hold one Scale, re-root it to the live project key each block, then fold:
private scale: Scale = new Scale(0, 0);
prepare(sampleRate: f64, maxBlock: i32): void {
this.scale = new Scale(0, 0);
}
process(ctx: Ctx): void {
this.scale.setMask(ctx.meshScaleMask(), ctx.meshKey()); // allocation-free
const count = ctx.midiInCount();
for (let e = 0; e < count; e++) {
const ev = ctx.midiEvent(e);
if (ev.isNoteOn()) {
ctx.noteOn(ev.offset, this.scale.fold(ev.note), ev.velocity, ev.channel);
} else if (ev.isNoteOff()) {
ctx.noteOff(ev.offset, this.scale.fold(ev.note), ev.channel);
}
}
}Construct the Scale in prepare (it allocates); setMask and fold are safe to
call per block.
For a fixed scale instead of the project key, snap with one call:
foldToScale(note, root, scaleId) (root is a pitch class, scaleId a ScaleType).
To let the user choose, ParamDef.scaleSelect(...) and ParamDef.rootSelect(...) give
selects whose value is the catalog index, ready to read straight back.
See Harmony for the scale catalog and degree math.