From 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 13 Apr 2025 18:48:02 +0100 Subject: initial --- site/udo/scss/mixer/_effects.udo | 247 ++++++++++++++++++++++++++++++++++++++ site/udo/scss/mixer/base.udo | 253 +++++++++++++++++++++++++++++++++++++++ site/udo/scss/mixer/test.csd | 41 +++++++ 3 files changed, 541 insertions(+) create mode 100755 site/udo/scss/mixer/_effects.udo create mode 100755 site/udo/scss/mixer/base.udo create mode 100755 site/udo/scss/mixer/test.csd (limited to 'site/udo/scss/mixer') diff --git a/site/udo/scss/mixer/_effects.udo b/site/udo/scss/mixer/_effects.udo new file mode 100755 index 0000000..bc29661 --- /dev/null +++ b/site/udo/scss/mixer/_effects.udo @@ -0,0 +1,247 @@ +#ifndef UDO_SCSS_MIXER_EFFECTS +#define UDO_SCSS_MIXER_EFFECTS ## + +#include "/frequency_tools.udo" +#include "/wavetables.udo" + +opcode _fxi_control, Sk, ii + ichannelindex, ieffectindex xin + SchanAppend = sprintf("_%d_%d", ichannelindex, ieffectindex) + kon = chnget:k(strcat("fxi_on", SchanAppend)) + xout SchanAppend, kon +endop + +opcode fxi_param, k, SS + SchanAppend, Sname xin + xout chnget:k(strcat(strcat("fxi_", Sname), SchanAppend)) +endop + +opcode fxi_param, i, SS + SchanAppend, Sname xin + xout chnget:i(strcat(strcat("fxi_", Sname), SchanAppend)) +endop + +gSeffectDefs = {{ + { + "effects": [ + { + "name": "Reverb", + "opcode": "fxi_reverb", + "parameters": [ + {"name": "roomsize", "description": "Room size", "default": 0.3, "max": 1, "min": 0}, + {"name": "hfdamp", "description": "High frequency damping", "default": 0.5, "max": 1, "min": 0} + ] + }, + { + "name": "Frequency shifter 1", + "opcode": "fxi_freqshift1", + "parameters": [ + {"name": "shift", "description": "Shift frequency", "default": 0, "max": 2000, "min": -2000} + ] + }, + { + "name": "Frequency shifter 2", + "opcode": "fxi_freqshift2", + "parameters": [ + {"name": "shift", "description": "Shift frequency", "default": 0, "max": 2000, "min": -2000} + ] + }, + { + "name": "Ring modulator", + "opcode": "fxi_ringmod", + "parameters": [ + {"name": "freq", "description": "Frequency", "default": 440, "max": 10000, "min": 20} + ] + + }, + { + "name": "Bit crusher", + "opcode": "fxi_bitcrush", + "parameters": [ + {"name": "crush", "description": "Bits", "default": 8, "max": 64, "min": 2, "type": "int"} + ] + }, + { + "name": "Delay tuner", + "opcode": "fxi_delaytuner", + "parameters": [ + {"name": "freq", "description": "Frequency", "default": 110, "max": 4000, "min": 50}, + {"name": "feedback", "description": "Feedback", "default": 0.4, "max": 0.9, "min": 0} + ] + }, + { + "name": "Low pass filter", + "opcode": "fxi_lowpass", + "parameters": [ + {"name": "freq", "description": "Frequency", "default": 10000, "max": 20000, "min": 20} + ] + }, + { + "name": "Highpass filter", + "opcode": "fxi_highpass", + "parameters": [ + {"name": "freq", "description": "Frequency", "default": 1000, "max": 20000, "min": 20} + ] + }, + { + "name": "Simple chorus", + "opcode": "fxi_simplechorus", + "parameters": [ + {"name": "rateL", "description": "Rate (Left)", "default": 0.01, "max": 0.1, "min": 0.001}, + {"name": "rateR", "description": "Rate (Right)", "default": 0.01, "max": 0.1, "min": 0.001} + ] + }, + { + "name": "Simple delay", + "opcode": "fxi_simpledelay", + "parameters": [ + {"name": "time", "description": "Delay time", "default": 1000, "max": 4000, "min": 1} + ] + }, + { + "name": "Waveshaping distortion", + "opcode": "fxi_distort", + "parameters": [ + {"name": "distortion", "description": "Amount", "default": 0.1, "max": 2, "min": 0.001}, + {"name": "wave", "description": "Wave shape", "options": [ + {"name": "Sine", "value": 0}, + {"name": "Square", "value": 1}, + {"name": "Saw", "value": 2} + ]} + ] + } + ] + } +}} +giJinsertDefs = jsonloads(gSeffectDefs) + + + +opcode fxi_reverb, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kroomsize = fxi_param:k(SchanAppend, "roomsize") + khfdamp = fxi_param:k(SchanAppend, "hfdamp") + aL, aR freeverb aL, aR, kroomsize, khfdamp + endif + xout aL, aR +endop + + +opcode fxi_simpledelay, aa, aaii ; TODO have beats delay option + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + ktime = fxi_param:k(SchanAppend, "time") + aL vdelay aL, ktime, 4000 + aR vdelay aR, ktime, 4000 + endif + xout aL, aR +endop + +opcode fxi_distort, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kdist = fxi_param:k(SchanAppend, "distortion") + kwave = fxi_param:k(SchanAppend, "wave") ; TODO k to i... reinit?? + aL distort aL, kdist, gifnSine ;ifn + aR distort aR, kdist, gifnSine ;ifn + endif + xout aL, aR +endop + + +opcode fxi_freqshift1, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kshift = fxi_param:k(SchanAppend, "shift") + aL, aR freqshift1 aL, aR, kshift + endif + xout aL, aR +endop + + +opcode fxi_freqshift2, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kshift = fxi_param:k(SchanAppend, "shift") + aL, aR freqshift2 aL, aR, kshift + endif + xout aL, aR +endop + + +opcode fxi_ringmod, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kfreq = fxi_param:k(SchanAppend, "freq") + aL, aR ringmod1 aL, aR, kfreq + endif + xout aL, aR +endop + + +opcode fxi_bitcrush, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kcrush = fxi_param:k(SchanAppend, "crush") + aL, aR bitcrush aL, aR, kcrush + endif + xout aL, aR +endop + + +opcode fxi_delaytuner, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kfreq = fxi_param:k(SchanAppend, "freq") + kfeedback = fxi_param:k(SchanAppend, "feedback") + aL, aR delaytuner aL, aR, kfreq, kfeedback + + endif + xout aL, aR +endop + + +opcode fxi_lowpass, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kfreq = fxi_param:k(SchanAppend, "freq") + aL butterlp aL, kfreq + aR butterlp aR, kfreq + endif + xout aL, aR +endop + + +opcode fxi_highpass, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + kfreq = fxi_param:k(SchanAppend, "freq") + aL butterhp aL, kfreq + aR butterhp aR, kfreq + endif + xout aL, aR +endop + +opcode fxi_simplechorus, aa, aaii + aL, aR, ichannelindex, ieffectindex xin + SchanAppend, kon _fxi_control ichannelindex, ieffectindex + if (kon == 1) then + krateL = fxi_param:k(SchanAppend, "rateL") + krateR = fxi_param:k(SchanAppend, "rateR") + aL, aR simplechorus aL, aR, krateL, krateR + endif + xout aL, aR +endop + +#end diff --git a/site/udo/scss/mixer/base.udo b/site/udo/scss/mixer/base.udo new file mode 100755 index 0000000..9721357 --- /dev/null +++ b/site/udo/scss/mixer/base.udo @@ -0,0 +1,253 @@ +#ifndef UDO_SCSS_MIXER_BASE +#define UDO_SCSS_MIXER_BASE ## +/* + SCSS mixer and dynamic insert system + + This file is part of the SONICS UDO collection by Richard Knight 2022 + License: GPL-2.0-or-later + http://1bpm.net +*/ + +#include "/bussing.udo" +#include "/interop.udo" + + + +gSchannelHeader = {{ + ichannelindex = p4 + kamp chnget sprintf("mxamp%d", ichannelindex) + kpan chnget sprintf("mxpan%d", ichannelindex) + aL, aR bus_read sprintf("mxchan%d", ichannelindex) + kenv linsegr 0, 0.05, 1, p3, 1, 0.1, 0 +}} + + +gSchannelFooter = {{ + bus_masterout(aL*kenv*(1-kpan)*kamp, aR*kenv*kpan*kamp) +endin +}} + + +; default globals: set in mx_boot +gichannelnumber = -1 +gisendchannels = -1 +gimixermaxinserts = -1 +giinserts[][] init 1, 1 +gimxinitialised[] init 1, 1 + + +; actual effects and definitions +#include "/scss/mixer/_effects.udo" +/* + Set insert for channel and insert index ; apply defaults +*/ +opcode apply_insert, 0, iii + iinsertdefindex, ichannelindex, iinsertindex xin + + giinserts[ichannelindex][iinsertindex] = iinsertdefindex + + ; set defaults + iJparams = jsonptr(giJinsertDefs, sprintf("/effects/%d/parameters", iinsertdefindex)) + index = 0 + while (index < jsonsize(iJparams)) do + iJparam jsonget iJparams, index ;jsonptr(iJparams, sprintf("/%d", index)) + SparameterName jsongetval iJparam, "name" + idefault jsongetval iJparam, "default" + chnset idefault, sprintf("fxi_%s_%d_%d", SparameterName, ichannelindex, iinsertindex) + jsondestroy(iJparam) + index += 1 + od + jsondestroy(iJparams) + + ; initial state is on + chnset 1, sprintf("fxi_on_%d_%d", ichannelindex, iinsertindex) +endop + + + +instr mx_effect_definitions_get + icbid = p4 + iJson = jsonloads(gSeffectDefs) + jsoninsertval iJson, "cbid", icbid + io_sendstring("callback", jsondumps(iJson, 0)) + turnoff +endin + +instr mx_insert_info_get + icbid = p4 + indexchan = p5 + + inserts[] init gimixermaxinserts + indexinsert = 0 + while (indexinsert < gimixermaxinserts) do + inserts[indexinsert] = giinserts[indexchan][indexinsert] + indexinsert += 1 + od + iJson = jsoninit() + jsoninsertval iJson, "cbid", icbid + jsoninsertval iJson, "inserts", inserts + io_sendstring("callback", jsondumps(iJson, 0)) + turnoff +endin + + +/* + recompile channel instrument, inserting insert effects as specified +*/ +instr mx_recompile_channel + ichannelindex = p4 + SinstrName = sprintf("mixer_channel%d", ichannelindex) + Sinstr = sprintf("instr %s\n%s", SinstrName, gSchannelHeader) + index = 0 + while (index < gimixermaxinserts) do + iinsert = giinserts[ichannelindex][index] + if (iinsert != -1) then + Sopcode jsonptrval giJinsertDefs, sprintf("/effects/%d/opcode", iinsert) + Sinstr = strcat(Sinstr, sprintf("aL, aR %s aL, aR, ichannelindex, %d\n", Sopcode, index)) + endif + index += 1 + od + + ; initial amps and pans if not initialised + if (gimxinitialised[ichannelindex] == 0) then + gimxinitialised[ichannelindex] = 1 + chnset 0.8, sprintf("mxamp%d", ichannelindex) + chnset 0.5, sprintf("mxpan%d", ichannelindex) + endif + + ; channel footer with global sends + SchannelFooter = "" + isendindex = 0 + if (gisendchannels != 0 && ichannelindex < gichannelnumber - 1) then + SchannelFooter = "" + while (isendindex < gisendchannels) do + SchannelFooter = strcat(SchannelFooter, sprintf("ksend%d chnget sprintf(\"mxsend%%d_%d\", ichannelindex)\nbus_mix \"mxchan%d\", aL*ksend%d, aR*ksend%d\n", isendindex, isendindex, gichannelnumber + isendindex, isendindex, isendindex)) + isendindex += 1 + od + Sinstr = strcat(Sinstr, SchannelFooter) + endif + Sinstr = strcat(Sinstr, gSchannelFooter) + ires compilestr Sinstr + turnoff2 SinstrName, 0, 1 + schedule SinstrName, 0.02, 3600, ichannelindex + turnoff +endin + + +instr mx_setstate + Sjson = p4 + iJson = jsonloads(Sjson) + +endin + + +instr mx_getstate + iJsonCSChannels = jsoninit() + iJsonBlankArray = jsonloads("[]") + iJsonInserts = jsonloads("[]") + ichannelindex = 0 + SchanDefaults[] fillarray "mxamp", "mxpan" + while (ichannelindex < gichannelnumber + gisendchannels) do + ichandefaultindex = 0 + while (ichandefaultindex < lenarray(SchanDefaults)) do + jsoninsertval iJsonCSChannels, sprintf("%s%d", SchanDefaults[ichandefaultindex], ichannelindex), chnget:i(sprintf("%s%d", SchanDefaults[ichandefaultindex], ichannelindex)) + ichandefaultindex += 1 + od + + jsonptradd iJsonInserts, sprintf("/%d", ichannelindex), iJsonBlankArray + + indexinsert = 0 + while (indexinsert < gimixermaxinserts) do + iinsert = giinserts[ichannelindex][indexinsert] + jsonptraddval iJsonInserts, sprintf("/%d/%d", ichannelindex, indexinsert), iinsert + if (iinsert != -1) then + iJparams = jsonptr(giJinsertDefs, sprintf("/effects/%d/parameters", iinsert)) + index = 0 + while (index < jsonsize(iJparams)) do + iJparam jsonget iJparams, index + SparameterName jsongetval iJparam, "name" + SparameterChan = sprintf("fxi_%s_%d_%d", SparameterName, ichannelindex, indexinsert) + jsoninsertval iJsonCSChannels, SparameterChan, chnget:i(SparameterChan) + jsondestroy(iJparam) + index += 1 + od + jsondestroy(iJparams) + + endif + indexinsert += 1 + od + + ichannelindex += 1 + od + iJson = jsoninit() + jsoninsertval iJson, "channelnumber", gichannelnumber + jsoninsertval iJson, "sendchannels", gisendchannels + jsoninsertval iJson, "maxinserts", gimixermaxinserts + jsoninsert iJson, "cschannels", iJsonCSChannels + jsoninsert iJson, "inserts", iJsonInserts + + prints jsondumps(iJson, 1) + + jsondestroy(iJsonBlankArray) + jsondestroy(iJsonCSChannels) + jsondestroy(iJsonInserts) + jsondestroy(iJson) + turnoff + +endin + + +instr mx_alter_insert + ichannelindex = p4 + iinsertindex = p5 + iinsertdefindex = p6 + + apply_insert iinsertdefindex, ichannelindex, iinsertindex + + ;schedule "mx_sendstate", 0, 1 + schedule "mx_recompile_channel", 0, 1, ichannelindex + turnoff +endin + +instr mx_move_insert + ichannelindex = p4 + iinsertindex_from = p5 + iinsertindex_to = p6 + + ; keep state... +endin + + +/* + set up channels +*/ +instr mx_boot + gichannelnumber = (p4 == 0) ? 12 : p4 + gisendchannels = (p5 == 0) ? 2 : p5 + gimixermaxinserts = (p6 == 0) ? 6 : p6 + + giinserts[][] init gichannelnumber + gisendchannels, gimixermaxinserts + + ; set all to -1 as 0 is a valid entry + index1 = 0 + while (index1 < lenarray(giinserts, 1)) do + index2 = 0 + while (index2 < lenarray(giinserts, 2)) do + giinserts[index1][index2] = -1 + index2 += 1 + od + index1 += 1 + od + + ; if initialised, don't reset amp/pan etc + gimxinitialised[] init gichannelnumber + gisendchannels + + ichannelindex = 0 + while (ichannelindex < gichannelnumber + gisendchannels) do + schedule "mx_recompile_channel", 0, 1, ichannelindex + ichannelindex += 1 + od + turnoff +endin + +#end diff --git a/site/udo/scss/mixer/test.csd b/site/udo/scss/mixer/test.csd new file mode 100755 index 0000000..1fc6099 --- /dev/null +++ b/site/udo/scss/mixer/test.csd @@ -0,0 +1,41 @@ + + +-odac + + +sr = 48000 +ksmps = 64 +nchnls = 2 +0dbfs = 1 +seed 0 + +#include "scss/mixer/base.udo" + +instr test_boot + schedule("mx_boot", 0, 1) ; defaults: 12 channels + schedule("test_set", 0.5, 1) +endin + +instr test_set + schedule("mx_alter_insert", 0.1, 1, 12, 0, 0) ; reverb on return 1 fx 1 (chan 12) + schedule("mx_alter_insert", 0, 1, 12, 1, 3) ; ring mod on return 1 fx 0 (chan 12) + chnset 1, "mxsend0_0" ; send chan 0 to reverb + schedule("test_sound", 0.5, 50) ; test sound + schedule("mx_getstate", 1, 1) ; test state output +endin + + +instr test_sound + prints "\n\ntest sound\n\n" + aL, aR diskin2 "d:/temp/drive.wav", 1, 0, 1 + bus_mix("mxchan0", aL, aR) + + kfreq = abs:k(oscil:k(2200, 1)) + 220 + chnset kfreq, "fxi_freq_12_1" +endin + + + +i"test_boot" 0 60 + + \ No newline at end of file -- cgit v1.2.3