simvx.core.audio_synth¶
Procedural audio synthesis: oscillators, envelopes, AudioSynth.
Pure-numpy DSP for generating audio at runtime without external assets.
The common pattern is “bake then play”: build a synth, render a short
clip into an AudioStream, and play it through any AudioStreamPlayer
on either backend (desktop / web).
Quick start::
from simvx.core import AudioSynth, Oscillator, ADSR, AudioStreamPlayer
synth = AudioSynth()
synth.add(
Oscillator.sine(440.0),
envelope=ADSR(attack=0.01, decay=0.1, sustain=0.6, release=0.2),
)
pluck = synth.bake(duration=0.4) # → AudioStream
player = AudioStreamPlayer(stream=pluck)
self.add_child(player)
player.play()
Multiple sources mix together in one bake::
synth = AudioSynth()
synth.add(Oscillator.sine(220.0), gain=0.5) # root
synth.add(Oscillator.sine(330.0), gain=0.3) # fifth
synth.add(Oscillator.noise.white(), gain=0.1, pan=0.3) # snare hiss
chord = synth.bake(duration=1.0)
AudioSynth.bake() returns an AudioStream whose backend_data is a
float32 interleaved-stereo ndarray. Both MiniaudioBackend (native and
legacy) and WebAudioBackend accept that directly: no file I/O, no
extra serialization.
For live procedural synthesis with parameter control, see
AudioSynth.attach_to() (streams via backend.open_stream and feeds
chunks per frame).
Module Contents¶
Classes¶
Anything that produces mono float32 audio samples on demand. |
|
Uniform-distribution white noise. Deterministic via the bundled RNG seed. |
|
Approximate pink noise via the Voss-McCartney algorithm. |
|
Namespace for oscillator + noise constructors. |
|
Multiplier curve applied over the total duration of a baked clip. |
|
Attack / Decay / Sustain / Release. |
|
Straight-line ramp from |
|
Exponential curve: |
|
Per-source DSP filter applied after the source render + envelope. |
|
First-order one-pole low-pass. |
|
First-order one-pole high-pass. |
|
Composes audio sources into a bakeable / streamable synth. |
Data¶
API¶
- simvx.core.audio_synth.log¶
‘getLogger(…)’
- simvx.core.audio_synth.__all__¶
[‘AudioSource’, ‘Envelope’, ‘Filter’, ‘LowPass’, ‘HighPass’, ‘Oscillator’, ‘WhiteNoise’, ‘PinkNoise’…
- class simvx.core.audio_synth.AudioSource[source]¶
Bases:
abc.ABCAnything that produces mono float32 audio samples on demand.
Subclasses implement
render(cursor_samples, n_samples, sample_rate)wherecursor_samplesis the global sample offset (so periodic waves stay phase-continuous across multiple chunked renders). Instantiating an incomplete subclass raisesTypeError.- __slots__¶
()
- class simvx.core.audio_synth.WhiteNoise(seed: int | None = None)[source]¶
Bases:
simvx.core.audio_synth.AudioSourceUniform-distribution white noise. Deterministic via the bundled RNG seed.
Initialization
- __slots__¶
()
- class simvx.core.audio_synth.PinkNoise(seed: int | None = None)[source]¶
Bases:
simvx.core.audio_synth.AudioSourceApproximate pink noise via the Voss-McCartney algorithm.
Cheap to compute, sounds substantially warmer than white noise. Useful for ambient layers and percussion.
Initialization
- __slots__¶
()
- class simvx.core.audio_synth.Oscillator[source]¶
Namespace for oscillator + noise constructors.
Mirrors Web Audio’s
OscillatorNodewaveform set plus anoisesub-namespace for stochastic sources. All members are static: never instantiateOscillatoritself; call its classmethods directly::Oscillator.sine(440.0) Oscillator.square(220.0, duty=0.25) Oscillator.noise.white(seed=42)
- noise: ClassVar[simvx.core.audio_synth._NoiseFactory]¶
‘_NoiseFactory(…)’
- static sine(freq: float, *, phase: float = 0.0) simvx.core.audio_synth.AudioSource[source]¶
- static square(freq: float, *, duty: float = 0.5) simvx.core.audio_synth.AudioSource[source]¶
- static saw(freq: float) simvx.core.audio_synth.AudioSource[source]¶
- static triangle(freq: float) simvx.core.audio_synth.AudioSource[source]¶
- class simvx.core.audio_synth.Envelope[source]¶
Bases:
abc.ABCMultiplier curve applied over the total duration of a baked clip.
Envelopes don’t know about gate-on/off: they receive the full duration and lay out their shape inside it. For real-time note-on/note-off behaviour, render shorter clips and crossfade in user code (or stream via
AudioSynth.attach_to). Instantiating an incomplete subclass raisesTypeError.- __slots__¶
()
- class simvx.core.audio_synth.ADSR(*, attack: float = 0.01, decay: float = 0.05, sustain: float = 1.0, release: float = 0.05)[source]¶
Bases:
simvx.core.audio_synth.EnvelopeAttack / Decay / Sustain / Release.
attack,decay,releaseare in seconds;sustainis a level [0, 1] held for whatever time remains after the attack and decay portions consume their share oftotal_duration. If the total duration is too short to fit the full ADSR, the release portion truncates from the start of the release phase.Initialization
- __slots__¶
()
- class simvx.core.audio_synth.Linear(*, start: float = 1.0, end: float = 0.0)[source]¶
Bases:
simvx.core.audio_synth.EnvelopeStraight-line ramp from
starttoendover the whole duration.Initialization
- __slots__¶
()
- class simvx.core.audio_synth.Exponential(*, start: float = 1.0, end: float = 0.01, power: float = 1.0)[source]¶
Bases:
simvx.core.audio_synth.EnvelopeExponential curve:
start * (end/start)^(t/duration).startandendmust be strictly positive (exponential interpolation is undefined through zero). UseLinearfor fades to silence.Initialization
- __slots__¶
()
- class simvx.core.audio_synth.Filter[source]¶
Bases:
abc.ABCPer-source DSP filter applied after the source render + envelope.
Lighter than bus-level effects (
simvx.core.audio_effect.LowPassFilteretc.): these run in numpy insideAudioSynth.bake()andrender_chunk(), so the filter shape is baked into the resultingAudioStreamand travels with it through any backend.Use bus effects when you want the filter to apply to everything routed through a bus (mood-driven low-pass underwater scenes, sidechain compression). Use
Filterwhen you want a single source in anAudioSynthto have a specific filter shape baked in (e.g. q1k3’s filtered noise bursts for shotgun blasts). Instantiating an incomplete subclass raisesTypeError.- __slots__¶
()
- class simvx.core.audio_synth.LowPass(cutoff_hz: float)[source]¶
Bases:
simvx.core.audio_synth.FilterFirst-order one-pole low-pass.
a = exp(-2*pi*cutoff_hz / sample_rate)y[k] = a * y[k-1] + (1-a) * x[k]First-order: -6 dB/octave above cutoff. Use the bus-level
simvx.core.audio_effect.LowPassFilterfor steeper (2nd-order biquad) cuts.Initialization
- __slots__¶
()
- class simvx.core.audio_synth.HighPass(cutoff_hz: float)[source]¶
Bases:
simvx.core.audio_synth.FilterFirst-order one-pole high-pass.
a = exp(-2*pi*cutoff_hz / sample_rate)y[k] = a * (y[k-1] + x[k] - x[k-1])-6 dB/octave below cutoff. Use the bus-level
simvx.core.audio_effect.HighPassFilterfor steeper biquad cuts.Initialization
- __slots__¶
()
- class simvx.core.audio_synth.AudioSynth[source]¶
Composes audio sources into a bakeable / streamable synth.
Construct,
add()one or more(source, envelope, gain, pan)voices, thenbake(duration)to render anAudioStreamready to play.Initialization
- add(source: simvx.core.audio_synth.AudioSource, *, envelope: simvx.core.audio_synth.Envelope | None = None, filter: simvx.core.audio_synth.Filter | None = None, gain: float = 1.0, pan: float = 0.0) int[source]¶
Add a voice. Returns the voice id (index) for later mutation.
filteris an optional per-source DSP filter (e.g.LowPass,HighPass) applied after the source render and envelope but before gain / pan. For bus-wide effects useAudioBus.add_effect.
- property voices: list[simvx.core.audio_synth._Voice][source]¶
Read-only-ish view of the voice list. Mutate elements in place.
- set_param(voice_id: int, name: str, value: object) None[source]¶
Mutate a parameter on a voice’s source. Useful for live tweaks.
Looks up
nameon the source viasetattr. Common targets:freq,duty,phase. Voices with no such attribute raiseAttributeError.
- render_chunk(cursor_samples: int, n_samples: int, *, sample_rate: int = 48000, channels: int = 2, soft_clip: bool = True) numpy.ndarray[source]¶
Render
n_samplesof mixed output starting at the given cursor.Voices keep phase across consecutive calls (sources receive
cursor_samplesso periodic waves remain continuous), so this is the canonical “chunk this synth for streaming” API. Envelopes are not applied here: usebake()for one-shot baked clips where the envelope shape spans the whole clip.Returns a float32 interleaved buffer (
n_samples * channels).
- attach_to(player, *, chunk_seconds: float = 0.1, sample_rate: int = 48000, channels: int = 2)[source]¶
Drive
playerwith live synth output as long as the driver lives.Adds a small
_AudioSynthDrivernode as a child ofplayerwhich opens a streaming channel on the active audio backend and feeds chunks ofchunk_secondsworth of synth output every process tick.set_parammutations on the synth take effect at the start of the next chunk (so a 100 ms chunk has up to 100 ms parameter latency). Lowerchunk_secondsfor more responsive control at the cost of more per-frame work.Returns the driver node so the caller can
remove_childit to stop streaming.Backend support: works on all three backends. The native ma_engine path uses an
ma_pcm_rbring buffer (default 0.5 s); the legacy path appends to a Pythonbytearray; the web path posts to an AudioWorkletNode. Underrun is silent padding on all three.
- bake(duration: float, *, sample_rate: int = 48000, channels: int = 2, soft_clip: bool = True) simvx.core.audio.AudioStream[source]¶
Render the synth into an
AudioStreamof lengthdurationseconds.All voices mix into a single buffer; the result is stored as a float32 interleaved ndarray (
backend_data) on the returned AudioStream. Both desktop and web backends accept this format directly.soft_clip=True(default) clips the final mix to [-1, +1] so over-mixed voices don’t wrap. Set to False for clean overflow handling upstream (rare).