Table of Contents

Class Sound

Namespace
Sigil.Sound
Assembly
Sigil.Sound.dll

An immutable description of a sound. Composing a Sigil.Sound.Sound never renders anything and never mutates the original; every operator returns a new value, so a sound can be reused, shared and rendered many times. State only exists in the Sigil.Sound.IOperator produced by Sigil.Sound.Sound.Compile(Sigil.Sound.RenderContext).

public sealed class Sound

Inheritance

Inherited Members

Methods

Adsr(double, double, double, double, double?)

Shapes the amplitude with an attack-decay-sustain-release envelope. Times are in seconds. Inside a note voice (Play) the release starts when the note ends. Anywhere else the sound sustains until you give hold, or for as long as it plays.

public Sound Adsr(double attack = 0.01, double decay = 0.1, double sustain = 0.7, double release = 0.3, double? hold = null)

Parameters

attack double

Seconds to rise from silence to full level. Defaults to 0.01.

decay double

Seconds to fall from full level to sustain. Defaults to 0.1.

sustain double

The level held after the decay, from 0 to 1. Defaults to 0.7.

release double

Seconds to fall to silence once the release starts. Defaults to 0.3.

hold double?

Seconds from the start until the release begins. Omit it (the default) to follow the note, or to sustain.

Returns

Sound

Examples

Saw(220).Adsr(0.01, 0.2, 0.6, 1.5, hold: 3)

Adsr(Param, Param?, Param?, Param?, double?)

Applies an attack-decay-sustain-release envelope driven by constant or modulated parameters. Times are in seconds; sustain is a level from 0 to 1.

public Sound Adsr(Param attack, Param? decay = null, Param? sustain = null, Param? release = null, double? hold = null)

Parameters

attack Param

Seconds to ramp from 0 to peak. A number, or a sound/knob to modulate it.

decay Param?

Seconds to fall from peak to the sustain level. A number, or a sound/knob to modulate it. Defaults to 0.1.

sustain Param?

Level held until release begins, between 0 and 1. A number, or a sound/knob to modulate it. Defaults to 0.7.

release Param?

Seconds to fade to silence once released. A number, or a sound/knob to modulate it. Defaults to 0.3.

hold double?

Seconds from the start until the release begins. Omit it (the default) to follow the note, or to sustain.

Returns

Sound

Examples

Saw(220).Adsr(0.01, 0.2, 0.6, 1.5, hold: 3)

Balance(Param)

Balances the stereo field by turning down the side you pan away from; the other side is left alone. A mono source balanced to one side ends up silent on the other, as with a balance knob, and a stereo image is kept.

public Sound Balance(Param position)

Parameters

position Param

-1 is hard left, 0 the center (unchanged), 1 hard right. A number, or a sound or Fn(t => ...) to move it.

Returns

Sound

Examples

// A wide pad nudged left: the right side is turned down, the stereo image is kept.
var pad = (Saw(110) + Saw(165)).LowPass(1500).Chorus(rate: 0.5, voices: 4) * 0.3;
pad.Reverb().Balance(-0.3)

BandPass(Param?, Param?)

A band-pass filter: keeps a band around center and removes the rest.

public Sound BandPass(Param? center = null, Param? q = null)

Parameters

center Param?

Frequency in Hz at the middle of the band. A number, or a sound or Fn(t => ...) to sweep it. Defaults to 1000.

q Param?

Narrowness of the band: higher is narrower. Defaults to 0.7071.

Returns

Sound

Examples

Noise().BandPass(Sine(0.3).Range(300, 3000), q: 8)

BitCrush(Param?, Param?)

A lo-fi crusher: reduces the bit depth, and optionally the sample rate, for a gritty, aliased sound.

public Sound BitCrush(Param? bits = null, Param? rate = null)

Parameters

bits Param?

Bits of resolution: 16 is clean, 8 is classic lo-fi, 2 or 3 is brutal. Fractions work. Defaults to 8.

rate Param?

The reduced sample rate in Hz. 8000 sounds like an old sampler; omit it to keep the full rate, which is the default.

Returns

Sound

Examples

Saw(110).BitCrush(bits: 5, rate: 6000)

Chorus(Param?, Param?, Param?, int)

A chorus: thickens the sound by mixing in slightly detuned, slowly drifting copies spread across the stereo field.

public Sound Chorus(Param? rate = null, Param? depth = null, Param? mix = null, int voices = 3)

Parameters

rate Param?

How fast the copies drift, in Hz. Defaults to 0.8.

depth Param?

How far they drift, from 0 (none) to 1 (up to 12 ms). Defaults to 0.5.

mix Param?

0 is only the original, 1 only the copies. Defaults to 0.5.

voices int

How many copies, from 1 to 8. More is wider and thicker. Defaults to 3.

Returns

Sound

Examples

(Saw(110) + Saw(165)).LowPass(1500).Chorus(rate: 0.5, voices: 4) * 0.3

Comb(Param?, Param?, Param?)

A comb filter tuned to a pitch: it resonates at frequency and its multiples. Fed a short burst it rings like a plucked string (Karplus-Strong); fed a steady sound it adds a metallic ring.

public Sound Comb(Param? frequency = null, Param? feedback = null, Param? damping = null)

Parameters

frequency Param?

The pitch it resonates at, in Hz (20 and up). A number, or a sound or Fn(t => ...) to sweep it. Defaults to 440 (A4).

feedback Param?

How long it rings, from 0 (not at all) to 0.999 (very long). Defaults to 0.95.

damping Param?

How quickly the highs fade while it rings, from 0 (bright) to 0.99 (dull). Defaults to 0.2.

Returns

Sound

Examples

Noise().Adsr(0, 0.01, 0, 0.001).Comb(220, feedback: 0.995)   // a plucked string

Compile(RenderContext)

Builds a running instance of this sound for context. May allocate; the returned operator's Sigil.Sound.IOperator.RenderBlock(System.Span{Sigil.Sound.StereoFrame}) must not.

public IOperator Compile(RenderContext context)

Parameters

context RenderContext

Returns

IOperator

Remarks

A sound used in several places of one graph is built once and fans out to all of them, so var bus = ...; bus + bus.Delay(0.2) renders bus a single time. The result is System.IDisposable when anything inside it is; dispose it when you are done rendering.

Compress(Param?, Param?, Param?, Param?, Param?, Param?, Sound?, double)

A dynamic range compressor: turns down the parts above thresholdDb, so loud and quiet passages sit closer together, then adds makeup gain to bring the overall level back up. Use it to glue a drum bus together or to tame a lead, or with a sidechain to duck one sound under another.

public Sound Compress(Param? thresholdDb = null, Param? ratio = null, Param? attack = null, Param? release = null, Param? kneeDb = null, Param? makeupGainDb = null, Sound? sidechain = null, double lookahead = 0.003)

Parameters

thresholdDb Param?

The level, in dBFS, above which gain reduction starts. Full scale is 0 and quieter is more negative. Defaults to -18.

ratio Param?

How hard it compresses above the threshold: 4 means 4 dB of input above the threshold gives 1 dB out. 1 does nothing, and very high values act as a limiter. Defaults to 4.

attack Param?

Seconds to react once the level rises. Shorter catches more transients but can dull the attack. Defaults to 0.01.

release Param?

Seconds for the gain to recover after the level falls. Shorter pumps more audibly. Defaults to 0.1.

kneeDb Param?

Width of the soft transition around the threshold, in dB. 0 is a hard knee. Defaults to 6.

makeupGainDb Param?

Gain added after compression, in dB, to restore the level. Defaults to 0.

sidechain Sound?

A sound whose level drives the compressor instead of this one: a kick keyed into a pad ducks the pad. By default (null) it uses this sound.

lookahead double

Seconds the detector runs ahead of the audio, so the gain is already falling when a transient arrives: 0 to 0.02. It adds that much latency. Defaults to 0.003 (3 ms); use 0 for none.

Returns

Sound

Examples

// A drum bus: a kick and a hat glued together, pushed down 4:1 above -16 dB, then made up.
var kick = Sine(Fn(t => 50 + 90 * Math.Exp(-(t % 0.5) * 30)))   // the pitch drops every half second
    * Fn(t => Math.Exp(-(t % 0.5) * 7));                         // and the kick fades out
var hat = Noise().HighPass(6000) * Fn(t => Math.Exp(-(t % 0.25) * 40)) * 0.3;

(kick + hat).Compress(thresholdDb: -16, ratio: 4, attack: 0.005, release: 0.1, kneeDb: 6, makeupGainDb: 3)
// Ducking: while the kick sounds the pad is turned down, then it swells back (a pumping pad).
var kick = Sine(Fn(t => 50 + 90 * Math.Exp(-(t % 0.5) * 30)))   // the pitch drops every half second
    * Fn(t => Math.Exp(-(t % 0.5) * 7));                         // and the kick fades out
var pad = (Saw(110) + Saw(165)).LowPass(900);

(pad.Compress(thresholdDb: -20, ratio: 8, attack: 0.002, release: 0.25, sidechain: kick) * 0.3 + kick * 0.6).Limit()

Remarks

The detector follows the louder of the left and right channels, with separate attack and release times. A soft knee eases the compression in around the threshold so there is no audible corner. Lookahead delays the audio, so the gain is already falling when a transient arrives, at the cost of that much latency.

Exceptions

ArgumentOutOfRangeException

A parameter is outside its range, such as a ratio below 1 or a lookahead above 0.02 seconds.

Convolve(AudioBuffer, Param?, IrNormalize)

Convolves the sound with an impulse response: a recording of how a space, a speaker cabinet or any other linear system answers a single click. The result is the sound as if it were played through that system.

public Sound Convolve(AudioBuffer impulse, Param? mix = null, IrNormalize normalize = IrNormalize.Energy)

Parameters

impulse AudioBuffer

The impulse response, such as AudioBuffer.FromWav(...) or Samples.Load("cab.wav"). It is copied, so changing the buffer afterwards has no effect.

mix Param?

0 is only the dry sound, 1 only the convolved sound. A number, or a sound or Fn(t => ...) to move it. Defaults to 1, for a cabinet. Use about 0.3 for a reverb.

normalize IrNormalize

How the response is scaled before use. Defaults to Sigil.Sound.IrNormalize.Energy.

Returns

Sound

Examples

// A guitar cabinet, as it would load from a file: a short response whose resonance colours the sound.
// This one is built in code, a 1.2 kHz ring that fades in 12 ms. Listen for the saw going from buzzy to boxy.
const int rate = 48_000;
var frames = new StereoFrame[rate * 12 / 1000];
for (int i = 0; i < frames.Length; i++)
    frames[i] = StereoFrame.Mono((float)(Sin(2 * PI * 1200 * i / rate) * Exp(-i / 90.0)));
var cabinet = new AudioBuffer(frames, rate);

Saw(110).Distort(drive: 4).Convolve(cabinet) * 0.4
// A room from noise: a response that starts loud and dies away over 1.5 s is what a reverb tail is.
// Left and right get different noise, so the room is wide. Plucks go in, the room comes back with them.
const int rate = 48_000;
var random = new Random(7);
var frames = new StereoFrame[rate * 3 / 2];
for (int i = 0; i < frames.Length; i++)
{
    double fade = Exp(-4.6 * i / frames.Length);
    frames[i] = new StereoFrame((float)((random.NextDouble() * 2 - 1) * fade), (float)((random.NextDouble() * 2 - 1) * fade));
}
var room = new AudioBuffer(frames, rate);

var pluck = Saw(220).LowPass(2000).Adsr(0.002, 0.2, 0, 0.1) * Fn(t => t % 1 < 0.5 ? 1 : 0);
pluck.Convolve(room, mix: 0.4) * 0.5

Remarks

The convolver adds no latency, and its cost grows slowly with the length of the response (the first 128 taps run directly, the rest in partitions of an FFT), so a ten second cathedral is practical. A response longer than 10 seconds is cut there with a short fade-out. A response recorded at another sample rate is converted to the rate of the render when the sound is compiled.

A stereo response convolves the left channel with its left side and the right channel with its right side, so a stereo room keeps its image. A response whose two channels are identical (a mono file, for instance) is applied to both channels at half the cost.

The response is prepared when the sound is compiled, not while it plays, and the preparation is shared by every voice and every recompile of the same sound. Memory: the prepared response takes about 32 bytes per tap for a stereo response (16 for mono), and each running instance, such as each voice of a Play, adds the same again for its own signal state. A ten second stereo response is about 16 MB shared plus 16 MB per instance, so use shorter responses where many voices run at once. Pre-delay, a low or high cut and the width are not parameters here: compose them (Sigil.Sound.Sound.Delay(System.Nullable{Sigil.Sound.Param},System.Nullable{Sigil.Sound.Param},System.Nullable{Sigil.Sound.Param},System.Double), Sigil.Sound.Sound.HighPass(System.Nullable{Sigil.Sound.Param},System.Nullable{Sigil.Sound.Param}), Sigil.Sound.Sound.Spread(System.Nullable{Sigil.Sound.Param})) before or after.

Exceptions

ArgumentNullException

impulse is null.

ArgumentException

impulse has no frames.

Delay(Param?, Param?, Param?, double)

An echo: repeats the sound after time seconds, and repeats the repeats.

public Sound Delay(Param? time = null, Param? feedback = null, Param? mix = null, double maxTime = 2)

Parameters

time Param?

Seconds between repeats, up to maxTime. A number, or a sound or Fn(t => ...) to bend the pitch of the echoes. Defaults to 0.375, a dotted eighth at 120 bpm.

feedback Param?

How much of each echo feeds the next, from 0 (one echo) to 0.99 (nearly endless). Defaults to 0.4.

mix Param?

0 is only the original, 1 only the echoes. Defaults to 0.5.

maxTime double

The longest delay time in seconds, above 0 and at most 60; it sizes the buffer. Defaults to 2.

Returns

Sound

Examples

Saw(220).Adsr(0.01, 0.1, 0, 0.1).Delay(0.375, feedback: 0.5)

Exceptions

ArgumentOutOfRangeException

The maximum time is not above 0 or is over 60 seconds.

Distort(Param?, DistortionMode, Param?)

Distortion: pushes the sound through a curve that adds harmonics, from warm saturation to fuzz.

public Sound Distort(Param? drive = null, DistortionMode mode = DistortionMode.Soft, Param? level = null)

Parameters

drive Param?

How hard the sound is pushed into the curve. 1 is gentle; 10 is heavy. A number, or a sound or Fn(t => ...). Defaults to 4.

mode DistortionMode

The curve: Sigil.Sound.DistortionMode.Soft (default), Sigil.Sound.DistortionMode.Hard, Sigil.Sound.DistortionMode.Fold or Sigil.Sound.DistortionMode.Fuzz.

level Param?

The output level, to balance against the clean sound. Defaults to 1.

Returns

Sound

Examples

Saw(110).Distort(drive: 8, DistortionMode.Fold, level: 0.5)

Equalize(double, double, double)

A simple three-band tone control: a low shelf at 200 Hz, a broad bell at 1 kHz and a high shelf at 5 kHz. Bands at 0 dB are skipped, so an all-zero call returns this sound unchanged.

public Sound Equalize(double lowDb = 0, double midDb = 0, double highDb = 0)

Parameters

lowDb double

Boost or cut of the low shelf, in decibels. Defaults to 0.

midDb double

Boost or cut of the 1 kHz bell, in decibels. Defaults to 0.

highDb double

Boost or cut of the high shelf, in decibels. Defaults to 0.

Returns

Sound

Examples

// A tone control: a little more low end and treble, a little less mid.
(Saw(110) + Saw(220) * 0.5 + Noise() * 0.02).Equalize(lowDb: 2, midDb: -1.5, highDb: 3) * 0.3

FdnReverb(double, double, double, double, Param?, Param?)

A large algorithmic reverb built from eight feedback delay lines that mix into one another. It gives a denser, smoother tail than Sigil.Sound.Sound.Reverb(System.Double,System.Double,System.Double,System.Nullable{Sigil.Sound.Param}), and with sheen it can shimmer upward like a cathedral.

public Sound FdnReverb(double decay = 3, double size = 1, double damping = 0.35, double predelay = 0.01, Param? sheen = null, Param? mix = null)

Parameters

decay double

Seconds for the tail to fade by 60 dB, above 0 and at most 120. Defaults to 3.

size double

Scales the room: 0.5 is a small room, 1 a medium one, 4 a hall. Defaults to 1.

damping double

How fast the high frequencies die in the tail, from 0 (bright) to 0.99 (dark). Defaults to 0.35.

predelay double

Seconds between the dry sound and the first reflection, from 0 to 0.5. A longer predelay separates the sound from its room. Defaults to 0.01.

sheen Param?

How much of an octave-up copy of the tail is mixed in, from 0 (none) to 0.7 (very ethereal). The decay time stays the same. A number, or a sound to move it. Defaults to 0.

mix Param?

0 is only the dry sound, 1 only the reverb. Defaults to 0.3.

Returns

Sound

Examples

// A shimmering hall around a pad.
var pad = (Saw(110) + Saw(165) + Saw(220)).LowPass(1200) * 0.2;

pad.FdnReverb(decay: 5.0, size: 1.5, damping: 0.35, predelay: 0.03, sheen: 0.4, mix: 0.4)

Exceptions

ArgumentOutOfRangeException

A parameter is outside its range.

Feedback(Func<Sound, Sound>, double)

Feeds the sound's own output back through a chain you write, so echoes, shimmer and resonances that grow from what came before can be built from the same pieces as everything else. The result is this sound plus loop applied to that result, a moment late.

public Sound Feedback(Func<Sound, Sound> loop, double latencySeconds = 0.001)

Parameters

loop Func<Sound, Sound>

Builds what is fed back from the delayed output.

latencySeconds double

How late the loop sees the output, from 0.0001 to 0.05. Defaults to 0.001.

Returns

Sound

Examples

Saw(220).Adsr(0.005, 0.1, 0, 0.1).Feedback(echo => echo.Delay(0.35, mix: 1).LowPass(3000) * 0.6)

Remarks

The loop sees the output one fixed chunk (latencySeconds, 1 ms by default) late, so a loop with a delay of 0.35 s repeats every 0.35 s plus that chunk. The chunk is the same whatever block size the host renders in, so the sound does not change between hosts. Loops shorter than the chunk cannot be built this way; use Sigil.Sound.Sound.Comb(System.Nullable{Sigil.Sound.Param},System.Nullable{Sigil.Sound.Param},System.Nullable{Sigil.Sound.Param}).

Give the loop a gain below 1 so the repeats die away. A loop that keeps growing is held in check instead of running away: values past full scale are eased toward a ceiling of 2 and anything that is not a number is dropped, so a mistake gives a loud sound, not silence or a crash.

loop is called each time the sound is compiled, with a stand-in for the delayed output. It should only build a sound from it, and may use the stand-in more than once.

Fold(Param?, Param?, Param?)

A West Coast wavefolder: drives the sound past ±1 and folds it back, adding harmonics that sound bright, bell-like and metallic rather than clipped. Symmetry adds warm even harmonics on top of the odd ones.

public Sound Fold(Param? amount = null, Param? symmetry = null, Param? mix = null)

Parameters

amount Param?

How many times the sound folds: 1 is barely touched, 3 to 5 is typical, and higher gets very bright. A number, or a sound to sweep it. Defaults to 3.

symmetry Param?

An offset applied before folding, from -1 to 1. Zero keeps only odd harmonics; a small value brings in even ones. Defaults to 0.

mix Param?

0 is only the dry sound, 1 only the folded sound. Defaults to 1.

Returns

Sound

Examples

Sine(110).Fold(amount: Sine(0.2).Range(1.0, 7.0), symmetry: 0.25, mix: 1.0).LowPass(4000)

FollowEnvelope(Param?, Param?)

Tracks the loudness of this sound as a control signal from 0 to 1, so the sound can drive other parameters. Pair it with Sigil.Sound.Sound.Range(System.Double,System.Double) to map the level onto a filter cutoff, a gain or a pitch.

public Sound FollowEnvelope(Param? attack = null, Param? release = null)

Parameters

attack Param?

Seconds for the output to rise when the sound gets louder. Defaults to 0.01.

release Param?

Seconds for the output to fall when the sound gets quieter. Defaults to 0.1.

Returns

Sound

Examples

// An auto-wah: the loudness of the plucks opens the filter. Each pluck is brighter at its start.
var guitar = Saw(110) * Fn(t => Math.Exp(-(t % 0.6) * 5));
var level = guitar.FollowEnvelope(attack: 0.005, release: 0.08);

guitar.LowPass(level.Range(350, 3200), q: 4.0) * 0.5

FromOperator(Func<RenderContext, IOperator>, string?)

Wraps a custom operator. The factory is called once per Sigil.Sound.Sound.Compile(Sigil.Sound.RenderContext) and must return a fresh instance each time.

public static Sound FromOperator(Func<RenderContext, IOperator> factory, string? description = null)

Parameters

factory Func<RenderContext, IOperator>

Creates the running operator for a render context. It runs off the audio thread, so allocate here.

description string?

Text shown for the sound in Sigil.Sound.Sound.ToString. Defaults to "Custom".

Returns

Sound

Gain(Param)

Scales the amplitude.

public Sound Gain(Param gain)

Parameters

gain Param

The factor: a number, another sound (amplitude modulation) or Fn(t => ...). 1 leaves the level unchanged.

Returns

Sound

Examples

Saw(110).Gain(0.5)   // same as Saw(110) * 0.5

HighPass(Param?, Param?)

A resonant two-pole high-pass filter: removes what is below cutoff. Mirror image of Sigil.Sound.Sound.LowPass(System.Nullable{Sigil.Sound.Param},System.Nullable{Sigil.Sound.Param}).

public Sound HighPass(Param? cutoff = null, Param? q = null)

Parameters

cutoff Param?

Frequency in Hz below which the sound is attenuated. A number, or a sound or Fn(t => ...) to sweep it. Defaults to 100, which clears rumble and mud and leaves the body of most sounds alone.

q Param?

Resonance at the cutoff. 0.7 is flat; higher values ring. Defaults to 0.7071.

Returns

Sound

Examples

Noise().HighPass(Sine(0.2).Range(500, 6000), q: 3) * 0.3

HighShelf(Param?, Param?, Param?)

A high shelf: boosts or cuts everything above frequency by the same amount, for air or for taming hiss.

public Sound HighShelf(Param? frequency = null, Param? gainDb = null, Param? q = null)

Parameters

frequency Param?

The shelf's corner in Hz, roughly where the change is half of gainDb. A number, or a sound or Fn(t => ...). Defaults to 5000.

gainDb Param?

Boost (positive) or cut (negative) above the corner, in decibels. Defaults to 4, an audible lift.

q Param?

How steep the transition is: 0.7 is gentle and musical, higher is sharper. Defaults to 0.7071.

Returns

Sound

Examples

// A bright pad with the top end softened.
(Saw(220) + Saw(330)).HighShelf(8000, gainDb: -4) * 0.2

Limit(double, double)

A limiter: keeps the sound from passing a ceiling by turning it down just enough, then lets the level recover. Put it last in a chain so a loud mix cannot clip.

public Sound Limit(double ceiling = 0.95, double release = 0.08)

Parameters

ceiling double

The highest level that may come out, above 0 and up to 1. Defaults to 0.95.

release double

Seconds for the level to recover after a peak. Defaults to 0.08.

Returns

Sound

Examples

(Saw(110) + Saw(165) + Saw(220)).Limit()

LowPass(Param?, Param?)

A resonant two-pole low-pass filter. cutoff is in Hz.

public Sound LowPass(Param? cutoff = null, Param? q = null)

Parameters

cutoff Param?

Frequency in Hz above which the sound is attenuated. A number, or a sound or Fn(t => ...) to sweep it. Defaults to 1000.

q Param?

Resonance at the cutoff. 0.7 is flat; higher values ring. Defaults to 0.7071.

Returns

Sound

Examples

Saw(110).LowPass(Sine(0.25).Range(200, 2000), q: 4)

LowShelf(Param?, Param?, Param?)

A low shelf: boosts or cuts everything below frequency by the same amount, for bass weight or thinning.

public Sound LowShelf(Param? frequency = null, Param? gainDb = null, Param? q = null)

Parameters

frequency Param?

The shelf's corner in Hz, roughly where the change is half of gainDb. A number, or a sound or Fn(t => ...). Defaults to 200.

gainDb Param?

Boost (positive) or cut (negative) below the corner, in decibels. Defaults to 4, an audible lift.

q Param?

How steep the transition is: 0.7 is gentle and musical, higher is sharper. Defaults to 0.7071.

Returns

Sound

Examples

// A bass with a little more weight below 120 Hz.
Saw(55).LowPass(900).LowShelf(120, gainDb: 3) * 0.4

Notch(Param?, Param?)

A notch filter: removes a narrow band around center and keeps the rest.

public Sound Notch(Param? center = null, Param? q = null)

Parameters

center Param?

Frequency in Hz at the middle of the notch. A number, or a sound or Fn(t => ...) to sweep it. Defaults to 1000.

q Param?

Narrowness of the notch: higher is narrower. Defaults to 0.7071.

Returns

Sound

Examples

Saw(110).Notch(1000, q: 4)

Pan(Param)

Places the sound in the stereo field with an equal-power law, so it keeps the same loudness wherever it sits. A stereo sound is folded to mono first; use Sigil.Sound.Sound.Balance(Sigil.Sound.Param) to keep a stereo image.

public Sound Pan(Param position)

Parameters

position Param

-1 is hard left, 0 the center (a mono source keeps its level), 1 hard right. A number, or a sound or Fn(t => ...) to move it.

Returns

Sound

Examples

Saw(110).Pan(Sine(0.3))   // drifts between the speakers

Peak(Param?, Param?, Param?)

A peaking (bell) equalizer band: boosts or cuts a band around frequency and leaves the rest of the spectrum alone. Chain several for a parametric EQ.

public Sound Peak(Param? frequency = null, Param? q = null, Param? gainDb = null)

Parameters

frequency Param?

Centre of the band in Hz. A number, or a sound or Fn(t => ...) to sweep it. Defaults to 1000.

q Param?

How narrow the band is: 0.7 is broad and musical, 3 to 10 is surgical. Defaults to 0.7071.

gainDb Param?

Boost (positive) or cut (negative) at the centre, in decibels. 0 changes nothing. Defaults to 4, an audible lift.

Returns

Sound

Examples

// A lead with a presence lift: the 2.5 kHz band is pushed up 4 dB.
Saw(220).LowPass(6000).Peak(2500, q: 3, gainDb: 4) * 0.3

Phaser(Param?, Param?, Param?, int, double, double)

A phaser: sweeps notches up and down the spectrum with a chain of all-pass filters, for a swirling, jet-like movement.

public Sound Phaser(Param? rate = null, Param? feedback = null, Param? mix = null, int stages = 4, double minFrequency = 400, double maxFrequency = 1500)

Parameters

rate Param?

Sweeps per second. Defaults to 0.4.

feedback Param?

Sharpens the notches, from 0 to 0.95. Negative values change the character. Defaults to 0.3.

mix Param?

0 is only the original, 1 only the filtered sound; around 0.5 gives the deepest notches. Defaults to 0.5.

stages int

All-pass stages, from 1 to 12; every two make one notch. Defaults to 4.

minFrequency double

The bottom of the sweep in Hz. Defaults to 400.

maxFrequency double

The top of the sweep in Hz. Defaults to 1500.

Returns

Sound

Examples

Saw(110).Phaser(rate: 0.2, feedback: 0.6, stages: 6)

PitchShift(Param?, Param?)

Shifts the pitch up or down without changing the speed, by moving every frequency in the sound's spectrum. Whatever shape the spectrum has moves with it, so formants follow the shift: a voice shifted up gets smaller, shifted down gets bigger, as when a tape is sped up or slowed. Shifted down, the top of the spectrum falls too, so a dive darkens as well as lowers.

public Sound PitchShift(Param? semitones = null, Param? mix = null)

Parameters

semitones Param?

How far to move the pitch: 12 is an octave up, -12 an octave down, 0 leaves it alone. Limited to ±24. A number, or a sound, Control or Fn(t => ...) to bend it; it is read once per frame, so a fast sweep steps every 10 ms. Defaults to 12, an octave up.

mix Param?

0 is only the original, 1 only the shifted sound. Use a middle value to add a harmony. Defaults to 1.

Returns

Sound

Examples

Saw(220).PitchShift(7, mix: 0.5)                 // the sound plus a fifth above it
// A two-second dive down an octave.
var pad = (Saw(220) + Saw(330)).LowPass(2000) * 0.2;

pad.PitchShift(Fn(t => -12 * Math.Min(t / 2, 1)))

Remarks

This is a phase-vocoder shifter that finds each spectral peak, moves it and the bins around it together, and keeps their phases in step. It is clean on steady and layered material (chords, pads, voices) and does best within about an octave either way. Sharp transients are softened a little by the analysis window.

The frame is about 40 ms (2048 samples at 44.1 and 48 kHz) and the sound comes out that much later than it went in; the mix dry signal is delayed to match. For a short-latency shifter, such as inside a feedback loop, shift in the time domain instead. A stereo sound is shifted channel by channel.

Range(double, double)

Maps a bipolar signal in [-1, 1] to [min, max]; handy for LFOs.

public Sound Range(double min = 0, double max = 1)

Parameters

min double

The value the signal's -1 maps to. Defaults to 0.

max double

The value the signal's +1 maps to. Defaults to 1, so with no arguments an LFO becomes a 0 to 1 control.

Returns

Sound

Examples

Sine(0.25).Range(200, 2000)   // sweeps between 200 and 2000, four seconds per cycle

Render(double, int, int, double)

Renders seconds of audio offline, beginning startSeconds into the sound. The skipped part is still run (and discarded) so oscillators, envelopes, delay lines and filters are in the state they would have reached by then, and the result matches that same stretch of a longer render from zero.

public AudioBuffer Render(double seconds, int sampleRate = 48000, int blockSize = 256, double startSeconds = 0)

Parameters

seconds double

How much audio to keep, in seconds.

sampleRate int

Sample rate in Hz.

blockSize int

Processing block size in frames. The skipped part is processed in the same block grid as a render from zero.

startSeconds double

How far into the sound to begin, in seconds. Defaults to 0 (the start).

Returns

AudioBuffer

RenderToWav(string, double, int, WavFormat, double)

Renders seconds of audio and writes it as a WAV file. Pass startSeconds to capture a stretch from well into the sound (see Sigil.Sound.Sound.Render(System.Double,System.Int32,System.Int32,System.Double)).

public void RenderToWav(string path, double seconds, int sampleRate = 48000, WavFormat format = WavFormat.Pcm16, double startSeconds = 0)

Parameters

path string
seconds double
sampleRate int
format WavFormat
startSeconds double

RenderToWav(Stream, double, int, WavFormat, double)

Renders seconds of audio and writes it as WAV to a stream (works without a file system). Pass startSeconds to capture a stretch from well into the sound (see Sigil.Sound.Sound.Render(System.Double,System.Int32,System.Int32,System.Double)).

public void RenderToWav(Stream stream, double seconds, int sampleRate = 48000, WavFormat format = WavFormat.Pcm16, double startSeconds = 0)

Parameters

stream Stream
seconds double
sampleRate int
format WavFormat
startSeconds double

Reverb(double, double, double, Param?)

A reverb: the sound as if played in a room. Built from parallel comb filters and all-pass filters (a Schroeder design), with a slightly different room for each ear.

public Sound Reverb(double size = 1, double decay = 2.5, double damping = 0.4, Param? mix = null)

Parameters

size double

Scales the room: below 1 is smaller, above 1 larger, up to 100. Defaults to 1.

decay double

Seconds for the tail to fade by 60 dB, above 0 and at most 120. Defaults to 2.5.

damping double

How fast high frequencies die in the tail, from 0 (bright) to 1 (dark). Defaults to 0.4.

mix Param?

0 is only the dry sound, 1 only the reverb. Defaults to 0.3.

Returns

Sound

Examples

Sine(440).Adsr(0.005, 0.2, 0, 0.1).Reverb(decay: 4, mix: 0.4)

Exceptions

ArgumentOutOfRangeException

The size is not above 0 or is over 100, the decay is not above 0 or is over 120 seconds, or the damping is outside 0 to 1.

Spread(Param?)

Stereo width: scales the difference between the left and right channels while keeping their total level. Narrowing collapses a stereo sound toward mono; widening spreads it out. Mono input stays mono.

public Sound Spread(Param? width = null)

Parameters

width Param?

0 is mono, 1 leaves the sound unchanged, and values above 1 widen it, up to 4. A number, or a sound or Fn(t => ...) to move it. Defaults to 1.5, a clearly wider image.

Returns

Sound

Examples

// Width 0 collapses a stereo sound to mono: how you keep the low end centered for club systems.
var bass = Saw(55).LowPass(300).Chorus(rate: 0.4, voices: 3) * 0.5;
bass.Spread(0.0)
// Width above 1 pushes the chorus voices further apart: listen on headphones.
var pad = (Saw(110) + Saw(165)).LowPass(1500) * 0.3;
pad.Chorus().Spread(1.8)

TapeDelay(Param?, Param?, Param?, Param?, Param?, double, bool)

A tape echo: repeats the sound, and each repeat is darkened and saturated as it feeds back, like a tape delay. Changing the delay time glides the pitch of the repeats, as a tape head does, instead of clicking.

public Sound TapeDelay(Param? time = null, Param? feedback = null, Param? damping = null, Param? drive = null, Param? mix = null, double maxTime = 2, bool pingPong = false)

Parameters

time Param?

Seconds between repeats, up to maxTime. A sound or Fn(t => ...) makes the time wander for warble and dub-style sweeps. Defaults to 0.375, a dotted eighth at 120 bpm.

feedback Param?

How much of each repeat feeds the next, from 0 (one echo) to 0.95 (a long, slowly dying trail). Defaults to 0.5.

damping Param?

The cutoff in Hz of the low-pass in the feedback loop. Lower values make each repeat darker. Defaults to 3000.

drive Param?

How hard each repeat is pushed into the tape saturation: 1 or more. 1 is gentle; 3 or more is heavy. Defaults to 1.

mix Param?

0 is only the original, 1 only the echoes. Defaults to 0.35.

maxTime double

The longest delay time in seconds, above 0 and at most 60; it sizes the buffer. Defaults to 2.

pingPong bool

When true, the repeats bounce between the left and right channels, so each echo lands on the opposite side. Defaults to false.

Returns

Sound

Examples

// Each repeat is darker and more saturated, and the wobbling delay time bends their pitch like a tape head.
var synth = Saw(220).LowPass(1500) * Fn(t => Math.Exp(-(t % 0.5) * 9));

synth.TapeDelay(time: Sine(0.2).Range(0.2, 0.4), feedback: 0.65, damping: 2800, drive: 1.4, mix: 0.35) * 0.4
// Echoes that bounce across the stereo field.
var pluck = Saw(330).LowPass(2500).Adsr(0.002, 0.15, 0, 0.1) * Fn(t => Math.Exp(-(t % 1) * 2));

pluck.TapeDelay(time: 0.375, feedback: 0.5, pingPong: true) * 0.5

Exceptions

ArgumentOutOfRangeException

The maximum time is not above 0 or is over 60 seconds, or a constant drive is below 1.

Through(Func<IOperator, IOperator>, string?)

Escape hatch to the operator layer: feeds this sound into an operator you write.

public Sound Through(Func<IOperator, IOperator> wrap, string? description = null)

Parameters

wrap Func<IOperator, IOperator>

Called once per compile with this sound's running operator; returns the operator that processes it. Create any state inside it so every compile starts fresh. Use Sigil.Sound.Operator.From(Sigil.Sound.BlockRenderer) to write one as a lambda.

description string?

Text shown for the result in Sigil.Sound.Sound.ToString. Defaults to a description built from the sound it wraps.

Returns

Sound

Examples

Saw(110).Through(input => Operator.From(block =>
{
    input.RenderBlock(block);
    for (int i = 0; i != block.Length; i++)
        block[i] = block[i] * 0.5f;
}))

ToString()

A description of how the sound is built, such as Saw(110).LowPass(800).

public override string ToString()

Returns

string

Vowel(Param?, Param?)

A vowel filter: four formant resonators that make a sound speak. The position morphs through A, E, I, O and U. It is good for talkbox leads, choir-like sweeps and humanizing a synth.

public Sound Vowel(Param? vowel = null, Param? resonance = null)

Parameters

vowel Param?

The position from 0 (A) through 0.25 (E), 0.5 (I), 0.75 (O) to 1 (U), with the vowels blended in between. A number, or a sound or Fn(t => ...) to talk. Values outside 0 to 1 are clamped. Defaults to 0 (A).

resonance Param?

How sharp the formants are, from 0 (soft and broad) to 1 (sharp and ringing). Defaults to 0.5.

Returns

Sound

Examples

Saw(110).Vowel(vowel: Sine(0.5).Range(0.0, 1.0), resonance: 0.8).Distort(drive: 1.5)

Operators

operator +(Sound, Param)

Adds a constant or a modulating signal to this sound; sound + 12 shifts a control signal up by 12.

public static Sound operator +(Sound a, Param offset)

Parameters

a Sound
offset Param

Returns

Sound

operator +(Param, Sound)

Adds a constant or a modulating signal to this sound.

public static Sound operator +(Param offset, Sound a)

Parameters

offset Param
a Sound

Returns

Sound

operator +(Sound, Sound)

Mixes two sounds by summing them.

public static Sound operator +(Sound a, Sound b)

Parameters

a Sound
b Sound

Returns

Sound

operator *(Sound, Param)

Scales a by a parameter (a constant, a function or another sound).

public static Sound operator *(Sound a, Param gain)

Parameters

a Sound
gain Param

Returns

Sound

operator *(Param, Sound)

Scales a by a parameter (a constant, a function or another sound).

public static Sound operator *(Param gain, Sound a)

Parameters

gain Param
a Sound

Returns

Sound

operator *(Sound, Sound)

Ring modulation: multiplies a by b channel by channel, left by left and right by right. A mono modulator (an LFO) scales both sides alike; to scale by the mono mix of a stereo sound, use a * (Param)b.

public static Sound operator *(Sound a, Sound b)

Parameters

a Sound
b Sound

Returns

Sound