From b1d644c172681dfb93052c2cbe0e3898272099db Mon Sep 17 00:00:00 2001 From: Misha Nasledov Date: Sat, 3 Oct 2026 15:24:27 -0700 Subject: [PATCH 1/2] env: dx, the DX7's envelope Four rates and four levels, 0 to 99. Levels are 0.75 dB a step and segments move at a steady dB a second, the rate doubling every 6.2 steps. A rising segment jumps to -44 dB and slows as it climbs, and ratescale speeds every segment up the keyboard. --- docs/NODES.md | 21 ++++ plugins/CMakeLists.txt | 2 +- plugins/env/dx.cpp | 258 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 plugins/env/dx.cpp diff --git a/docs/NODES.md b/docs/NODES.md index 3d6b0f5f..9829a9f3 100644 --- a/docs/NODES.md +++ b/docs/NODES.md @@ -246,6 +246,27 @@ ADSR Envelope Generator | `play` | out | 1 while the note is sounding | | 0 to 1 | | | | `position` | state | | | | | | +### env::dx + +DX7 Envelope (four rates, four levels, in dB) + +| Arg | Dir | Description | Default | Range | Units | Values | +|---|---|---|---|---|---|---| +| `r1` | in | Rate of the first segment, toward l1 | | 0 to 99 | | | +| `r2` | in | Rate of the second segment, toward l2 | | 0 to 99 | | | +| `r3` | in | Rate of the third segment, toward l3, where it holds | | 0 to 99 | | | +| `r4` | in | Rate of the release, toward l4 | | 0 to 99 | | | +| `l1` | in | Level the first segment heads for | | 0 to 99 | | | +| `l2` | in | Level the second segment heads for | | 0 to 99 | | | +| `l3` | in | Level the third heads for and holds while the key is down | | 0 to 99 | | | +| `l4` | in | Level the release heads for | | 0 to 99 | | | +| `trigger` | in | Note Trigger: 0 released, 1 held, 2 held by the pedal | | 0 to 2 | | | +| `note` | in | The MIDI note, for rate scaling | | 0 to 127 | | | +| `ratescale` | in | How much faster every segment runs up the keyboard | | 0 to 7 | | | +| `out` | out | The envelope, as an amplitude | | 0 to 1 | full scale | | +| `play` | out | 1 while the note is sounding | | 0 to 1 | | | +| `state` | state | | | | | | + ### env::dynmap Maps a stream to a new value range (dynamic) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index ab2a9945..3d8421aa 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -35,7 +35,7 @@ think_add_plugin_category(dist clip saturate waveman inksat) think_add_plugin_category(dyn compressor) -think_add_plugin_category(env adsr adsfr ad dynmap follower followavg map) +think_add_plugin_category(env adsr adsfr ad dx dynmap follower followavg map) think_add_plugin_category(filt allpass comb resonator ink ink2 ink3 inkshape rds ds dshape divbuf res1pole diff --git a/plugins/env/dx.cpp b/plugins/env/dx.cpp new file mode 100644 index 00000000..84b6dd76 --- /dev/null +++ b/plugins/env/dx.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2004-2026 The thinksynth authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General + * Public License along with this program; if not, write to the + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* The DX7's envelope: four rates, four levels, all 0 to 99. + * + * Not an ADSR with different knob names. A DX envelope is four segments, + * each heading for its own level at its own rate -- L1 at R1, then L2 at + * R2, then L3 at R3, where it holds while the key is down, then L4 at R4 + * once it is up -- and nothing says a level must be lower than the one + * before. A patch that dips and swells, or that releases *upward*, is + * written in the same eight numbers as one that does not, which is half + * of why DX patches sound like DX patches. + * + * THE LEVEL IS IN DECIBELS. Each step of 0..99 is 0.75 dB, so 99 is full + * scale, 91 is 6 dB down and 0 is silence, and a segment moves at a + * steady number of dB a second. A straight line in dB is an exponential + * in amplitude, which is what a decaying string or bell does; a linear + * ramp in amplitude, which is what env::adsr draws, sounds like a fader. + * The output is the amplitude, 0 to 1, so it multiplies like any other + * envelope -- and a modulator's index through it is in dB too, which is + * why a DX timbre closes the way a real one does. + * + * THE RATE IS EXPONENTIAL: the speed doubles every 6.2 steps, from about + * a third of a dB a second at 0 (a sound that takes minutes to fade) to + * an instant at 99. That is the hardware's `rate * 41 / 64' quarter- + * octave scale, calibrated so a full-range fall takes about a second at + * 50. Rising is faster than falling and faster still from far below: + * the hardware jumps a rising segment to about -44 dB before it starts + * and then climbs on a curve that slows as it nears the top, and this + * does both, because a DX attack at any rate is snappier than its decay + * at the same number. + * + * `ratescale' is the hardware's keyboard rate scaling, 0 to 7: higher + * notes run every segment faster, as a high string dies sooner than a + * low one, adding up to `ratescale * 31 / 8' to the quarter-octave + * count at the top of the keyboard. + * + * `play' goes to 0 once the release has reached L4 *and* L4 is 0. A + * patch whose L4 is above silence holds there for ever, as the hardware + * did; that is a carrier nobody writes, and a modulator's play is read + * by nothing. + */ + +#include +#include +#include +#include + +#include "think.h" + +#include "thArg.h" +#include "thPlugin.h" +#include "thPluginManager.h" +#include "thNode.h" +#include "thSynthTree.h" +#include "thSynth.h" + +enum {IN_R1, IN_R2, IN_R3, IN_R4, IN_L1, IN_L2, IN_L3, IN_L4, + IN_TRIGGER, IN_NOTE, IN_RATESCALE, OUT_ARG, OUT_PLAY, INOUT_STATE}; + +std::atomic args[INOUT_STATE + 1]; + +static const char desc[] = "DX7 Envelope (four rates, four levels, in dB)"; +thPlugin::State mystate = thPlugin::ACTIVE; + +/* Where a rising segment that starts under it jumps to first: about + -44 dB, the hardware's. */ +#define DX_JUMP_LEVEL 40.0f + +/* The segments. HELD is L3 with the key down; DONE is L4 reached. */ +enum { SEG_1, SEG_2, SEG_3, SEG_HELD, SEG_RELEASE, SEG_DONE }; + +static float dxClamp99 (float x) +{ + if (!thIsFinite(x) || x < 0) + return 0; + return x > 99 ? 99 : x; +} + +/* Levels per second at a rate, after rate scaling. */ +static double dxSpeed (float rate, double scale) +{ + double q = dxClamp99(rate) * 41.0 / 64.0 + scale; + if (q > 63) + q = 63; + /* 0.348 dB/s at q = 0, doubling every four; a level is 0.75 dB. */ + return 0.348 * pow(2.0, q / 4.0) / 0.75; +} + +void module_cleanup (thPlugin *plugin) +{ +} + +int module_init (thPlugin *plugin) +{ + static const char *const rname[4] = {"r1", "r2", "r3", "r4"}; + static const char *const lname[4] = {"l1", "l2", "l3", "l4"}; + static const char *const rdesc[4] = { + "Rate of the first segment, toward l1", + "Rate of the second segment, toward l2", + "Rate of the third segment, toward l3, where it holds", + "Rate of the release, toward l4"}; + static const char *const ldesc[4] = { + "Level the first segment heads for", + "Level the second segment heads for", + "Level the third heads for and holds while the key is down", + "Level the release heads for"}; + + plugin->setDesc (desc); + plugin->setState (mystate); + + for (int k = 0; k < 4; k++) + { + args[IN_R1 + k] = plugin->regArg(rname[k], thPlugin::ARG_IN); + plugin->setArgDesc(args[IN_R1 + k], rdesc[k]); + plugin->setArgRange(args[IN_R1 + k], 0, 99); + } + for (int k = 0; k < 4; k++) + { + args[IN_L1 + k] = plugin->regArg(lname[k], thPlugin::ARG_IN); + plugin->setArgDesc(args[IN_L1 + k], ldesc[k]); + plugin->setArgRange(args[IN_L1 + k], 0, 99); + } + + args[IN_TRIGGER] = plugin->regArg("trigger", thPlugin::ARG_IN); + plugin->setArgDesc(args[IN_TRIGGER], + "Note Trigger: 0 released, 1 held, 2 held by the pedal"); + plugin->setArgRange(args[IN_TRIGGER], 0, 2); + args[IN_NOTE] = plugin->regArg("note", thPlugin::ARG_IN); + plugin->setArgDesc(args[IN_NOTE], + "The MIDI note, for rate scaling"); + plugin->setArgRange(args[IN_NOTE], 0, 127); + args[IN_RATESCALE] = plugin->regArg("ratescale", thPlugin::ARG_IN); + plugin->setArgDesc(args[IN_RATESCALE], + "How much faster every segment runs up the keyboard"); + plugin->setArgRange(args[IN_RATESCALE], 0, 7); + + args[OUT_ARG] = plugin->regArg("out", thPlugin::ARG_OUT); + plugin->setArgDesc(args[OUT_ARG], "The envelope, as an amplitude"); + plugin->setArgRange(args[OUT_ARG], 0, TH_MAX); + plugin->setArgUnits(args[OUT_ARG], "full scale"); + args[OUT_PLAY] = plugin->regArg("play", thPlugin::ARG_OUT); + plugin->setArgDesc(args[OUT_PLAY], "1 while the note is sounding"); + plugin->setArgRange(args[OUT_PLAY], 0, 1); + + /* [0] the level, 0..99; [1] the segment. */ + args[INOUT_STATE] = plugin->regArg("state", thPlugin::ARG_STATE); + + return 0; +} + +int module_callback (thNode *node, thSynthTree *mod, unsigned int windowlen, + unsigned int samples) +{ + thArg *in_r[4], *in_l[4], *in_trigger, *in_note, *in_scale; + thArg *out_arg, *play_arg, *inout_state; + float *out, *play, *state; + float level; + int seg; + + for (int k = 0; k < 4; k++) + { + in_r[k] = mod->getArg(node, args[IN_R1 + k]); + in_l[k] = mod->getArg(node, args[IN_L1 + k]); + } + in_trigger = mod->getArg(node, args[IN_TRIGGER]); + in_note = mod->getArg(node, args[IN_NOTE]); + in_scale = mod->getArg(node, args[IN_RATESCALE]); + + inout_state = mod->getArg(node, args[INOUT_STATE]); + level = (*inout_state)[0]; + seg = (int)(*inout_state)[1]; + state = inout_state->allocate(2); + + out_arg = mod->getArg(node, args[OUT_ARG]); + out = out_arg->allocate(windowlen); + play_arg = mod->getArg(node, args[OUT_PLAY]); + play = play_arg->allocate(windowlen); + + for (unsigned int i = 0; i < windowlen; i++) + { + /* `> 0', so a choked voice's -1 is a release like a 0. */ + const bool held = (*in_trigger)[i] > 0; + double scale, target, speed; + int k; + + if (!held && seg < SEG_RELEASE) + seg = SEG_RELEASE; + + k = seg < SEG_HELD ? seg : 3; + target = dxClamp99((*in_l[k])[i]); + + if (seg == SEG_HELD) + { + level = (float)dxClamp99((*in_l[2])[i]); + } + else if (seg != SEG_DONE) + { + const float note = thIsFinite((*in_note)[i]) ? (*in_note)[i] : 60; + double x = note / 3.0 - 7; + if (x < 0) + x = 0; + if (x > 31) + x = 31; + scale = thClampArg((*in_scale)[i], 0, 7) * x / 8.0; + speed = dxSpeed((*in_r[k])[i], scale) / samples; + + if (target > level) + { + if (level < DX_JUMP_LEVEL && target > DX_JUMP_LEVEL) + level = DX_JUMP_LEVEL; + /* Seventeen times as fast from silence as at the top, + slowing as it climbs. */ + level += (float)(speed * (17.0 - level * 16.0 / 99.0)); + if (level >= target) + level = (float)target; + } + else + { + level -= (float)speed; + if (level <= target) + level = (float)target; + } + + if (level == (float)target) + { + if (seg == SEG_RELEASE) + seg = SEG_DONE; + else if (seg < SEG_HELD) + seg++; + } + } + + out[i] = level > 0 ? (float)(TH_MAX * pow(2.0, (level - 99) / 8.0)) + : 0; + play[i] = (seg == SEG_DONE && level <= 0) ? 0 : 1; + } + + state[0] = level; + state[1] = seg; + + return 0; +} From c17788e701784e15c6afee2dc20820ca5fe96068 Mon Sep 17 00:00:00 2001 From: Misha Nasledov Date: Sat, 3 Oct 2026 15:24:27 -0700 Subject: [PATCH 2/2] dsp: DX7 algorithms 5, 16, 22 and 32 on six operators Six osc::fmop and six env::dx per graph, wired as the algorithm, with the panel's knobs per op: ratio, detune, output level, velocity sensitivity, four rates and four levels, plus feedback on op 6 and rate scaling. Each ships as a sound: electric piano, bass, brass, organ. --- dsp/dx16.dsp | 785 ++++++++++++++++++++++++++++++++++++++++++++++++++ dsp/dx22.dsp | 787 +++++++++++++++++++++++++++++++++++++++++++++++++++ dsp/dx32.dsp | 780 ++++++++++++++++++++++++++++++++++++++++++++++++++ dsp/dx5.dsp | 785 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 3137 insertions(+) create mode 100644 dsp/dx16.dsp create mode 100644 dsp/dx22.dsp create mode 100644 dsp/dx32.dsp create mode 100644 dsp/dx5.dsp diff --git a/dsp/dx16.dsp b/dsp/dx16.dsp new file mode 100644 index 00000000..377a547f --- /dev/null +++ b/dsp/dx16.dsp @@ -0,0 +1,785 @@ +# DX 16 -- five operators into one carrier, the DX7's algorithm 16. +# +# 2 straight into 1, 4 through 3, and 6 -- feeding back -- through 5, so +# one sine is pushed three ways at once. A single carrier is a single +# pitch with everything spent on timbre, which is a bass. As shipped, op 2 +# is the pluck, the 3/4 branch the growl, and 5/6 the buzz under it. +# +# THE KNOBS ARE THE DX7'S, op by op: a frequency ratio, a detune in +# cents, an output level and a velocity sensitivity, and an env::dx with +# four rates and four levels, all 0 to 99. Output level is in the +# envelope's own decibels -- 0.75 dB a step -- so on a modulator it is +# the brightness and on a carrier the volume, as on the hardware. +# Velocity sensitivity, 0 to 7, takes up to 25 dB off an op played +# softly; on a modulator that is a duller note rather than a quieter one. +# +# THE WIRING IS THE ALGORITHM. Each op is an osc::fmop and a modulator's +# output, through its envelope and level, is summed onto the `mod' of the +# op it feeds; the sum is scaled so a modulator at 99 swings the phase by +# 8 radians. Another algorithm is another file, not a knob -- see +# osc::fmop's head for why. `Feedback', 0 to 7 as on the panel, is op 6 +# on itself. + +name "DX 16: One Carrier"; +author "Misha Nasledov"; +description "Six FM operators into one carrier, the DX7's algorithm 16, set up as a bass."; +category "Keys"; + + @o1ratio = 0.5; + @o1ratio.widget = 1; + @o1ratio.min = 0.5; + @o1ratio.max = 31; + @o1ratio.label = "Ratio"; + @o1ratio.group = "Op 1"; + + @o1det = 0; + @o1det.widget = 1; + @o1det.min = -50; + @o1det.max = 50; + @o1det.label = "Detune (cents)"; + @o1det.group = "Op 1"; + + @o1level = 99; + @o1level.widget = 1; + @o1level.min = 0; + @o1level.max = 99; + @o1level.step = 1; + @o1level.label = "Level"; + @o1level.group = "Op 1"; + + @o1vel = 1; + @o1vel.widget = 1; + @o1vel.min = 0; + @o1vel.max = 7; + @o1vel.step = 1; + @o1vel.label = "Velocity"; + @o1vel.group = "Op 1"; + + @o1r1 = 99; + @o1r1.widget = 1; + @o1r1.min = 0; + @o1r1.max = 99; + @o1r1.step = 1; + @o1r1.label = "Rate 1"; + @o1r1.group = "Op 1"; + + @o1r2 = 30; + @o1r2.widget = 1; + @o1r2.min = 0; + @o1r2.max = 99; + @o1r2.step = 1; + @o1r2.label = "Rate 2"; + @o1r2.group = "Op 1"; + + @o1r3 = 30; + @o1r3.widget = 1; + @o1r3.min = 0; + @o1r3.max = 99; + @o1r3.step = 1; + @o1r3.label = "Rate 3"; + @o1r3.group = "Op 1"; + + @o1r4 = 70; + @o1r4.widget = 1; + @o1r4.min = 0; + @o1r4.max = 99; + @o1r4.step = 1; + @o1r4.label = "Rate 4"; + @o1r4.group = "Op 1"; + + @o1l1 = 99; + @o1l1.widget = 1; + @o1l1.min = 0; + @o1l1.max = 99; + @o1l1.step = 1; + @o1l1.label = "Level 1"; + @o1l1.group = "Op 1"; + + @o1l2 = 88; + @o1l2.widget = 1; + @o1l2.min = 0; + @o1l2.max = 99; + @o1l2.step = 1; + @o1l2.label = "Level 2"; + @o1l2.group = "Op 1"; + + @o1l3 = 80; + @o1l3.widget = 1; + @o1l3.min = 0; + @o1l3.max = 99; + @o1l3.step = 1; + @o1l3.label = "Level 3"; + @o1l3.group = "Op 1"; + + @o1l4 = 0; + @o1l4.widget = 1; + @o1l4.min = 0; + @o1l4.max = 99; + @o1l4.step = 1; + @o1l4.label = "Level 4"; + @o1l4.group = "Op 1"; + + @o2ratio = 0.5; + @o2ratio.widget = 1; + @o2ratio.min = 0.5; + @o2ratio.max = 31; + @o2ratio.label = "Ratio"; + @o2ratio.group = "Op 2"; + + @o2det = 0; + @o2det.widget = 1; + @o2det.min = -50; + @o2det.max = 50; + @o2det.label = "Detune (cents)"; + @o2det.group = "Op 2"; + + @o2level = 82; + @o2level.widget = 1; + @o2level.min = 0; + @o2level.max = 99; + @o2level.step = 1; + @o2level.label = "Level"; + @o2level.group = "Op 2"; + + @o2vel = 5; + @o2vel.widget = 1; + @o2vel.min = 0; + @o2vel.max = 7; + @o2vel.step = 1; + @o2vel.label = "Velocity"; + @o2vel.group = "Op 2"; + + @o2r1 = 99; + @o2r1.widget = 1; + @o2r1.min = 0; + @o2r1.max = 99; + @o2r1.step = 1; + @o2r1.label = "Rate 1"; + @o2r1.group = "Op 2"; + + @o2r2 = 55; + @o2r2.widget = 1; + @o2r2.min = 0; + @o2r2.max = 99; + @o2r2.step = 1; + @o2r2.label = "Rate 2"; + @o2r2.group = "Op 2"; + + @o2r3 = 40; + @o2r3.widget = 1; + @o2r3.min = 0; + @o2r3.max = 99; + @o2r3.step = 1; + @o2r3.label = "Rate 3"; + @o2r3.group = "Op 2"; + + @o2r4 = 70; + @o2r4.widget = 1; + @o2r4.min = 0; + @o2r4.max = 99; + @o2r4.step = 1; + @o2r4.label = "Rate 4"; + @o2r4.group = "Op 2"; + + @o2l1 = 99; + @o2l1.widget = 1; + @o2l1.min = 0; + @o2l1.max = 99; + @o2l1.step = 1; + @o2l1.label = "Level 1"; + @o2l1.group = "Op 2"; + + @o2l2 = 60; + @o2l2.widget = 1; + @o2l2.min = 0; + @o2l2.max = 99; + @o2l2.step = 1; + @o2l2.label = "Level 2"; + @o2l2.group = "Op 2"; + + @o2l3 = 0; + @o2l3.widget = 1; + @o2l3.min = 0; + @o2l3.max = 99; + @o2l3.step = 1; + @o2l3.label = "Level 3"; + @o2l3.group = "Op 2"; + + @o2l4 = 0; + @o2l4.widget = 1; + @o2l4.min = 0; + @o2l4.max = 99; + @o2l4.step = 1; + @o2l4.label = "Level 4"; + @o2l4.group = "Op 2"; + + @o3ratio = 1; + @o3ratio.widget = 1; + @o3ratio.min = 0.5; + @o3ratio.max = 31; + @o3ratio.label = "Ratio"; + @o3ratio.group = "Op 3"; + + @o3det = 2; + @o3det.widget = 1; + @o3det.min = -50; + @o3det.max = 50; + @o3det.label = "Detune (cents)"; + @o3det.group = "Op 3"; + + @o3level = 72; + @o3level.widget = 1; + @o3level.min = 0; + @o3level.max = 99; + @o3level.step = 1; + @o3level.label = "Level"; + @o3level.group = "Op 3"; + + @o3vel = 3; + @o3vel.widget = 1; + @o3vel.min = 0; + @o3vel.max = 7; + @o3vel.step = 1; + @o3vel.label = "Velocity"; + @o3vel.group = "Op 3"; + + @o3r1 = 99; + @o3r1.widget = 1; + @o3r1.min = 0; + @o3r1.max = 99; + @o3r1.step = 1; + @o3r1.label = "Rate 1"; + @o3r1.group = "Op 3"; + + @o3r2 = 45; + @o3r2.widget = 1; + @o3r2.min = 0; + @o3r2.max = 99; + @o3r2.step = 1; + @o3r2.label = "Rate 2"; + @o3r2.group = "Op 3"; + + @o3r3 = 30; + @o3r3.widget = 1; + @o3r3.min = 0; + @o3r3.max = 99; + @o3r3.step = 1; + @o3r3.label = "Rate 3"; + @o3r3.group = "Op 3"; + + @o3r4 = 70; + @o3r4.widget = 1; + @o3r4.min = 0; + @o3r4.max = 99; + @o3r4.step = 1; + @o3r4.label = "Rate 4"; + @o3r4.group = "Op 3"; + + @o3l1 = 99; + @o3l1.widget = 1; + @o3l1.min = 0; + @o3l1.max = 99; + @o3l1.step = 1; + @o3l1.label = "Level 1"; + @o3l1.group = "Op 3"; + + @o3l2 = 70; + @o3l2.widget = 1; + @o3l2.min = 0; + @o3l2.max = 99; + @o3l2.step = 1; + @o3l2.label = "Level 2"; + @o3l2.group = "Op 3"; + + @o3l3 = 50; + @o3l3.widget = 1; + @o3l3.min = 0; + @o3l3.max = 99; + @o3l3.step = 1; + @o3l3.label = "Level 3"; + @o3l3.group = "Op 3"; + + @o3l4 = 0; + @o3l4.widget = 1; + @o3l4.min = 0; + @o3l4.max = 99; + @o3l4.step = 1; + @o3l4.label = "Level 4"; + @o3l4.group = "Op 3"; + + @o4ratio = 3; + @o4ratio.widget = 1; + @o4ratio.min = 0.5; + @o4ratio.max = 31; + @o4ratio.label = "Ratio"; + @o4ratio.group = "Op 4"; + + @o4det = 0; + @o4det.widget = 1; + @o4det.min = -50; + @o4det.max = 50; + @o4det.label = "Detune (cents)"; + @o4det.group = "Op 4"; + + @o4level = 60; + @o4level.widget = 1; + @o4level.min = 0; + @o4level.max = 99; + @o4level.step = 1; + @o4level.label = "Level"; + @o4level.group = "Op 4"; + + @o4vel = 3; + @o4vel.widget = 1; + @o4vel.min = 0; + @o4vel.max = 7; + @o4vel.step = 1; + @o4vel.label = "Velocity"; + @o4vel.group = "Op 4"; + + @o4r1 = 99; + @o4r1.widget = 1; + @o4r1.min = 0; + @o4r1.max = 99; + @o4r1.step = 1; + @o4r1.label = "Rate 1"; + @o4r1.group = "Op 4"; + + @o4r2 = 50; + @o4r2.widget = 1; + @o4r2.min = 0; + @o4r2.max = 99; + @o4r2.step = 1; + @o4r2.label = "Rate 2"; + @o4r2.group = "Op 4"; + + @o4r3 = 40; + @o4r3.widget = 1; + @o4r3.min = 0; + @o4r3.max = 99; + @o4r3.step = 1; + @o4r3.label = "Rate 3"; + @o4r3.group = "Op 4"; + + @o4r4 = 70; + @o4r4.widget = 1; + @o4r4.min = 0; + @o4r4.max = 99; + @o4r4.step = 1; + @o4r4.label = "Rate 4"; + @o4r4.group = "Op 4"; + + @o4l1 = 99; + @o4l1.widget = 1; + @o4l1.min = 0; + @o4l1.max = 99; + @o4l1.step = 1; + @o4l1.label = "Level 1"; + @o4l1.group = "Op 4"; + + @o4l2 = 60; + @o4l2.widget = 1; + @o4l2.min = 0; + @o4l2.max = 99; + @o4l2.step = 1; + @o4l2.label = "Level 2"; + @o4l2.group = "Op 4"; + + @o4l3 = 0; + @o4l3.widget = 1; + @o4l3.min = 0; + @o4l3.max = 99; + @o4l3.step = 1; + @o4l3.label = "Level 3"; + @o4l3.group = "Op 4"; + + @o4l4 = 0; + @o4l4.widget = 1; + @o4l4.min = 0; + @o4l4.max = 99; + @o4l4.step = 1; + @o4l4.label = "Level 4"; + @o4l4.group = "Op 4"; + + @o5ratio = 1; + @o5ratio.widget = 1; + @o5ratio.min = 0.5; + @o5ratio.max = 31; + @o5ratio.label = "Ratio"; + @o5ratio.group = "Op 5"; + + @o5det = -2; + @o5det.widget = 1; + @o5det.min = -50; + @o5det.max = 50; + @o5det.label = "Detune (cents)"; + @o5det.group = "Op 5"; + + @o5level = 68; + @o5level.widget = 1; + @o5level.min = 0; + @o5level.max = 99; + @o5level.step = 1; + @o5level.label = "Level"; + @o5level.group = "Op 5"; + + @o5vel = 2; + @o5vel.widget = 1; + @o5vel.min = 0; + @o5vel.max = 7; + @o5vel.step = 1; + @o5vel.label = "Velocity"; + @o5vel.group = "Op 5"; + + @o5r1 = 99; + @o5r1.widget = 1; + @o5r1.min = 0; + @o5r1.max = 99; + @o5r1.step = 1; + @o5r1.label = "Rate 1"; + @o5r1.group = "Op 5"; + + @o5r2 = 40; + @o5r2.widget = 1; + @o5r2.min = 0; + @o5r2.max = 99; + @o5r2.step = 1; + @o5r2.label = "Rate 2"; + @o5r2.group = "Op 5"; + + @o5r3 = 30; + @o5r3.widget = 1; + @o5r3.min = 0; + @o5r3.max = 99; + @o5r3.step = 1; + @o5r3.label = "Rate 3"; + @o5r3.group = "Op 5"; + + @o5r4 = 70; + @o5r4.widget = 1; + @o5r4.min = 0; + @o5r4.max = 99; + @o5r4.step = 1; + @o5r4.label = "Rate 4"; + @o5r4.group = "Op 5"; + + @o5l1 = 99; + @o5l1.widget = 1; + @o5l1.min = 0; + @o5l1.max = 99; + @o5l1.step = 1; + @o5l1.label = "Level 1"; + @o5l1.group = "Op 5"; + + @o5l2 = 70; + @o5l2.widget = 1; + @o5l2.min = 0; + @o5l2.max = 99; + @o5l2.step = 1; + @o5l2.label = "Level 2"; + @o5l2.group = "Op 5"; + + @o5l3 = 60; + @o5l3.widget = 1; + @o5l3.min = 0; + @o5l3.max = 99; + @o5l3.step = 1; + @o5l3.label = "Level 3"; + @o5l3.group = "Op 5"; + + @o5l4 = 0; + @o5l4.widget = 1; + @o5l4.min = 0; + @o5l4.max = 99; + @o5l4.step = 1; + @o5l4.label = "Level 4"; + @o5l4.group = "Op 5"; + + @o6ratio = 1; + @o6ratio.widget = 1; + @o6ratio.min = 0.5; + @o6ratio.max = 31; + @o6ratio.label = "Ratio"; + @o6ratio.group = "Op 6"; + + @o6det = 0; + @o6det.widget = 1; + @o6det.min = -50; + @o6det.max = 50; + @o6det.label = "Detune (cents)"; + @o6det.group = "Op 6"; + + @o6level = 72; + @o6level.widget = 1; + @o6level.min = 0; + @o6level.max = 99; + @o6level.step = 1; + @o6level.label = "Level"; + @o6level.group = "Op 6"; + + @o6vel = 0; + @o6vel.widget = 1; + @o6vel.min = 0; + @o6vel.max = 7; + @o6vel.step = 1; + @o6vel.label = "Velocity"; + @o6vel.group = "Op 6"; + + @o6r1 = 99; + @o6r1.widget = 1; + @o6r1.min = 0; + @o6r1.max = 99; + @o6r1.step = 1; + @o6r1.label = "Rate 1"; + @o6r1.group = "Op 6"; + + @o6r2 = 40; + @o6r2.widget = 1; + @o6r2.min = 0; + @o6r2.max = 99; + @o6r2.step = 1; + @o6r2.label = "Rate 2"; + @o6r2.group = "Op 6"; + + @o6r3 = 30; + @o6r3.widget = 1; + @o6r3.min = 0; + @o6r3.max = 99; + @o6r3.step = 1; + @o6r3.label = "Rate 3"; + @o6r3.group = "Op 6"; + + @o6r4 = 70; + @o6r4.widget = 1; + @o6r4.min = 0; + @o6r4.max = 99; + @o6r4.step = 1; + @o6r4.label = "Rate 4"; + @o6r4.group = "Op 6"; + + @o6l1 = 99; + @o6l1.widget = 1; + @o6l1.min = 0; + @o6l1.max = 99; + @o6l1.step = 1; + @o6l1.label = "Level 1"; + @o6l1.group = "Op 6"; + + @o6l2 = 80; + @o6l2.widget = 1; + @o6l2.min = 0; + @o6l2.max = 99; + @o6l2.step = 1; + @o6l2.label = "Level 2"; + @o6l2.group = "Op 6"; + + @o6l3 = 70; + @o6l3.widget = 1; + @o6l3.min = 0; + @o6l3.max = 99; + @o6l3.step = 1; + @o6l3.label = "Level 3"; + @o6l3.group = "Op 6"; + + @o6l4 = 0; + @o6l4.widget = 1; + @o6l4.min = 0; + @o6l4.max = 99; + @o6l4.step = 1; + @o6l4.label = "Level 4"; + @o6l4.group = "Op 6"; + + @feedback = 7; + @feedback.widget = 1; + @feedback.min = 0; + @feedback.max = 7; + @feedback.step = 1; + @feedback.label = "Feedback"; + + @ratescale = 2; + @ratescale.widget = 1; + @ratescale.min = 0; + @ratescale.max = 7; + @ratescale.step = 1; + @ratescale.label = "Rate Scaling"; + + @mix = 0.3; + @mix.widget = 1; + @mix.min = 0; + @mix.max = 1; + @mix.label = "Output"; + +node ionode { + channels = 2; + out0 = mix->out; + out1 = mix->out; + play = e1->play; +}; + +node freq misc::midi2freq { + note = ionode->note; +}; + +node e1 env::dx { + r1 = @o1r1; + r2 = @o1r2; + r3 = @o1r3; + r4 = @o1r4; + l1 = @o1l1; + l2 = @o1l2; + l3 = @o1l3; + l4 = @o1l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e2 env::dx { + r1 = @o2r1; + r2 = @o2r2; + r3 = @o2r3; + r4 = @o2r4; + l1 = @o2l1; + l2 = @o2l2; + l3 = @o2l3; + l4 = @o2l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e3 env::dx { + r1 = @o3r1; + r2 = @o3r2; + r3 = @o3r3; + r4 = @o3r4; + l1 = @o3l1; + l2 = @o3l2; + l3 = @o3l3; + l4 = @o3l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e4 env::dx { + r1 = @o4r1; + r2 = @o4r2; + r3 = @o4r3; + r4 = @o4r4; + l1 = @o4l1; + l2 = @o4l2; + l3 = @o4l3; + l4 = @o4l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e5 env::dx { + r1 = @o5r1; + r2 = @o5r2; + r3 = @o5r3; + r4 = @o5r4; + l1 = @o5l1; + l2 = @o5l2; + l3 = @o5l3; + l4 = @o5l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e6 env::dx { + r1 = @o6r1; + r2 = @o6r2; + r3 = @o6r3; + r4 = @o6r4; + l1 = @o6l1; + l2 = @o6l2; + l3 = @o6l3; + l4 = @o6l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +# Op 6 as heard: its sine through its envelope and level. +node s6 math::mul { + in0 = op6->out; + in1 = e6->out * pow(2, (@o6level - 99) / 8) * pow(2, @o6vel * (ionode->velocity - 1) * 0.6); +}; + +node op6 osc::fmop { + freq = freq->out; + ratio = @o6ratio * exp2(@o6det / 1200); + feedback = @feedback / 7; +}; + +# Op 5 as heard: its sine through its envelope and level. +node s5 math::mul { + in0 = op5->out; + in1 = e5->out * pow(2, (@o5level - 99) / 8) * pow(2, @o5vel * (ionode->velocity - 1) * 0.6); +}; + +node op5 osc::fmop { + freq = freq->out; + ratio = @o5ratio * exp2(@o5det / 1200); + mod = s6->out; + index = 8; +}; + +# Op 4 as heard: its sine through its envelope and level. +node s4 math::mul { + in0 = op4->out; + in1 = e4->out * pow(2, (@o4level - 99) / 8) * pow(2, @o4vel * (ionode->velocity - 1) * 0.6); +}; + +node op4 osc::fmop { + freq = freq->out; + ratio = @o4ratio * exp2(@o4det / 1200); +}; + +# Op 3 as heard: its sine through its envelope and level. +node s3 math::mul { + in0 = op3->out; + in1 = e3->out * pow(2, (@o3level - 99) / 8) * pow(2, @o3vel * (ionode->velocity - 1) * 0.6); +}; + +node op3 osc::fmop { + freq = freq->out; + ratio = @o3ratio * exp2(@o3det / 1200); + mod = s4->out; + index = 8; +}; + +# Op 2 as heard: its sine through its envelope and level. +node s2 math::mul { + in0 = op2->out; + in1 = e2->out * pow(2, (@o2level - 99) / 8) * pow(2, @o2vel * (ionode->velocity - 1) * 0.6); +}; + +node op2 osc::fmop { + freq = freq->out; + ratio = @o2ratio * exp2(@o2det / 1200); +}; + +# Op 1 as heard: its sine through its envelope and level. +node s1 math::mul { + in0 = op1->out; + in1 = e1->out * pow(2, (@o1level - 99) / 8) * pow(2, @o1vel * (ionode->velocity - 1) * 0.6); +}; + +node op1 osc::fmop { + freq = freq->out; + ratio = @o1ratio * exp2(@o1det / 1200); + mod = s2->out + s3->out + s5->out; + index = 8; +}; + +node mix math::mul { + in0 = s1->out; + in1 = @mix / 1; +}; + +io ionode; diff --git a/dsp/dx22.dsp b/dsp/dx22.dsp new file mode 100644 index 00000000..4c1cd3a9 --- /dev/null +++ b/dsp/dx22.dsp @@ -0,0 +1,787 @@ +# DX 22 -- four carriers, one of them alone, the DX7's algorithm 22. +# +# 2 into 1, and 6 -- feeding back -- into 3, 4 and 5 at once, so three +# carriers share one modulator and move as a section. As shipped, they +# are tuned a few cents apart and swell in together with the modulator's +# index opening a moment behind them: the brass. +# +# THE KNOBS ARE THE DX7'S, op by op: a frequency ratio, a detune in +# cents, an output level and a velocity sensitivity, and an env::dx with +# four rates and four levels, all 0 to 99. Output level is in the +# envelope's own decibels -- 0.75 dB a step -- so on a modulator it is +# the brightness and on a carrier the volume, as on the hardware. +# Velocity sensitivity, 0 to 7, takes up to 25 dB off an op played +# softly; on a modulator that is a duller note rather than a quieter one. +# +# THE WIRING IS THE ALGORITHM. Each op is an osc::fmop and a modulator's +# output, through its envelope and level, is summed onto the `mod' of the +# op it feeds; the sum is scaled so a modulator at 99 swings the phase by +# 8 radians. Another algorithm is another file, not a knob -- see +# osc::fmop's head for why. `Feedback', 0 to 7 as on the panel, is op 6 +# on itself. + +name "DX 22: Four Carriers"; +author "Misha Nasledov"; +description "Six FM operators, one modulator over three carriers, the DX7's algorithm 22, set up as brass."; +category "Keys"; + + @o1ratio = 1; + @o1ratio.widget = 1; + @o1ratio.min = 0.5; + @o1ratio.max = 31; + @o1ratio.label = "Ratio"; + @o1ratio.group = "Op 1"; + + @o1det = 0; + @o1det.widget = 1; + @o1det.min = -50; + @o1det.max = 50; + @o1det.label = "Detune (cents)"; + @o1det.group = "Op 1"; + + @o1level = 90; + @o1level.widget = 1; + @o1level.min = 0; + @o1level.max = 99; + @o1level.step = 1; + @o1level.label = "Level"; + @o1level.group = "Op 1"; + + @o1vel = 2; + @o1vel.widget = 1; + @o1vel.min = 0; + @o1vel.max = 7; + @o1vel.step = 1; + @o1vel.label = "Velocity"; + @o1vel.group = "Op 1"; + + @o1r1 = 72; + @o1r1.widget = 1; + @o1r1.min = 0; + @o1r1.max = 99; + @o1r1.step = 1; + @o1r1.label = "Rate 1"; + @o1r1.group = "Op 1"; + + @o1r2 = 40; + @o1r2.widget = 1; + @o1r2.min = 0; + @o1r2.max = 99; + @o1r2.step = 1; + @o1r2.label = "Rate 2"; + @o1r2.group = "Op 1"; + + @o1r3 = 35; + @o1r3.widget = 1; + @o1r3.min = 0; + @o1r3.max = 99; + @o1r3.step = 1; + @o1r3.label = "Rate 3"; + @o1r3.group = "Op 1"; + + @o1r4 = 70; + @o1r4.widget = 1; + @o1r4.min = 0; + @o1r4.max = 99; + @o1r4.step = 1; + @o1r4.label = "Rate 4"; + @o1r4.group = "Op 1"; + + @o1l1 = 99; + @o1l1.widget = 1; + @o1l1.min = 0; + @o1l1.max = 99; + @o1l1.step = 1; + @o1l1.label = "Level 1"; + @o1l1.group = "Op 1"; + + @o1l2 = 92; + @o1l2.widget = 1; + @o1l2.min = 0; + @o1l2.max = 99; + @o1l2.step = 1; + @o1l2.label = "Level 2"; + @o1l2.group = "Op 1"; + + @o1l3 = 90; + @o1l3.widget = 1; + @o1l3.min = 0; + @o1l3.max = 99; + @o1l3.step = 1; + @o1l3.label = "Level 3"; + @o1l3.group = "Op 1"; + + @o1l4 = 0; + @o1l4.widget = 1; + @o1l4.min = 0; + @o1l4.max = 99; + @o1l4.step = 1; + @o1l4.label = "Level 4"; + @o1l4.group = "Op 1"; + + @o2ratio = 1; + @o2ratio.widget = 1; + @o2ratio.min = 0.5; + @o2ratio.max = 31; + @o2ratio.label = "Ratio"; + @o2ratio.group = "Op 2"; + + @o2det = 0; + @o2det.widget = 1; + @o2det.min = -50; + @o2det.max = 50; + @o2det.label = "Detune (cents)"; + @o2det.group = "Op 2"; + + @o2level = 74; + @o2level.widget = 1; + @o2level.min = 0; + @o2level.max = 99; + @o2level.step = 1; + @o2level.label = "Level"; + @o2level.group = "Op 2"; + + @o2vel = 4; + @o2vel.widget = 1; + @o2vel.min = 0; + @o2vel.max = 7; + @o2vel.step = 1; + @o2vel.label = "Velocity"; + @o2vel.group = "Op 2"; + + @o2r1 = 60; + @o2r1.widget = 1; + @o2r1.min = 0; + @o2r1.max = 99; + @o2r1.step = 1; + @o2r1.label = "Rate 1"; + @o2r1.group = "Op 2"; + + @o2r2 = 30; + @o2r2.widget = 1; + @o2r2.min = 0; + @o2r2.max = 99; + @o2r2.step = 1; + @o2r2.label = "Rate 2"; + @o2r2.group = "Op 2"; + + @o2r3 = 35; + @o2r3.widget = 1; + @o2r3.min = 0; + @o2r3.max = 99; + @o2r3.step = 1; + @o2r3.label = "Rate 3"; + @o2r3.group = "Op 2"; + + @o2r4 = 70; + @o2r4.widget = 1; + @o2r4.min = 0; + @o2r4.max = 99; + @o2r4.step = 1; + @o2r4.label = "Rate 4"; + @o2r4.group = "Op 2"; + + @o2l1 = 99; + @o2l1.widget = 1; + @o2l1.min = 0; + @o2l1.max = 99; + @o2l1.step = 1; + @o2l1.label = "Level 1"; + @o2l1.group = "Op 2"; + + @o2l2 = 90; + @o2l2.widget = 1; + @o2l2.min = 0; + @o2l2.max = 99; + @o2l2.step = 1; + @o2l2.label = "Level 2"; + @o2l2.group = "Op 2"; + + @o2l3 = 82; + @o2l3.widget = 1; + @o2l3.min = 0; + @o2l3.max = 99; + @o2l3.step = 1; + @o2l3.label = "Level 3"; + @o2l3.group = "Op 2"; + + @o2l4 = 0; + @o2l4.widget = 1; + @o2l4.min = 0; + @o2l4.max = 99; + @o2l4.step = 1; + @o2l4.label = "Level 4"; + @o2l4.group = "Op 2"; + + @o3ratio = 1; + @o3ratio.widget = 1; + @o3ratio.min = 0.5; + @o3ratio.max = 31; + @o3ratio.label = "Ratio"; + @o3ratio.group = "Op 3"; + + @o3det = 5; + @o3det.widget = 1; + @o3det.min = -50; + @o3det.max = 50; + @o3det.label = "Detune (cents)"; + @o3det.group = "Op 3"; + + @o3level = 94; + @o3level.widget = 1; + @o3level.min = 0; + @o3level.max = 99; + @o3level.step = 1; + @o3level.label = "Level"; + @o3level.group = "Op 3"; + + @o3vel = 2; + @o3vel.widget = 1; + @o3vel.min = 0; + @o3vel.max = 7; + @o3vel.step = 1; + @o3vel.label = "Velocity"; + @o3vel.group = "Op 3"; + + @o3r1 = 68; + @o3r1.widget = 1; + @o3r1.min = 0; + @o3r1.max = 99; + @o3r1.step = 1; + @o3r1.label = "Rate 1"; + @o3r1.group = "Op 3"; + + @o3r2 = 40; + @o3r2.widget = 1; + @o3r2.min = 0; + @o3r2.max = 99; + @o3r2.step = 1; + @o3r2.label = "Rate 2"; + @o3r2.group = "Op 3"; + + @o3r3 = 35; + @o3r3.widget = 1; + @o3r3.min = 0; + @o3r3.max = 99; + @o3r3.step = 1; + @o3r3.label = "Rate 3"; + @o3r3.group = "Op 3"; + + @o3r4 = 70; + @o3r4.widget = 1; + @o3r4.min = 0; + @o3r4.max = 99; + @o3r4.step = 1; + @o3r4.label = "Rate 4"; + @o3r4.group = "Op 3"; + + @o3l1 = 99; + @o3l1.widget = 1; + @o3l1.min = 0; + @o3l1.max = 99; + @o3l1.step = 1; + @o3l1.label = "Level 1"; + @o3l1.group = "Op 3"; + + @o3l2 = 95; + @o3l2.widget = 1; + @o3l2.min = 0; + @o3l2.max = 99; + @o3l2.step = 1; + @o3l2.label = "Level 2"; + @o3l2.group = "Op 3"; + + @o3l3 = 92; + @o3l3.widget = 1; + @o3l3.min = 0; + @o3l3.max = 99; + @o3l3.step = 1; + @o3l3.label = "Level 3"; + @o3l3.group = "Op 3"; + + @o3l4 = 0; + @o3l4.widget = 1; + @o3l4.min = 0; + @o3l4.max = 99; + @o3l4.step = 1; + @o3l4.label = "Level 4"; + @o3l4.group = "Op 3"; + + @o4ratio = 1; + @o4ratio.widget = 1; + @o4ratio.min = 0.5; + @o4ratio.max = 31; + @o4ratio.label = "Ratio"; + @o4ratio.group = "Op 4"; + + @o4det = -5; + @o4det.widget = 1; + @o4det.min = -50; + @o4det.max = 50; + @o4det.label = "Detune (cents)"; + @o4det.group = "Op 4"; + + @o4level = 94; + @o4level.widget = 1; + @o4level.min = 0; + @o4level.max = 99; + @o4level.step = 1; + @o4level.label = "Level"; + @o4level.group = "Op 4"; + + @o4vel = 2; + @o4vel.widget = 1; + @o4vel.min = 0; + @o4vel.max = 7; + @o4vel.step = 1; + @o4vel.label = "Velocity"; + @o4vel.group = "Op 4"; + + @o4r1 = 68; + @o4r1.widget = 1; + @o4r1.min = 0; + @o4r1.max = 99; + @o4r1.step = 1; + @o4r1.label = "Rate 1"; + @o4r1.group = "Op 4"; + + @o4r2 = 40; + @o4r2.widget = 1; + @o4r2.min = 0; + @o4r2.max = 99; + @o4r2.step = 1; + @o4r2.label = "Rate 2"; + @o4r2.group = "Op 4"; + + @o4r3 = 35; + @o4r3.widget = 1; + @o4r3.min = 0; + @o4r3.max = 99; + @o4r3.step = 1; + @o4r3.label = "Rate 3"; + @o4r3.group = "Op 4"; + + @o4r4 = 70; + @o4r4.widget = 1; + @o4r4.min = 0; + @o4r4.max = 99; + @o4r4.step = 1; + @o4r4.label = "Rate 4"; + @o4r4.group = "Op 4"; + + @o4l1 = 99; + @o4l1.widget = 1; + @o4l1.min = 0; + @o4l1.max = 99; + @o4l1.step = 1; + @o4l1.label = "Level 1"; + @o4l1.group = "Op 4"; + + @o4l2 = 95; + @o4l2.widget = 1; + @o4l2.min = 0; + @o4l2.max = 99; + @o4l2.step = 1; + @o4l2.label = "Level 2"; + @o4l2.group = "Op 4"; + + @o4l3 = 92; + @o4l3.widget = 1; + @o4l3.min = 0; + @o4l3.max = 99; + @o4l3.step = 1; + @o4l3.label = "Level 3"; + @o4l3.group = "Op 4"; + + @o4l4 = 0; + @o4l4.widget = 1; + @o4l4.min = 0; + @o4l4.max = 99; + @o4l4.step = 1; + @o4l4.label = "Level 4"; + @o4l4.group = "Op 4"; + + @o5ratio = 2; + @o5ratio.widget = 1; + @o5ratio.min = 0.5; + @o5ratio.max = 31; + @o5ratio.label = "Ratio"; + @o5ratio.group = "Op 5"; + + @o5det = 0; + @o5det.widget = 1; + @o5det.min = -50; + @o5det.max = 50; + @o5det.label = "Detune (cents)"; + @o5det.group = "Op 5"; + + @o5level = 82; + @o5level.widget = 1; + @o5level.min = 0; + @o5level.max = 99; + @o5level.step = 1; + @o5level.label = "Level"; + @o5level.group = "Op 5"; + + @o5vel = 2; + @o5vel.widget = 1; + @o5vel.min = 0; + @o5vel.max = 7; + @o5vel.step = 1; + @o5vel.label = "Velocity"; + @o5vel.group = "Op 5"; + + @o5r1 = 66; + @o5r1.widget = 1; + @o5r1.min = 0; + @o5r1.max = 99; + @o5r1.step = 1; + @o5r1.label = "Rate 1"; + @o5r1.group = "Op 5"; + + @o5r2 = 40; + @o5r2.widget = 1; + @o5r2.min = 0; + @o5r2.max = 99; + @o5r2.step = 1; + @o5r2.label = "Rate 2"; + @o5r2.group = "Op 5"; + + @o5r3 = 35; + @o5r3.widget = 1; + @o5r3.min = 0; + @o5r3.max = 99; + @o5r3.step = 1; + @o5r3.label = "Rate 3"; + @o5r3.group = "Op 5"; + + @o5r4 = 70; + @o5r4.widget = 1; + @o5r4.min = 0; + @o5r4.max = 99; + @o5r4.step = 1; + @o5r4.label = "Rate 4"; + @o5r4.group = "Op 5"; + + @o5l1 = 99; + @o5l1.widget = 1; + @o5l1.min = 0; + @o5l1.max = 99; + @o5l1.step = 1; + @o5l1.label = "Level 1"; + @o5l1.group = "Op 5"; + + @o5l2 = 92; + @o5l2.widget = 1; + @o5l2.min = 0; + @o5l2.max = 99; + @o5l2.step = 1; + @o5l2.label = "Level 2"; + @o5l2.group = "Op 5"; + + @o5l3 = 88; + @o5l3.widget = 1; + @o5l3.min = 0; + @o5l3.max = 99; + @o5l3.step = 1; + @o5l3.label = "Level 3"; + @o5l3.group = "Op 5"; + + @o5l4 = 0; + @o5l4.widget = 1; + @o5l4.min = 0; + @o5l4.max = 99; + @o5l4.step = 1; + @o5l4.label = "Level 4"; + @o5l4.group = "Op 5"; + + @o6ratio = 1; + @o6ratio.widget = 1; + @o6ratio.min = 0.5; + @o6ratio.max = 31; + @o6ratio.label = "Ratio"; + @o6ratio.group = "Op 6"; + + @o6det = 0; + @o6det.widget = 1; + @o6det.min = -50; + @o6det.max = 50; + @o6det.label = "Detune (cents)"; + @o6det.group = "Op 6"; + + @o6level = 80; + @o6level.widget = 1; + @o6level.min = 0; + @o6level.max = 99; + @o6level.step = 1; + @o6level.label = "Level"; + @o6level.group = "Op 6"; + + @o6vel = 4; + @o6vel.widget = 1; + @o6vel.min = 0; + @o6vel.max = 7; + @o6vel.step = 1; + @o6vel.label = "Velocity"; + @o6vel.group = "Op 6"; + + @o6r1 = 55; + @o6r1.widget = 1; + @o6r1.min = 0; + @o6r1.max = 99; + @o6r1.step = 1; + @o6r1.label = "Rate 1"; + @o6r1.group = "Op 6"; + + @o6r2 = 35; + @o6r2.widget = 1; + @o6r2.min = 0; + @o6r2.max = 99; + @o6r2.step = 1; + @o6r2.label = "Rate 2"; + @o6r2.group = "Op 6"; + + @o6r3 = 35; + @o6r3.widget = 1; + @o6r3.min = 0; + @o6r3.max = 99; + @o6r3.step = 1; + @o6r3.label = "Rate 3"; + @o6r3.group = "Op 6"; + + @o6r4 = 70; + @o6r4.widget = 1; + @o6r4.min = 0; + @o6r4.max = 99; + @o6r4.step = 1; + @o6r4.label = "Rate 4"; + @o6r4.group = "Op 6"; + + @o6l1 = 99; + @o6l1.widget = 1; + @o6l1.min = 0; + @o6l1.max = 99; + @o6l1.step = 1; + @o6l1.label = "Level 1"; + @o6l1.group = "Op 6"; + + @o6l2 = 88; + @o6l2.widget = 1; + @o6l2.min = 0; + @o6l2.max = 99; + @o6l2.step = 1; + @o6l2.label = "Level 2"; + @o6l2.group = "Op 6"; + + @o6l3 = 80; + @o6l3.widget = 1; + @o6l3.min = 0; + @o6l3.max = 99; + @o6l3.step = 1; + @o6l3.label = "Level 3"; + @o6l3.group = "Op 6"; + + @o6l4 = 0; + @o6l4.widget = 1; + @o6l4.min = 0; + @o6l4.max = 99; + @o6l4.step = 1; + @o6l4.label = "Level 4"; + @o6l4.group = "Op 6"; + + @feedback = 7; + @feedback.widget = 1; + @feedback.min = 0; + @feedback.max = 7; + @feedback.step = 1; + @feedback.label = "Feedback"; + + @ratescale = 2; + @ratescale.widget = 1; + @ratescale.min = 0; + @ratescale.max = 7; + @ratescale.step = 1; + @ratescale.label = "Rate Scaling"; + + @mix = 0.5; + @mix.widget = 1; + @mix.min = 0; + @mix.max = 1; + @mix.label = "Output"; + +node ionode { + channels = 2; + out0 = mix->out; + out1 = mix->out; + play = max(e5->play, max(e4->play, max(e3->play, e1->play))); +}; + +node freq misc::midi2freq { + note = ionode->note; +}; + +node e1 env::dx { + r1 = @o1r1; + r2 = @o1r2; + r3 = @o1r3; + r4 = @o1r4; + l1 = @o1l1; + l2 = @o1l2; + l3 = @o1l3; + l4 = @o1l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e2 env::dx { + r1 = @o2r1; + r2 = @o2r2; + r3 = @o2r3; + r4 = @o2r4; + l1 = @o2l1; + l2 = @o2l2; + l3 = @o2l3; + l4 = @o2l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e3 env::dx { + r1 = @o3r1; + r2 = @o3r2; + r3 = @o3r3; + r4 = @o3r4; + l1 = @o3l1; + l2 = @o3l2; + l3 = @o3l3; + l4 = @o3l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e4 env::dx { + r1 = @o4r1; + r2 = @o4r2; + r3 = @o4r3; + r4 = @o4r4; + l1 = @o4l1; + l2 = @o4l2; + l3 = @o4l3; + l4 = @o4l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e5 env::dx { + r1 = @o5r1; + r2 = @o5r2; + r3 = @o5r3; + r4 = @o5r4; + l1 = @o5l1; + l2 = @o5l2; + l3 = @o5l3; + l4 = @o5l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e6 env::dx { + r1 = @o6r1; + r2 = @o6r2; + r3 = @o6r3; + r4 = @o6r4; + l1 = @o6l1; + l2 = @o6l2; + l3 = @o6l3; + l4 = @o6l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +# Op 6 as heard: its sine through its envelope and level. +node s6 math::mul { + in0 = op6->out; + in1 = e6->out * pow(2, (@o6level - 99) / 8) * pow(2, @o6vel * (ionode->velocity - 1) * 0.6); +}; + +node op6 osc::fmop { + freq = freq->out; + ratio = @o6ratio * exp2(@o6det / 1200); + feedback = @feedback / 7; +}; + +# Op 5 as heard: its sine through its envelope and level. +node s5 math::mul { + in0 = op5->out; + in1 = e5->out * pow(2, (@o5level - 99) / 8) * pow(2, @o5vel * (ionode->velocity - 1) * 0.6); +}; + +node op5 osc::fmop { + freq = freq->out; + ratio = @o5ratio * exp2(@o5det / 1200); + mod = s6->out; + index = 8; +}; + +# Op 4 as heard: its sine through its envelope and level. +node s4 math::mul { + in0 = op4->out; + in1 = e4->out * pow(2, (@o4level - 99) / 8) * pow(2, @o4vel * (ionode->velocity - 1) * 0.6); +}; + +node op4 osc::fmop { + freq = freq->out; + ratio = @o4ratio * exp2(@o4det / 1200); + mod = s6->out; + index = 8; +}; + +# Op 3 as heard: its sine through its envelope and level. +node s3 math::mul { + in0 = op3->out; + in1 = e3->out * pow(2, (@o3level - 99) / 8) * pow(2, @o3vel * (ionode->velocity - 1) * 0.6); +}; + +node op3 osc::fmop { + freq = freq->out; + ratio = @o3ratio * exp2(@o3det / 1200); + mod = s6->out; + index = 8; +}; + +# Op 2 as heard: its sine through its envelope and level. +node s2 math::mul { + in0 = op2->out; + in1 = e2->out * pow(2, (@o2level - 99) / 8) * pow(2, @o2vel * (ionode->velocity - 1) * 0.6); +}; + +node op2 osc::fmop { + freq = freq->out; + ratio = @o2ratio * exp2(@o2det / 1200); +}; + +# Op 1 as heard: its sine through its envelope and level. +node s1 math::mul { + in0 = op1->out; + in1 = e1->out * pow(2, (@o1level - 99) / 8) * pow(2, @o1vel * (ionode->velocity - 1) * 0.6); +}; + +node op1 osc::fmop { + freq = freq->out; + ratio = @o1ratio * exp2(@o1det / 1200); + mod = s2->out; + index = 8; +}; + +node mix math::mul { + in0 = s1->out + s3->out + s4->out + s5->out; + in1 = @mix / 4; +}; + +io ionode; diff --git a/dsp/dx32.dsp b/dsp/dx32.dsp new file mode 100644 index 00000000..c98900ec --- /dev/null +++ b/dsp/dx32.dsp @@ -0,0 +1,780 @@ +# DX 32 -- six carriers and no modulators, the DX7's algorithm 32. +# +# Additive synthesis: six sines summed, op 6's feedback the only FM left, +# which turns one of them toward a sawtooth. Set up as an organ -- the +# ratios are drawbar footages, 16' to 2' -- because six sines at +# harmonic ratios with square envelopes is exactly what a drawbar organ +# is. +# +# THE KNOBS ARE THE DX7'S, op by op: a frequency ratio, a detune in +# cents, an output level and a velocity sensitivity, and an env::dx with +# four rates and four levels, all 0 to 99. Output level is in the +# envelope's own decibels -- 0.75 dB a step -- so on a modulator it is +# the brightness and on a carrier the volume, as on the hardware. +# Velocity sensitivity, 0 to 7, takes up to 25 dB off an op played +# softly; on a modulator that is a duller note rather than a quieter one. +# +# THE WIRING IS THE ALGORITHM. Each op is an osc::fmop and a modulator's +# output, through its envelope and level, is summed onto the `mod' of the +# op it feeds; the sum is scaled so a modulator at 99 swings the phase by +# 8 radians. Another algorithm is another file, not a knob -- see +# osc::fmop's head for why. `Feedback', 0 to 7 as on the panel, is op 6 +# on itself. + +name "DX 32: Six Carriers"; +author "Misha Nasledov"; +description "Six FM operators all heard, the DX7's algorithm 32, set up as an organ."; +category "Keys"; + + @o1ratio = 0.5; + @o1ratio.widget = 1; + @o1ratio.min = 0.5; + @o1ratio.max = 31; + @o1ratio.label = "Ratio"; + @o1ratio.group = "Op 1"; + + @o1det = 0; + @o1det.widget = 1; + @o1det.min = -50; + @o1det.max = 50; + @o1det.label = "Detune (cents)"; + @o1det.group = "Op 1"; + + @o1level = 92; + @o1level.widget = 1; + @o1level.min = 0; + @o1level.max = 99; + @o1level.step = 1; + @o1level.label = "Level"; + @o1level.group = "Op 1"; + + @o1vel = 0; + @o1vel.widget = 1; + @o1vel.min = 0; + @o1vel.max = 7; + @o1vel.step = 1; + @o1vel.label = "Velocity"; + @o1vel.group = "Op 1"; + + @o1r1 = 99; + @o1r1.widget = 1; + @o1r1.min = 0; + @o1r1.max = 99; + @o1r1.step = 1; + @o1r1.label = "Rate 1"; + @o1r1.group = "Op 1"; + + @o1r2 = 99; + @o1r2.widget = 1; + @o1r2.min = 0; + @o1r2.max = 99; + @o1r2.step = 1; + @o1r2.label = "Rate 2"; + @o1r2.group = "Op 1"; + + @o1r3 = 99; + @o1r3.widget = 1; + @o1r3.min = 0; + @o1r3.max = 99; + @o1r3.step = 1; + @o1r3.label = "Rate 3"; + @o1r3.group = "Op 1"; + + @o1r4 = 70; + @o1r4.widget = 1; + @o1r4.min = 0; + @o1r4.max = 99; + @o1r4.step = 1; + @o1r4.label = "Rate 4"; + @o1r4.group = "Op 1"; + + @o1l1 = 99; + @o1l1.widget = 1; + @o1l1.min = 0; + @o1l1.max = 99; + @o1l1.step = 1; + @o1l1.label = "Level 1"; + @o1l1.group = "Op 1"; + + @o1l2 = 99; + @o1l2.widget = 1; + @o1l2.min = 0; + @o1l2.max = 99; + @o1l2.step = 1; + @o1l2.label = "Level 2"; + @o1l2.group = "Op 1"; + + @o1l3 = 99; + @o1l3.widget = 1; + @o1l3.min = 0; + @o1l3.max = 99; + @o1l3.step = 1; + @o1l3.label = "Level 3"; + @o1l3.group = "Op 1"; + + @o1l4 = 0; + @o1l4.widget = 1; + @o1l4.min = 0; + @o1l4.max = 99; + @o1l4.step = 1; + @o1l4.label = "Level 4"; + @o1l4.group = "Op 1"; + + @o2ratio = 1; + @o2ratio.widget = 1; + @o2ratio.min = 0.5; + @o2ratio.max = 31; + @o2ratio.label = "Ratio"; + @o2ratio.group = "Op 2"; + + @o2det = 0; + @o2det.widget = 1; + @o2det.min = -50; + @o2det.max = 50; + @o2det.label = "Detune (cents)"; + @o2det.group = "Op 2"; + + @o2level = 99; + @o2level.widget = 1; + @o2level.min = 0; + @o2level.max = 99; + @o2level.step = 1; + @o2level.label = "Level"; + @o2level.group = "Op 2"; + + @o2vel = 0; + @o2vel.widget = 1; + @o2vel.min = 0; + @o2vel.max = 7; + @o2vel.step = 1; + @o2vel.label = "Velocity"; + @o2vel.group = "Op 2"; + + @o2r1 = 99; + @o2r1.widget = 1; + @o2r1.min = 0; + @o2r1.max = 99; + @o2r1.step = 1; + @o2r1.label = "Rate 1"; + @o2r1.group = "Op 2"; + + @o2r2 = 99; + @o2r2.widget = 1; + @o2r2.min = 0; + @o2r2.max = 99; + @o2r2.step = 1; + @o2r2.label = "Rate 2"; + @o2r2.group = "Op 2"; + + @o2r3 = 99; + @o2r3.widget = 1; + @o2r3.min = 0; + @o2r3.max = 99; + @o2r3.step = 1; + @o2r3.label = "Rate 3"; + @o2r3.group = "Op 2"; + + @o2r4 = 70; + @o2r4.widget = 1; + @o2r4.min = 0; + @o2r4.max = 99; + @o2r4.step = 1; + @o2r4.label = "Rate 4"; + @o2r4.group = "Op 2"; + + @o2l1 = 99; + @o2l1.widget = 1; + @o2l1.min = 0; + @o2l1.max = 99; + @o2l1.step = 1; + @o2l1.label = "Level 1"; + @o2l1.group = "Op 2"; + + @o2l2 = 99; + @o2l2.widget = 1; + @o2l2.min = 0; + @o2l2.max = 99; + @o2l2.step = 1; + @o2l2.label = "Level 2"; + @o2l2.group = "Op 2"; + + @o2l3 = 99; + @o2l3.widget = 1; + @o2l3.min = 0; + @o2l3.max = 99; + @o2l3.step = 1; + @o2l3.label = "Level 3"; + @o2l3.group = "Op 2"; + + @o2l4 = 0; + @o2l4.widget = 1; + @o2l4.min = 0; + @o2l4.max = 99; + @o2l4.step = 1; + @o2l4.label = "Level 4"; + @o2l4.group = "Op 2"; + + @o3ratio = 1.5; + @o3ratio.widget = 1; + @o3ratio.min = 0.5; + @o3ratio.max = 31; + @o3ratio.label = "Ratio"; + @o3ratio.group = "Op 3"; + + @o3det = 0; + @o3det.widget = 1; + @o3det.min = -50; + @o3det.max = 50; + @o3det.label = "Detune (cents)"; + @o3det.group = "Op 3"; + + @o3level = 88; + @o3level.widget = 1; + @o3level.min = 0; + @o3level.max = 99; + @o3level.step = 1; + @o3level.label = "Level"; + @o3level.group = "Op 3"; + + @o3vel = 0; + @o3vel.widget = 1; + @o3vel.min = 0; + @o3vel.max = 7; + @o3vel.step = 1; + @o3vel.label = "Velocity"; + @o3vel.group = "Op 3"; + + @o3r1 = 99; + @o3r1.widget = 1; + @o3r1.min = 0; + @o3r1.max = 99; + @o3r1.step = 1; + @o3r1.label = "Rate 1"; + @o3r1.group = "Op 3"; + + @o3r2 = 99; + @o3r2.widget = 1; + @o3r2.min = 0; + @o3r2.max = 99; + @o3r2.step = 1; + @o3r2.label = "Rate 2"; + @o3r2.group = "Op 3"; + + @o3r3 = 99; + @o3r3.widget = 1; + @o3r3.min = 0; + @o3r3.max = 99; + @o3r3.step = 1; + @o3r3.label = "Rate 3"; + @o3r3.group = "Op 3"; + + @o3r4 = 70; + @o3r4.widget = 1; + @o3r4.min = 0; + @o3r4.max = 99; + @o3r4.step = 1; + @o3r4.label = "Rate 4"; + @o3r4.group = "Op 3"; + + @o3l1 = 99; + @o3l1.widget = 1; + @o3l1.min = 0; + @o3l1.max = 99; + @o3l1.step = 1; + @o3l1.label = "Level 1"; + @o3l1.group = "Op 3"; + + @o3l2 = 99; + @o3l2.widget = 1; + @o3l2.min = 0; + @o3l2.max = 99; + @o3l2.step = 1; + @o3l2.label = "Level 2"; + @o3l2.group = "Op 3"; + + @o3l3 = 99; + @o3l3.widget = 1; + @o3l3.min = 0; + @o3l3.max = 99; + @o3l3.step = 1; + @o3l3.label = "Level 3"; + @o3l3.group = "Op 3"; + + @o3l4 = 0; + @o3l4.widget = 1; + @o3l4.min = 0; + @o3l4.max = 99; + @o3l4.step = 1; + @o3l4.label = "Level 4"; + @o3l4.group = "Op 3"; + + @o4ratio = 2; + @o4ratio.widget = 1; + @o4ratio.min = 0.5; + @o4ratio.max = 31; + @o4ratio.label = "Ratio"; + @o4ratio.group = "Op 4"; + + @o4det = 0; + @o4det.widget = 1; + @o4det.min = -50; + @o4det.max = 50; + @o4det.label = "Detune (cents)"; + @o4det.group = "Op 4"; + + @o4level = 92; + @o4level.widget = 1; + @o4level.min = 0; + @o4level.max = 99; + @o4level.step = 1; + @o4level.label = "Level"; + @o4level.group = "Op 4"; + + @o4vel = 0; + @o4vel.widget = 1; + @o4vel.min = 0; + @o4vel.max = 7; + @o4vel.step = 1; + @o4vel.label = "Velocity"; + @o4vel.group = "Op 4"; + + @o4r1 = 99; + @o4r1.widget = 1; + @o4r1.min = 0; + @o4r1.max = 99; + @o4r1.step = 1; + @o4r1.label = "Rate 1"; + @o4r1.group = "Op 4"; + + @o4r2 = 99; + @o4r2.widget = 1; + @o4r2.min = 0; + @o4r2.max = 99; + @o4r2.step = 1; + @o4r2.label = "Rate 2"; + @o4r2.group = "Op 4"; + + @o4r3 = 99; + @o4r3.widget = 1; + @o4r3.min = 0; + @o4r3.max = 99; + @o4r3.step = 1; + @o4r3.label = "Rate 3"; + @o4r3.group = "Op 4"; + + @o4r4 = 70; + @o4r4.widget = 1; + @o4r4.min = 0; + @o4r4.max = 99; + @o4r4.step = 1; + @o4r4.label = "Rate 4"; + @o4r4.group = "Op 4"; + + @o4l1 = 99; + @o4l1.widget = 1; + @o4l1.min = 0; + @o4l1.max = 99; + @o4l1.step = 1; + @o4l1.label = "Level 1"; + @o4l1.group = "Op 4"; + + @o4l2 = 99; + @o4l2.widget = 1; + @o4l2.min = 0; + @o4l2.max = 99; + @o4l2.step = 1; + @o4l2.label = "Level 2"; + @o4l2.group = "Op 4"; + + @o4l3 = 99; + @o4l3.widget = 1; + @o4l3.min = 0; + @o4l3.max = 99; + @o4l3.step = 1; + @o4l3.label = "Level 3"; + @o4l3.group = "Op 4"; + + @o4l4 = 0; + @o4l4.widget = 1; + @o4l4.min = 0; + @o4l4.max = 99; + @o4l4.step = 1; + @o4l4.label = "Level 4"; + @o4l4.group = "Op 4"; + + @o5ratio = 3; + @o5ratio.widget = 1; + @o5ratio.min = 0.5; + @o5ratio.max = 31; + @o5ratio.label = "Ratio"; + @o5ratio.group = "Op 5"; + + @o5det = 0; + @o5det.widget = 1; + @o5det.min = -50; + @o5det.max = 50; + @o5det.label = "Detune (cents)"; + @o5det.group = "Op 5"; + + @o5level = 82; + @o5level.widget = 1; + @o5level.min = 0; + @o5level.max = 99; + @o5level.step = 1; + @o5level.label = "Level"; + @o5level.group = "Op 5"; + + @o5vel = 0; + @o5vel.widget = 1; + @o5vel.min = 0; + @o5vel.max = 7; + @o5vel.step = 1; + @o5vel.label = "Velocity"; + @o5vel.group = "Op 5"; + + @o5r1 = 99; + @o5r1.widget = 1; + @o5r1.min = 0; + @o5r1.max = 99; + @o5r1.step = 1; + @o5r1.label = "Rate 1"; + @o5r1.group = "Op 5"; + + @o5r2 = 99; + @o5r2.widget = 1; + @o5r2.min = 0; + @o5r2.max = 99; + @o5r2.step = 1; + @o5r2.label = "Rate 2"; + @o5r2.group = "Op 5"; + + @o5r3 = 99; + @o5r3.widget = 1; + @o5r3.min = 0; + @o5r3.max = 99; + @o5r3.step = 1; + @o5r3.label = "Rate 3"; + @o5r3.group = "Op 5"; + + @o5r4 = 70; + @o5r4.widget = 1; + @o5r4.min = 0; + @o5r4.max = 99; + @o5r4.step = 1; + @o5r4.label = "Rate 4"; + @o5r4.group = "Op 5"; + + @o5l1 = 99; + @o5l1.widget = 1; + @o5l1.min = 0; + @o5l1.max = 99; + @o5l1.step = 1; + @o5l1.label = "Level 1"; + @o5l1.group = "Op 5"; + + @o5l2 = 99; + @o5l2.widget = 1; + @o5l2.min = 0; + @o5l2.max = 99; + @o5l2.step = 1; + @o5l2.label = "Level 2"; + @o5l2.group = "Op 5"; + + @o5l3 = 99; + @o5l3.widget = 1; + @o5l3.min = 0; + @o5l3.max = 99; + @o5l3.step = 1; + @o5l3.label = "Level 3"; + @o5l3.group = "Op 5"; + + @o5l4 = 0; + @o5l4.widget = 1; + @o5l4.min = 0; + @o5l4.max = 99; + @o5l4.step = 1; + @o5l4.label = "Level 4"; + @o5l4.group = "Op 5"; + + @o6ratio = 4; + @o6ratio.widget = 1; + @o6ratio.min = 0.5; + @o6ratio.max = 31; + @o6ratio.label = "Ratio"; + @o6ratio.group = "Op 6"; + + @o6det = 0; + @o6det.widget = 1; + @o6det.min = -50; + @o6det.max = 50; + @o6det.label = "Detune (cents)"; + @o6det.group = "Op 6"; + + @o6level = 80; + @o6level.widget = 1; + @o6level.min = 0; + @o6level.max = 99; + @o6level.step = 1; + @o6level.label = "Level"; + @o6level.group = "Op 6"; + + @o6vel = 0; + @o6vel.widget = 1; + @o6vel.min = 0; + @o6vel.max = 7; + @o6vel.step = 1; + @o6vel.label = "Velocity"; + @o6vel.group = "Op 6"; + + @o6r1 = 99; + @o6r1.widget = 1; + @o6r1.min = 0; + @o6r1.max = 99; + @o6r1.step = 1; + @o6r1.label = "Rate 1"; + @o6r1.group = "Op 6"; + + @o6r2 = 99; + @o6r2.widget = 1; + @o6r2.min = 0; + @o6r2.max = 99; + @o6r2.step = 1; + @o6r2.label = "Rate 2"; + @o6r2.group = "Op 6"; + + @o6r3 = 99; + @o6r3.widget = 1; + @o6r3.min = 0; + @o6r3.max = 99; + @o6r3.step = 1; + @o6r3.label = "Rate 3"; + @o6r3.group = "Op 6"; + + @o6r4 = 70; + @o6r4.widget = 1; + @o6r4.min = 0; + @o6r4.max = 99; + @o6r4.step = 1; + @o6r4.label = "Rate 4"; + @o6r4.group = "Op 6"; + + @o6l1 = 99; + @o6l1.widget = 1; + @o6l1.min = 0; + @o6l1.max = 99; + @o6l1.step = 1; + @o6l1.label = "Level 1"; + @o6l1.group = "Op 6"; + + @o6l2 = 99; + @o6l2.widget = 1; + @o6l2.min = 0; + @o6l2.max = 99; + @o6l2.step = 1; + @o6l2.label = "Level 2"; + @o6l2.group = "Op 6"; + + @o6l3 = 99; + @o6l3.widget = 1; + @o6l3.min = 0; + @o6l3.max = 99; + @o6l3.step = 1; + @o6l3.label = "Level 3"; + @o6l3.group = "Op 6"; + + @o6l4 = 0; + @o6l4.widget = 1; + @o6l4.min = 0; + @o6l4.max = 99; + @o6l4.step = 1; + @o6l4.label = "Level 4"; + @o6l4.group = "Op 6"; + + @feedback = 0; + @feedback.widget = 1; + @feedback.min = 0; + @feedback.max = 7; + @feedback.step = 1; + @feedback.label = "Feedback"; + + @ratescale = 0; + @ratescale.widget = 1; + @ratescale.min = 0; + @ratescale.max = 7; + @ratescale.step = 1; + @ratescale.label = "Rate Scaling"; + + @mix = 0.3; + @mix.widget = 1; + @mix.min = 0; + @mix.max = 1; + @mix.label = "Output"; + +node ionode { + channels = 2; + out0 = mix->out; + out1 = mix->out; + play = max(e6->play, max(e5->play, max(e4->play, max(e3->play, max(e2->play, e1->play))))); +}; + +node freq misc::midi2freq { + note = ionode->note; +}; + +node e1 env::dx { + r1 = @o1r1; + r2 = @o1r2; + r3 = @o1r3; + r4 = @o1r4; + l1 = @o1l1; + l2 = @o1l2; + l3 = @o1l3; + l4 = @o1l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e2 env::dx { + r1 = @o2r1; + r2 = @o2r2; + r3 = @o2r3; + r4 = @o2r4; + l1 = @o2l1; + l2 = @o2l2; + l3 = @o2l3; + l4 = @o2l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e3 env::dx { + r1 = @o3r1; + r2 = @o3r2; + r3 = @o3r3; + r4 = @o3r4; + l1 = @o3l1; + l2 = @o3l2; + l3 = @o3l3; + l4 = @o3l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e4 env::dx { + r1 = @o4r1; + r2 = @o4r2; + r3 = @o4r3; + r4 = @o4r4; + l1 = @o4l1; + l2 = @o4l2; + l3 = @o4l3; + l4 = @o4l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e5 env::dx { + r1 = @o5r1; + r2 = @o5r2; + r3 = @o5r3; + r4 = @o5r4; + l1 = @o5l1; + l2 = @o5l2; + l3 = @o5l3; + l4 = @o5l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e6 env::dx { + r1 = @o6r1; + r2 = @o6r2; + r3 = @o6r3; + r4 = @o6r4; + l1 = @o6l1; + l2 = @o6l2; + l3 = @o6l3; + l4 = @o6l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +# Op 6 as heard: its sine through its envelope and level. +node s6 math::mul { + in0 = op6->out; + in1 = e6->out * pow(2, (@o6level - 99) / 8) * pow(2, @o6vel * (ionode->velocity - 1) * 0.6); +}; + +node op6 osc::fmop { + freq = freq->out; + ratio = @o6ratio * exp2(@o6det / 1200); + feedback = @feedback / 7; +}; + +# Op 5 as heard: its sine through its envelope and level. +node s5 math::mul { + in0 = op5->out; + in1 = e5->out * pow(2, (@o5level - 99) / 8) * pow(2, @o5vel * (ionode->velocity - 1) * 0.6); +}; + +node op5 osc::fmop { + freq = freq->out; + ratio = @o5ratio * exp2(@o5det / 1200); +}; + +# Op 4 as heard: its sine through its envelope and level. +node s4 math::mul { + in0 = op4->out; + in1 = e4->out * pow(2, (@o4level - 99) / 8) * pow(2, @o4vel * (ionode->velocity - 1) * 0.6); +}; + +node op4 osc::fmop { + freq = freq->out; + ratio = @o4ratio * exp2(@o4det / 1200); +}; + +# Op 3 as heard: its sine through its envelope and level. +node s3 math::mul { + in0 = op3->out; + in1 = e3->out * pow(2, (@o3level - 99) / 8) * pow(2, @o3vel * (ionode->velocity - 1) * 0.6); +}; + +node op3 osc::fmop { + freq = freq->out; + ratio = @o3ratio * exp2(@o3det / 1200); +}; + +# Op 2 as heard: its sine through its envelope and level. +node s2 math::mul { + in0 = op2->out; + in1 = e2->out * pow(2, (@o2level - 99) / 8) * pow(2, @o2vel * (ionode->velocity - 1) * 0.6); +}; + +node op2 osc::fmop { + freq = freq->out; + ratio = @o2ratio * exp2(@o2det / 1200); +}; + +# Op 1 as heard: its sine through its envelope and level. +node s1 math::mul { + in0 = op1->out; + in1 = e1->out * pow(2, (@o1level - 99) / 8) * pow(2, @o1vel * (ionode->velocity - 1) * 0.6); +}; + +node op1 osc::fmop { + freq = freq->out; + ratio = @o1ratio * exp2(@o1det / 1200); +}; + +node mix math::mul { + in0 = s1->out + s2->out + s3->out + s4->out + s5->out + s6->out; + in1 = @mix / 6; +}; + +io ionode; diff --git a/dsp/dx5.dsp b/dsp/dx5.dsp new file mode 100644 index 00000000..ebec023c --- /dev/null +++ b/dsp/dx5.dsp @@ -0,0 +1,785 @@ +# DX 5 -- three two-operator stacks, the DX7's algorithm 5. +# +# 2 into 1, 4 into 3, 6 into 5, and op 6 feeding back on itself: three +# voices in one, which is why this algorithm carries the electric piano. +# As shipped, the 1/2 pair is the body, 3/4 at fourteen times the pitch +# is the tine that barks when hit hard, and 5/6 is the low thump. +# +# THE KNOBS ARE THE DX7'S, op by op: a frequency ratio, a detune in +# cents, an output level and a velocity sensitivity, and an env::dx with +# four rates and four levels, all 0 to 99. Output level is in the +# envelope's own decibels -- 0.75 dB a step -- so on a modulator it is +# the brightness and on a carrier the volume, as on the hardware. +# Velocity sensitivity, 0 to 7, takes up to 25 dB off an op played +# softly; on a modulator that is a duller note rather than a quieter one. +# +# THE WIRING IS THE ALGORITHM. Each op is an osc::fmop and a modulator's +# output, through its envelope and level, is summed onto the `mod' of the +# op it feeds; the sum is scaled so a modulator at 99 swings the phase by +# 8 radians. Another algorithm is another file, not a knob -- see +# osc::fmop's head for why. `Feedback', 0 to 7 as on the panel, is op 6 +# on itself. + +name "DX 5: Three Stacks"; +author "Misha Nasledov"; +description "Six FM operators as three pairs, the DX7's algorithm 5, set up as the electric piano."; +category "Keys"; + + @o1ratio = 1; + @o1ratio.widget = 1; + @o1ratio.min = 0.5; + @o1ratio.max = 31; + @o1ratio.label = "Ratio"; + @o1ratio.group = "Op 1"; + + @o1det = 3; + @o1det.widget = 1; + @o1det.min = -50; + @o1det.max = 50; + @o1det.label = "Detune (cents)"; + @o1det.group = "Op 1"; + + @o1level = 99; + @o1level.widget = 1; + @o1level.min = 0; + @o1level.max = 99; + @o1level.step = 1; + @o1level.label = "Level"; + @o1level.group = "Op 1"; + + @o1vel = 2; + @o1vel.widget = 1; + @o1vel.min = 0; + @o1vel.max = 7; + @o1vel.step = 1; + @o1vel.label = "Velocity"; + @o1vel.group = "Op 1"; + + @o1r1 = 96; + @o1r1.widget = 1; + @o1r1.min = 0; + @o1r1.max = 99; + @o1r1.step = 1; + @o1r1.label = "Rate 1"; + @o1r1.group = "Op 1"; + + @o1r2 = 25; + @o1r2.widget = 1; + @o1r2.min = 0; + @o1r2.max = 99; + @o1r2.step = 1; + @o1r2.label = "Rate 2"; + @o1r2.group = "Op 1"; + + @o1r3 = 25; + @o1r3.widget = 1; + @o1r3.min = 0; + @o1r3.max = 99; + @o1r3.step = 1; + @o1r3.label = "Rate 3"; + @o1r3.group = "Op 1"; + + @o1r4 = 67; + @o1r4.widget = 1; + @o1r4.min = 0; + @o1r4.max = 99; + @o1r4.step = 1; + @o1r4.label = "Rate 4"; + @o1r4.group = "Op 1"; + + @o1l1 = 99; + @o1l1.widget = 1; + @o1l1.min = 0; + @o1l1.max = 99; + @o1l1.step = 1; + @o1l1.label = "Level 1"; + @o1l1.group = "Op 1"; + + @o1l2 = 75; + @o1l2.widget = 1; + @o1l2.min = 0; + @o1l2.max = 99; + @o1l2.step = 1; + @o1l2.label = "Level 2"; + @o1l2.group = "Op 1"; + + @o1l3 = 0; + @o1l3.widget = 1; + @o1l3.min = 0; + @o1l3.max = 99; + @o1l3.step = 1; + @o1l3.label = "Level 3"; + @o1l3.group = "Op 1"; + + @o1l4 = 0; + @o1l4.widget = 1; + @o1l4.min = 0; + @o1l4.max = 99; + @o1l4.step = 1; + @o1l4.label = "Level 4"; + @o1l4.group = "Op 1"; + + @o2ratio = 1; + @o2ratio.widget = 1; + @o2ratio.min = 0.5; + @o2ratio.max = 31; + @o2ratio.label = "Ratio"; + @o2ratio.group = "Op 2"; + + @o2det = -3; + @o2det.widget = 1; + @o2det.min = -50; + @o2det.max = 50; + @o2det.label = "Detune (cents)"; + @o2det.group = "Op 2"; + + @o2level = 76; + @o2level.widget = 1; + @o2level.min = 0; + @o2level.max = 99; + @o2level.step = 1; + @o2level.label = "Level"; + @o2level.group = "Op 2"; + + @o2vel = 4; + @o2vel.widget = 1; + @o2vel.min = 0; + @o2vel.max = 7; + @o2vel.step = 1; + @o2vel.label = "Velocity"; + @o2vel.group = "Op 2"; + + @o2r1 = 95; + @o2r1.widget = 1; + @o2r1.min = 0; + @o2r1.max = 99; + @o2r1.step = 1; + @o2r1.label = "Rate 1"; + @o2r1.group = "Op 2"; + + @o2r2 = 50; + @o2r2.widget = 1; + @o2r2.min = 0; + @o2r2.max = 99; + @o2r2.step = 1; + @o2r2.label = "Rate 2"; + @o2r2.group = "Op 2"; + + @o2r3 = 35; + @o2r3.widget = 1; + @o2r3.min = 0; + @o2r3.max = 99; + @o2r3.step = 1; + @o2r3.label = "Rate 3"; + @o2r3.group = "Op 2"; + + @o2r4 = 78; + @o2r4.widget = 1; + @o2r4.min = 0; + @o2r4.max = 99; + @o2r4.step = 1; + @o2r4.label = "Rate 4"; + @o2r4.group = "Op 2"; + + @o2l1 = 99; + @o2l1.widget = 1; + @o2l1.min = 0; + @o2l1.max = 99; + @o2l1.step = 1; + @o2l1.label = "Level 1"; + @o2l1.group = "Op 2"; + + @o2l2 = 75; + @o2l2.widget = 1; + @o2l2.min = 0; + @o2l2.max = 99; + @o2l2.step = 1; + @o2l2.label = "Level 2"; + @o2l2.group = "Op 2"; + + @o2l3 = 0; + @o2l3.widget = 1; + @o2l3.min = 0; + @o2l3.max = 99; + @o2l3.step = 1; + @o2l3.label = "Level 3"; + @o2l3.group = "Op 2"; + + @o2l4 = 0; + @o2l4.widget = 1; + @o2l4.min = 0; + @o2l4.max = 99; + @o2l4.step = 1; + @o2l4.label = "Level 4"; + @o2l4.group = "Op 2"; + + @o3ratio = 1; + @o3ratio.widget = 1; + @o3ratio.min = 0.5; + @o3ratio.max = 31; + @o3ratio.label = "Ratio"; + @o3ratio.group = "Op 3"; + + @o3det = 0; + @o3det.widget = 1; + @o3det.min = -50; + @o3det.max = 50; + @o3det.label = "Detune (cents)"; + @o3det.group = "Op 3"; + + @o3level = 92; + @o3level.widget = 1; + @o3level.min = 0; + @o3level.max = 99; + @o3level.step = 1; + @o3level.label = "Level"; + @o3level.group = "Op 3"; + + @o3vel = 2; + @o3vel.widget = 1; + @o3vel.min = 0; + @o3vel.max = 7; + @o3vel.step = 1; + @o3vel.label = "Velocity"; + @o3vel.group = "Op 3"; + + @o3r1 = 95; + @o3r1.widget = 1; + @o3r1.min = 0; + @o3r1.max = 99; + @o3r1.step = 1; + @o3r1.label = "Rate 1"; + @o3r1.group = "Op 3"; + + @o3r2 = 20; + @o3r2.widget = 1; + @o3r2.min = 0; + @o3r2.max = 99; + @o3r2.step = 1; + @o3r2.label = "Rate 2"; + @o3r2.group = "Op 3"; + + @o3r3 = 20; + @o3r3.widget = 1; + @o3r3.min = 0; + @o3r3.max = 99; + @o3r3.step = 1; + @o3r3.label = "Rate 3"; + @o3r3.group = "Op 3"; + + @o3r4 = 50; + @o3r4.widget = 1; + @o3r4.min = 0; + @o3r4.max = 99; + @o3r4.step = 1; + @o3r4.label = "Rate 4"; + @o3r4.group = "Op 3"; + + @o3l1 = 99; + @o3l1.widget = 1; + @o3l1.min = 0; + @o3l1.max = 99; + @o3l1.step = 1; + @o3l1.label = "Level 1"; + @o3l1.group = "Op 3"; + + @o3l2 = 95; + @o3l2.widget = 1; + @o3l2.min = 0; + @o3l2.max = 99; + @o3l2.step = 1; + @o3l2.label = "Level 2"; + @o3l2.group = "Op 3"; + + @o3l3 = 0; + @o3l3.widget = 1; + @o3l3.min = 0; + @o3l3.max = 99; + @o3l3.step = 1; + @o3l3.label = "Level 3"; + @o3l3.group = "Op 3"; + + @o3l4 = 0; + @o3l4.widget = 1; + @o3l4.min = 0; + @o3l4.max = 99; + @o3l4.step = 1; + @o3l4.label = "Level 4"; + @o3l4.group = "Op 3"; + + @o4ratio = 14; + @o4ratio.widget = 1; + @o4ratio.min = 0.5; + @o4ratio.max = 31; + @o4ratio.label = "Ratio"; + @o4ratio.group = "Op 4"; + + @o4det = 0; + @o4det.widget = 1; + @o4det.min = -50; + @o4det.max = 50; + @o4det.label = "Detune (cents)"; + @o4det.group = "Op 4"; + + @o4level = 66; + @o4level.widget = 1; + @o4level.min = 0; + @o4level.max = 99; + @o4level.step = 1; + @o4level.label = "Level"; + @o4level.group = "Op 4"; + + @o4vel = 6; + @o4vel.widget = 1; + @o4vel.min = 0; + @o4vel.max = 7; + @o4vel.step = 1; + @o4vel.label = "Velocity"; + @o4vel.group = "Op 4"; + + @o4r1 = 95; + @o4r1.widget = 1; + @o4r1.min = 0; + @o4r1.max = 99; + @o4r1.step = 1; + @o4r1.label = "Rate 1"; + @o4r1.group = "Op 4"; + + @o4r2 = 29; + @o4r2.widget = 1; + @o4r2.min = 0; + @o4r2.max = 99; + @o4r2.step = 1; + @o4r2.label = "Rate 2"; + @o4r2.group = "Op 4"; + + @o4r3 = 20; + @o4r3.widget = 1; + @o4r3.min = 0; + @o4r3.max = 99; + @o4r3.step = 1; + @o4r3.label = "Rate 3"; + @o4r3.group = "Op 4"; + + @o4r4 = 50; + @o4r4.widget = 1; + @o4r4.min = 0; + @o4r4.max = 99; + @o4r4.step = 1; + @o4r4.label = "Rate 4"; + @o4r4.group = "Op 4"; + + @o4l1 = 99; + @o4l1.widget = 1; + @o4l1.min = 0; + @o4l1.max = 99; + @o4l1.step = 1; + @o4l1.label = "Level 1"; + @o4l1.group = "Op 4"; + + @o4l2 = 95; + @o4l2.widget = 1; + @o4l2.min = 0; + @o4l2.max = 99; + @o4l2.step = 1; + @o4l2.label = "Level 2"; + @o4l2.group = "Op 4"; + + @o4l3 = 0; + @o4l3.widget = 1; + @o4l3.min = 0; + @o4l3.max = 99; + @o4l3.step = 1; + @o4l3.label = "Level 3"; + @o4l3.group = "Op 4"; + + @o4l4 = 0; + @o4l4.widget = 1; + @o4l4.min = 0; + @o4l4.max = 99; + @o4l4.step = 1; + @o4l4.label = "Level 4"; + @o4l4.group = "Op 4"; + + @o5ratio = 1; + @o5ratio.widget = 1; + @o5ratio.min = 0.5; + @o5ratio.max = 31; + @o5ratio.label = "Ratio"; + @o5ratio.group = "Op 5"; + + @o5det = -7; + @o5det.widget = 1; + @o5det.min = -50; + @o5det.max = 50; + @o5det.label = "Detune (cents)"; + @o5det.group = "Op 5"; + + @o5level = 80; + @o5level.widget = 1; + @o5level.min = 0; + @o5level.max = 99; + @o5level.step = 1; + @o5level.label = "Level"; + @o5level.group = "Op 5"; + + @o5vel = 0; + @o5vel.widget = 1; + @o5vel.min = 0; + @o5vel.max = 7; + @o5vel.step = 1; + @o5vel.label = "Velocity"; + @o5vel.group = "Op 5"; + + @o5r1 = 95; + @o5r1.widget = 1; + @o5r1.min = 0; + @o5r1.max = 99; + @o5r1.step = 1; + @o5r1.label = "Rate 1"; + @o5r1.group = "Op 5"; + + @o5r2 = 20; + @o5r2.widget = 1; + @o5r2.min = 0; + @o5r2.max = 99; + @o5r2.step = 1; + @o5r2.label = "Rate 2"; + @o5r2.group = "Op 5"; + + @o5r3 = 20; + @o5r3.widget = 1; + @o5r3.min = 0; + @o5r3.max = 99; + @o5r3.step = 1; + @o5r3.label = "Rate 3"; + @o5r3.group = "Op 5"; + + @o5r4 = 50; + @o5r4.widget = 1; + @o5r4.min = 0; + @o5r4.max = 99; + @o5r4.step = 1; + @o5r4.label = "Rate 4"; + @o5r4.group = "Op 5"; + + @o5l1 = 99; + @o5l1.widget = 1; + @o5l1.min = 0; + @o5l1.max = 99; + @o5l1.step = 1; + @o5l1.label = "Level 1"; + @o5l1.group = "Op 5"; + + @o5l2 = 95; + @o5l2.widget = 1; + @o5l2.min = 0; + @o5l2.max = 99; + @o5l2.step = 1; + @o5l2.label = "Level 2"; + @o5l2.group = "Op 5"; + + @o5l3 = 0; + @o5l3.widget = 1; + @o5l3.min = 0; + @o5l3.max = 99; + @o5l3.step = 1; + @o5l3.label = "Level 3"; + @o5l3.group = "Op 5"; + + @o5l4 = 0; + @o5l4.widget = 1; + @o5l4.min = 0; + @o5l4.max = 99; + @o5l4.step = 1; + @o5l4.label = "Level 4"; + @o5l4.group = "Op 5"; + + @o6ratio = 1; + @o6ratio.widget = 1; + @o6ratio.min = 0.5; + @o6ratio.max = 31; + @o6ratio.label = "Ratio"; + @o6ratio.group = "Op 6"; + + @o6det = 7; + @o6det.widget = 1; + @o6det.min = -50; + @o6det.max = 50; + @o6det.label = "Detune (cents)"; + @o6det.group = "Op 6"; + + @o6level = 60; + @o6level.widget = 1; + @o6level.min = 0; + @o6level.max = 99; + @o6level.step = 1; + @o6level.label = "Level"; + @o6level.group = "Op 6"; + + @o6vel = 2; + @o6vel.widget = 1; + @o6vel.min = 0; + @o6vel.max = 7; + @o6vel.step = 1; + @o6vel.label = "Velocity"; + @o6vel.group = "Op 6"; + + @o6r1 = 95; + @o6r1.widget = 1; + @o6r1.min = 0; + @o6r1.max = 99; + @o6r1.step = 1; + @o6r1.label = "Rate 1"; + @o6r1.group = "Op 6"; + + @o6r2 = 29; + @o6r2.widget = 1; + @o6r2.min = 0; + @o6r2.max = 99; + @o6r2.step = 1; + @o6r2.label = "Rate 2"; + @o6r2.group = "Op 6"; + + @o6r3 = 20; + @o6r3.widget = 1; + @o6r3.min = 0; + @o6r3.max = 99; + @o6r3.step = 1; + @o6r3.label = "Rate 3"; + @o6r3.group = "Op 6"; + + @o6r4 = 50; + @o6r4.widget = 1; + @o6r4.min = 0; + @o6r4.max = 99; + @o6r4.step = 1; + @o6r4.label = "Rate 4"; + @o6r4.group = "Op 6"; + + @o6l1 = 99; + @o6l1.widget = 1; + @o6l1.min = 0; + @o6l1.max = 99; + @o6l1.step = 1; + @o6l1.label = "Level 1"; + @o6l1.group = "Op 6"; + + @o6l2 = 95; + @o6l2.widget = 1; + @o6l2.min = 0; + @o6l2.max = 99; + @o6l2.step = 1; + @o6l2.label = "Level 2"; + @o6l2.group = "Op 6"; + + @o6l3 = 0; + @o6l3.widget = 1; + @o6l3.min = 0; + @o6l3.max = 99; + @o6l3.step = 1; + @o6l3.label = "Level 3"; + @o6l3.group = "Op 6"; + + @o6l4 = 0; + @o6l4.widget = 1; + @o6l4.min = 0; + @o6l4.max = 99; + @o6l4.step = 1; + @o6l4.label = "Level 4"; + @o6l4.group = "Op 6"; + + @feedback = 6; + @feedback.widget = 1; + @feedback.min = 0; + @feedback.max = 7; + @feedback.step = 1; + @feedback.label = "Feedback"; + + @ratescale = 3; + @ratescale.widget = 1; + @ratescale.min = 0; + @ratescale.max = 7; + @ratescale.step = 1; + @ratescale.label = "Rate Scaling"; + + @mix = 0.45; + @mix.widget = 1; + @mix.min = 0; + @mix.max = 1; + @mix.label = "Output"; + +node ionode { + channels = 2; + out0 = mix->out; + out1 = mix->out; + play = max(e5->play, max(e3->play, e1->play)); +}; + +node freq misc::midi2freq { + note = ionode->note; +}; + +node e1 env::dx { + r1 = @o1r1; + r2 = @o1r2; + r3 = @o1r3; + r4 = @o1r4; + l1 = @o1l1; + l2 = @o1l2; + l3 = @o1l3; + l4 = @o1l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e2 env::dx { + r1 = @o2r1; + r2 = @o2r2; + r3 = @o2r3; + r4 = @o2r4; + l1 = @o2l1; + l2 = @o2l2; + l3 = @o2l3; + l4 = @o2l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e3 env::dx { + r1 = @o3r1; + r2 = @o3r2; + r3 = @o3r3; + r4 = @o3r4; + l1 = @o3l1; + l2 = @o3l2; + l3 = @o3l3; + l4 = @o3l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e4 env::dx { + r1 = @o4r1; + r2 = @o4r2; + r3 = @o4r3; + r4 = @o4r4; + l1 = @o4l1; + l2 = @o4l2; + l3 = @o4l3; + l4 = @o4l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e5 env::dx { + r1 = @o5r1; + r2 = @o5r2; + r3 = @o5r3; + r4 = @o5r4; + l1 = @o5l1; + l2 = @o5l2; + l3 = @o5l3; + l4 = @o5l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +node e6 env::dx { + r1 = @o6r1; + r2 = @o6r2; + r3 = @o6r3; + r4 = @o6r4; + l1 = @o6l1; + l2 = @o6l2; + l3 = @o6l3; + l4 = @o6l4; + trigger = ionode->trigger; + note = ionode->note; + ratescale = @ratescale; +}; + +# Op 6 as heard: its sine through its envelope and level. +node s6 math::mul { + in0 = op6->out; + in1 = e6->out * pow(2, (@o6level - 99) / 8) * pow(2, @o6vel * (ionode->velocity - 1) * 0.6); +}; + +node op6 osc::fmop { + freq = freq->out; + ratio = @o6ratio * exp2(@o6det / 1200); + feedback = @feedback / 7; +}; + +# Op 5 as heard: its sine through its envelope and level. +node s5 math::mul { + in0 = op5->out; + in1 = e5->out * pow(2, (@o5level - 99) / 8) * pow(2, @o5vel * (ionode->velocity - 1) * 0.6); +}; + +node op5 osc::fmop { + freq = freq->out; + ratio = @o5ratio * exp2(@o5det / 1200); + mod = s6->out; + index = 8; +}; + +# Op 4 as heard: its sine through its envelope and level. +node s4 math::mul { + in0 = op4->out; + in1 = e4->out * pow(2, (@o4level - 99) / 8) * pow(2, @o4vel * (ionode->velocity - 1) * 0.6); +}; + +node op4 osc::fmop { + freq = freq->out; + ratio = @o4ratio * exp2(@o4det / 1200); +}; + +# Op 3 as heard: its sine through its envelope and level. +node s3 math::mul { + in0 = op3->out; + in1 = e3->out * pow(2, (@o3level - 99) / 8) * pow(2, @o3vel * (ionode->velocity - 1) * 0.6); +}; + +node op3 osc::fmop { + freq = freq->out; + ratio = @o3ratio * exp2(@o3det / 1200); + mod = s4->out; + index = 8; +}; + +# Op 2 as heard: its sine through its envelope and level. +node s2 math::mul { + in0 = op2->out; + in1 = e2->out * pow(2, (@o2level - 99) / 8) * pow(2, @o2vel * (ionode->velocity - 1) * 0.6); +}; + +node op2 osc::fmop { + freq = freq->out; + ratio = @o2ratio * exp2(@o2det / 1200); +}; + +# Op 1 as heard: its sine through its envelope and level. +node s1 math::mul { + in0 = op1->out; + in1 = e1->out * pow(2, (@o1level - 99) / 8) * pow(2, @o1vel * (ionode->velocity - 1) * 0.6); +}; + +node op1 osc::fmop { + freq = freq->out; + ratio = @o1ratio * exp2(@o1det / 1200); + mod = s2->out; + index = 8; +}; + +node mix math::mul { + in0 = s1->out + s3->out + s5->out; + in1 = @mix / 3; +}; + +io ionode;