DSP#

Float-buffer storage (DspBuffer and its subclasses DrawableBuffer, FileBuffer and Wavetable, which mirror the C++ inheritance chain so the type system decides which operations a buffer supports) and chainable effect modules (DspObject, which flattens its hierarchy into named factory constructors and subclass-specific accessors).

Upstream: YSE::DSP (buffers), DSP modules and mixing effects.

Every buffer supports sample-wise math against another buffer (DspBuffer.addBuffer(), subBuffer, mulBuffer, divBuffer, over the shorter of the two lengths), span copies (DspBuffer.copyFrom(), which throws on an out-of-range span) and DspBuffer.swap() between buffers of equal length. A FileBuffer also reports the native rate of the file it loaded (FileBuffer.fileSampleRate), which stays valid across a re-init at another session rate.

DspMultiBuffer bundles a copy of several buffers as the channels of one stereo or surround source for Sound.fromMultiBuffer().

DspBuffer#

class DspBuffer implements Finalizable

Single-channel float audio buffer — the fundamental container for sample-level audio data in libYSE.

The engine’s buffer classes form a single inheritance chain, mirrored here so the type system decides which operations a buffer supports:

  • [DspBuffer] ([DspBuffer.plain]) — bare storage; pass to [Sound.fromBuffer].

  • [DrawableBuffer] — adds [DrawableBuffer.drawLine] / [DrawableBuffer.drawFlat].

  • [FileBuffer] — a drawable buffer that adds [FileBuffer.loadFile] / [FileBuffer.saveFile].

  • [Wavetable] — a file buffer that adds the band-limited waveform generators (saw / square / triangle).

The C API cannot check a buffer’s subclass at runtime (it static_cast`s the handle and trusts the caller), so calling a subclass entry point on a narrower buffer would write through a mistyped pointer. Each wrapper class is only ever constructed around a handle from its own native constructor, so a [DspBuffer.plain] simply has no `drawLine.

Lifetime: the buffer must outlive any [Sound] created from it. The engine keeps a reference for as long as the sound is live.

Constructors#

DspBuffer.plain({required int length, int overflow = 0})

Plain single-channel buffer.

[overflow] is extra samples appended past the end, used by sources that need a wrap-around copy of the first samples at the tail (wavetables set this internally).

Properties#

Pointer<YseDspBuffer> get handle

Internal: native handle (used by Sound.fromBuffer).

int get length

Length in samples (frames).

int get lengthMs

Length in milliseconds at the engine sample rate.

double get lengthSec

Length in seconds at the engine sample rate.

bool get isSilent

Whether every sample is zero.

double get maxValue

Peak absolute sample value.

double get back

The last sample of the buffer.

double get sampleRateAdjustment
set sampleRateAdjustment(double value)

Sample-rate adjustment factor used by the engine to play this buffer at the correct speed when its native rate differs from the engine rate.

Methods#

void resize(int length, {double value = 0.0})

Resize the buffer. Newly added samples are filled with [value].

void fill(double value)

Fill every sample with [value].

void addScalar(double value)

Add [value] to every sample.

void mulScalar(double value)

Multiply every sample by [value].

void addBuffer(DspBuffer other)

Add [other] sample by sample: this[i] += other[i].

Works over the first min(length, other.length) samples; samples past the shorter length are left untouched. [other] may be this buffer.

void subBuffer(DspBuffer other)

Subtract [other] sample by sample: this[i] -= other[i].

Same span rules as [addBuffer].

void mulBuffer(DspBuffer other)

Multiply by [other] sample by sample: this[i] *= other[i].

Same span rules as [addBuffer].

void divBuffer(DspBuffer other)

Divide by [other] sample by sample: this[i] /= other[i].

Same span rules as [addBuffer]. Dividing by a zero sample gives 0, not infinity.

void copyFrom(DspBuffer src, {int srcPos = 0, int dstPos = 0, required int count})

Copy [count] samples from src[srcPos …] into this[dstPos …].

Both ranges must lie inside their buffer’s [length]. A range that runs past the end copies nothing and throws [YseException]; a negative argument throws [RangeError]. [src] may be this buffer only when the two ranges do not overlap ([ArgumentError] otherwise). The overflow tail (see [DspBuffer.plain]) is refreshed after the copy, so copying into the head of a [Wavetable] keeps its wrap-around samples in sync.

void swap(DspBuffer other)

Exchange the samples of this buffer and [other].

Both buffers must have the same [length]; otherwise nothing moves and [YseException] is thrown. Only the sample values move: each buffer keeps its own storage, Dart class and [sampleRateAdjustment]. Swapping a buffer with itself does nothing.

Float32List read({int offset = 0, int? count})

Copy [count] samples starting at [offset] into a new [Float32List].

int write(Float32List samples, {int offset = 0})

Copy [samples] into the buffer starting at [offset].

Returns the number of samples actually written (clamped to the buffer length).

void dispose()

Destroy the underlying native buffer and detach the finalizer.

DrawableBuffer#

class DrawableBuffer extends DspBuffer

A [DspBuffer] with drawing primitives ([drawLine], [drawFlat]).

Mirrors the engine’s drawableBuffer.

Constructors#

DrawableBuffer({required int length, int overflow = 0})

Drawable buffer of [length] samples (plus [overflow] tail samples).

Methods#

void drawLine({required int start, required int stop, required double startValue, required double stopValue})

Draw a linear ramp from (start, startValue) to (stop, stopValue).

void drawFlat({required int start, required int stop, required double value})

Fill the range [start, stop] with [value].

FileBuffer#

class FileBuffer extends DrawableBuffer

A [DrawableBuffer] with built-in audio file load and save.

Mirrors the engine’s fileBuffer.

Constructors#

FileBuffer({required int length, int overflow = 0})

File buffer of [length] samples (plus [overflow] tail samples). Size it large enough for the file you intend to [loadFile].

Properties#

double get fileSampleRate

Native sample rate, in Hz, of the file this buffer was last loaded from with [loadFile]; 0 before a successful load.

Unlike [sampleRateAdjustment] (a ratio against the engine rate in effect at load time), this does not change when the engine is re-initialised at another rate. To re-derive the playback speed against the live session, use fileSampleRate / System.instance.sessionSampleRate and assign it to [sampleRateAdjustment].

Methods#

void loadFile(String filename, {int channel = 0})

Load one channel from an audio file.

Throws [YseException] on failure (file missing, wrong channel, etc.).

void saveFile(String filename)

Save the contents to a mono 32-bit float WAV file.

[filename] is used verbatim (no extension is appended). The file is written at [fileSampleRate] when this buffer was loaded from a file, or at the engine sample rate otherwise.

Throws [YseException] when the file cannot be created, the buffer is empty, or a custom [BufferIO] backend is active: that backend is read-only, so saving refuses rather than bypass it. File I/O — don’t call it from a time-critical path.

Wavetable#

class Wavetable extends FileBuffer

Pre-computed single-cycle wavetable. Populate it with [createSaw] / [createSquare] / [createTriangle].

Mirrors the engine’s wavetable, which derives from fileBuffer, so a wavetable can also be drawn into, loaded and saved.

Constructors#

Wavetable({required int length})

Wavetable of [length] samples.

Methods#

void createSaw({required int harmonics, required int length})

Fill the wavetable with a band-limited sawtooth wave.

void createSquare({required int harmonics, required int length})

Fill the wavetable with a band-limited square wave.

void createTriangle({required int harmonics, required int length})

Fill the wavetable with a band-limited triangle wave.

DspMultiBuffer#

class DspMultiBuffer implements Finalizable

A multichannel source buffer: one sample buffer per channel, for playing stereo or surround audio held in memory through [Sound.fromMultiBuffer].

Mirrors the engine’s MULTICHANNELBUFFER. The constructor copies each channel’s samples and [DspBuffer.sampleRateAdjustment], whatever its subclass, so the source buffers can be changed or disposed afterwards without affecting this one. channels[0] is the first output channel. Channels may differ in length; a sound plays the length of the shortest.

Lifetime: the engine reads this storage for as long as a sound plays it. A [Sound] built from it holds a Dart reference, so garbage collection never frees it under a live sound; don’t call [dispose] before disposing every sound made from it.

Constructors#

factory DspMultiBuffer(List<DspBuffer> channels)

Build a multichannel buffer from a copy of [channels], in order.

Throws [ArgumentError] when [channels] is empty and [YseException] when the engine refuses (for example a disposed channel buffer).

Properties#

Pointer<YseDspMultiBuffer> get handle

Internal: native handle (used by Sound.fromMultiBuffer).

int get channelCount

Number of channels; 0 after [dispose].

Methods#

void dispose()

Destroy the underlying native buffer and detach the finalizer.

Idempotent. Only call it once every sound made from this buffer has been disposed.

Effects chain with DspObject.link(), which inserts the new object between the head and its old successor. DspObject.next walks a chain: it reads the successor from the engine and hands back the very Dart object that was linked in (subclass intact), never a new wrapper.

DspObject#

class DspObject implements Finalizable

A chainable DSP effect module — wraps YSE::DSP::dspObject and its concrete subclasses (filters, delays, modulators).

Construct via the named factory matching the effect you need (e.g. [DspObject.lowpass], [DspObject.sweep], [DspObject.granulator]), then call the matching set* methods to configure parameters.

Inherited control surface ([bypass], [impact], [lfoType], [lfoFrequency], [link]) is available on every instance.

Attach to a [Sound] with [Sound.setDsp]. The engine holds a borrowed reference — the effect must outlive the sound (and per the YSE lifetime contract, also outlive the slow-pool delete tick that follows the sound’s destruction). The Dart side holds the attached head and every object [link]ed after it, so garbage collection never frees a chain under a live sound or channel.

Kind-specific accessors (e.g. [q], [sweepSpeed], [setDelayTap], [grainGain]) throw [YseException] when called on the wrong kind of effect. The underlying C entry points static_cast the handle without an RTTI check, so the wrapper checks the kind before calling into native code. Mix-grade modules ([Compressor], [ParametricEq], …) are typed subclasses, so their own accessors exist only on the right type.

Constructors#

factory DspObject.lowpass()

Low-pass filter.

factory DspObject.highpass()

High-pass filter.

factory DspObject.bandpass()

Resonant band-pass filter.

factory DspObject.sweep({SweepShape shape = SweepShape.saw})

Auto-wah / LFO-modulated resonant filter.

factory DspObject.basicDelay()

Three-tap delay line.

factory DspObject.lowpassDelay()

Three-tap delay with a low-pass filter in front (tape-style).

factory DspObject.highpassDelay()

Three-tap delay with a high-pass filter in front.

factory DspObject.phaser()

Four-stage all-pass cascade modulated by a triangle LFO.

factory DspObject.ringModulator()

Ring modulator — multiplies input by an internal sine carrier.

factory DspObject.difference()

FM difference-tone synthesis (clipper + sine carrier).

factory DspObject.granulator({int poolSize = 48000 * 5, int maxGrains = 16})

Granular synthesis — pool of recent input, spawn short grains.

[poolSize] is the size of the circular input buffer in samples (default 5 s at 48 kHz, the libYSE 3 default session rate; size it from System.sessionSampleRate when the session runs at another rate). [maxGrains] is the maximum number of grains alive simultaneously.

factory DspObject.patcherInsert(Patcher patcher)

Wrap a [Patcher] graph as a chainable insert effect (upstream #370).

The insert feeds the host buffer to the graph’s ~adc objects and copies the summed ~dac output back over it, so [patcher] should contain at least one of each. The insert borrows [patcher] — it never owns or destroys it, so [patcher] must outlive this object. The insert holds a Dart reference to [patcher], so garbage collection never frees it under a live insert; don’t .dispose() it first. Drive it with the inherited control surface ([bypass], [impact], …).

Properties#

Pointer<YseDspObject> get handle

Internal: native handle (used by Sound.setDsp).

bool get bypass
set bypass(bool value)

Bypass this effect. Bypassed effects still run but pass input through unchanged.

double get impact
set impact(double value)

Wet/dry mix in [0.0, 1.0]. 0 is fully dry, 1 is fully processed.

LfoType get lfoType
set lfoType(LfoType value)

Built-in modulation LFO shape. [LfoType.none] disables modulation.

double get lfoFrequency
set lfoFrequency(double value)

Built-in modulation LFO frequency in Hz.

DspObject? get next

The object linked after this one in its processing chain, or null at the end of the chain — so a chain can be walked:

for (DspObject? fx = head; fx != null; fx = fx.next) { ... }

The result is the same Dart object that was passed to [link] (identical(a.next, b) after a.link(b)), with its subclass intact — never a new wrapper. It is read from the engine, so it reflects the engine’s own relinking: [link] inserts between this object and its old successor, and disposing a chained object splices it out.

Throws [StateError] if the engine reports a successor that no live [DspObject] wraps. Each object holds its linked successor, so this only happens for a chain rewired outside this API.

double get frequency
set frequency(double value)

Cutoff (lowpass/highpass) or centre (bandpass/sweep) frequency in Hz.

double get q
set q(double value)

Bandpass-only: filter resonance (Q factor).

Throws [YseException] on any other kind.

double get sweepSpeed
set sweepSpeed(double value)

Sweep-only: LFO speed in Hz. Throws [YseException] on any other kind.

int get sweepDepth
set sweepDepth(int value)

Sweep-only: depth as 0..100. Throws [YseException] on any other kind.

int get sweepCentre
set sweepCentre(int value)

Sweep-only: centre frequency as 0..100. Throws [YseException] on any other kind.

double get phaserRange
set phaserRange(double value)

Phaser-only: sweep range coefficient. Throws [YseException] on any other kind.

double get differenceAmplitude
set differenceAmplitude(double value)

Difference-only: carrier amplitude. Throws [YseException] on any other kind.

int get grainFrequency
set grainFrequency(int value)

Granulator: spawn rate in grains per second.

int get grainLength

Granulator: the base grain length in samples (the [samples] argument last passed to [setGrainLength], without the per-grain randomisation).

double get grainTranspose

Granulator: the base pitch shift (the [pitch] argument last passed to [setGrainTranspose], without the per-grain randomisation).

double get grainGain
set grainGain(double value)

Granulator: output gain.

Methods#

void link(DspObject? next)

Insert [next] after this object in the processing chain.

Passing null detaches this object’s forward edge, terminating the chain at this object (tail termination). This is what lets a chain be re-linked into a new order without a former non-tail element keeping a stale next that closes a cycle when the engine walks it (upstream yvanvds/yse-soundengine#391).

The engine only borrows linked objects. Each object holds a Dart reference to the one linked after it (following the engine’s own insert-between, unlink and splice-on-dispose rewiring), so a chain whose head is held — by a [Sound], a Channel or your own variable — keeps every link alive (#84).

void unlink()

Detach this object’s forward edge, terminating the chain here.

Convenience for link(null) — see [link]. The detached successor is no longer held by this object.

void setDelayTap(DelayTap tap, {required double timeMs, required double gain})

basicDelay / lowpassDelay / highpassDelay: configure one of three taps.

Throws [YseException] on any other kind.

double delayTime(DelayTap tap)

Current delay time of [tap] in milliseconds (delay kinds only).

double delayGain(DelayTap tap)

Current gain of [tap] (delay kinds only).

void setGrainLength({required int samples, int random = 0})

Granulator: grain length in samples. [random] adds variation around [samples].

void setGrainTranspose({required double pitch, double random = 0})

Granulator: pitch shift (1.0 = unchanged, 2.0 = octave up). [random] adds variation.

void dispose()

Destroy the underlying native effect and detach the finalizer.

A chained object is spliced out: its predecessor is relinked to its successor, and from then on holds that successor instead.

Mix-grade modules#

Channel-strip effects. Each is a DspObject subclass — construct it, then attach it to a channel insert (Channel.dsp) or a sound (Sound.setDsp()) exactly like the base modules. The inherited DspObject.impact sets the wet/dry balance, except on MorphingReverb and Underwater, whose balance is inherent in their own control (the morphed presets’ dry/wet, and the depth).

Underwater is the engine’s underwater treatment as an ordinary insert you can place anywhere in a chain; it is independent of the engine’s own instance driven by System.underwaterFx / System.underwaterDepth.

Compressor#

class Compressor extends DspObject

Feed-forward, stereo-linked dynamics compressor — a mix-grade [DspObject] for a channel insert or a send return.

Attach with Channel.dsp or [Sound.setDsp]; the inherited [impact] controls the wet/dry balance and [bypass] the compression. All setters take effect immediately; the engine ramps the internal gain to keep moves click-free.

Constructors#

factory Compressor()

Construct a compressor with the engine’s default curve.

Properties#

CompressorDetector get detector
set detector(CompressorDetector value)

Level-detector mode (peak vs. RMS).

double get threshold
set threshold(double db)

Threshold in dB below which no gain reduction is applied.

double get ratio
set ratio(double value)

Compression ratio (e.g. 4.0 is 4:1). 1.0 is no compression.

double get attack
set attack(double ms)

Attack time in milliseconds.

double get release
set release(double ms)

Release time in milliseconds.

double get makeup
set makeup(double db)

Make-up gain in dB applied after compression.

double get gainReductionDb

Read-only meter: the gain reduction (in dB, <= 0) applied to the last processed sample.

ParametricEq#

class ParametricEq extends DspObject

Four-band parametric EQ (low shelf, two peaks, high shelf) — a mix-grade [DspObject] for a channel insert or a send return.

Every parameter is addressed by an [EqBand]; a band with 0 dB [getGain] is flat (bypassed). Attach with Channel.dsp or [Sound.setDsp].

Constructors#

factory ParametricEq()

Construct a flat four-band parametric EQ.

Methods#

double getFrequency(EqBand band)

Centre/corner frequency of [band] in Hz.

void setFrequency(EqBand band, double hz)

Set the centre/corner frequency of [band] to [hz].

double getGain(EqBand band)

Gain of [band] in dB (0 = flat / band bypass).

void setGain(EqBand band, double db)

Set the gain of [band] to [db] (0 = flat / band bypass).

double getQ(EqBand band)

Q (bandwidth) of [band].

void setQ(EqBand band, double value)

Set the Q (bandwidth) of [band] to [value].

Chorus#

class Chorus extends DspObject

Chorus / flanger — one modulated-delay [DspObject] with a [mode] switch, for a channel insert or a send return.

[spread] fans a per-channel LFO phase offset for stereo width. Attach with Channel.dsp or [Sound.setDsp]; the inherited [impact] sets the wet/dry balance.

Constructors#

factory Chorus()

Construct a chorus module (defaults to [ChorusMode.chorus]).

Properties#

ChorusMode get mode
set mode(ChorusMode value)

Chorus vs. flanger character.

double get rate
set rate(double hz)

LFO rate in Hz.

double get depth
set depth(double value)

Modulation depth.

double get feedback
set feedback(double value)

Feedback amount (comb resonance; most audible in flanger mode).

double get spread
set spread(double value)

Stereo spread — the per-channel LFO phase offset.

PlateReverb#

class PlateReverb extends DspObject

Dattorro plate reverb — a mix-grade [DspObject] for a channel insert or a send return.

Distinct from the engine’s global spatial [Reverb]. impact(0.25) is a natural insert mix; impact(1) is fully wet for send-return use. Attach with Channel.dsp or [Sound.setDsp].

Constructors#

factory PlateReverb()

Construct a plate reverb with the engine’s default tail.

Properties#

double get decay
set decay(double value)

Tail decay (feedback) in [0.0, 1.0).

double get damping
set damping(double hz)

High-frequency damping corner in Hz.

double get predelay
set predelay(double ms)

Pre-delay before the tail begins, in milliseconds.

FeedbackDelay#

class FeedbackDelay extends DspObject

Recirculating feedback delay — a mix-grade [DspObject] for a channel insert or a send return.

A per-channel delay line with a damping low-pass in the feedback path and [crossfeed] between channel pairs for ping-pong echoes. impact(1) is echoes-only (send use). Attach with Channel.dsp or [Sound.setDsp].

Constructors#

factory FeedbackDelay()

Construct a feedback delay with the engine’s default timing.

Properties#

double get time
set time(double ms)

Delay time in milliseconds.

double get feedback
set feedback(double amount)

Feedback amount in [0.0, 1.0).

double get damping
set damping(double hz)

Damping low-pass corner in the feedback path, in Hz.

double get crossfeed
set crossfeed(double amount)

Cross-feed between the channel pair for ping-pong echoes.

MorphingReverb#

class MorphingReverb extends DspObject

The engine’s zone/global reverb core packaged as a chainable insert whose preset blend is a control input (upstream #369) — a mix-grade [DspObject].

Two endpoints, slot A and slot B, are each either a named [ReverbPreset] (via [presetA] / [presetB]) or a custom [ReverbPresetValues] (via [presetAValues] / [presetBValues]). [morph] linearly interpolates between them (0 = pure A, 1 = pure B, clamped to [0, 1]). Defaults are A = [ReverbPreset.generic], B = [ReverbPreset.hall], [morph] = 0.

[morph] is a control-rate signal: writes are allocation- and click-free, so any control thread may sweep it. Its wet/dry balance rides the morphed presets (each carries its own dry/wet), so the inherited [impact] is not applied — for send/return use, give both slots custom values with dry = 0, wet = 1. Attach with Channel.dsp or [Sound.setDsp].

Constructors#

factory MorphingReverb()

Construct a morphing reverb (A = generic, B = hall, morph = 0).

Properties#

set presetA(ReverbPreset value)

Set slot A from a named [ReverbPreset].

set presetB(ReverbPreset value)

Set slot B from a named [ReverbPreset].

ReverbPresetValues get presetAValues
set presetAValues(ReverbPresetValues values)

Slot A’s current parameter set.

ReverbPresetValues get presetBValues
set presetBValues(ReverbPresetValues values)

Slot B’s current parameter set.

double get morph
set morph(double value)

The morph control input: 0 = pure slot A, 1 = pure slot B, clamped to [0, 1].

Underwater#

class Underwater extends DspObject

The engine’s underwater treatment as an ordinary chainable insert — a mix-grade [DspObject] that mixes every channel toward a position-neutral, low-passed average as [depth] grows.

[depth] is the distance below the water surface in world units: <= 1 is transparent, 1..5 crossfades, >= 5 is fully neutral, and negative values clamp to 0. Writes are allocation- and wait-free.

The wet/dry balance is inherent in [depth], so the inherited [impact] is not applied by this module. It is independent of the engine’s own instance driven by [System.underwaterFx] / [System.underwaterDepth]: use this one to put the treatment anywhere in your own insert chain. Attach with Channel.dsp or [Sound.setDsp].

Constructors#

factory Underwater()

Construct an underwater insert at depth 0 (transparent).

Properties#

double get depth
set depth(double value)

Distance below the water surface, in world units (negative clamps to 0).