Real-time Effects
Shiny.Audio can apply real-time DSP to the microphone (pitch, echo, reverb, filters and more) as
you capture or record. It’s all in the base Shiny.Audio package, so there is
nothing extra to install.
Why this is managed code
Section titled “Why this is managed code”The native capture APIs do not offer a consistent feature set. No platform supports pitch shift on capture at all; Windows and iOS offer echo and reverb; Android and Linux offer neither. On the other hand, every backend already normalizes capture to the same thing — 16 kHz mono PCM16 — so a managed DSP chain at that point behaves identically on iOS, Android, Windows, Linux and the browser, and is testable without a device.
| Pitch | Echo | Reverb | Filters | |
|---|---|---|---|---|
| iOS / macOS (AVAudioEngine) | ✅ | ✅ | ✅ | ✅ |
| Windows (AudioGraph) | ❌ | ✅ | ✅ | ✅ |
| Android (AudioRecord) | ❌ | ❌ | ❌ | ❌ |
| Linux (Pulse/PipeWire) | ❌ | ❌ | ❌ | ❌ |
| Browser (Web Audio) | ❌ | ✅ | ✅ | ✅ |
Controlling effects live
Section titled “Controlling effects live”This is the part worth getting right. An effect is an object you own — not a snapshot of settings passed at start. Build a chain, keep the references, and mutate them; the audio thread picks up each change on the next buffer.
using Shiny.Audio;
var chain = new AudioEffectChain();var pitch = chain.Add(new PitchShiftEffect { Semitones = 0 }); // Add returns the instancevar echo = chain.Add(new EchoEffect { DelayMs = 250, Mix = 0.35f, Enabled = false });
var stream = await audio.Source.StartCaptureAsync(new AudioCaptureOptions { Effects = chain });
// All of this applies mid-capture, from any thread — no restart, nothing to "apply":pitch.Semitones = 5; // change a valueecho.Enabled = true; // switch one effect onchain.Enabled = false; // master bypassThere are three levels of on/off:
| Control | Scope |
|---|---|
chain.Enabled |
Everything — master bypass |
effect.Enabled |
One effect in or out of circuit |
effect.Mix |
Wet/dry blend, on effects that have one |
Composition can change while audio flows too — Add and Remove publish a new array atomically, and
the audio thread snapshots it once per buffer, so it never sees a half-built chain.
Why it doesn’t click
Section titled “Why it doesn’t click”Two things would otherwise make live control unusable, and both are handled for you:
- Parameters are ramped, not stepped. Assigning a new value moves it across ~15 ms instead of jumping, so dragging a slider doesn’t put a discontinuity in the waveform.
- Bypass is crossfaded over ~12 ms rather than hard-switched, so flipping a switch mid-take is inaudible. An effect that has fully faded out is also reset, so switching it back on doesn’t replay a stale reverb tail from a minute ago.
Every parameter is a float and every setter clamps to its valid range, so you can bind a slider
directly without validating.
The effects
Section titled “The effects”new GainEffect { Gain = 1f }; // also GainDbnew NoiseGateEffect { ThresholdDb = -45f, AttackMs = 5f, ReleaseMs = 150f };new BiquadFilterEffect { Type = BiquadFilterType.LowPass, Frequency = 1000f, Q = 0.707f };BiquadFilterEffect.Telephone(); // 1.2 kHz band-passnew DistortionEffect { Drive = 8f, Mix = 1f };new RingModEffect { Frequency = 50f, Mix = 1f }; // 20–80 Hz = robot voicenew EchoEffect { DelayMs = 250f, Feedback = 0.35f, Mix = 0.35f };new ChorusEffect { RateHz = 0.8f, DepthMs = 6f, Mix = 0.5f, Feedback = 0f };new ReverbEffect { RoomSize = 0.6f, Damping = 0.4f, Mix = 0.35f };new PitchShiftEffect { Semitones = 0f }; // ±24ReverbEffect is a Freeverb — eight damped comb filters into four allpass diffusers, with the
classic tunings scaled to the session’s sample rate. PitchShiftEffect writes into a delay line at
normal speed while two read heads sweep it at the pitch ratio, crossfading as they wrap; that
crossfade is what changes pitch while holding duration exactly.
Presets
Section titled “Presets”var chain = AudioEffectPresets.Create(AudioEffectPreset.Robot);Robot, Chipmunk, DeepVoice, Cathedral, Telephone, Megaphone, Ensemble. A preset returns
an ordinary chain, so you can keep adjusting it:
var chain = AudioEffectPresets.Create(AudioEffectPreset.Robot);var ring = chain.Effects.OfType<RingModEffect>().First();ring.Frequency = 80f;Things to know
Section titled “Things to know”Latency. Everything except pitch shift is latency-free. PitchShiftEffect adds up to one
crossfade window (~50 ms), and beyond roughly ±7 semitones a voice starts to sound obviously
processed.
Cost. All of it is cheap at 16 kHz mono — comfortable on a Raspberry Pi.
Fidelity. Capture is 16 kHz mono, so reverb and pitch sound thinner than they would on full-bandwidth stereo. These are voice effects, not mastering tools.
Metering. When effects are set on AudioCaptureOptions, they run before the level is computed,
so IAudioSource.InputLevelChanged reflects the processed signal — a meter shows what you will
actually hear.
Where effects apply
Section titled “Where effects apply”Effects apply to IAudioSource capture (AudioCaptureOptions.Effects) and to
IAudioRecorder (AudioRecordingOptions.Effects), and to files you already have
through AudioEffectProcessor. The live monitor and duplex calls
don’t run an effect chain.


