From a13d8357dd6c69429f8f5c4a35daf019870cf24c Mon Sep 17 00:00:00 2001 From: Misha Nasledov Date: Sun, 4 Oct 2026 11:08:25 -0700 Subject: [PATCH 1/2] dsp: violins and horns, four players a note Each player is its own osc::blep with its own detune, drift, vibrato rate and entry time, panned to a seat across the stereo field. The violins share a body resonance at 290 Hz and 2.8 kHz; each horn scoops up into the note and brightens with velocity. --- dsp/horns.dsp | 286 ++++++++++++++++++++++++++++++++++++++++++++++++ dsp/violins.dsp | 256 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 542 insertions(+) create mode 100644 dsp/horns.dsp create mode 100644 dsp/violins.dsp diff --git a/dsp/horns.dsp b/dsp/horns.dsp new file mode 100644 index 00000000..9954b360 --- /dev/null +++ b/dsp/horns.dsp @@ -0,0 +1,286 @@ +# Horns -- a four-piece horn section, as on a funk or disco record. +# +# brass.dsp is one synth brass voice. This is four players on the note, +# and each does what a horn player does and a synth does not: comes in +# from under the pitch -- `Scoop' cents flat, rising over `Scoop Time' +# -- and gets brighter the harder it blows, its filter opening with its +# own envelope by `Bite' times the velocity. The brightness is a +# multiple of the note, so the timbre is the same up the range. +# +# THE SECTION IS LOOSE ON PURPOSE. Each player is `Detune' cents from +# the others and drifting, their scoops and entries are staggered by +# `Stagger', their vibratos run at different rates, and they stand +# across the stereo field by `Width'. A section that hit together to the +# sample would be one horn played loud. + +name "Horns"; +author "Misha Nasledov"; +description "Four brass players a note, each scooping up into it, brightening with velocity, entering a little apart and seated across the stereo field."; +category "Leads and stabs"; + + @detune = 6; + @detune.widget = 1; + @detune.min = 0; + @detune.max = 30; + @detune.label = "Detune (cents)"; + @detune.group = "Players"; + + @drift = 3; + @drift.widget = 1; + @drift.min = 0; + @drift.max = 20; + @drift.label = "Drift (cents)"; + @drift.group = "Players"; + + @stagger = 40 ms; + @stagger.widget = 1; + @stagger.min = 0 ms; + @stagger.max = 200 ms; + @stagger.label = "Stagger"; + @stagger.group = "Players"; + + @width = 0.8; + @width.widget = 1; + @width.min = 0; + @width.max = 1; + @width.label = "Width"; + @width.group = "Players"; + + @scoop = 40; + @scoop.widget = 1; + @scoop.min = 0; + @scoop.max = 200; + @scoop.label = "Scoop (cents)"; + @scoop.group = "Players"; + + @scooptime = 60 ms; + @scooptime.widget = 1; + @scooptime.min = 5 ms; + @scooptime.max = 400 ms; + @scooptime.label = "Scoop Time"; + @scooptime.group = "Players"; + + @bite = 8; + @bite.widget = 1; + @bite.min = 0; + @bite.max = 20; + @bite.label = "Bite"; + @bite.group = "Tone"; + + @vibrato = 8; + @vibrato.widget = 1; + @vibrato.min = 0; + @vibrato.max = 50; + @vibrato.label = "Vibrato (cents)"; + @vibrato.group = "Vibrato"; + + @vibrate = 5.2; + @vibrate.widget = 1; + @vibrate.min = 3; + @vibrate.max = 8; + @vibrate.label = "Vibrato Rate (Hz)"; + @vibrate.group = "Vibrato"; + + @vibdelay = 450 ms; + @vibdelay.widget = 1; + @vibdelay.min = 0 ms; + @vibdelay.max = 2000 ms; + @vibdelay.label = "Vibrato Delay"; + @vibdelay.group = "Vibrato"; + + @a = 25 ms; + @a.widget = 1; + @a.min = 1 ms; + @a.max = 1000 ms; + @a.label = "Attack"; + @a.group = "Envelope"; + + @d = 250 ms; + @d.widget = 1; + @d.min = 0 ms; + @d.max = 3000 ms; + @d.label = "Decay"; + @d.group = "Envelope"; + + @s = 0.75; + @s.widget = 1; + @s.min = 0.01; + @s.max = 1; + @s.label = "Sustain"; + @s.group = "Envelope"; + + @r = 160 ms; + @r.widget = 1; + @r.min = 70 ms; + @r.max = 3000 ms; + @r.label = "Release"; + @r.group = "Envelope"; + +node ionode { + channels = 2; + out0 = outl->out; + out1 = outr->out; + play = max(max(env1->play, env2->play), max(env3->play, env4->play)); +}; + +node freq misc::midi2freq { + note = ionode->note; +}; + +# Player 1. +node drift1 misc::drift { + rate = 0.3; depth = @drift; seed = ionode->note + 1000; +}; +node scoop1 env::ad { a = 0; d = @scooptime + @stagger * 0.0 * 0.5; }; +node vib1 misc::vibrato { + in = freq->out * exp2((drift1->out + @detune * -1.0 - + @scoop * scoop1->out) / 1200); + rate = @vibrate * 0.94; + depth = @vibrato; + delay = @vibdelay; + rise = @vibdelay; +}; +node osc1 osc::blep { freq = vib1->out; waveform = 0; phase = 0.0; }; +node fenv1 env::adsr { + a = @a + @stagger * 0.0; + d = @d; + s = 0.6; + r = @r; + trigger = ionode->trigger; +}; +node tone1 filt::svf { + in = osc1->out; + cutoff = min(freq->out * (1 + fenv1->out * @bite * ionode->velocity), 16000); + res = 0.2; +}; +node env1 env::adsr { + a = @a + @stagger * 0.0; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat1 mixer::pan { in = tone1->out_low * env1->out; pan = -0.8 * @width; }; + +# Player 2. +node drift2 misc::drift { + rate = 0.3; depth = @drift; seed = ionode->note + 2000; +}; +node scoop2 env::ad { a = 0; d = @scooptime + @stagger * 0.6 * 0.5; }; +node vib2 misc::vibrato { + in = freq->out * exp2((drift2->out + @detune * -0.33 - + @scoop * scoop2->out) / 1200); + rate = @vibrate * 1.03; + depth = @vibrato; + delay = @vibdelay; + rise = @vibdelay; +}; +node osc2 osc::blep { freq = vib2->out; waveform = 0; phase = 0.6; }; +node fenv2 env::adsr { + a = @a + @stagger * 0.6; + d = @d; + s = 0.6; + r = @r; + trigger = ionode->trigger; +}; +node tone2 filt::svf { + in = osc2->out; + cutoff = min(freq->out * (1 + fenv2->out * @bite * ionode->velocity), 16000); + res = 0.2; +}; +node env2 env::adsr { + a = @a + @stagger * 0.6; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat2 mixer::pan { in = tone2->out_low * env2->out; pan = -0.3 * @width; }; + +# Player 3. +node drift3 misc::drift { + rate = 0.3; depth = @drift; seed = ionode->note + 3000; +}; +node scoop3 env::ad { a = 0; d = @scooptime + @stagger * 0.25 * 0.5; }; +node vib3 misc::vibrato { + in = freq->out * exp2((drift3->out + @detune * 0.33 - + @scoop * scoop3->out) / 1200); + rate = @vibrate * 0.98; + depth = @vibrato; + delay = @vibdelay; + rise = @vibdelay; +}; +node osc3 osc::blep { freq = vib3->out; waveform = 0; phase = 0.25; }; +node fenv3 env::adsr { + a = @a + @stagger * 0.25; + d = @d; + s = 0.6; + r = @r; + trigger = ionode->trigger; +}; +node tone3 filt::svf { + in = osc3->out; + cutoff = min(freq->out * (1 + fenv3->out * @bite * ionode->velocity), 16000); + res = 0.2; +}; +node env3 env::adsr { + a = @a + @stagger * 0.25; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat3 mixer::pan { in = tone3->out_low * env3->out; pan = 0.3 * @width; }; + +# Player 4. +node drift4 misc::drift { + rate = 0.3; depth = @drift; seed = ionode->note + 4000; +}; +node scoop4 env::ad { a = 0; d = @scooptime + @stagger * 0.9 * 0.5; }; +node vib4 misc::vibrato { + in = freq->out * exp2((drift4->out + @detune * 1.0 - + @scoop * scoop4->out) / 1200); + rate = @vibrate * 1.07; + depth = @vibrato; + delay = @vibdelay; + rise = @vibdelay; +}; +node osc4 osc::blep { freq = vib4->out; waveform = 0; phase = 0.9; }; +node fenv4 env::adsr { + a = @a + @stagger * 0.9; + d = @d; + s = 0.6; + r = @r; + trigger = ionode->trigger; +}; +node tone4 filt::svf { + in = osc4->out; + cutoff = min(freq->out * (1 + fenv4->out * @bite * ionode->velocity), 16000); + res = 0.2; +}; +node env4 env::adsr { + a = @a + @stagger * 0.9; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat4 mixer::pan { in = tone4->out_low * env4->out; pan = 0.8 * @width; }; + +node suml math::add { + in0 = seat1->out0 + seat2->out0; + in1 = seat3->out0 + seat4->out0; +}; +node sumr math::add { + in0 = seat1->out1 + seat2->out1; + in1 = seat3->out1 + seat4->out1; +}; + +node outl math::mul { in0 = suml->out; in1 = 0.18; }; +node outr math::mul { in0 = sumr->out; in1 = 0.18; }; + +io ionode; diff --git a/dsp/violins.dsp b/dsp/violins.dsp new file mode 100644 index 00000000..c0e455e4 --- /dev/null +++ b/dsp/violins.dsp @@ -0,0 +1,256 @@ +# Violins -- four desks, each its own player. +# +# strings.dsp and section.dsp each make one voice sound like many with a +# chorus. This one is four: four sawtooth players on every note, each a +# few cents from the others by `Detune', each wandering by `Drift', each +# with a vibrato at its own rate that it leans into late, and each +# coming in `Stagger' after the one before -- bows do not touch the +# string at once. They sit across the stage by `Width', first desks +# left, seconds right. +# +# THE BODY IS SHARED. What makes a saw a violin is less the string than +# the box: a wooden resonance near 290 Hz and the bridge's hill around +# 2.8 kHz. Every instrument in a section has them, so they are applied +# once to each side of the summed section rather than per player, by +# `Body'. The tone filter before it tracks the note and opens with +# velocity, a harder bow being a brighter one. + +name "Violins"; +author "Misha Nasledov"; +description "Four detuned, drifting players a note, each with its own vibrato and entry, seated across the stereo field through a violin body."; +category "Strings and pads"; + + @detune = 6; + @detune.widget = 1; + @detune.min = 0; + @detune.max = 30; + @detune.label = "Detune (cents)"; + @detune.group = "Players"; + + @drift = 3; + @drift.widget = 1; + @drift.min = 0; + @drift.max = 20; + @drift.label = "Drift (cents)"; + @drift.group = "Players"; + + @stagger = 40 ms; + @stagger.widget = 1; + @stagger.min = 0 ms; + @stagger.max = 200 ms; + @stagger.label = "Stagger"; + @stagger.group = "Players"; + + @width = 0.8; + @width.widget = 1; + @width.min = 0; + @width.max = 1; + @width.label = "Width"; + @width.group = "Players"; + + @vibrato = 14; + @vibrato.widget = 1; + @vibrato.min = 0; + @vibrato.max = 50; + @vibrato.label = "Vibrato (cents)"; + @vibrato.group = "Vibrato"; + + @vibrate = 5.6; + @vibrate.widget = 1; + @vibrate.min = 3; + @vibrate.max = 8; + @vibrate.label = "Vibrato Rate (Hz)"; + @vibrate.group = "Vibrato"; + + @vibdelay = 350 ms; + @vibdelay.widget = 1; + @vibdelay.min = 0 ms; + @vibdelay.max = 2000 ms; + @vibdelay.label = "Vibrato Delay"; + @vibdelay.group = "Vibrato"; + + @bright = 6; + @bright.widget = 1; + @bright.min = 1; + @bright.max = 16; + @bright.label = "Brightness"; + @bright.group = "Tone"; + + @body = 0.8; + @body.widget = 1; + @body.min = 0; + @body.max = 2; + @body.label = "Body"; + @body.group = "Tone"; + + @a = 180 ms; + @a.widget = 1; + @a.min = 5 ms; + @a.max = 3000 ms; + @a.label = "Attack"; + @a.group = "Envelope"; + + @d = 300 ms; + @d.widget = 1; + @d.min = 0 ms; + @d.max = 3000 ms; + @d.label = "Decay"; + @d.group = "Envelope"; + + @s = 0.85; + @s.widget = 1; + @s.min = 0.01; + @s.max = 1; + @s.label = "Sustain"; + @s.group = "Envelope"; + + @r = 450 ms; + @r.widget = 1; + @r.min = 70 ms; + @r.max = 4000 ms; + @r.label = "Release"; + @r.group = "Envelope"; + +node ionode { + channels = 2; + out0 = outl->out; + out1 = outr->out; + play = max(max(env1->play, env2->play), max(env3->play, env4->play)); +}; + +node freq misc::midi2freq { + note = ionode->note; +}; + +# Player 1. +node drift1 misc::drift { + rate = 0.25; depth = @drift; seed = ionode->note + 1000; +}; +node vib1 misc::vibrato { + in = freq->out * exp2((drift1->out + @detune * -1.0) / 1200); + rate = @vibrate * 0.94; + depth = @vibrato; + delay = @vibdelay + @stagger * 0.0; + rise = @vibdelay; +}; +node osc1 osc::blep { freq = vib1->out; waveform = 0; phase = 0.0; }; +node tone1 filt::svf { + in = osc1->out; + cutoff = min(freq->out * @bright * (0.6 + 0.4 * ionode->velocity), 16000); + res = 0.1; +}; +node env1 env::adsr { + a = @a + @stagger * 0.0; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat1 mixer::pan { in = tone1->out_low * env1->out; pan = -0.8 * @width; }; + +# Player 2. +node drift2 misc::drift { + rate = 0.25; depth = @drift; seed = ionode->note + 2000; +}; +node vib2 misc::vibrato { + in = freq->out * exp2((drift2->out + @detune * -0.33) / 1200); + rate = @vibrate * 1.03; + depth = @vibrato; + delay = @vibdelay + @stagger * 0.6; + rise = @vibdelay; +}; +node osc2 osc::blep { freq = vib2->out; waveform = 0; phase = 0.6; }; +node tone2 filt::svf { + in = osc2->out; + cutoff = min(freq->out * @bright * (0.6 + 0.4 * ionode->velocity), 16000); + res = 0.1; +}; +node env2 env::adsr { + a = @a + @stagger * 0.6; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat2 mixer::pan { in = tone2->out_low * env2->out; pan = -0.3 * @width; }; + +# Player 3. +node drift3 misc::drift { + rate = 0.25; depth = @drift; seed = ionode->note + 3000; +}; +node vib3 misc::vibrato { + in = freq->out * exp2((drift3->out + @detune * 0.33) / 1200); + rate = @vibrate * 0.98; + depth = @vibrato; + delay = @vibdelay + @stagger * 0.25; + rise = @vibdelay; +}; +node osc3 osc::blep { freq = vib3->out; waveform = 0; phase = 0.25; }; +node tone3 filt::svf { + in = osc3->out; + cutoff = min(freq->out * @bright * (0.6 + 0.4 * ionode->velocity), 16000); + res = 0.1; +}; +node env3 env::adsr { + a = @a + @stagger * 0.25; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat3 mixer::pan { in = tone3->out_low * env3->out; pan = 0.3 * @width; }; + +# Player 4. +node drift4 misc::drift { + rate = 0.25; depth = @drift; seed = ionode->note + 4000; +}; +node vib4 misc::vibrato { + in = freq->out * exp2((drift4->out + @detune * 1.0) / 1200); + rate = @vibrate * 1.07; + depth = @vibrato; + delay = @vibdelay + @stagger * 0.9; + rise = @vibdelay; +}; +node osc4 osc::blep { freq = vib4->out; waveform = 0; phase = 0.9; }; +node tone4 filt::svf { + in = osc4->out; + cutoff = min(freq->out * @bright * (0.6 + 0.4 * ionode->velocity), 16000); + res = 0.1; +}; +node env4 env::adsr { + a = @a + @stagger * 0.9; + d = @d; + s = @s; + r = @r; + p = ionode->velocity; + trigger = ionode->trigger; +}; +node seat4 mixer::pan { in = tone4->out_low * env4->out; pan = 0.8 * @width; }; + +node suml math::add { + in0 = seat1->out0 + seat2->out0; + in1 = seat3->out0 + seat4->out0; +}; +node sumr math::add { + in0 = seat1->out1 + seat2->out1; + in1 = seat3->out1 + seat4->out1; +}; + +node bodyl1 filt::svf { in = suml->out; cutoff = 290; res = 0.6; }; +node bodyl2 filt::svf { in = suml->out; cutoff = 2800; res = 0.4; }; +node bodyr1 filt::svf { in = sumr->out; cutoff = 290; res = 0.6; }; +node bodyr2 filt::svf { in = sumr->out; cutoff = 2800; res = 0.4; }; + +node outl math::mul { + in0 = suml->out + (bodyl1->out_band + bodyl2->out_band * 0.8) * @body; + in1 = 0.11; +}; +node outr math::mul { + in0 = sumr->out + (bodyr1->out_band + bodyr2->out_band * 0.8) * @body; + in1 = 0.11; +}; + +io ionode; From d41b606c6fc753a99205c2468de7ccee156edc98 Mon Sep 17 00:00:00 2001 From: Misha Nasledov Date: Sun, 4 Oct 2026 12:10:42 -0700 Subject: [PATCH 2/2] dsp: violins and horns fixes from review Sustain scales with velocity, so a soft note does not swell up to the sustain level. Stagger is an entry: each player's attack, filter and scoop wait on a gate a timer envelope opens Stagger times its share after the key, rather than the attack being stretched and the scoop lengthened. Needs env::adsr to wait for a wired trigger (#337); until then every player enters with the first. --- dsp/horns.dsp | 112 ++++++++++++++++++++++++++++++++++-------------- dsp/violins.dsp | 57 ++++++++++++++++++------ 2 files changed, 123 insertions(+), 46 deletions(-) diff --git a/dsp/horns.dsp b/dsp/horns.dsp index 9954b360..641248b9 100644 --- a/dsp/horns.dsp +++ b/dsp/horns.dsp @@ -8,10 +8,10 @@ # multiple of the note, so the timbre is the same up the range. # # THE SECTION IS LOOSE ON PURPOSE. Each player is `Detune' cents from -# the others and drifting, their scoops and entries are staggered by -# `Stagger', their vibratos run at different rates, and they stand -# across the stereo field by `Width'. A section that hit together to the -# sample would be one horn played loud. +# the others and drifting, they come in up to `Stagger' after the key, +# each scooping from its own entry, their vibratos run at different +# rates, and they stand across the stereo field by `Width'. A section +# that hit together to the sample would be one horn played loud. name "Horns"; author "Misha Nasledov"; @@ -128,25 +128,37 @@ node freq misc::midi2freq { }; # Player 1. +# Player 1 comes in `Stagger' times 0.0 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait1 env::adsr { + a = @stagger * 0.0; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate1 math::clamp { in = (wait1->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift1 misc::drift { rate = 0.3; depth = @drift; seed = ionode->note + 1000; }; -node scoop1 env::ad { a = 0; d = @scooptime + @stagger * 0.0 * 0.5; }; +# From `Scoop' cents flat to the note over `Scoop Time', from the entry. +# A sustain just over 0, since an env::adsr that decays to 0 restarts. +node scoop1 env::adsr { + a = 0; d = @scooptime; s = 0.001; r = 0; + trigger = gate1->out; +}; node vib1 misc::vibrato { in = freq->out * exp2((drift1->out + @detune * -1.0 - @scoop * scoop1->out) / 1200); rate = @vibrate * 0.94; depth = @vibrato; - delay = @vibdelay; + delay = @vibdelay + @stagger * 0.0; rise = @vibdelay; }; node osc1 osc::blep { freq = vib1->out; waveform = 0; phase = 0.0; }; node fenv1 env::adsr { - a = @a + @stagger * 0.0; + a = @a; d = @d; s = 0.6; r = @r; - trigger = ionode->trigger; + trigger = gate1->out; }; node tone1 filt::svf { in = osc1->out; @@ -154,35 +166,47 @@ node tone1 filt::svf { res = 0.2; }; node env1 env::adsr { - a = @a + @stagger * 0.0; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate1->out; }; node seat1 mixer::pan { in = tone1->out_low * env1->out; pan = -0.8 * @width; }; # Player 2. +# Player 2 comes in `Stagger' times 0.6 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait2 env::adsr { + a = @stagger * 0.6; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate2 math::clamp { in = (wait2->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift2 misc::drift { rate = 0.3; depth = @drift; seed = ionode->note + 2000; }; -node scoop2 env::ad { a = 0; d = @scooptime + @stagger * 0.6 * 0.5; }; +# From `Scoop' cents flat to the note over `Scoop Time', from the entry. +# A sustain just over 0, since an env::adsr that decays to 0 restarts. +node scoop2 env::adsr { + a = 0; d = @scooptime; s = 0.001; r = 0; + trigger = gate2->out; +}; node vib2 misc::vibrato { in = freq->out * exp2((drift2->out + @detune * -0.33 - @scoop * scoop2->out) / 1200); rate = @vibrate * 1.03; depth = @vibrato; - delay = @vibdelay; + delay = @vibdelay + @stagger * 0.6; rise = @vibdelay; }; node osc2 osc::blep { freq = vib2->out; waveform = 0; phase = 0.6; }; node fenv2 env::adsr { - a = @a + @stagger * 0.6; + a = @a; d = @d; s = 0.6; r = @r; - trigger = ionode->trigger; + trigger = gate2->out; }; node tone2 filt::svf { in = osc2->out; @@ -190,35 +214,47 @@ node tone2 filt::svf { res = 0.2; }; node env2 env::adsr { - a = @a + @stagger * 0.6; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate2->out; }; node seat2 mixer::pan { in = tone2->out_low * env2->out; pan = -0.3 * @width; }; # Player 3. +# Player 3 comes in `Stagger' times 0.25 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait3 env::adsr { + a = @stagger * 0.25; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate3 math::clamp { in = (wait3->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift3 misc::drift { rate = 0.3; depth = @drift; seed = ionode->note + 3000; }; -node scoop3 env::ad { a = 0; d = @scooptime + @stagger * 0.25 * 0.5; }; +# From `Scoop' cents flat to the note over `Scoop Time', from the entry. +# A sustain just over 0, since an env::adsr that decays to 0 restarts. +node scoop3 env::adsr { + a = 0; d = @scooptime; s = 0.001; r = 0; + trigger = gate3->out; +}; node vib3 misc::vibrato { in = freq->out * exp2((drift3->out + @detune * 0.33 - @scoop * scoop3->out) / 1200); rate = @vibrate * 0.98; depth = @vibrato; - delay = @vibdelay; + delay = @vibdelay + @stagger * 0.25; rise = @vibdelay; }; node osc3 osc::blep { freq = vib3->out; waveform = 0; phase = 0.25; }; node fenv3 env::adsr { - a = @a + @stagger * 0.25; + a = @a; d = @d; s = 0.6; r = @r; - trigger = ionode->trigger; + trigger = gate3->out; }; node tone3 filt::svf { in = osc3->out; @@ -226,35 +262,47 @@ node tone3 filt::svf { res = 0.2; }; node env3 env::adsr { - a = @a + @stagger * 0.25; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate3->out; }; node seat3 mixer::pan { in = tone3->out_low * env3->out; pan = 0.3 * @width; }; # Player 4. +# Player 4 comes in `Stagger' times 0.9 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait4 env::adsr { + a = @stagger * 0.9; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate4 math::clamp { in = (wait4->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift4 misc::drift { rate = 0.3; depth = @drift; seed = ionode->note + 4000; }; -node scoop4 env::ad { a = 0; d = @scooptime + @stagger * 0.9 * 0.5; }; +# From `Scoop' cents flat to the note over `Scoop Time', from the entry. +# A sustain just over 0, since an env::adsr that decays to 0 restarts. +node scoop4 env::adsr { + a = 0; d = @scooptime; s = 0.001; r = 0; + trigger = gate4->out; +}; node vib4 misc::vibrato { in = freq->out * exp2((drift4->out + @detune * 1.0 - @scoop * scoop4->out) / 1200); rate = @vibrate * 1.07; depth = @vibrato; - delay = @vibdelay; + delay = @vibdelay + @stagger * 0.9; rise = @vibdelay; }; node osc4 osc::blep { freq = vib4->out; waveform = 0; phase = 0.9; }; node fenv4 env::adsr { - a = @a + @stagger * 0.9; + a = @a; d = @d; s = 0.6; r = @r; - trigger = ionode->trigger; + trigger = gate4->out; }; node tone4 filt::svf { in = osc4->out; @@ -262,12 +310,12 @@ node tone4 filt::svf { res = 0.2; }; node env4 env::adsr { - a = @a + @stagger * 0.9; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate4->out; }; node seat4 mixer::pan { in = tone4->out_low * env4->out; pan = 0.8 * @width; }; diff --git a/dsp/violins.dsp b/dsp/violins.dsp index c0e455e4..947a54d2 100644 --- a/dsp/violins.dsp +++ b/dsp/violins.dsp @@ -4,8 +4,9 @@ # chorus. This one is four: four sawtooth players on every note, each a # few cents from the others by `Detune', each wandering by `Drift', each # with a vibrato at its own rate that it leans into late, and each -# coming in `Stagger' after the one before -- bows do not touch the -# string at once. They sit across the stage by `Width', first desks +# coming in up to `Stagger' after the key -- bows do not touch the +# string at once. A player's gate opens late and its whole note starts +# then; a note shorter than that leaves that player out. They sit across the stage by `Width', first desks # left, seconds right. # # THE BODY IS SHARED. What makes a saw a violin is less the string than @@ -123,6 +124,13 @@ node freq misc::midi2freq { }; # Player 1. +# Player 1 comes in `Stagger' times 0.0 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait1 env::adsr { + a = @stagger * 0.0; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate1 math::clamp { in = (wait1->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift1 misc::drift { rate = 0.25; depth = @drift; seed = ionode->note + 1000; }; @@ -140,16 +148,23 @@ node tone1 filt::svf { res = 0.1; }; node env1 env::adsr { - a = @a + @stagger * 0.0; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate1->out; }; node seat1 mixer::pan { in = tone1->out_low * env1->out; pan = -0.8 * @width; }; # Player 2. +# Player 2 comes in `Stagger' times 0.6 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait2 env::adsr { + a = @stagger * 0.6; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate2 math::clamp { in = (wait2->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift2 misc::drift { rate = 0.25; depth = @drift; seed = ionode->note + 2000; }; @@ -167,16 +182,23 @@ node tone2 filt::svf { res = 0.1; }; node env2 env::adsr { - a = @a + @stagger * 0.6; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate2->out; }; node seat2 mixer::pan { in = tone2->out_low * env2->out; pan = -0.3 * @width; }; # Player 3. +# Player 3 comes in `Stagger' times 0.25 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait3 env::adsr { + a = @stagger * 0.25; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate3 math::clamp { in = (wait3->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift3 misc::drift { rate = 0.25; depth = @drift; seed = ionode->note + 3000; }; @@ -194,16 +216,23 @@ node tone3 filt::svf { res = 0.1; }; node env3 env::adsr { - a = @a + @stagger * 0.25; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate3->out; }; node seat3 mixer::pan { in = tone3->out_low * env3->out; pan = 0.3 * @width; }; # Player 4. +# Player 4 comes in `Stagger' times 0.9 after the key: a timer that +# opens its gate then, and shuts it with the key. +node wait4 env::adsr { + a = @stagger * 0.9; d = 0; s = 1; r = 0; + trigger = ionode->trigger; +}; +node gate4 math::clamp { in = (wait4->out - 0.999) * 1000; lo = 0; hi = 1; }; node drift4 misc::drift { rate = 0.25; depth = @drift; seed = ionode->note + 4000; }; @@ -221,12 +250,12 @@ node tone4 filt::svf { res = 0.1; }; node env4 env::adsr { - a = @a + @stagger * 0.9; + a = @a; d = @d; - s = @s; + s = @s * ionode->velocity; r = @r; p = ionode->velocity; - trigger = ionode->trigger; + trigger = gate4->out; }; node seat4 mixer::pan { in = tone4->out_low * env4->out; pan = 0.8 * @width; };