diff --git a/README.md b/README.md index fcd8823..b28d963 100755 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Please refer to original [docs](https://github.com/hundredrabbits/Orca#operators #### Operators - `$` [**r.note**(scale-mode note)](#-rnote): Outputs random note within scale. +- `'` [**scale.degree**(*scale* octave degree root-note)](#-scale-degree): Outputs octave and note for a scale degree. - `?` [**levels**(*param* value)](#-levels): Sets selected volume level on bang. - `/` [**softcut**(*playhead* *rec* *play* level rate position)](#-softcut). - `\` [**softcut param**(*playhead* *param* value)](#-softcut-params): Sets softcut param on bang. @@ -188,6 +189,12 @@ This operator generates a scale based on the given mode (default is Dorian) and - `z`: East Indian Purvi +### `'` SCALE.DEGREE + +The **SCALE.DEGREE** operator `'` takes 4 inputs(*`scale`*, *`octave`*, *`degree`*, *`root-note`*). + +This operator uses the same scale logic as the R.NOTE operator, but generates a specific octave/note pair based on an octave, scale degree, and root note for the scale. For example: given a major scale (`1`), and octave of 3, a degree of 2, and a root note of `C`, the operator will output `3D`. Note that degrees are 1 indexed, following the Lua convention, but they will increment the octave when the degree is greater than the number of notes in the scale. In the above example, a degree of `10` would produce `4E`. + ### `?` LEVELS The **LEVELS** operator `?` takes 2 inputs(*`param`*, `value`). @@ -327,6 +334,16 @@ However, By reducing the sample rate you can use longer clips with the `position - and so on... +#### MacroB, MacroP, ModalE, ResonateR, and TextureC + + Engines implementing ports of the Mutable Instruments Braids, Plaits, Elements, Rings, and Clouds sysnthesis code to Supercollider and Norns. All are provided by the required [MI-Engines](https://llllllll.co/t/mi-engines/32338) library. The run op for these synths handles only pitch and velocity, all other parameters are controlled via the synth_param operator `-`. When one of these engines are selected, the **SYNTH** operator `|` takes up to 3 different inputs (*`octave`*, *`note`*, velocity). For example `|4Cz` will play a C at the 4th octave (MIDI Scale) with a velocity of 255. + +- *`octave`* + - values: `0`-`7` +- *`note`* + - values: `A`–`G` +- `velocity` + - values: `0`: 0, `z`: 125 ### `-` SYNTH PARAMS @@ -447,6 +464,191 @@ When this engine is seleted the **SYNTH PARAM** operator `-` takes up to 3 input - `z`: End frame +#### MacroB + +When this engine is selected the **SYNTH PARAM** operator `-` takes up to 2 inputs (*`param`* *`value`*). For example `-21` sets the "Model " to `"Kick"`. + +- `1`: Model (first 35) +- `2`: Model (remaining) +- `3`: Timbre +- `4`: Color +- `5`: Resamp +- `6`: Decim +- `7`: Bits +- `8`: WS +- `9`: Attack +- `a`: Decay +- `b`: Sustain +- `c`: Release + +The available Braids "Synthesis Models" are provided via two sets of params. Param 1: + +- `1`: CSAW +- `2`: Morph +- `3`: Saw Square +- `4`: Sine Triangle +- `5`: Buzz +- `6`: Square Sub +- `7`: Saw Sub +- `8`: Square Sync +- `9`: Saw Sync +- `a`: Triple Saw +- `b`: Triple Square +- `c`: Triple Triangle +- `d`: Triple Sine +- `e`: Triple Ring Mod +- `f`: Saw Swarm +- `g`: Saw Comb +- `h`: Toy +- `i`: Digital Filter Lp +- `j`: Digital Filter Pk +- `k`: Digital Filter Bp +- `l`: Digital Filter Hp +- `m`: Vosim +- `n`: Vowel +- `o`: Vowel Fof +- `p`: Harmonics +- `q`: Fm +- `r`: Feedback Fm +- `s`: Chaotic Feedback Fm +- `t`: Plucked +- `u`: Bowed +- `v`: Blown +- `w`: Fluted +- `x`: Struck Bell +- `y`: Struck Drum + +The additional models are availale via param 2 (kick included in this set to make beats easier): + +- `1`: Kick +- `2`: Cymbal +- `3`: Snare +- `4`: Wavetables +- `5`: Wave Map +- `6`: Wave Line +- `7`: Wave Paraphonic +- `8`: Filtered Noise +- `9`: Twin Peaks Noise +- `a`: Clocked Noise +- `b`: Granular Cloud +- `c`: Particle Noise +- `d`: Digital Modulation +- `e`: "Question Mark" + +#### MacroP + +When this engine is selected the **SYNTH PARAM** operator `-` takes up to 2 inputs (*`param`* *`value`*). For example `-16` sets the "Synthesis Engine" to `"wavetable"`. + +- `1`: Engine (different from the norns engine, internal to the MacroP norns engine) +- `2`: Harmonics +- `3`: Timbre +- `4`: Morph +- `5`: Level +- `6`: FM Mod +- `7`: Timbre Mod +- `8`: Morph Mod +- `9`: Decay +- `a`: LPG Colour + +The available Plaits synthesis engines are: + +- `1`: virtual analog +- `2`: waveshaping +- `3`: fm +- `4`: grain +- `5`: additive +- `6`: wavetable +- `7`: chord +- `8`: speech +- `9`: swarm +- `a`: noise +- `b`: particle +- `c`: string +- `d`: modal +- `e`: bass drum +- `f`: snare drum +- `g`: hi hat + +#### ModalE + +When this engine is selected the **SYNTH PARAM** operator `-` takes up to 2 inputs (*`param`* *`value`*). For example `-1z` sets the "Strength" to `1.0`. All params map `0 - z` to `0.0 - 1.0` unless otherwise noted. + +- `1`: "Strength", +- `2`: "Contour", +- `3`: "Bow Level", +- `4`: "Blow Level", +- `5`: "Strike Level", +- `6`: "Flow", +- `7`: "Mallet", +- `8`: "Bow Timb", +- `9`: "Blow Timb", +- `a`: "Strike Timb", +- `b`: "Geom", +- `c`: "Bright", +- `d`: "Damp", +- `e`: "Pos", +- `f`: "Space", +- `g`: "Model", +- `h`: "Mul", +- `i`: "Add" + +#### ResonateR + +When this engine is selected the **SYNTH PARAM** operator `-` takes up to 2 inputs (*`param`* *`value`*). For example `-1z` sets the "Strength" to `1.0`. All params map `0 - z` to `0.0 - 1.0` unless otherwise noted. + +- `1`: Model (`1 - 6`) +- `2`: Struct +- `3`: Bright +- `4`: Damp +- `5`: Position +- `6`: Poly (`1 - n`) +- `7`: Intern Exciter +- `8`: Bypass (`0 - 1`) +- `9`: Easter Egg (`1 - 6`) + +Available models are: + +- `1`: "Modal Resonator" +- `2`: "Sympathetic String" +- `3`: "Mod/Inharm String" +- `4`: "2-Op Fm Voice" +- `5`: "Sympth Str Quant" +- `6`: "String And Reverb" + +Easter egg modes are: + +- `1`: FX Formant +- `2`: FX Chorus +- `3`: FX Reverb +- `4`: FX Formant +- `5`: FX Ensemble +- `6`: FX Reverb + +#### TextureC + +When this engine is selected the **SYNTH PARAM** operator `-` takes up to 2 inputs (*`param`* *`value`*). For example `-1z` sets the "Strength" to `1.0`. All params map `0 - z` to `0.0 - 1.0` unless otherwise noted. + +- `1`: Mode (`1-4`) +- `2`: Position +- `3`: Size +- `4`: Density +- `5`: Texture +- `6`: Dry/Wet +- `7`: In Gain +- `8`: Spread +- `9`: Reverb +- `a`: Feedback +- `b`: Freeze +- `c`: Lofi +- `d`: Trigger (`0-1`, not really implemented yet) + +Available modes are: + +- `1`: Granular +- `2`: Stretch +- `3`: Looping_Delay +- `4`: Spectral + ### `:` MIDI The **MIDI** operator `:` takes up to 5 inputs (*`channel`* `octave` `note` `velocity` `length`). diff --git a/lib/engines.lua b/lib/engines.lua index 253f431..2213a71 100644 --- a/lib/engines.lua +++ b/lib/engines.lua @@ -4,15 +4,20 @@ local fm7 = include("lib/engines/_fm7") local passersby = include("lib/engines/_passersby") local polyperc = include("lib/engines/_polyperc") local timber = include("lib/engines/_timber") +local macroB = include("lib/engines/_macroB") +local macroP = include("lib/engines/_macroP") +local modalE = include("lib/engines/_modalE") +local resonateR = include("lib/engines/_resonateR") +local textureC = include("lib/engines/_textureC") -engine.name = "Timber" +engine.name = "MacroP" --- Softcut only supports max. 2 mono samples local NUM_SAMPLES = 2 local engines = { change_init = false, - engine_list = {"FM7", "Passersby", "PolyPerc", "Timber"}, + engine_list = {"FM7", "Passersby", "PolyPerc", "MacroB", "MacroP", "ModalE","ResonateR","TextureC", "Timber"}, self = nil, } @@ -147,6 +152,11 @@ function engines.get_synth() elseif string.lower(engine.name) == "passersby" then return passersby elseif string.lower(engine.name) == "polyperc" then return polyperc elseif string.lower(engine.name) == "timber" then return timber + elseif string.lower(engine.name) == "macrob" then return macroB + elseif string.lower(engine.name) == "macrop" then return macroP + elseif string.lower(engine.name) == "modale" then return modalE + elseif string.lower(engine.name) == "resonater" then return resonateR + elseif string.lower(engine.name) == "texturec" then return textureC end end @@ -158,12 +168,13 @@ function engines.init(self) audio.level_cut(1) audio.level_adc_cut(1) audio.level_eng_cut(1) + audio.level_tape_cut(1) for i = 1, 6 do softcut.level(i,1) softcut.level_input_cut(1, i, 1.0) softcut.level_input_cut(2, i, 1.0) - softcut.pan(i, 0.5) + softcut.pan(i, 0) softcut.play(i, 0) softcut.rate(i, 1) softcut.loop_start(i, 0) diff --git a/lib/engines/_macroB.lua b/lib/engines/_macroB.lua new file mode 100644 index 0000000..50c091f --- /dev/null +++ b/lib/engines/_macroB.lua @@ -0,0 +1,160 @@ + +local engine_macroB = { + input_ids = { + "octave", + "note", + "vel" + }, + param_ids = { + "model", + "model", + "timbre", + "color", + "resamp", + "decim", + "bits", + "ws", + "ampAtk", + "ampDec", + "ampSus", + "ampRel" + }, + param_names = { + "Model", + "Model+", + "Timbre", + "Color", + "Resamp", + "Decim", + "Bits", + "WS", + "Attack", + "Decay", + "Sustain", + "Release" + }, + param_display_value = nil, + ports = {} +} + +local prev_transposed +local all_models = {"CSAW","/\\/|-_-_","/|/|-_-_","FOLD","_|_|_|_|_","-_-_SUB","/|/|SUB","SYN-_-_","SYN/|","/|/|x3","-_-_x3","/\\x3","SIx3","RING","/|/|/|/|","/|/|_|_|_","TOY*","ZLPF","ZPKF","ZBPF","ZHPF","VOSM","VOWL","VFOF","HARM","FM","FBFM","WTFM","PLUK","BOWD","BLOW","FLUTE","BELL","DRUM","KICK","CYMB","SNAR","WTBL","WMAP","WLIN","WTx4","NOIS","TWNQ","CLKN","CLOU","PRTC","QPSK","????"} + +local function model_set_1(index) + index = index or 1 + return util.clamp(index,1,35) - 1 +end + +local function model_set_2(index) + index = index or 1 + -- NOT WORKING: + -- local items = table.unpack(all_models,34,#all_models) + -- print("models 2 len " .. #items .. " of " .. #all_models) + -- return 34 + util.clamp(index, 1, #items) + -- ALTERNATE: + -- I'd rather do this programmatically, but table.unpack(all_models,34) is returning a len 4 table + return 34 + util.clamp(index, 1, 14) - 1 +end + +local function resamp(num) + return util.linlin(0, 1, 0, 1, num) or 0.01 +end + +local function decim(num) + if not num then + num = 0 + end + return util.clamp(num,0,32) +end + +local function bits(num) + if not num then + num = 0 + end + return util.clamp(num,0,7) +end + +local function ws(num) + return util.linlin(0, 1, 0, 1, num) or 0.01 +end + +local function attack(num) + return util.linlin(0, 1, 0, 1, num) or 0.01 +end + +local function decay(num) + return util.linlin(0, 1, 0, 1, num) or 0.01 +end + +local function sustain(num) + return util.linlin(0, 1, 0, 1, num) or 1 +end + +local function release(num) + return util.linlin(0, 1, 0, 1, num) or 1 +end + + +--- Initializes engine and sets default values. +function engine_macroB.init() + print("macroB init()") + + -- for id in engine_macroB.param_ids do + -- engine.commands[id].func(0) + -- end +end + +--- Executes/plays the engine, "|" operator plays on bang. +-- @param octave {int} octave index +-- @param note {string} note letter +-- @param cls {class} Orca class +function engine_macroB.run(octave, note, cls) + local transposed = cls:transpose(note, octave) + local vel = cls:listen(cls.x + 3, cls.y) or 100 + + if cls:neighbor(cls.x, cls.y, "*") and note ~= "." and note ~= "" then + engine.noteOn(transposed[1], vel) + prev_transposed = transposed + else + if prev_transposed ~= nil then + engine.noteOff(prev_transposed[1]) + end + end +end + +--- Executes the engines params, "-" operator updates on bang. +-- @param cls {class} Orca class +function engine_macroB.param(cls) + local param = util.clamp(cls:listen(cls.x + 1, cls.y) or 1, 1, #engine_macroB.param_ids - 1) + local val = cls:listen(cls.x + 2, cls.y) or 0 + local val_norm = (val / 35) or 0 + local num = (param == 0 or param == 1) and model_set_1(val) -- model, first 36 + or param == 2 and model_set_2(val) -- model, > 36 + or param == 5 and resamp(val_norm) -- resamp + or param == 6 and decim(val) -- decim + or param == 7 and bits(val) -- bits + or param == 8 and ws(val) -- ws + or param == 9 and attack(val_norm) -- ampAtk + or param == 10 and decay(val_norm) -- ampDec + or param == 11 and sustain(val_norm) -- ampSys + or param == 12 and release(val_norm) -- ampRel + or val_norm or 0 + + engine_macroB.param_display_value = ((param == 0 or param == 1 or param == 2) and all_models[num+1]) or nil + + local id = engine_macroB.param_ids[param] + + if cls:neighbor(cls.x, cls.y, "*") and param ~= "." and param ~= "" then + if num == nil or unexpected_condition then + print("Oops! Unexpected " .. engine.name .. " engine error.") + else + engine.commands[id].func(num) + end + end +end + +function engine_macroB.param_value() + +end + +return engine_macroB \ No newline at end of file diff --git a/lib/engines/_macroP.lua b/lib/engines/_macroP.lua new file mode 100644 index 0000000..919821e --- /dev/null +++ b/lib/engines/_macroP.lua @@ -0,0 +1,102 @@ + +local engine_macroP = { + input_ids = { + "octave", + "note", + "vel" + }, + param_ids = { + "eng", + "harm", + "timbre", + "morph", + "level", + "fm_mod", + "timb_mod", + "morph_mod", + "decay", + "lpg_colour" + }, + param_names = { + "Engine", + "Harmonics", + "Timbre", + "Morph", + "Level", + "FM Mod", + "Timbre Mod", + "Morph Mod", + "Decay", + "LPG Colour" + }, + param_display_value = nil, + ports = {} +} + +local prev_transposed +local synthesis_engines = {"virtual analog","waveshaping","fm","grain","additive","wavetable","chord","speech","swarm","noise","particle","string","modal","bass drum","snare drum","hi hat"} + +local function engine_map(index) + index = index or 1 + return ((index - 1) % #synthesis_engines) + 1 +end + +--- Initializes engine and sets default values. +function engine_macroP.init() + print("macroP init()") + + -- for id in engine_macroP.param_ids do + -- engine.commands[id].func(0) + -- end +end + +--- Executes/plays the engine, "|" operator plays on bang. +-- @param octave {int} octave index +-- @param note {string} note letter +-- @param cls {class} Orca class +function engine_macroP.run(octave, note, cls) + local transposed = cls:transpose(note, octave) + local vel = cls:listen(cls.x + 3, cls.y) or 100 + + if cls:neighbor(cls.x, cls.y, "*") and note ~= "." and note ~= "" then + engine.noteOn(transposed[1], vel) + prev_transposed = transposed + else + if prev_transposed ~= nil then + engine.noteOff(prev_transposed[1]) + end + end +end + +--- Executes the engines params, "-" operator updates on bang. +-- @param cls {class} Orca class +function engine_macroP.param(cls) + local param = util.clamp(cls:listen(cls.x + 1, cls.y) or 1, 1, #engine_macroP.param_ids - 1) + local val = cls:listen(cls.x + 2, cls.y) or 0 + local val_norm = (val / 35) or 0 + local num = (param == 0 or param == 1) and engine_map(val) -- engine, have to rename to avoid name conflict + or param == 2 and (val_norm or 0.5) -- harmonics + or param == 3 and (val_norm or 0.5) -- timbre + or param == 4 and (val_norm or 0.5) -- morph + or param == 5 and (val_norm or 0) -- level + or param == 6 and (val_norm or 0) -- fm_mod + or param == 7 and (val_norm or 0) -- timbre_mod + or param == 8 and (val_norm or 0) -- morph_mod + or param == 9 and (val_norm or 0.5) -- decay + or param == 10 and (val_norm or 0.5) -- lpg_colour + or val_norm + + local id = engine_macroP.param_ids[param] + + engine_macroP.param_display_value = ((param == 0 or param == 1) and synthesis_engines[num+1]) or nil + + if cls:neighbor(cls.x, cls.y, "*") and param ~= "." and param ~= "" then + if num == nil or unexpected_condition then + print("Oops! Unexpected " .. engine.name .. " engine error.") + else + engine.commands[id].func(num) + end + end +end + +return engine_macroP \ No newline at end of file diff --git a/lib/engines/_modalE.lua b/lib/engines/_modalE.lua new file mode 100644 index 0000000..0944fb4 --- /dev/null +++ b/lib/engines/_modalE.lua @@ -0,0 +1,117 @@ +local engine_modalE = { + input_ids = { + "octave", + "note", + "vel" + }, + param_ids = { + "strength", + "contour", + "bow_level", + "blow_level", + "strike_level", + "flow", + "mallet", + "bow_timb", + "blow_timb", + "strike_timb", + "geom", + "bright", + "damp", + "pos", + "space", + "model", + "mul", + "add" + }, + param_names = { + "Strength", + "Contour", + "Bow Level", + "Blow Level", + "Strike Level", + "Flow", + "Mallet", + "Bow Timb", + "Blow Timb", + "Strike Timb", + "Geom", + "Bright", + "Damp", + "Pos", + "Space", + "Model", + "Mul", + "Add" + }, + param_display_value = nil, + ports = {} +} + +local prev_transposed + +--- Initializes engine and sets default values. +function engine_modalE.init() + print("modalE init()") + + -- for id in engine_modalE.param_ids do + -- engine.commands[id].func(0) + -- end +end + +--- Executes/plays the engine, "|" operator plays on bang. +-- @param octave {int} octave index +-- @param note {string} note letter +-- @param cls {class} Orca class +function engine_modalE.run(octave, note, cls) + local transposed = cls:transpose(note, octave) + local vel = cls:listen(cls.x + 3, cls.y) or 100 + + if cls:neighbor(cls.x, cls.y, "*") and note ~= "." and note ~= "" then + engine.noteOn(transposed[1], vel) + prev_transposed = transposed + else + if prev_transposed ~= nil then + engine.noteOff(prev_transposed[1]) + end + end +end + +--- Executes the engines params, "-" operator updates on bang. +-- @param cls {class} Orca class +function engine_modalE.param(cls) + local param = util.clamp(cls:listen(cls.x + 1, cls.y) or 1, 1, #engine_modalE.param_ids - 1) + local val = cls:listen(cls.x + 2, cls.y) or 0 + local val_norm = (val / 35) or 0 + local num = (param == 0 or param == 1) and (val_norm or 0.5) -- strength + or param == 2 and (val_norm or 0.5) -- contour + or param == 3 and (val_norm or 1) -- bow_level + or param == 4 and (val_norm or 0) -- blow_level + or param == 5 and (val_norm or 0) -- strike_level + or param == 6 and (val_norm or 0.25) -- flow + or param == 7 and (val_norm or 0.5) -- mallet + or param == 8 and (val_norm or 0.4) -- bow_timb + or param == 9 and (val_norm or 0.6) -- blow_timb + or param == 10 and (val_norm or 0.5) -- strike_timb + or param == 11 and (val_norm or 0.4) -- geom + or param == 12 and (val_norm or 0.2) -- bright + or param == 13 and (val_norm or 0.5) -- damp + or param == 14 and (val_norm or 0) -- pos + or param == 15 and (val_norm or 0.25) -- space + or param == 16 and (val_norm or 0) -- model + or param == 17 and (val_norm or 1.0) -- mul + or param == 18 and (val_norm or 0) -- add + or val_norm + + local id = engine_modalE.param_ids[param] + + if cls:neighbor(cls.x, cls.y, "*") and param ~= "." and param ~= "" then + if num == nil or unexpected_condition then + print("Oops! Unexpected " .. engine.name .. " engine error.") + else + engine.commands[id].func(num) + end + end +end + +return engine_modalE diff --git a/lib/engines/_resonateR.lua b/lib/engines/_resonateR.lua new file mode 100644 index 0000000..951a806 --- /dev/null +++ b/lib/engines/_resonateR.lua @@ -0,0 +1,106 @@ +local engine_resonateR = { + input_ids = { + "octave", + "note", + "vel" + }, + param_ids = { + "model", + "struct", + "bright", + "damp", + "position", + "poly", + "intern_exciter", + "bypass", + "easteregg", + }, + param_names = { + "Model", + "Struct", + "Bright", + "Damp", + "Position", + "Poly", + "Intern Exciter", + "Bypass", + "Easter Egg", + }, + param_display_value = nil, + ports = {} +} + +local prev_transposed + +local rings_models = {"Modal Resonator","Sympathetic String","Mod/Inharm String","2-Op Fm Voice","Sympth Str Quant","String And Reverb"} +local rings_egg_models = {"FX Formant","FX Chorus","FX Reverb","FX Formant","FX Ensemble","FX Reverb"} + +--- Initializes engine and sets default values. +function engine_resonateR.init() + print("resonateR init()") + + -- for id in engine_resonateR.param_ids do + -- engine.commands[id].func(0) + -- end +end + +--- Executes/plays the engine, "|" operator plays on bang. +-- @param octave {int} octave index +-- @param note {string} note letter +-- @param cls {class} Orca class +function engine_resonateR.run(octave, note, cls) + local transposed = cls:transpose(note, octave) + local vel = cls:listen(cls.x + 3, cls.y) or 100 + + if cls:neighbor(cls.x, cls.y, "*") and note ~= "." and note ~= "" then + engine.noteOn(transposed[1], vel) + prev_transposed = transposed + else + if prev_transposed ~= nil then + engine.noteOff(prev_transposed[1]) + end + end +end + +local function model_value(index) + index = index or 1 + return util.clamp(index,1,6) - 1 +end + +local function easteregg_value(index) + index = index or 1 + return util.clamp(index,1,6) - 1 +end + +--- Executes the engines params, "-" operator updates on bang. +-- @param cls {class} Orca class +function engine_resonateR.param(cls) + local param = util.clamp(cls:listen(cls.x + 1, cls.y) or 1, 1, #engine_resonateR.param_ids - 1) + local val = cls:listen(cls.x + 2, cls.y) or 0 + local val_norm = (val / 35) or 0 + local num = (param == 0 or param == 1) and model_value(val) -- model + or param == 2 and (val_norm or 0.5) -- struct + or param == 3 and (val_norm or 0.3) -- bright + or param == 4 and (val_norm or 0.25) -- damp + or param == 5 and (val_norm or 0.5) -- position + or param == 6 and (val or 4) -- poly + or param == 7 and (val_norm or 0) -- intern_exciter + or param == 8 and (val or 0) -- bypass + or param == 9 and easteregg_value(val) -- easteregg + or val_norm + + local id = engine_resonateR.param_ids[param] + + engine_resonateR.param_display_value = ((param == 0 or param == 1) and rings_models[num+1]) or nil + + if cls:neighbor(cls.x, cls.y, "*") and param ~= "." and param ~= "" then + if num == nil or unexpected_condition then + print("Oops! Unexpected " .. engine.name .. " engine error.") + else + engine.commands[id].func(num) + end + end +end + +return engine_resonateR + diff --git a/lib/engines/_textureC.lua b/lib/engines/_textureC.lua new file mode 100644 index 0000000..f9e45c6 --- /dev/null +++ b/lib/engines/_textureC.lua @@ -0,0 +1,112 @@ +local engine_textureC = { + input_ids = { + "octave", + "note", + "vel" + }, + param_ids = { + "mode", + "position", + "size", + "dens", + "texture", + "drywet", + "in_gain", + "spread", + "rvb", + "feedback", + "freeze", + "lofi", + "trig", + }, + param_names = { + "Mode", + "Position", + "Size", + "Density", + "Texture", + "Dry/Wet", + "In Gain", + "Spread", + "Reverb", + "Feedback", + "Freeze", + "Lofi", + "Trigger", + }, + param_display_value = nil, + ports = {} +} + +local prev_transposed + +local clouds_mode = {"Granular","Stretch","Looping_Delay","Spectral"} + +--- Initializes engine and sets default values. +function engine_textureC.init() + print("textureC init()") + + -- for id in engine_textureC.param_ids do + -- engine.commands[id].func(0) + -- end +end + +--- Executes/plays the engine, "|" operator plays on bang. +-- @param octave {int} octave index +-- @param note {string} note letter +-- @param cls {class} Orca class +function engine_textureC.run(octave, note, cls) + local transposed = cls:transpose(note, octave) + local vel = cls:listen(cls.x + 3, cls.y) or 100 + + if cls:neighbor(cls.x, cls.y, "*") and note ~= "." and note ~= "" then + engine.noteOn(transposed[1], vel) + prev_transposed = transposed + else + if prev_transposed ~= nil then + engine.noteOff(prev_transposed[1]) + end + end +end + +local function mode_value(index) + index = index or 1 + return util.clamp(index,1,4) - 1 +end + +--- Executes the engines params, "-" operator updates on bang. +-- @param cls {class} Orca class +function engine_textureC.param(cls) + local param = util.clamp(cls:listen(cls.x + 1, cls.y) or 1, 1, #engine_textureC.param_ids - 1) + local val = cls:listen(cls.x + 2, cls.y) or 0 + local val_norm = (val / 35) or 0 + local num = (param == 0 or param == 1) and mode_value(val) -- mode (0 -- 3) + or param == 3 and (val_norm or 0.6) -- position -- (0 -- 1) + or param == 3 and (val_norm or 0.2) -- size -- (0 -- 1) + or param == 3 and (val_norm or 0.25) -- dens -- (0 -- 1) + or param == 3 and (val_norm or 0.1) -- texture -- (0 -- 1) + or param == 3 and (val_norm or 0.5) -- drywet -- (0 -- 1) + or param == 3 and (val_norm or 2) -- in_gain -- 0.125 -- 8 + or param == 3 and (val_norm or 1) -- spread -- (0 -- 1) + or param == 3 and (val_norm or 0.2) -- rvb -- (0 -- 1) + or param == 3 and (val_norm or 0.2) -- feedback -- (0 -- 1) + or param == 3 and (val_norm or 0) -- freeze -- (0 -- 1) + or param == 3 and (val_norm or 0) -- lofi -- (0 -- 1) + or param == 3 and (val_norm or 0) -- trig -- (0 -- 1) + or val_norm + + local id = engine_textureC.param_ids[param] + + engine_textureC.param_display_value = ((param == 0 or param == 1) and clouds_mode[num+1]) or nil + + if cls:neighbor(cls.x, cls.y, "*") and param ~= "." and param ~= "" then + if num == nil or unexpected_condition then + print("Oops! Unexpected " .. engine.name .. " engine error.") + else + engine.commands[id].func(num) + end + end +end + +return engine_textureC + diff --git a/lib/library.lua b/lib/library.lua index e37573d..e83c7a1 100644 --- a/lib/library.lua +++ b/lib/library.lua @@ -2,6 +2,7 @@ local ops = {} ops["*"] = include("lib/library/_bang") ops["#"] = include("lib/library/_comment") ops["$"] = include("lib/library/_rnote") +ops["'"] = include("lib/library/_scale_degree") ops["?"] = include("lib/library/_levels") ops["/"] = include("lib/library/_softcut_op") ops["\\"] = include("lib/library/_softcut_param") diff --git a/lib/library/_scale_degree.lua b/lib/library/_scale_degree.lua new file mode 100644 index 0000000..a15c8fe --- /dev/null +++ b/lib/library/_scale_degree.lua @@ -0,0 +1,29 @@ +local scale_degree = function(self, x, y) + self.y = y + self.x = x + self.name = "scale.degree" + self.ports = { {-1, 0, "in-scale"}, {1, 0, "in-octave"}, {2, 0, "in-degree"}, {3, 0, "in-root"}, {0, 1, "^-octave-out"}, {1, 1, "^-note-out"} } + + local scale = self:listen(self.x - 1, self.y) or 1 scale = scale == 0 and 1 or scale + local in_octave = self:listen(self.x + 1, self.y) or 1 + local in_degree = self:listen(self.x + 2, self.y) or 1 + local root = self:glyph_at(self.x + 3, self.y) or "C" + + local transposed = self:transpose(root, 0) or 1 + local get_scale = self:get_scale(scale, transposed[1] + 1) + local scale_name = get_scale[1] + local note_array = get_scale[2] + local note_out = self.notes[note_array[(in_degree - 1) % (#note_array - 1) + 1]] + local octave_out = in_octave + math.floor(in_degree / #note_array) + + self.ports[1][3] = scale_name + self:spawn(self.ports) + + -- if self:neighbor(self.x, self.y, "*") then + self:write(self.ports[5][1], self.ports[5][2], octave_out) + self:write(self.ports[6][1], self.ports[6][2], note_out) + -- end +end + +return scale_degree + diff --git a/lib/library/_synth_param.lua b/lib/library/_synth_param.lua index 1aaa36b..077db83 100644 --- a/lib/library/_synth_param.lua +++ b/lib/library/_synth_param.lua @@ -11,7 +11,7 @@ local synth_param = function(self, x, y) local param = util.clamp(self:listen(self.x + 1, self.y) or 1, 1, #engine_synth.param_ids) - self.ports = { {1, 0, engine_synth.param_names[param] or "in-param", "input"}, {2, 0, "in-value", "input"} } + self.ports = { {1, 0, engine_synth.param_names[param] or "in-param", "input"}, {2, 0, engine_synth.param_display_value or "in-value", "input"} } if engine_synth.ports then for i = 1, #engine_synth.ports do self.ports[2 + i] = {2 + i, 0, "in-" .. engine_synth.ports[i], "input"}