Table of Contents

Class NoteStream

Namespace
Sigil.Sound
Assembly
Sigil.Sound.Notes.dll

Notes as they happen: a description of live notes (from a keyboard, a device, a pattern) and what is done to them on the way to an instrument or the next device. Where a Sigil.Sound.Pattern`1 is a score, a function of time, a stream is a performance: its notes start when a key goes down and end when it comes up, and each has an identity that every operator keeps.

public sealed class NoteStream

Inheritance

Inherited Members

Examples

var keys = new NoteInput();
// Every key you press sounds with a fifth above it, and twice more a beat later, softer.
var plucks = keys.Stream.Harmonize(0, 7).Echo(2, Beats.Quarter, 0.5).Play(v => Saw(v.Frequency).LowPass(2000).Adsr(0.003, 0.2, 0, 0.2));
keys.NoteOn("c3 e3");
plucks * 0.3

Remarks

A stream is immutable, like a Sigil.Sound.Sound: every operator returns a new one, and nothing runs until something plays it (Sigil.Sound.NoteStream.Play(System.Func{Sigil.Sound.Voice,Sigil.Sound.Sound},System.Int32)), sends it on (Sigil.Sound.NoteStream.Compile(Sigil.Sound.HostClock,System.Int32)) or reads it (Sigil.Sound.NoteStream.Chord(System.Double,Sigil.Sound.ChordLatch,Sigil.Sound.ChordSwitch), Sigil.Sound.NoteStream.Gate). Start from Sigil.Sound.NoteInput.Stream; pattern.ToStream() turns a score into one.

Notes always end. Whatever an operator does, the end of a note ends exactly the notes it made from it, even a function of your own (Sigil.Sound.NoteStream.Select(System.Func{Sigil.Sound.NoteEvent,Sigil.Sound.NoteEvent}), Sigil.Sound.NoteStream.Where(System.Func{Sigil.Sound.NoteEvent,System.Boolean}), Sigil.Sound.NoteStream.SelectMany(Sigil.Sound.NoteExpander)), and everything still sounding is ended when the stream is reset, replaced or disposed. Capacities are fixed (128 notes sounding per step, 512 events a block); past them a note's start is dropped and its end never sent, never the other way round.

Time. Operators that work in beats (Sigil.Sound.NoteStream.Delay(System.Double), Sigil.Sound.NoteStream.Echo(System.Int32,System.Double,System.Double), Sigil.Sound.NoteStream.Strum(System.Double)) use the tempo of the Sigil.Sound.HostClock of the input (or 120 bpm without one) and keep running while the transport is stopped. Events are quantized to the block they fall in, under 6 ms at the default block size.

Properties

Silence

A stream with no notes.

public static NoteStream Silence { get; }

Property Value

NoteStream

Methods

Chord(double, ChordLatch, ChordSwitch)

The chord the stream is playing, as a Sigil.Sound.Pattern`1 to write against: stream.Chord().Arp(ArpMode.Down) arpeggiates whatever chord arrives, and stream.Chord().Steps(Mini.Integers("7 6 5 4")) plays steps of it. The same choices as the AutoArp device decide what counts as a chord and when a new one takes over.

public Pattern<Chord> Chord(double settle = 0.035, ChordLatch latch = ChordLatch.Infinite, ChordSwitch switchOn = ChordSwitch.Beat)

Parameters

settle double

Notes that start within this many seconds of each other are one chord, so a strum or a source that releases and presses voice by voice never shows a half-changed chord. 0 to 1; defaults to 0.035 (35 ms), as the AutoArp device.

latch ChordLatch

Whether the chord outlives its keys. Defaults to keeping the last chord.

switchOn ChordSwitch

When a new chord takes over. Sigil.Sound.ChordSwitch.Cycle makes every bar-long event (a run, an arpeggio) use one chord from start to finish; Sigil.Sound.ChordSwitch.Beat (the default) lets it change on the beat; Sigil.Sound.ChordSwitch.Instant as soon as it settles. The first chord is taken at once, and so is every chord while the transport is stopped.

Returns

Pattern<Chord>

A live pattern: one event per cycle holding the chord, answering for the present whatever time it is asked about. It is empty before the first chord, and (with Sigil.Sound.ChordLatch.Gated) while no key is down. It cannot be written out with Sigil.Sound.NotePatterns.ToPattern(Sigil.Sound.Pattern{Sigil.Sound.Note},System.Int32), which throws.

Examples

// Whatever chord is held, a run through its tones: step 0 is the lowest tone, 1 the next, past the top it climbs an octave.
// Hold a different chord and the run follows it. (settle: 0 takes the chord at once, which a quick demo needs; the default is 35 ms.)
var keys = new NoteInput();
var chord = keys.Stream.Chord(settle: 0, switchOn: ChordSwitch.Instant);
var run = chord.Steps(Mini.Integers("0 1 2 3 4 3 2 1")).Transpose(12)
    .Play(v => Triangle(v.Frequency).LowPass(2400).Adsr(0.005, 0.15, 0, 0.2), bpm: 120);
keys.NoteOn("c3 e3 g3");
run * 0.4

Remarks

The chord is the set of keys down when the last note of a group has settled, sorted from low to high. Letting go of keys never changes it (with the default latch), so a chord released key by key keeps its shape. The pattern is caught up whenever it is queried, once per block by whatever plays it, and reads the time from Sigil.Sound.NoteInput.Clock.

Exceptions

ArgumentOutOfRangeException

The settle time is outside 0 to 1, or an option is not defined.

Compile(HostClock?, int)

Builds the stream so a host can run it block by block and read the notes that come out: the way to send a stream to a device's Notes out jack. Dispose the runner when done; it stops listening to the input.

public NoteStreamRunner Compile(HostClock? clock = null, int sampleRate = 48000)

Parameters

clock HostClock?

The clock the host advances, which gives beats their tempo and the grid its position. Null uses the Sigil.Sound.NoteInput.Clock of the input, and without one 120 bpm and a clock of the runner's own.

sampleRate int

Frames per second of the blocks passed to Sigil.Sound.NoteStreamRunner.Process(System.Int32).

Returns

NoteStreamRunner

Delay(double)

Plays every note later, start and end alike, so each keeps its length.

public NoteStream Delay(double beats)

Parameters

beats double

How much later, 0 to 64 beats (Sigil.Sound.Beats).

Returns

NoteStream

Examples

// The notes arrive half a beat late, at 120 bpm a quarter of a second.
var keys = new NoteInput();
var sound = keys.Stream.Delay(0.5)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3");
sound * 0.3

Exceptions

ArgumentOutOfRangeException

Outside 0 to 64.

Echo(int, double, double)

Repeats every note, each repeat softer and as long as the note itself.

public NoteStream Echo(int repeats, double beats, double decay = 0.6)

Parameters

repeats int

How many repeats, 1 to 16.

beats double

The time between repeats, above 0 up to 8 beats.

decay double

The velocity of each repeat as a fraction of the one before, 0 to 1.

Returns

NoteStream

Examples

// Each note comes back three times, a sixteenth apart, each a little softer.
var keys = new NoteInput();
var sound = keys.Stream.Echo(3, Beats.Sixteenth, 0.6)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3");
sound * 0.3

Exceptions

ArgumentOutOfRangeException

An argument is out of range.

Fold<TState>(TState, Func<TState, NoteEvent, TState>)

Folds the stream's events into a state with a function of your own and exposes it as a live pattern: one event per cycle holding the state as it is now. This is the reduce of the stream (a count of keys down, the last velocity, a chord of your own making).

public Pattern<TState> Fold<TState>(TState seed, Func<TState, NoteEvent, TState> step)

Parameters

seed TState

The state before the first event.

step Func<TState, NoteEvent, TState>

Called for every start and end with the state so far; returns the new state. Runs on the thread that queries the pattern.

Returns

Pattern<TState>

Type Parameters

TState

The state.

Examples

// The number of keys held is a control signal: the more keys, the brighter the sound.
var keys = new NoteInput();
var held = keys.Stream.Fold(0, (n, e) => e.IsOn ? n + 1 : n - 1);
var cutoff = held.Select(n => 300.0 + n * 700.0).ToSignal(smoothSeconds: 0.05);
keys.NoteOn("c3 e3 g3");
Saw(110).LowPass(cutoff, q: 2) * 0.3

Remarks

Like Sigil.Sound.NoteStream.Chord(System.Double,Sigil.Sound.ChordLatch,Sigil.Sound.ChordSwitch), a live pattern that cannot be written out. Graph-mode scripts cannot call this.

Gate()

A control signal that is 1 while any note of the stream sounds and 0 otherwise: it lets a held key open a filter, gate an effect or trigger an envelope elsewhere. It steps; smooth it with a lowpass if you need a ramp.

public Sound Gate()

Returns

Sound

Examples

// The gate is 1 while any key is down, so a held key opens a sound that would otherwise be shut.
var keys = new NoteInput();
var gate = keys.Stream.Gate();
keys.NoteOn("c3");
Saw(110).LowPass(1500) * gate * 0.3

Harmonize(params int[])

Plays each note and the notes semitones away from it: Harmonize(0, 7) is the note and its fifth, Harmonize(-12) only the note an octave down. Include 0 to keep the original.

public NoteStream Harmonize(params int[] semitones)

Parameters

semitones int[]

One to eight distances from the note.

Returns

NoteStream

Examples

// A major triad on every key: the note, a major third and a fifth above it.
var keys = new NoteInput();
var sound = keys.Stream.Harmonize(0, 4, 7)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3");
sound * 0.3

Exceptions

ArgumentException

None, or more than eight.

Harmonize(Scale, params int[])

Plays each note and the tones of scale degrees steps away from it (a diatonic harmony): in C major, Harmonize(scale, 0, 2) adds a third above every note. A note between two scale tones keeps its distance from the one below.

public NoteStream Harmonize(Scale scale, params int[] degrees)

Parameters

scale Scale

The scale the steps are counted in.

degrees int[]

One to eight steps from the note's own degree; 0 is the note, 2 a third above, -3 a fourth below.

Returns

NoteStream

Examples

// Diatonic triads: each key with the tones two and four steps above it in C major.
var keys = new NoteInput();
var sound = keys.Stream.Harmonize(Scale.Parse("c major"), 0, 2, 4)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("d3 g3");
sound * 0.3

Exceptions

ArgumentException

None, or more than eight.

Humanize(double, double, int)

Makes the playing a little less exact: each note starts later by up to timing beats and its velocity moves by up to velocity. The amounts depend only on the seed and the note, so the same performance humanizes the same way.

public NoteStream Humanize(double timing, double velocity = 0.1, int seed = 0)

Parameters

timing double

The most a note is late, 0 to 1 beat.

velocity double

The most a velocity changes either way, 0 to 1.

seed int

Another seed gives another, equally repeatable, feel.

Returns

NoteStream

Examples

// A chord that is not quite together: each note a little late and a little softer or louder, the same way every time.
var keys = new NoteInput();
var sound = keys.Stream.Humanize(0.15, 0.2, seed: 3)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3 g3 c4");
sound * 0.3

Exceptions

ArgumentOutOfRangeException

Outside the range.

Latch()

Keeps notes sounding after their keys come up, until the next group of keys goes down.

public NoteStream Latch()

Returns

NoteStream

Examples

// Each note of this tune lasts a quarter, but the latch holds it until the next note starts, so the tune is played legato.
Mini.Notes("c3 ~ e3 ~ g3 ~ e3 ~").ToStream().Latch()
    .Play(v => Triangle(v.Frequency).LowPass(2000).Adsr(0.01, 0.2, 0.7, 0.4)) * 0.3

Length(double)

Gives every note the same length, whatever the key does: it ends after beats, held or not.

public NoteStream Length(double beats)

Parameters

beats double

The length, above 0 up to 64 beats.

Returns

NoteStream

Examples

// Each note lasts exactly a quarter of a beat, however long the key is held.
var keys = new NoteInput();
var sound = keys.Stream.Length(0.25)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 g3");
sound * 0.3

Exceptions

ArgumentOutOfRangeException

Outside the range.

Merge(params NoteStream[])

This stream and the others as one. Each keeps its notes; their ids are made distinct so two streams can sound the same pitch at once and each ends its own.

public NoteStream Merge(params NoteStream[] others)

Parameters

others NoteStream[]

The streams to add, at least one.

Returns

NoteStream

Examples

// A note and the same note an octave down, as one stream.
var keys = new NoteInput();
var sound = keys.Stream.Merge(keys.Stream.Transpose(-12))
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3");
sound * 0.3

Mono(NotePriority)

One note at a time: of the keys held, only the one priority picks sounds, and it changes as keys go down and up.

public NoteStream Mono(NotePriority priority = NotePriority.Last)

Parameters

priority NotePriority

Which held key wins. Defaults to the last pressed.

Returns

NoteStream

Examples

// Of the keys held, only the lowest sounds.
var keys = new NoteInput();
var sound = keys.Stream.Mono(NotePriority.Lowest)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("e3 c3 g3");
sound * 0.3

Play(Func<Voice, Sound>, int)

Plays the notes of the stream: each start sounds a voice built from voice until its end. Like Sigil.Sound.NoteInput.Play(System.Func{Sigil.Sound.Voice,Sigil.Sound.Sound},System.Int32), but for notes that have been through operators. Its notes carry no bend, pressure or slide.

public Sound Play(Func<Voice, Sound> voice, int polyphony = 8)

Parameters

voice Func<Voice, Sound>

Describes the sound of one note from its Sigil.Sound.Voice signals. Called while the sound is built, not per note.

polyphony int

The most notes sounding at once, from 1 to 64. Defaults to 8.

Returns

Sound

Examples

// A voice for every note of the stream, here the keys with a fifth added.
var keys = new NoteInput();
var sound = keys.Stream.Harmonize(0, 7)
    .Play(v => Triangle(v.Frequency).LowPass(2200).Adsr(0.01, 0.2, 0.6, 0.4), polyphony: 6);
keys.NoteOn("a2 e3");
sound * 0.3

Scan<TState>(TState, Func<TState, NoteEvent, NoteEmitter, TState>)

Carries a state through the stream's events, with a function of your own that sees every start and every end and may start notes. The notes it makes end when the note they came from ends.

public NoteStream Scan<TState>(TState seed, Func<TState, NoteEvent, NoteEmitter, TState> step)

Parameters

seed TState

The state before the first event.

step Func<TState, NoteEvent, NoteEmitter, TState>

Called for each event with the state so far; returns the new state. Add notes to make to emit (only on a start).

Returns

NoteStream

Type Parameters

TState

What the function remembers.

Examples

// The second key held gets a fifth above it. The function counts the keys down and starts a note of its own.
var keys = new NoteInput();
var sound = keys.Stream.Scan(0, (down, e, emit) =>
{
    if (e.IsOn && down == 1)
        emit.Add(e.Pitch + 7, 0.6f);
    return e.IsOn ? down + 1 : down - 1;
})
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3 g3");
sound * 0.3

Select(Func<NoteEvent, NoteEvent>)

Changes each note with a function of your own. The function sees the note as it starts and returns the note to play; its Sigil.Sound.NoteEvent.Id and kind are ignored, so the engine still ends the note when the key comes up, and a result outside the MIDI range drops it. Everything else about a note is one expression away: n with { Velocity = 0.5f }, n.Transpose(7), n.Snap(scale), n.Detune(20).

public NoteStream Select(Func<NoteEvent, NoteEvent> map)

Parameters

map Func<NoteEvent, NoteEvent>

Called once per note, on the audio thread: it must not allocate, lock, or take long.

Returns

NoteStream

Examples

// The note an octave up when it is struck hard, and a fifth below it when it is not.
var keys = new NoteInput();
var sound = keys.Stream.Select(n => n.Velocity > 0.7f ? n.Transpose(12) : n.Transpose(-5))
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.4, 0.2));
keys.NoteOn("c3:0.9 e3:0.4");
sound * 0.3

Remarks

A host that runs scripts it did not write may accept only a body that is an expression over the note and arithmetic. The Sigil Workbench's Graph mode does, and refuses anything that calls other code. A function that throws stops the stream; the host decides what that means.

SelectMany(NoteExpander)

Turns each note into none, one or several with a function of your own: add the notes to make to emit. See Sigil.Sound.NoteStream.Select(System.Func{Sigil.Sound.NoteEvent,Sigil.Sound.NoteEvent}).

public NoteStream SelectMany(NoteExpander expand)

Parameters

expand NoteExpander

Called once per note, on the audio thread. At most Sigil.Sound.NoteEmitter.Capacity notes are taken.

Returns

NoteStream

Examples

// Each note also sounds an octave up, softer. A function of your own that makes two notes from one.
var keys = new NoteInput();
var sound = keys.Stream.SelectMany((n, emit) =>
{
    emit.Add(n);
    emit.Add(n.Pitch + 12, 0.4f);
})
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3");
sound * 0.3

Strum(double)

Spreads notes that start together (a chord) one after the other, in the order they came, each beats after the one before. Each keeps its length.

public NoteStream Strum(double beats)

Parameters

beats double

The gap between notes, above 0 up to 1 beat.

Returns

NoteStream

Examples

// A chord played one note at a time, a sixteenth apart, in the order the notes came.
var keys = new NoteInput();
var sound = keys.Stream.Strum(0.25)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3 g3 c4");
sound * 0.3

Exceptions

ArgumentOutOfRangeException

Outside the range.

Transpose(int)

Moves every note by semitones. A note that would leave the MIDI range is dropped.

public NoteStream Transpose(int semitones)

Parameters

semitones int

How far to move, up (positive) or down.

Returns

NoteStream

Examples

// Every key you press sounds a fifth higher.
var keys = new NoteInput();
var sound = keys.Stream.Transpose(7)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3");
sound * 0.3

Velocity(double)

Plays every note at the same velocity.

public NoteStream Velocity(double velocity)

Parameters

velocity double

Above 0 up to 1.

Returns

NoteStream

Examples

// Every note at the same soft velocity, however hard it was played.
var keys = new NoteInput();
var sound = keys.Stream.Velocity(0.4)
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3:1 e3:0.2");
sound * 0.3

Exceptions

ArgumentOutOfRangeException

Not above 0 and at most 1.

Where(Func<NoteEvent, bool>)

Keeps the notes a function of your own says yes to; a note it refuses has no end to send either. See Sigil.Sound.NoteStream.Select(System.Func{Sigil.Sound.NoteEvent,Sigil.Sound.NoteEvent}) for what the function may do.

public NoteStream Where(Func<NoteEvent, bool> keep)

Parameters

keep Func<NoteEvent, bool>

Called once per note, on the audio thread.

Returns

NoteStream

Examples

// Only the notes of D dorian, and only a third of them, picked the same way every time.
var keys = new NoteInput();
var dorian = Scale.Parse("d dorian");
var sound = keys.Stream.Where(n => n.InScale(dorian) && n.Chance(0.7))
    .Play(v => Square(v.Frequency).LowPass(1500).Adsr(0.005, 0.2, 0.4, 0.2));
keys.NoteOn("d3 e3 f3 g3 a3 b3 c4 f#3");
sound * 0.2

Operators

operator +(NoteStream, NoteStream)

The notes of both streams as one; the same as Sigil.Sound.NoteStream.Merge(Sigil.Sound.NoteStream[]).

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

Parameters

a NoteStream

The first stream.

b NoteStream

The second stream.

Returns

NoteStream

Examples

// Two streams added: the key itself, and a fifth above it.
var keys = new NoteInput();
var sound = (keys.Stream + keys.Stream.Transpose(7))
    .Play(v => Saw(v.Frequency).LowPass(1800).Adsr(0.005, 0.2, 0.5, 0.25));
keys.NoteOn("c3 e3");
sound * 0.3