Synth blocks
Oscillators, voices, envelopes, LFOs, meters, and random sources.The synth and modulation parts ship in @msh/device-sdk/device/assembly. Build them once in
prepare, drive them per sample in process.
let osc: Oscillator;
let env: Envelope;
prepare(sampleRate: f64, maxBlock: i32): void {
osc = new Oscillator(sampleRate as f32);
osc.setWaveform(Waveform.Saw);
env = new Envelope(sampleRate as f32);
}Warning
Construct these in prepare, never in process. The
why is worth a read.
Oscillators and voices
| Block | Build | Use |
|---|---|---|
Oscillator | new Oscillator(sr) | setWaveform(Waveform.Saw), process(freqHz) per sample. Band-limited (PolyBLEP). |
VoiceAllocator | new VoiceAllocator(count) | noteOn(note) / noteOff(note) return a voice index (steals oldest when full); noteOf(voice), reset(). |
VoiceBank<T> | new VoiceBank<T>(count, (i) => new T(...)) | get(i), count, norm (= 1/count, multiply a summed mix by it). |
AudioBuffer | new AudioBuffer(maxFrames, channels?) | push(l, r?) to record, read(ch, fractionalFrame) cubic-interpolated playback; at, set, length, reset. |
KarplusStrong | new KarplusStrong(sr, minFreqHz?) | Plucked string / resonator: setPitch(hz), setDamping(0..1), setFeedback(0..1), process(x) (excite with a noise burst or live audio). setDispersion(-1..1): positive = stiff-string inharmonicity (piano bass, bells), negative = amplitude-dependent curved-bridge buzz (sitar). Re-call setPitch after changing damping or dispersion. |
Waveform values: Sine, Saw, Square, Triangle.
A polyphonic synth is a VoiceAllocator paired with a VoiceBank of per-voice DSP:
voices = new VoiceBank<Oscillator>(
8,
(_i) => new Oscillator(sampleRate as f32),
);
alloc = new VoiceAllocator(8);
// in process, on a note-on: const v = alloc.noteOn(m.note);
// then sum voices: sum += voices.get(v).process(freq); out = sum * voices.norm;Envelopes and LFOs
| Block | Build | Use |
|---|---|---|
Envelope | new Envelope(sr) | AHDSR. noteOn(peak, attackS, holdS, attackCurve?), noteOff(releaseS, releaseCurve?), tick(holdS, decayS, sustain, decayCurve?) per sample, isActive(). |
Lfo | new Lfo(sr) | setShape(LfoShape.Sine), setFreq(hz) or setTempoSync(beats, bpm), setPhase(p), retrigger(), process() (-1..1), processUnipolar() (0..1). |
RandomLfo | new RandomLfo(sr, seed?, smoothingMs?) | process(rateHz, sr): a smoothed random LFO. setSmoothing(sr, timeMs). |
LowPassGate | new LowPassGate(sr) | process(x, gate): one control opens amplitude AND a gentle low-pass together through a vactrol-style lag (fast attack, slow decay): the west-coast pluck. setColor(bleed), setRange(minHz, maxHz), setResponse(attackMs, decayMs), gateValue(). Feed a raw gate or an envelope. |
Stage times pass to tick each sample, so they modulate freely; noteOn latches attack,
hold, and peak. attackCurve / releaseCurve / decayCurve are in -1..1 (0 = linear).
LfoShape values: Sine, Triangle, Saw, Ramp, Square, SampleHold, Random,
Exp.
Metering and detection
| Block | Build | Use |
|---|---|---|
EnvelopeFollower | new EnvelopeFollower(sr, attackMs, releaseMs) | process(x) tracks the peak envelope; value(), setTimes(a, r), reset(). |
RmsMeter | new RmsMeter(sr, windowMs) | process(x) returns sliding-window RMS; reset(). |
PeakMeter | new PeakMeter(sr, releaseMs) | process(x) instant attack, exponential release; setRelease(ms), reset(). |
Trigger | new Trigger() | process(x, threshold) true on the rising edge; reset(). |
Gate | new Gate() | process(x, on, off) Schmitt gate with hysteresis (off < on); isOpen(), reset(). |
StrumDetector | new StrumDetector(sr, minIntervalMs?) | process(x) true on the sample a pluck/hit lands: 3-band energy analysis, AGC, adaptive threshold, minimum inter-onset spacing. Pair it with KarplusStrong / ModalBank to strum a resonator from live audio. |
HysteresisQuantizer | new HysteresisQuantizer() | process(value, numSteps, hysteresis?) quantizes a 0..1 value to an integer step without flutter at the boundaries: modes, wavetable frames, or scale degrees driven by a modulated param. value() reads the last step. |
Random sources
| Block | Build | Use |
|---|---|---|
Rng | new Rng(seed?) | uniform() ([0,1)), bipolar() ([-1,1)), nextU32(), reseed(seed). Seedable xorshift32. |
Noise | new Noise(seed?) | white() (-1..1), pink() (Paul Kellet, roughly -3 dB/oct). |
SampleHold | new SampleHold(seed?) | process(rateHz, sr) re-rolls when the phase wraps; trigger() forces a new value now; value(). |
All random sources are deterministic for a given seed, so a patch sounds the same on reload.
Stereo helpers
| Helper | Does |
|---|---|
panGainL(pan) / panGainR(pan) | Equal-power pan gain for the left / right channel; pan in -1..1. |
MidSide | encode(l, r) then mid() / side(); recombine with decodeL(m, s) / decodeR(m, s). |
For audio filters, delays, and drive, see DSP blocks. For pure math and curve helpers, see Helpers.