aboutsummaryrefslogtreecommitdiff
path: root/site/udo/scss/mixer
diff options
context:
space:
mode:
Diffstat (limited to 'site/udo/scss/mixer')
-rwxr-xr-xsite/udo/scss/mixer/_effects.udo247
-rwxr-xr-xsite/udo/scss/mixer/base.udo253
-rwxr-xr-xsite/udo/scss/mixer/test.csd41
3 files changed, 541 insertions, 0 deletions
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 @@
+<CsoundSynthesizer>
+<CsOptions>
+-odac
+</CsOptions>
+<CsInstruments>
+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
+
+</CsInstruments>
+<CsScore>
+i"test_boot" 0 60
+</CsScore>
+</CsoundSynthesizer> \ No newline at end of file