Synthesis#
The synth subsystem turns libYSE from a sample player into a polyphonic
instrument host. A YSE::synth owns a pool of voices, note
allocation, voice stealing and full keyboard state (pedals, controllers,
pitch wheel, aftertouch); a voice — a YSE::SYNTH::dspVoice
subclass — owns only what a single note sounds like. The synth is rendered
behind an ordinary positioned YSE::sound, so everything the
spatial engine already does (3D panning, channels, reverb) applies to it.
See the tutorials for worked, compilable walk-throughs: Your first synth, Writing a custom dspVoice, Loading SFZ instruments and DX7 banks, and Per-note 3D: position handlers and swarms.
Note
The instrument DSP below — the SFZ sampler voice and the DX7-class FM voice
— is always compiled into libyse; there is no YSE_ENABLE_FM /
YSE_ENABLE_SFZ switch. The factory sounds these voices load (SFZ
instruments and samples, and the DX7/FM .SYX banks) are not part of
the library. They ship as an opt-in content pack fetched only when
libYSE is configured with -DYSE_FETCH_CONTENT_PACK=ON (or
python yse.py build --content-pack). The DX7 factory-style banks are
tolerated, not legally cleared content — see the repository’s
CONTENT-LICENSES.md and content/fm/dx7-factory/README.md.
The synth#
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH#
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
Enums
-
enum MESSAGE#
The control events the interface can push onto an implementation’s lock-free inbox.
#153 wires up the note ops (NOTE_ON / NOTE_OFF / ALL_NOTES_OFF); the pedal / controller / wheel / aftertouch ops are declared here as the fixed contract but handled by #154 (keyboard state). There is deliberately no CALLBACK op — onNoteEvent is a direct atomic function pointer (#154), never a queued message.
Values:
-
enumerator NOTE_ON#
-
enumerator NOTE_OFF#
-
enumerator ALL_NOTES_OFF#
-
enumerator PITCH_WHEEL#
-
enumerator CONTROLLER#
-
enumerator AFTERTOUCH#
-
enumerator SUSTAIN#
-
enumerator SOSTENUTO#
-
enumerator SOFTPEDAL#
-
enumerator HANDLER_PARAM#
-
enumerator NOTE_POSITION#
-
enumerator NOTE_ON#
-
enum MESSAGE#
-
namespace SYNTH#
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
class interfaceObject#
A polyphonic synthesiser voice pool rendered behind one
YSE::sound.Write
YSE::synth(a typedef for this class). Build the pool from a prototype voice withaddVoices, attach it behind a positionedYSE::soundwithsound::create(synth&, ...), then drive it withnoteOn/noteOff. The engine owns polyphony, allocation, voice stealing and lifecycle; aSYNTH::dspVoicesubclass owns only what a single voice sounds like.Like
YSE::soundthe interface is non-copyable: the implementation holds this object’s address, so the address must stay stable.See also
YSE::SYNTH::dspVoice For user-subclassable voices.
See also
YSE::SYNTH::sineVoice The reference sine + ADSR voice.
Public Functions
-
interfaceObject()#
-
~interfaceObject()#
-
interfaceObject(const interfaceObject&) = delete#
Synths are non-copyable (the implementation holds our address).
-
interfaceObject &operator=(const interfaceObject&) = delete#
-
interfaceObject &create()#
Register this synth with the engine.
Must be called before
addVoices/noteOn. Idempotent-safe to omit:sound::create(synth&, ...)calls it for you if you have not.
-
interfaceObject &name(const std::string &n)#
Assign a bus-addressable name to this synth.
Names the synth on the global named bus (issue #388, following the sound/channel pattern of #123) so live coders can drive it by string address rather than by C++ handle. Once named
foo, the synth subscribes to (all payloads arelist[float]unless noted; the full shape contract lives in docs/design/live_coding_dsl.md):synth.foo.note→[channel, note, velocity], callsnoteOn()synth.foo.off→[channel, note(, velocity)], callsnoteOff()synth.foo.cc→[channel, number, value], callscontroller()synth.foo.bend→[channel, value], callspitchWheel()synth.foo.aftertouch→[channel, note, value], callsaftertouch()synth.foo.alloff→int/floatchannel (bang = all), callsallNotesOff()
The subscribers run on the control thread and enqueue through the same RT-safe message inbox as the C++ setters — no new audio-thread surface. Anonymous synths (the default) are not addressable. Passing an empty string clears the name and removes the bus subscriptions. Renaming re-subscribes under the new name.
Two synths cannot share a name: the second
name()is rejected and logged via the engine’s error path; the first registration wins. The bus is only live betweenSystem::init()andSystem::close()— naming a synth while the engine is down is a no-op.- Parameters:
n – The name, or
""to make the synth anonymous again.- Returns:
*thisfor fluent chaining.
-
interfaceObject &addVoices(dspVoice &prototype, int numVoices, int channel = 0, int lowestNote = 0, int highestNote = 127)#
Clone
prototypenumVoicestimes into a voice group.The group responds to note numbers in
[lowestNote, highestNote]on MIDIchannel(0 = omni). May be called several times to build layered or split keyboards; a note may sound in more than one matching group. Cloning happens off the audio thread, on the engine’s setup pool, so the synth becomes playable a short moment after this returns (when it reachesOBJECT_READY) — exactly like a file-backed sound is not playable until its buffer finishes loading.Warning
prototypemust outlive the resulting setup — the engine reads it to clone but neither copies nor owns it. CalladdVoicesbefore the synth is attached and played.
-
interfaceObject ¬eOn(int channel, int noteNumber, float velocity)#
Start a note.
velocityis normalised to [0, 1].
-
interfaceObject ¬eOff(int channel, int noteNumber, float velocity = 0.f)#
Release a note.
-
interfaceObject ¬eOn(const MUSIC::note ¬e)#
Start a note from a
MUSIC::note(uses its pitch/volume/channel).
-
interfaceObject ¬eOff(const MUSIC::note ¬e)#
Release the note matching a
MUSIC::note(pitch/channel).
-
interfaceObject &allNotesOff(int channel = 0)#
Release every held note on
channel(0 = all channels).A bulk note-off: voices enter their normal release, they are not cut.
-
interfaceObject &pitchWheel(int channel, float value)#
Bend every voice on
channel.valueis normalised to [-1, 1] (0 = centre). How far a voice bends is the voice’s concern.
-
interfaceObject &controller(int channel, int number, float value)#
Send a control-change.
valueis normalised to [0, 1].CC 64 / 66 / 67 act as the sustain / sostenuto / soft pedals; every other CC number is stored as the channel’s last controller value.
-
interfaceObject &aftertouch(int channel, int noteNumber, float value)#
Apply aftertouch pressure, normalised to [0, 1].
noteNumber == -1is channel-wide (every voice on the channel); otherwise only the voice(s) sounding that note receive it.
-
interfaceObject &sustain(int channel, bool down)#
Sustain pedal (CC 64).
Down defers NOTE_OFF releases on the channel; up releases every note that was waiting on it.
-
interfaceObject &sostenuto(int channel, bool down)#
Sostenuto pedal (CC 66).
Down captures the currently-held notes and sustains only those; up releases them.
-
interfaceObject &softPedal(int channel, bool down)#
Soft pedal (CC 67).
While down, notes that START scale their velocity down; sounding voices are unaffected.
-
interfaceObject &positionHandler(YSE::SYNTH::positionHandler &prototype)#
Clone
prototypeonce per voice slot as this synth’s position handler, giving every note its own 3D position and movement.With no handler attached, every voice uses the synth’s aggregate position — so this call is purely additive. Ship-in handlers are
SYNTH::staticHandler/SYNTH::randomSpreadHandler/SYNTH::orbitHandler, or derive your own fromSYNTH::positionHandler.Warning
prototypemust outlive setup — the engine reads it to clone but neither copies nor owns it. Call before the synth is attached and played (rejected after setup, likeaddVoices).
-
interfaceObject &handlerParam(int index, float value)#
Set a shared handler parameter by index (e.g.
the swarm centre at indices 0..2). All of the synth’s live handlers read it next block. A bounded, allocation-free message — safe to call every control tick.
-
interfaceObject ¬ePosition(int channel, int noteNumber, const Pos &pos)#
Imperatively place the voice(s) sounding
noteNumberonchannelatpos(app-driven trajectories).A bounded, allocation-free message. When a handler is attached it re-steers the voice next block, so this is primarily for the no-handler case.
-
Pos getVoicePosition(int channel, int noteNumber) const#
Current position of a voice sounding (
channel,noteNumber), or the origin if none is.Best-effort audio-thread snapshot for tests / metering.
-
interfaceObject &onNoteEvent(void (*func)(bool noteOn, float *noteNumber, float *velocity))#
Install an audio-thread note-rewrite hook, or clear it with
nullptr.funcruns on the audio thread for every NOTE_ON / NOTE_OFF, before keyboard bookkeeping and allocation, and may rewrite*noteNumberand*velocityin place — the classic use is transposition, retuning, velocity curves or note filtering. Only a free function or captureless lambda is accepted (it carries no heap closure). It must obey the same real-time rules as a voiceprocess(): no allocation, no locks, no blocking. See docs/design/synth_core.md §7.
-
int getNumVoices() const#
Total number of allocated voices across all groups.
-
bool isValid() const#
Whether this interface has a live implementation.
Private Members
-
implementationObject *pimpl = nullptr#
-
std::string _name#
-
std::uint64_t _busHandles[6] = {0, 0, 0, 0, 0, 0}#
-
bool _busOwner = {false}#
Friends
- friend class YSE::sound
- friend class SYNTH::implementationObject
-
interfaceObject()#
-
class interfaceObject#
-
namespace SYNTH
The voice model#
Derive from YSE::SYNTH::dspVoice to define a custom voice, or
use one of the built-in voices below.
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
class dspVoice : public YSE::DSP::dspSourceObject#
Base class for user-subclassable synthesiser voices.
Derive from
dspVoiceto define what a single note sounds like. The base is aDSP::dspSourceObject— it already provides thesamplesoutput buffers and theprocess(SOUND_STATUS&)entry point the sound renderer needs — extended with the pieces the voice allocator relies on: aclone()prototype hook, atomic frequency / velocity / aftertouch inputs, and a default atomicfrequency()that stores the note as Hz.The reference implementation is
SYNTH::sineVoice(a sine shaped by an ADSR envelope). See docs/design/synth_core.md §3 for the full contract.Subclassed by YSE::SYNTH::fmVoice, YSE::SYNTH::samplerVoice, YSE::SYNTH::sineVoice, YSE::SYNTH::vaVoice
Public Functions
-
inline dspVoice(int outputChannels = 1)#
Construct a voice with
outputChannelsoutput buffers.
-
inline virtual ~dspVoice()#
-
virtual void process(SOUND_STATUS &intent) override = 0#
Fill
samplesfor one block.You must implement this.
intentis this voice’s ownSOUND_STATUS:SS_WANTSTOPLAYon note start,SS_WANTSTOSTOPon note release, etc. Honour it to drive your amplitude envelope, and settle it toSS_STOPPEDonce your release tail has finished so the allocator can free the slot.Runs on the audio thread. Must be allocation-free, lock-free and non-blocking.
-
virtual dspVoice *clone() = 0#
Return a new, fully-allocated heap instance of your derived type.
You must implement this.
The typical body is
return new MyVoice(*this);— a copy-construct. Every buffer, table and filter state the returned voice will touch inprocess()must already exist, soprocess()is thereafter allocation-free. Called only on the setup thread (never the audio thread); allocation here is fine.
-
inline virtual void frequency(Flt midiNote) override#
Set the note frequency from a MIDI note number.
Stored internally as Hz. Called by the allocator on NOTE_ON; a voice reads the result with
getFrequency()inprocess().
-
inline Flt getFrequency() const#
Current note frequency in Hz.
-
inline void velocity(Flt v)#
Set the note velocity, normalised to [0, 1].
-
inline Flt getVelocity() const#
Current note velocity in [0, 1].
-
inline Flt getAftertouch() const#
Current aftertouch pressure in [0, 1].
-
inline Flt getPitchWheel() const#
Current pitch-wheel position in [-1, 1] for this voice’s channel.
The keyboard state machine forwards the channel’s pitch-wheel value to every voice sounding on that channel (and primes a new voice with the current value on note start). How far a voice bends for a given wheel position — the bend range in semitones — is the voice’s concern; the core only delivers the normalised position. See docs/design/synth_core.md §5.
Protected Functions
-
inline dspVoice(const dspVoice &o)#
Copy the note atomics (and base output buffers) into a fresh, independently-owned voice.
Provided so a derived
clone()can be a plain copy-construct (new MyVoice(*this)):std::atomicmembers are not implicitly copyable, so the base supplies this. Each instance keeps its own atomics, so a clone shares no mutable note state with its prototype.
Private Members
-
aFlt _frequency = {440.f}#
-
aFlt _velocity = {0.f}#
-
aFlt _aftertouch = {0.f}#
-
aFlt _pitchWheel = {0.f}#
Friends
- friend class implementationObject
-
inline dspVoice(int outputChannels = 1)#
-
class dspVoice : public YSE::DSP::dspSourceObject#
-
namespace SYNTH
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
class sineVoice : public YSE::SYNTH::dspVoice#
Reference voice — a sine oscillator gated by an ADSR envelope.
Pitched from
getFrequency(), scaled bygetVelocity()and shaped by a classic attack / decay / sustain / release envelope keyed off theSOUND_STATUSintent:SS_WANTSTOPLAYrestarts the oscillator phase and the envelope attack, then settles the intent toSS_PLAYING.SS_PLAYINGholds the sustain level.SS_WANTSTOSTOPenters the release tail; when the tail reaches zero the voice settles the intent toSS_STOPPED.
Everything is allocated up front (in the constructor and the envelope setters, both off the audio thread) so
process()andclone()’s copy stay allocation-clean on their respective threads.Public Functions
-
sineVoice(int outputChannels = 1)#
Construct a mono (or
outputChannels-wide) sine voice with a default ADSR.
-
inline Flt attack() const#
Current attack time in seconds.
-
inline Flt decay() const#
Current decay time in seconds.
-
inline Flt sustain() const#
Current sustain level in [0, 1].
-
inline Flt release() const#
Current release time in seconds.
-
virtual void process(SOUND_STATUS &intent) override#
Render one block, honouring and settling
intent.Audio-thread only.
Protected Functions
Private Functions
-
void buildEnvelope()#
-
class sineVoice : public YSE::SYNTH::dspVoice#
-
namespace SYNTH
Virtual-analog voice#
A multi-oscillator virtual-analog + wavetable voice with a Moog-style ladder filter and a live-editable shared patch.
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
Enums
-
class vaADSR#
Lightweight real-time linear ADSR.
A minimal segment-based envelope used for both the amplitude and filter contours. Unlike
DSP::ADSRenvelope(breakpoint list + an allocatinggenerate()), this recomputes its per-sample slopes from the current patch times every block, so envelope times are live-settable with no allocation — exactly what the sharedvaParamsrequires. Internal tovaVoice.Public Functions
-
void configure(Flt attack, Flt decay, Flt sustain, Flt release, Flt sampleRate)#
(Re)compute per-sample slopes from times (seconds) + sustain.
-
void gateOn()#
Begin the attack segment (from the current level — no click).
-
void gateOff()#
Begin the release segment from the current level.
-
Flt tick()#
Advance one sample and return the new level.
-
void advance(int n)#
Advance
nsamples (block-rate use).
-
inline Flt level() const#
Current level in [0, 1].
-
inline bool idle() const#
Whether the envelope has returned to rest after a release.
-
inline void reset()#
Reset to rest (level 0).
Private Types
-
void configure(Flt attack, Flt decay, Flt sustain, Flt release, Flt sampleRate)#
-
class vaParams#
Live-settable patch shared by every voice of one synth.
A
vaParamsis the “sound” — all continuous parameters are atomics read on the audio thread, so they can be tweaked from a control thread while voices play, glitch-free (no allocation, no locks). All voices of a synth share onevaParams(the prototype’s, carried acrossclone()by shared pointer), while each voice keeps its own independent oscillator phase, filter state and envelope state.Retain the shared pointer (
vaVoice::patch()) if you want to keep editing the patch after the prototype has been handed tosynth::addVoicesand destroyed.The wavetable bank (used by
VA_WAVETABLEmode) is built once and is intended to be populated before playback vialoadWavetable— like voice allocation, table (re)building is a setup-thread operation, not an audio-thread one. The morph position is the live control.Public Functions
-
vaParams()#
Construct a sensible default patch (osc 1 saw, gentle filter).
-
void loadWavetable(int slot, const std::vector<Flt> &cycle)#
Install a single-cycle waveform into bank slot
slot.cycleis one period of a normalised waveform (any length — AKWF tables are 600 samples). Slots beyond the current bank size are appended. Setup-thread only — this allocates/reshapes table storage and must not be called while the voice is rendering.
-
inline int wavetableCount() const#
Number of tables currently in the morph bank.
Public Members
-
aFlt wavetablePosition#
Morph position in [0, 1] for VA_WAVETABLE.
-
aFlt cutoff#
Base cutoff in Hz.
-
aFlt resonance#
Resonance in [0, 1].
-
aFlt keyTracking#
Key-follow in [0, 1] (0 = fixed, 1 = full tracking).
-
aFlt filterEnvAmount#
Filter-envelope depth in octaves (may be negative).
-
aFlt filterVelAmount#
Velocity → cutoff depth in octaves at full velocity.
-
aFlt ampAttack#
-
aFlt ampDecay#
-
aFlt ampSustain#
-
aFlt ampRelease#
-
aFlt ampVelAmount#
Velocity → amplitude amount in [0, 1].
-
aFlt filterAttack#
-
aFlt filterDecay#
-
aFlt filterSustain#
-
aFlt filterRelease#
-
aInt lfoType#
DSP::LFO_TYPE shape.
-
aFlt lfoRate#
LFO rate in Hz.
-
aFlt lfoToPitch#
LFO → pitch depth in semitones.
-
aFlt lfoToCutoff#
LFO → cutoff depth in octaves.
-
aFlt lfoToWavetable#
LFO → wavetable-position depth in [0, 1].
-
aFlt gain#
Master voice gain in [0, 1].
Public Static Attributes
-
static const int kNumOsc = 3#
Number of oscillators per voice.
-
vaParams()#
-
class vaVoice : public YSE::SYNTH::dspVoice#
Virtual-analog + wavetable synthesiser voice.
Derives
SYNTH::dspVoiceand consumes the synth-core voice contract (intent-driven lifecycle,clone()prototype, atomic note inputs). All tone parameters live in a sharedvaParamspatch; construct one prototype, dial in the patch, then hand it tosynth::addVoices— the clones share the patch and stay editable throughpatch().Everything the audio thread needs is allocated up front (tables in the patch, per-voice buffers/filter/LFO in the constructor), so
processandclone’s copy stay allocation-free on their respective threads.Public Functions
-
vaVoice(int outputChannels = 1)#
Construct a voice with a fresh default patch.
-
inline std::shared_ptr<vaParams> patch() const#
The shared, live-settable patch.
Retain to keep editing after the prototype is gone.
-
virtual void process(SOUND_STATUS &intent) override#
Render one block, honouring and settling
intent.Audio-thread only.
Protected Functions
Private Functions
-
Flt renderOsc(int index, Dbl phase, Flt pulseWidth, Flt wtPos)#
Private Members
-
UInt noiseState#
-
vaVoice(int outputChannels = 1)#
-
class vaADSR#
-
namespace SYNTH
SFZ sampler voice#
Loads an SFZ instrument (region map + resident PCM) and plays it as a multi-layer sampler voice. See Loading SFZ instruments and DX7 banks.
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
struct residentSample#
One fully-resident, de-duplicated sample (all channels in RAM).
Built once on the setup / slow-pool thread by
samplerInstrument::loadand immutable thereafter, so any number of voices read it concurrently on the audio thread without synchronisation (spec §10). Parallel tosfzInstrument::samples(index == sampleIndex).Public Members
-
std::vector<DSP::fileBuffer> channels#
One buffer per source channel.
-
long frames = 0#
Frame count (per channel).
-
Flt sampleRateAdjustment = 1.0f#
fileRate / deviceRate (spec §6).
-
bool silence = false#
sample=*silence — produces silence, no PCM.
-
bool loaded = false#
PCM decoded (or silence), ready to render.
-
std::vector<DSP::fileBuffer> channels#
-
class samplerConfig#
Chainable single-sample convenience facade (spec §11).
Sugar that builds a one-region instrument without an
.sfzfile. It emits the same flattened region model the parser produces, so asamplerConfigsampler behaves identically to the equivalent hand-written one-region file.Public Functions
-
inline samplerConfig &name(const char *n)#
Instrument label (identification only; not an SFZ opcode).
-
inline samplerConfig &file(const char *f)#
Absolute path to the sample file (
sample=).
-
inline samplerConfig &channel(U8 c)#
MIDI channel for addVoices (0 = omni).
Not an SFZ opcode.
-
inline samplerConfig &root(U8 rootNote)#
Root note — the key that plays the sample untransposed.
-
inline samplerConfig &range(U8 low, U8 high)#
Playable key range (also the addVoices window).
-
inline samplerConfig &envelope(Flt attack, Flt release, Flt maxLength)#
Amplitude envelope: attack / release (seconds) and a one-shot length cap (seconds; clamps
endwhen the region does not loop).
-
inline const std::string &name() const#
-
inline U8 channel() const#
-
inline U8 low() const#
-
inline U8 high() const#
-
bool build(samplerInstrument &inst) const#
Build the one-region model this facade describes into
instand decode its sample.Setup thread only. Returns true on success.
-
inline samplerConfig &name(const char *n)#
-
class samplerInstrument#
The shared, playable SFZ instrument: region table + resident PCM.
All voices cloned from one prototype share one
samplerInstrument(likevaParamsforvaVoice): the immutable region table and PCM are read-only on the audio thread, while the small cross-note round-robin and choke coordination state is touched only inside the audio-thread render pass (single-threaded — plain ints, no atomics, no locks).Public Functions
-
bool load()#
Decode every unique sample into RAM.
Setup / slow-pool thread only — never the audio thread. Returns true if the instrument is playable (valid region table + at least one loaded sample).
-
inline bool valid() const#
Whether this instrument is playable (valid region table + at least one resident sample).
Computed, so a caller may also build the model + samples in place without going through
load.
-
uint32_t bumpChoke(int g)#
Bump the choke generation for group
g(a region fired it).Returns the new generation. Groups outside [1,127] clamp into range.
-
uint32_t chokeGen(int g) const#
Current choke generation for group
g(0 if never fired).
Public Members
-
std::vector<residentSample> samples#
Resident PCM, parallel to model.samples.
-
std::array<int, 128> seqCounter = {}#
Per-key round-robin hit counter (spec §4).
Read+incremented at the NOTE_ON edge inside process(); the region table stays immutable.
Private Members
-
std::array<uint32_t, kMaxChokeGroups> chokeGenerations = {}#
Private Static Attributes
-
static constexpr int kMaxChokeGroups = 128#
-
bool load()#
-
class samplerVoice : public YSE::SYNTH::dspVoice#
SFZ sampler voice — renders the shared region table for one note.
Load an instrument (
loadSFZ/configure) into the prototype, then hand it tosynth::addVoices; every clone shares the same immutable region table and resident PCM (instrument()) while keeping its own independent per-layer playback state.Public Functions
-
samplerVoice(int outputChannels = 1)#
Construct an empty mono (or wider) sampler voice.
-
bool loadSFZ(const std::string &path)#
Load and preload an
.sfzfile into this prototype.Setup thread only (parses + decodes off the audio thread). Returns true when the instrument is playable.
-
bool configure(const samplerConfig &cfg)#
Build this prototype from a
samplerConfigfacade.Setup thread only. Returns true when the instrument is playable.
-
inline std::shared_ptr<samplerInstrument> instrument() const#
The shared instrument (region table + resident PCM).
Retain to keep it alive alongside the prototype and its clones.
Attach an already-built instrument.
Clones made afterwards share it (the region table + PCM are never duplicated, spec §10). Setup thread only.
-
virtual void process(SOUND_STATUS &intent) override#
Render one block, honouring and settling
intent.Audio-thread only.
-
virtual dspVoice *clone() override#
Return a new voice sharing this voice’s instrument, fresh state.
Setup-thread only.
-
virtual void frequency(Flt midiNote) override#
Store the note frequency (Hz, base) and the raw MIDI note the region matcher needs.
Called by the allocator on NOTE_ON.
-
inline int getNote() const#
Current raw MIDI note number for region selection.
-
int activeLayers() const#
Number of layers currently sounding.
-
int layerRegion(int i) const#
Region-table index of sounding layer
i(-1 if none).
-
Flt layerGain(int i) const#
Constant NOTE_ON amplitude gain of sounding layer
i.
-
inline int lastHit() const#
The round-robin hit number chosen at the last NOTE_ON.
Protected Functions
-
samplerVoice(const samplerVoice &other)#
Copy-construct: share the instrument, reset per-voice state.
Private Functions
-
void startNote()#
-
void releaseNote()#
-
bool renderLayers(int blockLen)#
-
void applyChoke()#
Private Members
-
std::shared_ptr<samplerInstrument> inst#
-
aInt note_ = {60}#
-
int lastHit_ = 0#
-
bool chokeFading = false#
-
int chokeFadePos = 0#
-
int chokeFadeSamps = 1#
Private Static Functions
-
static Flt cubic(const Flt *d, long n, double pos, long loopStart, long loopEnd, bool looping)#
-
struct Layer#
Public Members
-
bool active = false#
-
bool finished = false#
-
int regionIndex = -1#
-
const Flt *ch[2] = {nullptr, nullptr}#
-
int numCh = 0#
-
long frames = 0#
-
double pos = 0.0#
-
double baseSpeed = 1.0#
-
long offset = 0#
-
long endFrame = 0#
-
bool looping = false#
-
long loopStart = 0#
-
long loopEnd = 0#
-
bool sampleDone = false#
-
Flt gain = 1.0f#
-
bool releasing = false#
-
int chokeGroup = 0#
-
int offBy = 0#
-
uint32_t offByBaseline = 0#
-
bool active = false#
-
samplerVoice(int outputChannels = 1)#
-
class sfzADSR#
Amplitude EG for one layer — allocation-free DAHDSR.
The spec (§8) maps
ampeg_*onto the engine’sDSP::ADSRenvelope, but that envelope’sgenerate()allocates, and a sampler voice is reused across notes that resolve to different regions with differentampeg_*values — so rebuilding it at NOTE_ON would allocate on the audio path. This lightweight envelope reconfigures its per-sample slopes with no allocation, exactly the reasonvaVoiceintroducedvaADSR. It adds the delay + hold stages the full DAHDSR set needs.Public Functions
-
void configure(Flt delay, Flt attack, Flt hold, Flt decay, Flt sustain, Flt release, Flt sr)#
(Re)configure from
ampeg_*times (seconds), sustain [0,1].
-
void gateOn()#
Begin at the delay/attack stage from silence.
-
void gateOff()#
Enter the release stage from the current level.
-
Flt tick()#
Advance one sample; return the new level.
-
inline bool atEnd() const#
Whether the release has finished (envelope at rest after gateOff).
-
inline bool active() const#
Whether the envelope is producing sound (past IDLE, before DONE).
-
inline void reset()#
Reset to rest.
Private Types
-
void configure(Flt delay, Flt attack, Flt hold, Flt decay, Flt sustain, Flt release, Flt sr)#
-
struct residentSample#
-
namespace SYNTH
FM voice and DX7 banks#
A DX7-class 6-operator FM voice driven by an YSE::SYNTH::fmPatch,
plus the SysEx importer that fills patches from a vintage DX7 bank dump.
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
class fmVoice : public YSE::SYNTH::dspVoice#
DX7-class 6-operator FM voice.
Construct one, dial in its
fmPatch(or load a DX7 voice into it via #177), then hand it tosynth::addVoices. Clones share the patch and stay editable throughpatch(). Patch edits take effect on the next note-on (the FM core bakes operator parameters at key-down, exactly like the hardware reacting to a program change between notes).Public Functions
-
fmVoice(int outputChannels = 1)#
Construct a voice with the built-in sine test patch.
-
~fmVoice() override#
-
inline std::shared_ptr<fmPatch> patch() const#
The shared, live-editable patch.
Retain to keep editing after the prototype is gone.
-
inline fmVoice &setPatch(const fmPatch &p)#
Overwrite the shared patch; applied on the next note-on.
-
virtual void process(SOUND_STATUS &intent) override#
Render one block, honouring and settling
intent.Audio-thread only.
Protected Functions
Private Functions
-
void startNote()#
-
fmVoice(int outputChannels = 1)#
-
class fmVoice : public YSE::SYNTH::dspVoice#
-
namespace SYNTH
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
struct fmOperator#
One DX7 operator’s parameters (21 fields).
Maps to bytes
[op*21 .. op*21+20]of the 156-byte unpacked voice. Operators are stored in DX7 voice order:op[0]is OP1 …op[5]is OP6, matching the algorithm routing tables in the ported core.Public Members
-
uint8_t egRate[4]#
Envelope rates R1..R4 (0..99). [+0..3].
-
uint8_t egLevel[4]#
Envelope levels L1..L4 (0..99). [+4..7].
-
uint8_t levelScaleBreakPoint#
Keyboard level-scaling break point. [+8].
-
uint8_t levelScaleLeftDepth#
Level scaling, left depth (0..99). [+9].
-
uint8_t levelScaleRightDepth#
Level scaling, right depth (0..99). [+10].
-
uint8_t levelScaleLeftCurve#
Left curve (0..3: -lin,-exp,+exp,+lin).[+11].
-
uint8_t levelScaleRightCurve#
Right curve (0..3). [+12].
-
uint8_t rateScaling#
Keyboard rate scaling (0..7). [+13].
-
uint8_t ampModSens#
Amplitude-modulation sensitivity (0..3). [+14].
-
uint8_t keyVelSens#
Key-velocity sensitivity (0..7). [+15].
-
uint8_t outputLevel#
Operator output level (0..99). [+16].
-
uint8_t oscMode#
0 = frequency ratio, 1 = fixed frequency. [+17]
-
uint8_t freqCoarse#
Coarse frequency (0..31). [+18].
-
uint8_t freqFine#
Fine frequency (0..99). [+19].
-
uint8_t detune#
Detune (0..14, 7 = centre). [+20].
-
uint8_t egRate[4]#
-
struct fmPatch#
A complete DX7 6-operator voice.
Plain data — no behaviour, no atomics — so it is trivially copyable and cheap to hand across the setup boundary.
fmVoicesnapshots one of these at construction and serialises it to the MSFA core on each note-on viatoUnpacked; the shared, live-editable copy the voice keeps is thefmVoicepatch (see fmVoice.hpp).Built-in test voices (defined in code; #176 acceptance).
Public Functions
Public Members
-
fmOperator op[6]#
OP1..OP6. [0..125].
-
uint8_t pitchEgRate[4]#
Pitch envelope rates R1..R4 (0..99). [126..129].
-
uint8_t pitchEgLevel[4]#
Pitch envelope levels L1..L4 (0..99). [130..133].
-
uint8_t algorithm#
FM algorithm (0..31; DX7 shows 1..32). [134].
-
uint8_t feedback#
Feedback amount (0..7). [135].
-
uint8_t oscKeySync#
Oscillator key sync (0/1). [136].
-
uint8_t lfoSpeed#
LFO speed (0..99). [137].
-
uint8_t lfoDelay#
LFO delay (0..99). [138].
-
uint8_t lfoPitchModDepth#
LFO pitch-mod depth PMD (0..99). [139].
-
uint8_t lfoAmpModDepth#
LFO amp-mod depth AMD (0..99). [140].
-
uint8_t lfoSync#
LFO key sync (0/1). [141].
-
uint8_t lfoWaveform#
LFO waveform (0..5: tri,saw-d,saw-u,sqr,sine,s&h). [142].
-
uint8_t pitchModSens#
Pitch-mod sensitivity (0..7). [143].
-
uint8_t transpose#
Transpose in semitones (0..48, 24 = none). [144].
-
char name[10]#
Voice name, ASCII, space-padded. [145..154].
-
uint8_t opEnabled#
Operator on/off bitmask (bit n = OPn+1); 0x3f = all on. [155].
-
fmOperator op[6]#
-
struct fmOperator#
-
namespace SYNTH
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
struct dx7Bank#
A parsed DX7 bank: the voices plus name lookup helpers.
A packed bulk dump yields 32 voices; a single-voice dump yields one. Plain data — safe to copy and hand across the setup boundary.
Public Functions
-
inline size_t size() const#
Number of voices in the bank.
-
inline bool empty() const#
True when no voice was parsed.
-
std::string name(size_t index) const#
Voice name at
index, trimmed of trailing spaces.- Returns:
The name, or an empty string if
indexis out of range.
-
int indexOf(const char *voiceName) const#
First voice whose (trimmed) name equals
voiceName.- Returns:
The voice index, or -1 if no voice matches.
-
inline size_t size() const#
-
class dx7SysEx#
Stateless DX7 SysEx parser.
Offline / setup-thread only.
Public Static Functions
-
static bool parse(const uint8_t *data, size_t length, dx7Bank &out)#
Parse a DX7 SysEx image from memory.
- Parameters:
data – Raw bytes (a
.syxfile image or an in-memory dump).length – Number of bytes at
data.out – Filled with the parsed voices on success; untouched on failure.
- Returns:
true on success. On failure logs
E_FILE_ERRORand returns false (bad header, wrong length, or checksum mismatch).
-
static bool loadBank(const char *path, dx7Bank &out)#
Load and parse a DX7
.syxfile.- Parameters:
path – UTF-8 path to the SysEx file.
out – Filled with the parsed voices on success.
- Returns:
true on success; on failure logs
E_FILE_ERROR(file not found / unreadable, or a parse error) and returns false.
-
static bool parse(const uint8_t *data, size_t length, dx7Bank &out)#
-
struct dx7Bank#
-
namespace SYNTH
Per-note 3D positioning#
Attach a YSE::SYNTH::positionHandler to give every voice its own
3D position and movement — the basis of the “swarm” effect. Steer the whole
swarm live with YSE::synth::handlerParam(), or place a single note
imperatively with YSE::synth::notePosition(). See
Per-note 3D: position handlers and swarms.
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
-
class positionHandler#
Base class for user-subclassable per-note position behaviours.
Derive from
positionHandlerto decide where every note of a synth lives and how it moves — a static offset, a random scatter, a swarm orbiting a moving centre — without writing engine code per note. The returnedPosis the voice’s position in the same coordinate frame as aYSE::soundposition; the engine feeds it straight into that voice’s panner (see docs/design/per_note_positioning.md §6/§7).Lifecycle (mirrors the voice slot, §8/§10/§11). Attach a prototype with
synth::positionHandler(proto). The engine clones it once per voice slot on the setup pool (viaclone()) and reuses that one instance for every note the slot ever plays. When the allocator lands a note on a slot it callsnoteOn(); every audio block until the voice’s release tail ends it callsupdate(); on the note-off edge it callsonRelease()once. The instance is never freed at note rate — it is re-seeded by the nextnoteOn().Stealing (§11). Because one instance is permanently paired with a slot and reused, a stolen slot simply gets a fresh
noteOn()for the new note. Therefore **noteOn()must establish the note’s COMPLETE initial state** (reset every phase / counter / RNG draw) — the instance may have just finished another note. Do not assume construction state.Real-time discipline.
noteOn(),update()andonRelease()run on the audio thread. They must not allocate, lock, block on I/O, or log — identical rules to a voiceprocess(). Allocate everything the hooks touch in the constructor /clone()(which run only on the setup pool). Any state shared with another thread must arrive through the handler-param block or be atomic.The shipped reference implementations are
SYNTH::staticHandler,SYNTH::randomSpreadHandlerandSYNTH::orbitHandler(the last is the swarm workhorse and the template for a custom handler).See also
YSE::SYNTH::orbitHandler The swarm reference handler.
See also
YSE::synth::positionHandler To attach a prototype.
Subclassed by YSE::SYNTH::orbitHandler, YSE::SYNTH::randomSpreadHandler, YSE::SYNTH::staticHandler
Public Functions
-
inline virtual ~positionHandler()#
-
virtual positionHandler *clone() = 0#
Return a new, fully-allocated heap clone of your derived type.
You must implement this.
The typical body is
return new MyHandler(*this);. Everything the hooks will touch must already exist in the returned object, so the hooks stay allocation-free. Called only on the setup pool (never the audio thread); allocation here is fine.
-
virtual Pos noteOn(const voiceContext &ctx) = 0#
A note begins on this handler’s slot.
Return its initial position. You must implement this.
Runs on the audio thread; allocation-free. Because the instance is reused across notes (and across voice steals), this hook must fully reinitialise every piece of per-note state it keeps.
-
virtual Pos update(const voiceContext &ctx, Flt delta) = 0#
Control-rate steer, called once per audio block while the note sounds (through its release tail).
Return the new position. You must implement this.
deltais the block’s duration in seconds (buffer length / samplerate), so advancing state byrate * deltais frame-rate independent. Runs on the audio thread; allocation-free.
-
inline virtual void onRelease(const voiceContext&)#
The note entered its release tail (key up / note-off edge).
Optional — the default is a no-op.
Position control CONTINUES through the tail (
update()keeps being called until the voice stops), so a handler that ignores release keeps moving. Override to change behaviour for the fade (e.g. drift outward, slow an orbit). Called exactly once, on the edge. Audio thread; allocation-free.
-
inline virtual ~positionHandler()#
-
class voiceContext#
Read-only view of one voice’s live note state, handed to a positionHandler on every hook.
The synth fills a fresh voiceContext on the stack for each hook call — it is never a heap object and the handler never owns it. Everything on it is a value or a same-thread read; nothing here allocates or locks. Read it to modulate position (e.g. velocity -> orbit radius, aftertouch -> swarm width, a control-change -> height); you cannot write note state through it.
Public Functions
-
inline Flt controller(int number) const#
Live value of control-change
numberon this voice’s channel, normalised to [0, 1].Out-of-range numbers read 0. This is the synth forwarding a controller to the handler — same-thread read, no atomics.
-
inline Flt handlerParam(int index) const#
Shared handler parameter
index, written bysynth::handlerParam()on the audio thread (§9).All of a synth’s live handlers read the same block, so it is the natural home for a steerable swarm centre / radius. Out-of-range indices read 0.
Public Members
-
Flt frequency = 0.f#
The sounding pitch, in Hz.
-
Flt velocity = 0.f#
Note-on velocity, normalised to [0, 1].
-
Flt aftertouch = 0.f#
Live aftertouch pressure for this voice, [0, 1].
-
Flt pitchWheel = 0.f#
Live pitch-wheel position for the channel, [-1, 1].
-
Int channel = 0#
MIDI channel that triggered the note (1..16).
-
Int note = 0#
MIDI note number.
Private Members
-
const Flt *controllers_ = nullptr#
-
const Flt *handlerParams_ = nullptr#
-
int numHandlerParams_ = 0#
Friends
- friend class implementationObject
-
inline Flt controller(int number) const#
-
class positionHandler#
-
namespace SYNTH
-
namespace YSE
Public API of libYSE — sound playback, mixing, and 3D positional audio.
Entry points:
YSE::System(lifecycle and audio device),YSE::Listener(3D origin),YSE::sound(a playable source),YSE::channel(mixing tree),YSE::reverb(positioned reverb zone),YSE::patcher(modular DSP graph),YSE::player(note sequencer). Sub-namespaces group domain-specific types:YSE::DSPfor signal processing,YSE::MIDIfor MIDI I/O,YSE::MUSICfor note / chord / motif primitives.Note
Apart from their constructors, the oscillator and
vcfclasses must only be invoked from inside a DSP callback /processbody.-
namespace SYNTH
Every subSystem consists out of several classes which are meant to work together: an interface, implementation, manager, message and a message enumeration.
The synth subsystem mirrors the sound subsystem one-for-one.
Enums
-
class orbitHandler : public YSE::SYNTH::positionHandler#
The swarm handler — each note orbits a shared, steerable centre.
This is the epic’s showcase and the template for a user handler. Each note gets a distinct starting phase (from its note number), a radius derived from velocity (and widened live by aftertouch), and advances its phase at
raterad/s. The centre is read live from handler-params 0..2, sosynth::handlerParam()recentres the whole swarm from the main thread with one bounded message. On release the orbit slows, so a released note keeps moving — audibly correct — through its decay tail.Public Functions
-
inline orbitHandler &radius(Flt r)#
Base orbit radius (added to the velocity-scaled term).
-
inline orbitHandler &velocityRadius(Flt r)#
Extra radius added at full velocity.
-
inline orbitHandler &aftertouchWiden(Flt frac)#
Fraction of extra radius added at full aftertouch (swarm widening).
-
inline orbitHandler &rate(Flt radiansPerSecond)#
Orbit angular speed in radians per second.
-
inline orbitHandler &height(Flt h)#
Vertical offset of the orbit plane from the centre.
-
inline orbitHandler &releaseSlow(Flt factor)#
Multiplier applied to
rateonce the note is released.
-
virtual positionHandler *clone() override#
Return a new, fully-allocated heap clone of your derived type.
You must implement this.
The typical body is
return new MyHandler(*this);. Everything the hooks will touch must already exist in the returned object, so the hooks stay allocation-free. Called only on the setup pool (never the audio thread); allocation here is fine.
-
virtual Pos noteOn(const voiceContext &ctx) override#
A note begins on this handler’s slot.
Return its initial position. You must implement this.
Runs on the audio thread; allocation-free. Because the instance is reused across notes (and across voice steals), this hook must fully reinitialise every piece of per-note state it keeps.
-
virtual Pos update(const voiceContext &ctx, Flt delta) override#
Control-rate steer, called once per audio block while the note sounds (through its release tail).
Return the new position. You must implement this.
deltais the block’s duration in seconds (buffer length / samplerate), so advancing state byrate * deltais frame-rate independent. Runs on the audio thread; allocation-free.
-
virtual void onRelease(const voiceContext &ctx) override#
The note entered its release tail (key up / note-off edge).
Optional — the default is a no-op.
Position control CONTINUES through the tail (
update()keeps being called until the voice stops), so a handler that ignores release keeps moving. Override to change behaviour for the fade (e.g. drift outward, slow an orbit). Called exactly once, on the edge. Audio thread; allocation-free.
Private Functions
-
Pos positionAt(const voiceContext &ctx, Flt phase) const#
Private Members
-
Flt radius_ = 1.f#
-
Flt velocityRadius_ = 2.f#
-
Flt aftertouchWiden_ = 1.f#
-
Flt rate_ = 2.f#
-
Flt height_ = 0.f#
-
Flt releaseSlow_ = 0.5f#
-
Flt phase_ = 0.f#
current orbit angle
-
Flt speed_ = 2.f#
current angular speed (rate_, halved on release)
-
Flt noteRadius_ = 1.f#
radius chosen for the current note
-
inline orbitHandler &radius(Flt r)#
-
class randomSpreadHandler : public YSE::SYNTH::positionHandler#
Scatters each note to a random point within
radiusof the shared centre, drawn once at note-on and held for the note.The draw uses a small deterministic PRNG seeded from
seed()plus a per-slot offset, so the same seed reproduces the same scatter every run (the basis of the seeded-trajectory tests). A voice steal re-draws innoteOn(), so a stolen slot re-randomises correctly.Public Functions
-
inline randomSpreadHandler &radius(Flt r)#
Radius of the spread sphere around the centre.
-
inline Flt radius() const#
-
inline randomSpreadHandler &seed(uint32_t s)#
Base RNG seed.
Each cloned slot derives a distinct stream from it, so the whole synth is reproducible for a given seed.
-
virtual positionHandler *clone() override#
Return a new, fully-allocated heap clone of your derived type.
You must implement this.
The typical body is
return new MyHandler(*this);. Everything the hooks will touch must already exist in the returned object, so the hooks stay allocation-free. Called only on the setup pool (never the audio thread); allocation here is fine.
-
virtual Pos noteOn(const voiceContext &ctx) override#
A note begins on this handler’s slot.
Return its initial position. You must implement this.
Runs on the audio thread; allocation-free. Because the instance is reused across notes (and across voice steals), this hook must fully reinitialise every piece of per-note state it keeps.
-
virtual Pos update(const voiceContext &ctx, Flt delta) override#
Control-rate steer, called once per audio block while the note sounds (through its release tail).
Return the new position. You must implement this.
deltais the block’s duration in seconds (buffer length / samplerate), so advancing state byrate * deltais frame-rate independent. Runs on the audio thread; allocation-free.
-
inline randomSpreadHandler &radius(Flt r)#
-
class staticHandler : public YSE::SYNTH::positionHandler#
Places every note at one fixed position.
The trivial default.
Ignores the shared centre and all live values — a genuinely static source. Use
synth::notePosition()for app-driven trajectories when no movement behaviour is wanted.Public Functions
-
inline staticHandler &position(const Pos &p)#
Set the fixed position (same frame as a YSE::sound position).
-
virtual positionHandler *clone() override#
Return a new, fully-allocated heap clone of your derived type.
You must implement this.
The typical body is
return new MyHandler(*this);. Everything the hooks will touch must already exist in the returned object, so the hooks stay allocation-free. Called only on the setup pool (never the audio thread); allocation here is fine.
-
virtual Pos noteOn(const voiceContext &ctx) override#
A note begins on this handler’s slot.
Return its initial position. You must implement this.
Runs on the audio thread; allocation-free. Because the instance is reused across notes (and across voice steals), this hook must fully reinitialise every piece of per-note state it keeps.
-
virtual Pos update(const voiceContext &ctx, Flt delta) override#
Control-rate steer, called once per audio block while the note sounds (through its release tail).
Return the new position. You must implement this.
deltais the block’s duration in seconds (buffer length / samplerate), so advancing state byrate * deltais frame-rate independent. Runs on the audio thread; allocation-free.
-
inline staticHandler &position(const Pos &p)#
-
class orbitHandler : public YSE::SYNTH::positionHandler#
-
namespace SYNTH