K

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.