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/base.udo | 1046 +++++++++++++++++++++++++++++++ site/udo/scss/elasticlip.udo | 823 ++++++++++++++++++++++++ site/udo/scss/elasticlip_arranger.udo | 213 +++++++ site/udo/scss/elasticlip_sequencer.udo | 133 ++++ site/udo/scss/mixer/_effects.udo | 247 ++++++++ site/udo/scss/mixer/base.udo | 253 ++++++++ site/udo/scss/mixer/test.csd | 41 ++ site/udo/scss/persistence.udo | 366 +++++++++++ site/udo/scss/scss_persistence_test.csd | 100 +++ site/udo/scss/scss_test.csd | 125 ++++ site/udo/scss/seqtable.udo | 196 ++++++ 11 files changed, 3543 insertions(+) create mode 100755 site/udo/scss/base.udo create mode 100755 site/udo/scss/elasticlip.udo create mode 100755 site/udo/scss/elasticlip_arranger.udo create mode 100755 site/udo/scss/elasticlip_sequencer.udo 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 create mode 100755 site/udo/scss/persistence.udo create mode 100755 site/udo/scss/scss_persistence_test.csd create mode 100755 site/udo/scss/scss_test.csd create mode 100755 site/udo/scss/seqtable.udo (limited to 'site/udo/scss') diff --git a/site/udo/scss/base.udo b/site/udo/scss/base.udo new file mode 100755 index 0000000..07b339a --- /dev/null +++ b/site/udo/scss/base.udo @@ -0,0 +1,1046 @@ +#ifndef UDO_SCSS_BASE +#define UDO_SCSS_BASE ## +/* + SONICS Category Sequencer System + + Designed for use with an API host to/from which callbacks and JSON states can be exchanged + + Requires JSON opcodes + https://git.1bpm.net/csound-json + + This file is part of the SONICS UDO collection by Richard Knight 2022 + License: GPL-2.0-or-later + http://1bpm.net +*/ + + +#include "sequencing.udo" +#include "bussing.udo" +#include "interop.udo" +#include "sequencing_table.udo" +#include "sequencing_melodic.udo" +;#include "sequencing_melodic_persistence.udo" +#include "scss/seqtable.udo" + + +; session name for persistence +#ifndef SCSS_NAME +#define SCSS_NAME #default# +#end + + +; category types: trigger are pattern sequenced, continuous are always on but switchable, effects are always on +gSscss_catTypes[] fillarray "trigger", "continuous", "oneoff", "effects" + +; selected instrument indexes by category; hardcoded max of 32 categories here... +imaxcategories = 32 +giscss_instrState ftgen 0, 0, -imaxcategories, -2, 0 +giscss_catEnabled ftgen 0, 0, -imaxcategories, -2, 0 +giscss_catType ftgen 0, 0, -imaxcategories, -2, 0 + +; categories and effects for bus tracking / amp channel names +gSscss_categories[] init 1 +gSscss_effects[] init 1 + +; callback ID divisor for callback-tracked instrument numbers +gicbid_idiv = 10000000 + + + +#ifdef SCSS_MIDI +#define MIDI_NOTE_HANDLER_INSTRUMENT #_scss_midihandler# +#include "midi.udo" + +giscss_midicategories[] init 16 + +instr _scss_midihandler + ichannel = p4 + inote = p5 + ivelocity = p6 + icategoryid = giscss_midicategories[ichannel] + instrnum table icategoryid, giscss_instrState + instrnum += (99 + icategoryid + inote) / gicbid_idiv + schedule(instrnum, 0, -1) + kreleasing init 0 + if (release:k() == 1 && kreleasing == 0) then + turnoff2 instrnum, 4, 1 + kreleasing = 1 + endif +endin + +instr scss_setmidicategory + ichannel = p4 + icategoryid = p5 + giscss_midicategories[ichannel] = icategoryid + turnoff +endin + +#end + + +; default JSON object; categories will be filled by registering categories and instruments accordingly; default global parameters are defined here +giscss_stateJson = jsonloads({{ + { + "categories": [], + "parameters": [ + {"name": "bpm", "description": "BPM", "default": 120, "max": 240, "min": 30} + ] + } +}}) + +#include "scss/persistence.udo" + +/* + Get category ID for given category name, creating in giscss_stateJson if it does not exist + + icategoryid _scss_category_getorcreate Scategory, Sdescription [,itype = 0] + + icategoryid the ID + Scategory category name to get or create + Sdescription description to add if creating; pass "" if not required + itype type ID. Type is set to "effects" if category name is "effects" + 0 trigger + 1 continuous + +*/ +opcode _scss_category_getorcreate, i, SSo + Scategory, Sdescription, itype xin + iJcategories = jsonget(giscss_stateJson, "categories") + isize = jsonsize(iJcategories) + index = 0 + while (index < isize) do + iJcategory = jsonget(iJcategories, index) + SexistingCategory = jsongetval(iJcategory, "name") + if (strcmp(SexistingCategory, Scategory) == 0) then + goto complete + endif + jsondestroy(iJcategory) + index += 1 + od + + if (strcmp(Scategory, "effects") == 0) then + itype = 3 + endif + + Stype = gSscss_catTypes[itype] + + ; create + if (strcmp(Sdescription, "") == 0) then + iJsonNew = jsonloads(sprintf("{\"name\": \"%s\", \"type\": \"%s\", \"type_id\": %d, \"items\": []}", Scategory, Stype, itype)) + else + iJsonNew = jsonloads(sprintf("{\"name\": \"%s\", \"type\": \"%s\", \"type_id\": %d, \"description\": \"%s\", \"items\": []}", Scategory, Stype, itype, Sdescription)) + endif + + ; workaround for not being able to append with ptr + iJsonSz = jsonptr(giscss_stateJson, "/categories") + index = jsonsize(iJsonSz) + jsondestroy(iJsonSz) + + jsonptradd(giscss_stateJson, sprintf("/categories/%d", index), iJsonNew) + jsondestroy(iJsonNew) +complete: + jsondestroy(iJcategories) + xout index +endop + + +/* + Get category ID for given category name, creating in giscss_stateJson if it does not exist + + icategoryid scss_categoryid Scategory + + icategoryid the ID + Scategory category name to get or create +*/ +opcode scss_categoryid, i, S + Scategory xin + icategoryid = _scss_category_getorcreate(Scategory, "") + xout icategoryid +endop + + + +/* + Register an instrument name to a category by category ID + + scss_registerinstr icategoryid, SinstrumentJson + scss_registerinstr Scategory, SinstrumentJson + + icategoryid category ID to register to + Scategory category name to register to + SinstrumentJson definition of instrument +*/ +opcode scss_registerinstr, 0, iS + icategoryid, Sjson xin + iJson = jsonloads(Sjson) + + ; workaround for not being able to append with ptr + iJsonSz = jsonptr(giscss_stateJson, sprintf("/categories/%d/items", icategoryid)) + index = jsonsize(iJsonSz) + jsondestroy(iJsonSz) + + jsonptradd(giscss_stateJson, sprintf("/categories/%d/items/%d", icategoryid, index), iJson) + jsondestroy(iJson) +endop + +; override for named category +opcode scss_registerinstr, 0, SS + Scategory, Sjson xin + scss_registerinstr(scss_categoryid(Scategory), Sjson) +endop + + +/* + Register a category and set the default instrument name. An existing category could be specified with no harm + + icategoryid scss_registercategory Scategory, Sdescription, SdefaultInstrument + icategoryid scss_registercategory Scategory, SdefaultInstrument + + icategoryid the category ID + Scategory category name + Sdescription category description + SdefaultInstrument default instrument to set in giscss_instrState +*/ +opcode scss_registercategory, i, SSSo + Scategory, Sdescription, SdefaultInstrument, itypeid xin + icategoryid = _scss_category_getorcreate(Scategory, Sdescription, itypeid) + + ; set default instrument in state table + tablew nstrnum(SdefaultInstrument), icategoryid, giscss_instrState + + xout icategoryid +endop + +; override for when no description is provided +opcode scss_registercategory, i, SS + Scategory, SdefaultInstrument xin + xout scss_registercategory(Scategory, "", SdefaultInstrument) +endop + + +/* + Register a category with continous type and set the default instrument name. An existing category could be specified with no harm +*/ +opcode scss_registercategory_continuous, i, SSS + Scategory, Sdescription, SdefaultInstrument xin + xout scss_registercategory(Scategory, Sdescription, SdefaultInstrument, 1) +endop + +; override for when no description is provided +opcode scss_registercategory_continuous, i, SS + Scategory, SdefaultInstrument xin + xout scss_registercategory(Scategory, "", SdefaultInstrument, 1) +endop + + +/* + Sequence the trigger category instruments according to status in table giscss_catEnabled and the primary sequence in table giscss_stfn_trig + Instruments receive p4 as category ID (to allow for independent category specific opcode calls) and p5 as time index + + _scss_st_catseq icategoryid + + icategoryid category ID +*/ +opcode _scss_st_catseq, 0, i + icategoryid xin + Scategory = gSscss_categories[icategoryid] + ;ifn [, kreset=0, kdivisions=4, kchanceon=1, kchanceoff=1, klength=ftlen(ifn), kswing=gkseq_swing] + kreset = chnget:k(sprintf("%s_seq_reset", Scategory)) + kdivisions = chnget:k(sprintf("%s_seq_divisions", Scategory)) + kchanceon = chnget:k(sprintf("%s_seq_chanceon", Scategory)) + kchanceoff = chnget:k(sprintf("%s_seq_chanceoff", Scategory)) + ;klength = chnget:k(sprintf("seq_lengthratio_%d", icategoryid)) * ftlen(giscss_stfn_trig[icategoryid][0]) + ktrig, kindex seq_table giscss_stfn_trig[icategoryid][0], kreset, kdivisions, kchanceon, kchanceoff + + if (ktrig == 1 && table:k(icategoryid, giscss_catEnabled) == 1) then + kinstrnum table icategoryid, giscss_instrState + kduration table kindex, giscss_stfn_dur[icategoryid][0] + schedulek(kinstrnum, 0, kduration*gkseq_quartertime, icategoryid, kindex) + endif +endop + + +/* + React to changes in the giscss_catEnabled table and invoke or stop continuous category instruments accordingly + + _scss_st_catcontreactor icategoryid + + icategoryid category ID +*/ +opcode _scss_st_catcontreactor, 0, i + icategoryid xin + kenabled = table:k(icategoryid, giscss_catEnabled) + klastinstrnum init table:i(icategoryid, giscss_instrState) + kinstrnum = table:k(icategoryid, giscss_instrState) + + if (changed:k(kenabled) == 1 || changed:k(kinstrnum) == 1) then + if (kenabled == 1) then + turnoff2 klastinstrnum, 0, 1 + schedulek(kinstrnum, 0, 999999) ; not -1 in case of p3 linseg(r) usage + else + turnoff2 klastinstrnum, 0, 1 + endif + klastinstrnum = kinstrnum + endif +endop + + +/* + Sequencing and instrument launch control + Initialises all category sequencing for trigger and continuous type categories + No parameters should be provided, the index parameter is for internal recursion +*/ +opcode _scss_st_seq, 0, o + index xin + itypeid = table:i(index, giscss_catType) + + if (itypeid == 0) then ; is trigger type + _scss_st_catseq(index) + elseif (itypeid == 1) then ; is continuous type + _scss_st_catcontreactor(index) + endif + ; effects initialised by scss_boot + + if (index < lenarray(gSscss_categories) - 1) then + _scss_st_seq(index + 1) + endif +endop + + +/* + Get an init time parameter specific to the instrument/effect + + ivalue scss_param Sname + + ivalue parameter value + Sname parameter name, specific to the instrument/effect +*/ +opcode scss_param, i, S + Sname xin + ivalue = chnget(sprintf("%s_%s", nstrstr(p1), Sname)) + xout ivalue +endop + + +/* + Get a k-rate parameter specific to the instrument/effect + + kvalue scss_param Sname + + kvalue parameter value + Sname parameter name, specific to the instrument/effect +*/ +opcode scss_param, k, S + Sname xin + kvalue = chnget:k(sprintf("%s_%s", nstrstr(p1), Sname)) + xout kvalue +endop + + +opcode scss_catparam, i, S + Sname xin + xout chnget(sprintf("%s_%s", gSscss_categories[p4], Sname)) +endop + + +opcode scss_catparam, k, S + Sname xin + xout chnget:k(sprintf("%s_%s", gSscss_categories[p4], Sname)) +endop + +/* + Output to category bus; should be used by all SCSS category instruments in place of outs() + Instruments are invoked with the category ID as p4, so this is utilised here if icategoryid is not supplied + + scss_catout aL, aR [, icategoryid] + + aL, aR output of instrument + icategoryid category ID; taken as p4 if not supplied +*/ +opcode scss_catout, 0, aaj + aL, aR, icategoryid xin + icategoryid = (icategoryid == -1) ? p4 : icategoryid + bus_mix(sprintf("cat%d", icategoryid), aL, aR) +endop + + +/* + Get input from effects input bus; should be used by all SCSS effects to obtain input + + aL, aR scss_fxin + + aL, aR audio input to apply effect to +*/ +opcode scss_fxin, aa, 0 + aL, aR bus_read nstrstr(p1) + xout aL, aR +endop + + +/* + Output to effects output bus; should be used by all SCSS effects in place of outs() + + scss_fxout aL, aR + + aL, aR output of effect +*/ +opcode scss_fxout, 0, aa + aL, aR xin + bus_set(sprintf("%s_out", nstrstr(p1)), aL, aR) +endop + + +/* + Set category instrument + + scss_setcategoryinstrument icategoryid, Sinstrument + scss_setcategoryinstrument Scategory, Sinstrument + scss_setcategoryinstrument icategoryid, instrnum + scss_setcategoryinstrument Scategory, instrnum + + icategoryid category ID + Scategory category name + Sinstrument instrument name (should be within the category, would work if not though so host must impose any restrictions required) + instrnum instrument number +*/ +opcode scss_setcategoryinstrument, 0, ii + icategoryid, instrnum xin + tablew instrnum, icategoryid, giscss_instrState +endop + +; override for named instrument, int category +opcode scss_setcategoryinstrument, 0, iS + icategoryid, Sinstrument xin + scss_setcategoryinstrument(icategoryid, nstrnum(Sinstrument)) +endop + +; override for named instrument, named category +opcode scss_setcategoryinstrument, 0, SS + Scategory, Sinstrument xin + scss_setcategoryinstrument(scss_categoryid(Scategory), nstrnum(Sinstrument)) +endop + +; override for named category, int instrument +opcode scss_setcategoryinstrument, 0, Si + Scategory, instrument xin + scss_setcategoryinstrument(scss_categoryid(Scategory), instrument) +endop + + + +/* + Toggle category enabled/disabled (inverse of current) + + scss_togglecategory icategoryid + scss_togglecategory Scategory + + icategoryid category ID + Scategory category name +*/ +opcode scss_togglecategory, 0, i + icategoryid xin + tablew 1 - table:i(icategoryid, giscss_catEnabled), icategoryid, giscss_catEnabled +endop + +; override for named category +opcode scss_togglecategory, 0, S + Scategory xin + scss_togglecategory(scss_categoryid(Scategory)) +endop + + +/* + Set category enabled or disabled + + scss_setcategoryenabled icategoryid, istate + scss_setcategoryenabled Scategory, istate + + icategoryid category ID + Scategory category name + istate 0 = disabled, 1 = enabled +*/ +opcode scss_setcategoryenabled, 0, ii + icategoryid, istate xin + tabw_i istate, icategoryid, giscss_catEnabled +endop + +; override for named category +opcode scss_setcategoryenabled, 0, Si + Scategory, istate xin + scss_setcategoryenabled(scss_categoryid(Scategory), istate) +endop + + +; API evalCode not working right +instr scss_eval + ires = evalstr(strget(p4)) + turnoff +endin + +/* + Pause or unpause the melodic progression + + p4 0 = unpaused, 1 = paused +*/ +instr scss_mel_pause + istate = p4 + gkmel_pause = istate + turnoff +endin + + +/* + Schedule a series of init time instructions + When complete, the host is sent a callback + + p4 instructions string (any valid init time calls) + p5 mode: + -1 immediate + 0 next beat + 1 next bar + 2 next bar group + 3 next melodic section change + 4 next most important melodic section + p6 queue ID (to allow for dynamic change instrument separation) + p7 callback ID +*/ +instr scss_schedule_change + Sinstructions = p4 + imode = p5 + iqid = p6 + icbid = p7 + + p1 += (icbid / gicbid_idiv) + + ktrig init 0 + + ; immediate + if (imode == -1) then + ktrig = 1 + + ; next beat + elseif (imode == 0) then + ktrig = gkseq_beat + + ; next bar + elseif (imode == 1) then + ktrig = gkseq_bar_trig + + ; next bar group + elseif (imode == 2) then + ktrig = gkseq_bargroup + + ; next melodic section change + elseif (imode == 3) then + ktrig = gkmel_section_change + + ; next most important melodic section + elseif (imode == 4) then + ibestindex, iimportance, ibeats mel_future_mostimportant + kbeat init ibeats + if (gkseq_beat == 1) then + kbeat -= 1 + endif + + if (kbeat == 0) then + ktrig = 1 + endif + endif + + ; when ready, execute the instructions and notify host + if (ktrig == 1) then + kres evalstr Sinstructions, ktrig + if (iqid > -1) then + schedulek("scss_callbackcomplete", 0, 1, icbid) + endif + turnoff + endif +endin + + +; original with compilestr instead of evalstr +instr scss_schedule_change_original + Sinstructions = p4 + imode = p5 + iqid = p6 + icbid = p7 + + p1 += (icbid / gicbid_idiv) + + ires compilestr sprintf({{ + instr scss_change_%d + %s + turnoff + endin + }}, iqid, Sinstructions) + + ktrig init 0 + + ; immediate + if (imode == -1) then + ktrig = 1 + + ; next beat + elseif (imode == 0) then + ktrig = gkseq_beat + + ; next bar + elseif (imode == 1) then + ktrig = gkseq_bar_trig + + ; next bar group + elseif (imode == 2) then + ktrig = gkseq_bargroup + + ; next melodic section change + elseif (imode == 3) then + ktrig = gkmel_section_change + + ; next most important melodic section + elseif (imode == 4) then + ibestindex, iimportance, ibeats mel_future_mostimportant + kbeat init ibeats + if (gkseq_beat == 1) then + kbeat -= 1 + endif + + if (kbeat == 0) then + ktrig = 1 + endif + endif + + ; when ready, execute the instructions and notify host + if (ktrig == 1) then + schedulek(sprintf("scss_change_%d", iqid), 0, 1) + if (iqid > -1) then + schedulek("scss_callbackcomplete", 0, 1, icbid) + endif + turnoff + endif +endin + + + + +/* + Automate a parameter + + p4 callback ID + p5 mode: + 0 segmented between start/end values + 1 read values from a specified table + p6 report value back to host every p6 seconds. Channel is set as cbval appended with callback ID. If 0, value is not reported + p7 channel name to automate + p8 time mode: + 0 p3 is in absolute seconds + 1 p3 is in beats + 2 p3 is in bars + 3 p3 is in bargroups + 4 run until next most important melodic sequence + p9 start value if segmented mode + p10 end value if segmented mode + p11 if segmented mode; + 0 linear segment + 1 exponential segment + p12 table number if table read mode + p13 loop automation (0 = no loop, 1 = loop) + p14 if loop is specified, set ping pong loop (0 = normal loop, 1 = ping pong loop) +*/ +instr scss_automateparameter + icbid = p4 + imode = p5 ; 0 = seg, 1 = table + ireportvaluetime = p6 + SparameterChannel = p7 + itimemode = p8 + istart = p9 + iend = p10 + iexpon = p11 + ifn = p12 + iloop = p13 + ipingpong = p14 + + ; if the first iteration and callback ID has been specified, offset the instrument number to allow scss_cancelcallbackinstrument to be used + if (icbid > -1 && iloop <= 1) then + p1 += (icbid / gicbid_idiv) + io_sendstring("callback", sprintf("{\"cbid\": %d, \"keep_callback\": true, \"status\": \"started\"}", icbid)) + endif + + ; if first iteration, set the duration according to itimemode + if (iloop <= 1) then + if (itimemode == 1) then + p3 *= i(gkseq_beattime) + elseif (itimemode == 2) then + p3 *= i(gkseq_beattime) * giseq_barlength + elseif (itimemode == 3) then + p3 *= i(gkseq_beattime) * giseq_barlength * giseq_bargrouplength + elseif (itimemode == 4) then + ibestindex, iimportance, ibeats mel_future_mostimportant + p3 *= i(gkseq_beattime) * ibeats + endif + endif + + ; iloop is incremented each loop, so if ipingpong is specified, use this to determine if reverse read should occur + ireverse = (ipingpong == 1 && iloop != 0 && iloop % 2 == 0) ? 1 : 0 + + ; segmented mode + if (imode == 0) then + if (ireverse == 1) then + istarttemp = istart + istart = iend + iend = istarttemp + endif + + ; exponentials cannot be 0 + if (iexpon == 1) then + if (istart == 0) then + istart = 0.0000001 + endif + if (iend == 0) then + iend = 0.0000001 + endif + + kvalue expseg istart, p3, iend + else + kvalue linseg istart, p3, iend + endif + + ; table read mode + elseif (imode == 1) then + if (ireverse == 1) then + kindex line ftlen(ifn), p3, 0 + else + kindex line 0, p3, ftlen(ifn) + endif + kvalue table kindex, ifn + endif + + ; set the actual channel value + chnset kvalue, SparameterChannel + + ; report back to host if specified + if (ireportvaluetime > 0) then + ; send back to host for ui change + kmetro metro 1/ireportvaluetime + if (kmetro == 1) then + outvalue sprintf("cbval%d", icbid), kvalue + endif + endif + + ; loop or complete + if (icbid > -1) then + if (lastcycle:k() == 1) then + if (iloop > 0) then + schedulek(p1, 0, p3, icbid, imode, ireportvaluetime, SparameterChannel, itimemode, istart, iend, iexpon, ifn, iloop+1, ipingpong) + else + schedulek("scss_callbackcomplete", 0, 1, icbid) + endif + endif + endif +endin + + +/* + Send callback complete JSON to host + (due to outvalue(S,S) firing at i-time as well as k when we just want k) + + p4 callback ID +*/ +instr scss_callbackcomplete + icbid = p4 + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": \"complete\"}", icbid)) + turnoff +endin + + +/* + Cancel a callback instrument (one initiated with (instrument number + (icbid / gicbid_idiv)) + + p4 callback ID + p4 instrument name +*/ +instr scss_cancelcallbackinstrument + icbid = p4 + Sinstrument = p5 + instrnum = nstrnum(Sinstrument) + (icbid / gicbid_idiv) + turnoff2 instrnum, 4, 0 + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": \"complete\", \"cancelled\": true}", icbid)) + turnoff +endin + + + + +/* + Sum effects bus returns and include into one category bus for internal use +*/ +opcode _scss_getsetfxbusses, 0, jo + icategoryid, index xin + if (icategoryid == -1 ) then + icategoryid = scss_categoryid("effects") + endif + + Seffect = gSscss_effects[index] + aL, aR bus_read sprintf("%s_out", Seffect) + kamp chnget sprintf("%s_amp", Seffect) + aL *= kamp + aR *= kamp + bus_mix(sprintf("cat%d", icategoryid), aL, aR) + + if (index + 1 < lenarray(gSscss_effects)) then + _scss_getsetfxbusses icategoryid, index + 1 + endif +endop + + +/* + Get all category bus outputs, summed and with the relevant amplitude applied from the channel CATEGORYNAME_amp + + aL, aR scss_getcategorybusses + + aL, aR left and right audio outputs +*/ +opcode scss_getcategorybusses, aa, o + index xin + _scss_getsetfxbusses() + aL, aR bus_read sprintf("cat%d", index) + kamp chnget sprintf("%s_amp", gSscss_categories[index]) + aL *= kamp + aR *= kamp + if (index + 1 < lenarray(gSscss_categories)) then + aLx, aRx scss_getcategorybusses index + 1 + aL += aLx + aR += aRx + endif + xout aL, aR +endop + + + +/* + Set a trigger to 1 for one kcycle + + p4 channel name to set +*/ +instr scss_trigger + Schannel = p4 + chnset 1, Schannel + chnset k(0), Schannel + turnoff +endin + + +/* + Boot SCSS: + go through parameters and set defaults to channels accordingly + set default parameters where appropriate + set category bus amps to default + set ampchannel in JSON +*/ +instr scss_boot + SonComplete = strget(p4) + + SdefaultTriggerParameters = {{[ + {"name": "seq_reset", "description": "Reset", "type": "trigger"}, + {"name": "seq_divisions", "description": "Divisions", "type": "int", "min": 1, "max": 8, "default": 4}, + {"name": "seq_chanceon", "description": "On chance", "min": 0, "max": 1, "default": 1}, + {"name": "seq_chanceoff", "description": "Off chance", "min": 0, "max": 1, "default": 1}%s + ]}} + SdefaultParameters = {{ + {"name": "amp", "description": "Amplitude", "min": 0, "max": 1, "default": 1} + }} + + idefaultcategoryamp = 1 + idefaultfxamp = 0.8 + iJson = jsonptr(giscss_stateJson, "/categories") + + icatnum = jsonsize(iJson) + gSscss_categories[] init icatnum + scss_st_boot(icatnum, 32, 4) + + ; add effects default parameters; done to global for later defaults processing + index = 0 + while (index < icatnum) do + Scategory jsonptrval giscss_stateJson, sprintf("/categories/%d/name", index) + if (strcmp(Scategory, "effects") == 0) then + iJsonItems = jsonptr(giscss_stateJson, sprintf("/categories/%d/items", index)) + ijsize = jsonsize(iJsonItems) + jsondestroy(iJsonItems) + indexitem = 0 + while (indexitem < ijsize) do + SparameterPointer = sprintf("/categories/%d/items/%d/parameters", index, indexitem) + if (jsonptrhas(giscss_stateJson, SparameterPointer) == 1) then + iJparameters = jsonptr(giscss_stateJson, SparameterPointer) + indexparam = jsonsize(iJparameters) + jsondestroy(iJparameters) + iJdefaults = jsonloads(SdefaultParameters) + jsonptradd(giscss_stateJson, sprintf("%s/%d", SparameterPointer, indexparam), iJdefaults) + else + iJdefaults = jsonloads(sprintf("[%s]", SdefaultParameters)) + jsonptradd(giscss_stateJson, SparameterPointer, iJdefaults) + endif + jsondestroy(iJdefaults) + indexitem += 1 + od + goto process_categories + endif + index += 1 + od + +process_categories: + ; loop through instrument categories + index = 0 + while (index < icatnum) do + + ; all enabled to begin with + tablew 1, index, giscss_catEnabled + + iJsonCategory = jsonptr(giscss_stateJson, sprintf("/categories/%d", index)) + Scategory jsongetval iJsonCategory, "name" + iseffect = 1 - strcmp(Scategory, "effects") + itypeid jsongetval iJsonCategory, "type_id" + + tablew itypeid, index, giscss_catType + istriggered = (itypeid == 0) ? 1 : 0 + + ; set in global array for bus tracking + gSscss_categories[index] = Scategory + + iJsonItems = jsonptr(iJsonCategory, "/items") + iinstrnum = jsonsize(iJsonItems) + + ; add category ID + jsonptraddval giscss_stateJson, sprintf("/categories/%d/category_id", index), index + + if (iseffect == 1) then + gSscss_effects[] init iinstrnum + else + + if (istriggered == 1) then + ; add sequence tables + iJsonSeqtables = jsoninit() + jsoninsertval(iJsonSeqtables, "trigger", getrow(giscss_stfn_trig, index)) + jsoninsertval(iJsonSeqtables, "duration", getrow(giscss_stfn_dur, index)) + jsoninsertval(iJsonSeqtables, "parameter", getrow(giscss_stfn_params, index)) + jsonptradd giscss_stateJson, sprintf("/categories/%d/seqtables", index), iJsonSeqtables + jsondestroy(iJsonSeqtables) + iJsonInstrParams = jsonloads(sprintf(SdefaultTriggerParameters, sprintf(",%s", SdefaultParameters))) + else + iJsonInstrParams = jsonloads(sprintf("[%s]", SdefaultParameters)) + endif + + ; add default control channels + iparamsize = jsonsize(iJsonInstrParams) + indexparam = 0 + while (indexparam < iparamsize) do + SparamName jsonptrval iJsonInstrParams, sprintf("/%d/name", indexparam) + SdefaultPointer = sprintf("/%d/default", indexparam) + + ; if default is specified + if (jsonptrhas(iJsonInstrParams, SdefaultPointer) == 1) then + chnset jsonptrval:i(iJsonInstrParams, SdefaultPointer), sprintf("%s_%s", Scategory, SparamName) + endif + + indexparam += 1 + od + jsonptradd giscss_stateJson, sprintf("/categories/%d/parameters", index), iJsonInstrParams + jsondestroy(iJsonInstrParams) + endif + + ; loop through instrument items + indexItem = 0 + while (indexItem < iinstrnum) do + iJsonItem = jsonptr(iJsonItems, sprintf("/%d", indexItem)) + Sinstrument = jsongetval(iJsonItem, "name") + + if (iseffect == 1) then + gSscss_effects[indexItem] = Sinstrument + SampChannel = sprintf("%s_amp", Sinstrument) + chnset idefaultfxamp, SampChannel + jsonptraddval giscss_stateJson, sprintf("/categories/%d/items/%d/ampchannel", index, indexItem), SampChannel + endif + + ; add instrument number + jsonptraddval giscss_stateJson, sprintf("/categories/%d/items/%d/instrnum", index, indexItem), nstrnum(Sinstrument) + + SparameterPointer = "/parameters" + if (jsonptrhas(iJsonItem, SparameterPointer) == 1) then + iJsonParameters = jsonptr(iJsonItem, SparameterPointer) + iparamsize = jsonsize(iJsonParameters) + + ; loop through parameter keys + indexparam = 0 + while (indexparam < iparamsize) do + SparamName jsonptrval iJsonParameters, sprintf("/%d/name", indexparam) + SdefaultPointer = sprintf("/%d/default", indexparam) + + ; if default is specified + if (jsonptrhas(iJsonParameters, SdefaultPointer) == 1) then + chnset jsonptrval:i(iJsonParameters, SdefaultPointer), sprintf("%s_%s", Sinstrument, SparamName) + endif + indexparam += 1 + od + jsondestroy(iJsonParameters) + endif + + ; if instrument is in effects category, schedule endless play + if (iseffect == 1) then + schedule Sinstrument, 0.1, -1 + endif + + jsondestroy(iJsonItem) + indexItem += 1 + od + jsondestroy(iJsonItems) + jsondestroy(iJsonCategory) + index += 1 + od + jsondestroy(iJson) + + ; global parameters + SparameterPointer = "/parameters" + if (jsonptrhas(giscss_stateJson, SparameterPointer) == 1) then + iJsonParameters = jsonptr(giscss_stateJson, SparameterPointer) + iparamsize = jsonsize(iJsonParameters) + + ; loop through parameter keys + indexparam = 0 + while (indexparam < iparamsize) do + SparamName jsonptrval iJsonParameters, sprintf("/%d/name", indexparam) + SdefaultPointer = sprintf("/%d/default", indexparam) + + ; if default is specified + if (jsonptrhas(iJsonParameters, SdefaultPointer) == 1) then + chnset jsonptrval:i(iJsonParameters, SdefaultPointer), sprintf("scss_%s", SparamName) + endif + indexparam += 1 + od + jsondestroy(iJsonParameters) + endif + + ; append category state and enabled table numbers to JSON + jsoninsertval(giscss_stateJson, "catstatetable", giscss_instrState) + jsoninsertval(giscss_stateJson, "catenabledtable", giscss_catEnabled) + + ; send JSON state to host, init time only + io_sendstring("scss_state", jsondumps(giscss_stateJson, 0)) + ;prints jsondumps(giscss_stateJson) + + ; global parameter handling, explicitly specified right now + gkseq_tempo = chnget:k("scss_bpm") + + ; sequence triggered and continuous instruments + _scss_st_seq() + + ; launch onComplete instrument if specified + if (strcmp(SonComplete, "") != 0) then + schedule(SonComplete, 0, -1) + endif + + ; get all audio and output + aL, aR scss_getcategorybusses + outs aL, aR +endin + +schedule("scss_boot", 0, -1, "$SCSS_BOOT_INSTRUMENT") + +#end diff --git a/site/udo/scss/elasticlip.udo b/site/udo/scss/elasticlip.udo new file mode 100755 index 0000000..e5bd2ee --- /dev/null +++ b/site/udo/scss/elasticlip.udo @@ -0,0 +1,823 @@ +#ifndef UDO_SCSS_ELASTICLIP +#define UDO_SCSS_ELASTICLIP ## +/* + SCSS segemented timestretch record/playback loop engine + + This file is part of the SONICS UDO collection by Richard Knight 2024 + License: GPL-2.0-or-later + http://1bpm.net +*/ + +#include "/bussing.udo" +#include "/wavetables.udo" +#include "/interop.udo" +#include "/sequencing.udo" +#include "/sequencing_scheduled.udo" +#include "/table_tools.udo" +#include "/pvs_tools.udo" + +/* + Control Items + + 0 wave L + 1 wave R + 2 warp divisions per beat + 3 duration + 4 beats length + 5 utilised length + 6 warp mode ; 0 = repitch , 1 = texture , 2 = mincer , 2 = pvstab + 7 pitch, in semitones from original + 8 amp + 9 fft size + 10 texture window size + 11 texture random + 12 texture overlap + 13 loop switch + 14 warp switch + 15 texture window type ; 0 = hanning, 1 = hamming, 2 = half sine + 16 utilised start + 17 phase lock + 18 sample rate + 19 + = warp points +*/ +gSecp_clipnames[] init 9999 +giecp_fnclips[] init 9999 +giecp_clipindexmax = 0 + +giecp_controlitemnum = 19 + + +#ifndef ECP_NORECORDING +#ifndef ECP_RECORDBUFFERTIME +#define ECP_RECORDBUFFERTIME #10# +#end +giecp_recordbufferduration = $ECP_RECORDBUFFERTIME +giecp_recordbufferL ftgen 0, 0, -(giecp_recordbufferduration*sr), -7, 0 +giecp_recordbufferR ftgen 0, 0, -(giecp_recordbufferduration*sr), -7, 0 +#end + + +opcode ecp_set_warppoint, 0, iii + iclipindex, ipointindex, ivalue xin + ifndata = giecp_fnclips[iclipindex] + imax = tab_i(4, ifndata) * tab_i(2, ifndata) + if (ipointindex < imax) then + ipointindex += giecp_controlitemnum + tablew ivalue, ipointindex, ifndata + endif +endop + + + +opcode ecp_get_audiofn, ii, i + iclipindex xin + ifndata = giecp_fnclips[iclipindex] + ifnL tab_i 0, ifndata + ifnR tab_i 1, ifndata + xout ifnL, ifnR +endop + +opcode ecp_get_name, S, i + iclipindex xin + xout gSecp_clipnames[iclipindex] +endop + +opcode ecp_set_name, 0, iS + iclipindex, Sname xin + gSecp_clipnames[iclipindex] = Sname +endop + +opcode ecp_get_duration, i, i + iclipindex xin + xout tab_i(3, giecp_fnclips[iclipindex]) +endop + +opcode ecp_get_divisions, i, i + iclipindex xin + xout tab_i(2, giecp_fnclips[iclipindex]) +endop + +opcode ecp_set_pitch, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 7, giecp_fnclips[iclipindex] +endop + +opcode ecp_get_pitch, i, i + iclipindex xin + xout tab_i(7, giecp_fnclips[iclipindex]) +endop + +opcode ecp_get_warpmode, i, i + iclipindex xin + xout tab_i(6, giecp_fnclips[iclipindex]) +endop + +opcode ecp_set_warpmode, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 6, giecp_fnclips[iclipindex] +endop + +opcode ecp_set_texturesize, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 10, giecp_fnclips[iclipindex] +endop + +opcode ecp_get_texturesize, i, i + iclipindex xin + xout tab_i(10, giecp_fnclips[iclipindex]) +endop + +opcode ecp_set_texturerandom, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 11, giecp_fnclips[iclipindex] +endop + +opcode ecp_get_texturerandom, i, i + iclipindex xin + xout tab_i(11, giecp_fnclips[iclipindex]) +endop + +opcode ecp_set_textureoverlap, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 12, giecp_fnclips[iclipindex] +endop + +opcode ecp_get_textureoverlap, i, i + iclipindex xin + xout tab_i(12, giecp_fnclips[iclipindex]) +endop + +opcode ecp_set_loop, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 13, giecp_fnclips[iclipindex] +endop + +opcode ecp_get_loop, i, i + iclipindex xin + xout tab_i(13, giecp_fnclips[iclipindex]) +endop + +opcode ecp_set_warp, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 14, giecp_fnclips[iclipindex] +endop + +opcode ecp_get_warp, i, i + iclipindex xin + xout tab_i(14, giecp_fnclips[iclipindex]) +endop + +opcode ecp_set_texturewindow, 0, ii + iclipindex, ivalue xin + tabw_i ivalue, 15, giecp_fnclips[iclipindex] +endop + +opcode ecp_get_texturewindow, i, i + iclipindex xin + xout tab_i(15, giecp_fnclips[iclipindex]) +endop + + +opcode ecp_randomise_warppoints, 0, io + iclipindex, imode xin + ipoints = ecp_get_duration(iclipindex) * ecp_get_divisions(iclipindex) + iduration = ecp_get_duration(iclipindex) + iaveragedivision = iduration / ipoints + ilasttime = (imode == -1) ? iduration : 0 + index = 0 + while (index < ipoints) do + if (imode == 0) then + ecp_set_warppoint iclipindex, index, random(0, iduration) + else + itime = (imode == 1) ? min(random(ilasttime, ilasttime + iaveragedivision), iduration) : max(random(ilasttime - iaveragedivision, ilasttime), 0) + ecp_set_warppoint iclipindex, index, itime + ilasttime = itime + endif + index += 1 + od +endop + + +opcode ecp_replacetables, 0, iiop + iclipindex, ifnL, ifnR, ireplaceall xin + ifndata = giecp_fnclips[iclipindex] + + ifnLoriginal tab_i 0, ifndata + ifnRoriginal tab_i 1, ifndata + if (ifnL == ifnLoriginal) then + goto complete + endif + ioriginallen table 3, ifndata + ilen = ftlen(ifnL) / ftsr(ifnL) + + if (ireplaceall == 1) then + index = 0 + while (index < lenarray(giecp_fnclips)) do + if (index != iclipindex && giecp_fnclips[index] > 0) then + itfnL tab_i 0, giecp_fnclips[index] ; assuming ifnR will do as well + if (itfnL == ifnLoriginal) then + tablew ifnL, 0, giecp_fnclips[index] + tablew ifnR, 1, giecp_fnclips[index] + endif + endif + index += 1 + od + + if (ftexists(ifnLoriginal) == 1) then + ftfree ifnLoriginal, 0 + endif + if (ifnRoriginal != 0 && ftexists(ifnRoriginal) == 1) then + ftfree ifnRoriginal, 0 + endif + else + tablew ifnL, 0, ifndata + tablew ifnR, 1, ifndata + + endif + + + ; needs to be done for all if ireplaceall is set + if (ioriginallen != ilen) then + tablew ilen, 3, ifndata + ibeats table 4, ifndata + idivisionsperbeat table 2, ifndata + itotalpoints = ibeats * idivisionsperbeat + iparttime = ilen / itotalpoints + index = giecp_controlitemnum + itime = 0 + while (index < ftlen(ifndata)) do + tablew itime, index, ifndata + itime += iparttime + index += 1 + od + endif +complete: +endop + + +opcode ecp_removeclip, 0, i + iclipindex xin + ifndata = giecp_fnclips[iclipindex] + ifnL tab_i 0, ifndata + ifnR tab_i 1, ifndata + + iremovefn = 1 + index = 0 + while (index < lenarray(giecp_fnclips)) do + if (index != iclipindex && giecp_fnclips[index] > 0) then + itfnL tab_i 0, giecp_fnclips[index] ; assuming ifnR will do as well + if (itfnL == ifnL) then + iremovefn = 0 + goto complete + endif + endif + index += 1 + od +complete: + if (iremovefn == 1) then + ftfree ifnL, 0 + if (ifnR > 0) then + ftfree ifnR, 0 + endif + endif + ftfree ifndata, 0 + giecp_fnclips[iclipindex] = 0 +endop + +opcode ecp_importclip, i, i + ifndata xin + iclipindex = giecp_clipindexmax + giecp_clipindexmax += 1 + gSecp_clipnames[iclipindex] = "Imported" ; defunct really: TODO remove + giecp_fnclips[iclipindex] = ifndata + xout iclipindex +endop + +/* + Add clip to runtime engine + + iclipindex ecp_addclip Sname, ifnL, ifnR, ibeats [, idivisionsperbeat = 4] + + iclipindex index of the clip for recall + Sname name of the clip, can be passed blank to default + ifnL left ftable of audio + ifnR right ftable of audio, can be passed as 0 if mono + ibeats the length of the clip in beats + idivisionsperbeat resolution of markers per beat, default is 4 + +*/ +opcode ecp_addclip, i, Siiij + Sname, ifnL, ifnR, ibeats, idivisionsperbeat xin + idivisionsperbeat = (idivisionsperbeat == -1) ? 4 : idivisionsperbeat + iclipindex = giecp_clipindexmax + giecp_clipindexmax += 1 + + if (strcmp(Sname, "") == 0) then + Sname = sprintf("clip %d", iclipindex + 1) + endif + ilen = ftlen(ifnL) / ftsr(ifnL) + itotalpoints = ibeats * idivisionsperbeat + iparttime = ilen / itotalpoints + ifndata ftgen 0, 0, -(itotalpoints+giecp_controlitemnum), -2, 0 + + tablew ifnL, 0, ifndata + tablew ifnR, 1, ifndata + tablew idivisionsperbeat, 2, ifndata + tablew ilen, 3, ifndata ; seconds length + tablew ibeats, 4, ifndata ; beats length + tablew ilen, 5, ifndata ; seconds utilised length + tablew 0, 6, ifndata ; warp mode + tablew 0, 7, ifndata ; pitch + tablew 1, 8, ifndata ; amp + tablew 512, 9, ifndata ; fft size + tablew 4410, 10, ifndata ; texture window size + tablew 441, 11, ifndata ; texture random + tablew 2, 12, ifndata ; texture overlap + tablew 0, 13, ifndata ; whether to loop or one shot + tablew 0, 14, ifndata ; whether to warp or straight playback + tablew 0, 15, ifndata ; texture mode window shape, corresponds to hanning, hamming, half sine + tablew 0, 16, ifndata ; utilised start + tablew 1, 17, ifndata ; phase lock + tablew ftsr(ifnL), 18, ifndata ; samplerate + + index = giecp_controlitemnum + itime = 0 + while (index < ftlen(ifndata)) do + tablew itime, index, ifndata + itime += iparttime + index += 1 + od + + gSecp_clipnames[iclipindex] = Sname + giecp_fnclips[iclipindex] = ifndata + xout iclipindex +endop + + +opcode ecp_loadsound, i, Sio + Spath, ibeats, iforcemono xin + ichnls = (iforcemono == 1) ? 1 : filenchnls(Spath) + ifnL ftgen 0, 0, 0, 1, Spath, 0, 0, 1 + if (ichnls == 2) then + ifnR ftgen 0, 0, 0, 1, Spath, 0, 0, 2 + else + ifnR = 0 + endif + iclipindex = ecp_addclip(Spath, ifnL, ifnR, ibeats, 4) + xout iclipindex +endop + + +opcode ecp_setaudiounique, 0, i + iclipindex xin + ifndata = giecp_fnclips[iclipindex] + ifnL tab_i 0, ifndata + ifnR tab_i 1, ifndata + + irequired = 0 + index = 0 + while (index < lenarray(giecp_fnclips)) do + if (index != iclipindex && giecp_fnclips[index] > 0) then + itfnL tab_i 0, giecp_fnclips[index] ; assuming ifnR will do as well + if (itfnL == ifnL) then + irequired = 1 + goto complete + endif + endif + index += 1 + od +complete: + if (irequired == 1) then + isize = ftlen(ifnL) + ifnLnew ftgen 0, 0, -isize, -2, 0 + tableicopy ifnLnew, ifnL + tabw_i ifnLnew, 0, ifndata + if (ifnR > 0) then + ifnRnew ftgen 0, 0, -isize, -2, 0 + tableicopy ifnRnew, ifnR + tabw_i ifnRnew, 1, ifndata + endif + endif +endop + + +opcode ecp_cloneclip, i, i + iclipindexfrom xin + iclipindexto = giecp_clipindexmax + giecp_clipindexmax += 1 + + ifndatafrom = giecp_fnclips[iclipindexfrom] + ifndatato ftgen 0, 0, -ftlen(ifndatafrom), -2, 0 + + gSecp_clipnames[iclipindexto] = gSecp_clipnames[iclipindexfrom] + tableicopy ifndatato, ifndatafrom + + giecp_fnclips[iclipindexto] = ifndatato + xout iclipindexto +endop + + +instr ecp_stop + icbid = p4 + turnoff2 nstrnum("ecp_playback") + (icbid / 1000000), 4, 1 + turnoff +endin + + +opcode ecp_getwaveform, i, io + iclipindex, isamples xin + ifnL table 0, giecp_fnclips[iclipindex] + ifndata tab_overview ifnL, isamples + xout ifndata +endop + + +instr ecp_stopaudition + icbid = p4 + turnoff2 nstrnum("ecp_playaudition") + (icbid / 1000000), 4, 1 + turnoff +endin + +instr ecp_playaudition_complete + icbid = p4 + io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":0}", icbid)) + turnoff +endin + +instr ecp_playaudition + icbid = p4 + iclipindex = p5 + iduration = p6 + ichannel = p7 + p1 += (icbid / 1000000) + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": 1}", icbid)) + aL, aR subinstr "ecp_playback_tst", icbid, iclipindex, iduration + outs aL, aR ; channels?? + + kreleasing init 0 + if (lastcycle:k() == 1 || (kreleasing == 0 && release:k() == 1)) then + kreleasing = 1 + schedulek("ecp_playaudition_complete", 0, 1, icbid) + endif +endin + + +instr ecp_playback_tst + icbid = p4 + iclipindex = p5 + iduration = p6 + istartoffset = p7 + + ifndata = giecp_fnclips[iclipindex] + ifnL tab_i 0, ifndata + ifnR tab_i 1, ifndata + idivisions tab_i 2, ifndata + itotalduration tab_i 3, ifndata + + iutilisedlength tab_i 5, ifndata + iwarpmode tab_i 6, ifndata ; 0 = repitch, 1 = texture, 2 = mincer, 3 = pvstab + kpitch = pow:k(2, (tab:k(7, ifndata) / 12)) + kamp tab 8, ifndata + ifftsize tab_i 9, ifndata + iwindowsize tab_i 10, ifndata + irandwin tab_i 11, ifndata + ioverlap tab_i 12, ifndata + iloop tab_i 13, ifndata + iwarp tab_i 14, ifndata + iwindowtype tab_i 15, ifndata + istart = tab_i(16, ifndata) + istartoffset + kphaselock tab 17, ifndata + isr tab_i 18, ifndata + + if (iwindowtype == 0) then + iwindow = gifnHanning + elseif (iwindowtype == 1) then + iwindow = gifnHamming + else + iwindow = gifnHalfSine + endif + + if (iwarp == 1) then + atime linseg istart, iduration, iutilisedlength + else + atime = (phasor(1 / itotalduration) * (iutilisedlength - istart)) + istart + endif + + + ; repitch + if (iwarp == 0) then + aptime = atime * ftsr(ifnL) * kpitch + aoutL table3 aptime, ifnL, 0 + if (ifnR != 0) then + aoutR table3 aptime, ifnR, 0 + else + aoutR = aoutL + endif + + ; texture + elseif (iwarpmode == 1) then + isradjust = isr / sr + aoutL sndwarp 1, atime, a(kpitch) * isradjust, ifnL, 0, iwindowsize, irandwin, ioverlap, iwindow, 1 + if (ifnR != 0) then + aoutR sndwarp 1, atime, a(kpitch) * isradjust, ifnR, 0, iwindowsize, irandwin, ioverlap, iwindow, 1 + else + aoutR = aoutL + endif + + ; mincer + elseif (iwarpmode == 2) then + aoutL mincer atime, 1, kpitch, ifnL, kphaselock, ifftsize, 4 + if (ifnR != 0) then + aoutR mincer atime, 1, kpitch, ifnR, kphaselock, ifftsize, 4 + else + aoutR = aoutL + endif + + ; pvs + elseif (iwarpmode == 3) then ; contentious benefit + ipvsbufL pvs_ifn2buffer ifnL, ifftsize, ifftsize / 4, ifftsize, 2 + ffoutL pvsbufread k(atime), ipvsbufL + if (kpitch != 1) then + fscaleL pvscale ffoutL, kpitch + aoutL pvsynth fscaleL + else + aoutL pvsynth ffoutL + endif + + if (ifnR != 0) then + ipvsbufR pvs_ifn2buffer ifnR, ifftsize, ifftsize / 4, ifftsize, 2 + ffoutR pvsbufread k(atime), ipvsbufR + if (kpitch != 1) then + fscaleR pvscale ffoutR, kpitch + aoutR pvsynth fscaleR + else + aoutR pvsynth ffoutR + endif + else + ifnR = ifnL + endif + endif + + aamp linseg 1, iduration * 0.9999, 1, iduration * 0.0001, 0 + + aoutL *= aamp * kamp + aoutR *= aamp * kamp + outs aoutL, aoutR +endin + + +instr ecp_playback + icbid = p4 + iclipindex = p5 + + if (icbid > 0) then + p1 += icbid / 1000000 + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": 3}", icbid)) + endif + + Sbus = strget(p6) + if (strcmp(Sbus, "") == 0) then + Sbus = "main" + endif + + kmincelock init 1 + + ifndata = giecp_fnclips[iclipindex] + ifnL table 0, ifndata + ifnR table 1, ifndata + idivisions table 2, ifndata + itotalduration table 3, ifndata + + iwarpmode table 6, ifndata ; 0 = repitch, 1 = texture, 2 = pvs, 3 = mincer + kpitch = pow:k(2, (tab:k(7, ifndata) / 12)) + kamp table 8, ifndata + ifftsize table 9, ifndata + iwindowsize table 10, ifndata + irandwin table 11, ifndata + ioverlap table 12, ifndata + iloop table 13, ifndata + iwarp table 14, ifndata + iwindowtype table 15, ifndata + + if (iwindowtype == 0) then + iwindow = gifnHanning + elseif (iwindowtype == 1) then + iwindow = gifnHamming + else + iwindow = gifnHalfSine + endif + + + if (iloop == 0 && iwarp == 0) then + p3 = itotalduration + if (iwarpmode == 0) then + p3 /= tab_i(7, ifndata) + endif + endif + + if (iwarp == 1) then + if (iloop == 1) then + iduration = itotalduration + else + iduration = p3 + endif + + kdiv = gkseq_beattime / idivisions + as, aps syncphasor -(gkseq_beathz*idivisions), a(gkseq_beat) + ;ithresh = ((1 / sr) * ksmps) * 16 + kt trigger k(as), 0.1, 0 ; was 0.005.. works? + + ktime init 0 + kpoint init giecp_controlitemnum + kcps init 1 / iduration + kpointend init table:i(giecp_controlitemnum+1, ifndata) + + if (kt == 1) then + ktime = table:k(kpoint, ifndata) + + if (kpoint + 1 < ftlen(ifndata)) then + kpointend = table:k(kpoint + 1, ifndata) + kpoint += 1 + else + kpointend = iduration + kpoint = giecp_controlitemnum + endif + kcps = (kpointend - ktime) / kdiv + endif + atime, a_ syncphasor kcps, a(kt) ;a(gkseq_beat) ;aps + + else + ktime init 0 + atime phasor 1 / itotalduration + atime *= itotalduration + endif + + ; repitch + if (iwarpmode == 0) then + if (iwarp == 1) then + aptime = (atime + ktime) * ftsr(ifnL) + else + aptime = atime * ftsr(ifnL) * kpitch ; * 0.5 ; why is 0.5 required here? + endif + + aoutL table3 aptime, ifnL, 0 + if (ifnR != 0) then + aoutR tablekt aptime, ifnR, 0 + else + aoutR = aoutL + endif + + ; texture + elseif (iwarpmode == 1) then + isradjust = ftsr(ifnL) / sr + aoutL sndwarp 1, (atime + ktime), a(kpitch) * isradjust, ifnL, 0, iwindowsize, irandwin, ioverlap, iwindow, 1 + if (ifnR != 0) then + aoutR sndwarp 1, (atime + ktime), a(kpitch) * isradjust, ifnR, 0, iwindowsize, irandwin, ioverlap, iwindow, 1 + else + aoutR = aoutL + endif + + ; mincer + elseif (iwarpmode == 2) then + aoutL mincer atime + ktime, 1, kpitch, ifnL, kmincelock, ifftsize, 4 + if (ifnR != 0) then + aoutR mincer atime + ktime, 1, kpitch, ifnR, kmincelock, ifftsize, 4 + else + aoutR = aoutL + endif + + ; pvs + elseif (iwarpmode == 3) then ; contentious benefit + ipvsbufL pvs_ifn2buffer ifnL, ifftsize, ifftsize/4, ifftsize, 2 + ffoutL pvsbufread k(atime) + ktime, ipvsbufL + if (kpitch != 1) then + fscaleL pvscale ffoutL, kpitch + aoutL pvsynth fscaleL + else + aoutL pvsynth ffoutL + endif + + if (ifnR != 0) then + ipvsbufR pvs_ifn2buffer ifnR, ifftsize, ifftsize/4, ifftsize, 2 + ffoutR pvsbufread k(atime) + ktime, ipvsbufR + if (kpitch != 1) then + fscaleR pvscale ffoutR, kpitch + aoutR pvsynth fscaleR + else + aoutR pvsynth ffoutR + endif + else + ifnR = ifnL + endif + endif + + aamp linseg 1, p3 * 0.9999, 1, p3 * 0.0001, 0 + + aoutL *= aamp * kamp + aoutR *= aamp * kamp + bus_mix(Sbus, aoutL, aoutR) + if (icbid > 0) then + kreleasing init 0 + if (release:k() == 1 && kreleasing == 0) then + kreleasing = 1 + schedulek("ecp_stopped", 0, 1, icbid) + endif + endif +endin + + +instr ecp_stopped + icbid = p4 + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": 0}", icbid)) + turnoff +endin + + +#ifndef ECP_NORECORDING +instr ecp_record_stop + icbid = p4 + turnoff2 nstrnum("ecp_record") + (icbid / 1000000), 4, 1 + turnoff +endin + + +instr ecp_record + icbid = p4 + istereo = p5 + + p1 += icbid / 1000000 + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": 1}", icbid)) + + kbeats init 0 + if (gkseq_beat == 1) then + kbeats += 1 + endif + + awritepos lphasor 1 + ainL inch 1 + tabw ainL, awritepos, giecp_recordbufferL + if (istereo == 1) then + ainR inch 2 + tabw ainR, awritepos, giecp_recordbufferR + endif + + kreleasing init 0 + if (release:k() == 1 && kreleasing == 0) then + kreleasing = 1 + schedulek "ecp_record_post", 0, 10, icbid, istereo, kbeats + endif +endin + + +instr ecp_record_scheduled_cancel + icbid = p4 + turnoff2 nstrnum("ecp_record_scheduled") + (icbid / 1000000), 4, 1 + turnoff +endin + +instr ecp_record_scheduled + icbid = p4 + istereo = p5 + imode = p6 ; -1 = now, 0 = beat, 1 = bar , 2 = bargroup + + p1 += icbid / 1000000 + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": 4}", icbid)) + + if ((imode == -1) || (imode == 0 && gkseq_beat == 1) || (imode == 1 && gkseq_bar_trig == 1) || (imode == 2 && gkseq_bargroup_trig == 1)) then + schedulek("ecp_record", 0, giecp_recordbufferduration, icbid, istereo) + turnoff + endif +endin + + +instr ecp_record_post + icbid = p4 + istereo = p5 + ibeats = p6 + + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": 2}", icbid)) + + istart = 0 ;ksmps * 8 + iduration = i(gkseq_beattime) * ibeats + ilen = iduration * sr + ifnL ftgen 0, 0, -ilen, -7, 0 + ftslicei giecp_recordbufferL, ifnL, istart, ilen + if (istereo == 1) then + ifnR ftgen 0, 0, -ilen, -7, 0 + ftslicei giecp_recordbufferR, ifnR, istart, ilen + endif + iclipindex = ecp_addclip("", ifnL, ifnR, ibeats, 4) + + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": 0}", icbid)) + + ; testing + if (gkseq_bar_trig == 1) then + schedulek "ecp_playback", 0, 100, iclipindex, "mxchan0" + turnoff + endif + + ; turnoff +endin +#end + +#end diff --git a/site/udo/scss/elasticlip_arranger.udo b/site/udo/scss/elasticlip_arranger.udo new file mode 100755 index 0000000..2b0c09f --- /dev/null +++ b/site/udo/scss/elasticlip_arranger.udo @@ -0,0 +1,213 @@ +#ifndef UDO_ELASTICLIPARRANGER +#define UDO_ELASTICLIPARRANGER ## + +#include "scss/elasticlip.udo" +#include "scss/mixer/base.udo" +#include "array_3d.udo" +#include "sequencing_scheduled.udo" +#include "interop.udo" + +giecpa_fn = -1 +giecpa_scenenumber = -1 + +/* + Follow actions + 0 repeat + 1 next + 2 previous + 3 random in channel + 4 stop +*/ + +instr ecpa_boot + ichannelnumber = (p4 == 0) ? 12 : p4 + isendchannels = (p5 == 0) ? 2 : p5 + imixermaxinserts = (p6 == 0) ? 6 : p6 + giecpa_scenenumber = (p7 == 0) ? 16 : p7 + schedule("mx_boot", 0, 1, ichannelnumber, isendchannels, imixermaxinserts) + + giecpa_fn arr3d_init ichannelnumber, giecpa_scenenumber, 5 + turnoff +endin + + +instr ecpa_addclip + icbid = p4 + iclipid = p5 + ichannel = p6 + iscene = p7 + + index arr3d_index giecpa_fn, ichannel, iscene, 0 + tabw_i iclipid, index, giecpa_fn + + ; set default follow actions + tabw_i 0, index + 1, giecpa_fn + tabw_i 0.5, index + 2, giecpa_fn + tabw_i 0, index + 3, giecpa_fn + tabw_i 16, index + 4, giecpa_fn + io_sendstring("callback", sprintf("{\"cbid\": %d}", icbid)) + turnoff +endin + + +instr ecpa_moveclip + icbid = p4 + ichannelfrom = p5 + iscenefrom = p6 + ichannelto = p7 + isceneto = p8 + + index_from arr3d_index giecpa_fn, ichannelfrom, iscenefrom, 0 + index_to arr3d_index giecpa_fn, ichannelto, isceneto, 0 + + index = 0 + while (index < 5) do + tabw_i tab_i(index_from + index, giecpa_fn), index_to + index, giecpa_fn + index += 1 + od + io_sendstring("callback", sprintf("{\"cbid\": %d}", icbid)) + turnoff +endin + + +instr ecpa_rmclip + icbid = p4 + ichannel = p5 + iscene = p6 + iclipindex arr3d_index giecpa_fn, ichannel, iscene, 0 + + ; if playing, stop + + index = 0 + while (index < 5) do + tabw_i 0, iclipindex + index, giecpa_fn + index += 1 + od + io_sendstring("callback", sprintf("{\"cbid\": %d}", icbid)) + turnoff +endin + + +opcode _ecpa_getnext, i, iii + ichannel, iscene, ifollowaction xin + inextscene = iscene + + if (ifollowaction == 0) then ; repeat + goto output + + elseif (ifollowaction == 1 || ifollowaction == 2) then ; next or previous + + isceneindex = (ifollowaction == 1) ? iscene + 1 : iscene - 1 + while ((ifollowaction == 1 && isceneindex < giecpa_scenenumber) || (ifollowaction == 2 && isceneindex >= 0)) do + item arr3d_get ichannel, isceneindex, 0 + if (item != 0) then + inextscene = isceneindex + goto output + endif + isceneindex += (ifollowaction == 1) ? 1 : -1 + od + + isceneindex = (ifollowaction == 1) ? 0 : giecpa_scenenumber - 1 + while ((ifollowaction == 1 && isceneindex <= iscene) || (ifollowaction == 2 && isceneindex >= iscene)) do + item arr3d_get ichannel, isceneindex, 0 + if (item != 0) then + inextscene = isceneindex + goto output + endif + isceneindex += (ifollowaction == 1) ? 1 : -1 + od + + elseif (ifollowaction == 3) then ; random + isceneindex = 0 + insertindex = 0 + iactivescenesnum = 0 + iactivescenes[] init giecpa_scenenumber + while (isceneindex < giecpa_scenenumber) do + item arr3d_get ichannel, isceneindex, 0 + if (item != 0) then + iactivescenesnum += 1 + iactivescenes[insertindex] = isceneindex + insertindex += 1 + endif + isceneindex += 1 + od + inextscene = iactivescenes[round(random(0, iactivescenesnum - 1))] + goto output + endif + +output: + xout inextscene +endop + + +instr ecpa_playslot + icbid = p4 + ichannel = p5 + iscene = p6 + p1 += (ichannel + iscene) / 1000000 + p3 = 36000 + + io_sendstring("callback", sprintf("{\"cbid\": %d, \"status\": -1}", icbid)) + iclipindex arr3d_index giecpa_fn, ichannel, iscene, 0 + + ifollowaction1 = tab_i(iclipindex + 1, giecpa_fn) + ifollowchance = tab_i(iclipindex + 2, giecpa_fn) + ifollowaction2 = tab_i(iclipindex + 3, giecpa_fn) + ilength = tab_i(iclipindex + 4, giecpa_fn) + + ifollowaction = (random(0, 1) <= ifollowchance) ? ifollowaction1 : ifollowaction2 + + iplaybackinstr = nstrnum("ecp_playback") + (icbid / 1000000) + + klaunched init 0 + if (gkseq_bar_trig == 1 && klaunched == 0) then + klaunched = 1 + schedulek(iplaybackinstr, 0, -1, icbid, tab_i(iclipindex, giecpa_fn)) + + ; turnoff other scene instances + isceneindex = 0 + while (isceneindex < giecpa_scenenumber) do + if (isceneindex != iscene) then + turnoff2 (ichannel + iscene) / 1000000, 4, 1 + endif + isceneindex += 1 + od + endif + + if (klaunched == 1) then + ktrig nextbeatxof ilength + if (ktrig == 1) then + if (ifollowaction != 4) then ; if not stop + inextscene _ecpa_getnext ichannel, iscene, ifollowaction + schedulek("ecpa_playslot", 0, 1, icbid, ichannel, inextscene) + endif + turnoff + endif + endif + + kreleasing init 0 + if (release:k() == 1 && kreleasing == 0) then + kreleasing = 1 + turnoff2 iplaybackinstr, 4, 1 + endif +endin + + +instr ecpa_playscene + icbid = p4 + iscene = p5 + + klaunched init 0 + if (gkseq_bar_trig == 1 && klaunched == 0) then + klaunched = 1 + ibaseinstr = nstrnum("ecpa_playslot") + ichannel = 0 + while (ichannel < gichannelnumber) do + turnoff2 (ichannel + iscene) / 1000000, 4, 1 + ichannel += 1 + od + endif + +endin + +#end diff --git a/site/udo/scss/elasticlip_sequencer.udo b/site/udo/scss/elasticlip_sequencer.udo new file mode 100755 index 0000000..22144aa --- /dev/null +++ b/site/udo/scss/elasticlip_sequencer.udo @@ -0,0 +1,133 @@ +#ifndef UDO_ELASTICLIPSEQUENCER +#define UDO_ELASTICLIPSEQUENCER ## + +;#include "scss/mixer/base.udo" +#include "scss/elasticlip.udo" +#include "interop.udo" + + +giecpseq_maxclipindex = 0 +giecpseq_fn ftgen 0, 0, 5000, 7, 0 + +opcode ecpseq_getnewindex, i, 0 + index = giecpseq_maxclipindex + giecpseq_maxclipindex += 1 + xout index +endop + + +opcode ecpseq_pack, i, iii + ichannel, iclipindex, itime xin + xout (ichannel * 100000) + iclipindex + (itime / 100) +endop + + +opcode ecpseq_unpack, iii, i + iencoded xin + ichannel = int(iencoded / 100000) + iencoded = iencoded - ichannel * 100000 + iclipindex = int(iencoded) + itime = frac(iencoded) * 100 + xout ichannel, iclipindex, itime +endop + + +opcode ecpseq_get, iii, i + iseqindex xin + ichannel, iclipindex, itime ecpseq_unpack tab_i(iseqindex, giecpseq_fn) + xout ichannel, iclipindex, itime +endop + +opcode ecpseq_set, 0, iiii + iseqindex, ichannel, iclipindex, itime xin + ipacked ecpseq_pack ichannel, iclipindex, itime + tabw_i ipacked, iseqindex, giecpseq_fn +endop + + +instr ecpseq_alterclip + iseqindex = p4 + ichannel = p5 + iclipindex = p6 + itime = p7 + + ecpseq_set iseqindex, ichannel, iclipindex, itime +endin + +instr ecpseq_rmclip + iseqindex = p4 + tabw_i 0, iseqindex, giecpseq_fn +endin + +instr _ecpseq_notify + icbid = p4 + istate = p5 + if (istate == 1) then + Sstate = "playing" + elseif (istate == 0) then + Sstate = "stopped" + endif + io_sendstring("callback", sprintf("{\"cbid\":%d,\"state\":\"%s\"}", icbid, Sstate)) + turnoff +endin + +giecpseq_playbackcbid = -1 + +instr osctest + a1 oscil 1, 440 + a1 *= linseg(1, p3, 0) + outs a1, a1 +endin + + + +instr ecpseq_playclip + icbid = p4 + iclipid = p5 + ival tab_i iclipid, giecpseq_fn + ichannel, iclipindex, itime ecpseq_unpack ival + schedule("ecp_playback", 0, 1, icbid, iclipindex) ;, sprintf("mxchan%d", ichannel)) + turnoff +endin + + +instr ecpseq_play + icbid = p4 + istartbeat = p5 + ibeattime = i(gkseq_beattime) + imaxbeat = istartbeat + index = 0 + while (index < ftlen(giecpseq_fn)) do + ival = tab_i(index, giecpseq_fn) + if (ival > 0) then + ichannel, iclipindex, itime ecpseq_unpack ival + if (itime >= istartbeat) then + irunbeat = itime - istartbeat + if (irunbeat > imaxbeat) then + imaxbeat = irunbeat + endif + schedule("ecp_playback", irunbeat * ibeattime, 1, -1, iclipindex) ;, sprintf("mxchan%d", ichannel)) + endif + endif + index += 1 + od + schedule("_ecpseq_notify", 0, 1, icbid, 1) + giecpseq_playbackcbid = icbid + + imaxtime = imaxbeat * ibeattime + ktimek timeinsts + if (ktimek > imaxtime) then + schedulek("_ecpseq_notify", 0, 1, icbid, 0) + turnoff + endif +endin + +instr ecpseq_stop + icbid = giecpseq_playbackcbid + turnoff2 "ecp_playback", 0, 1 + turnoff3 "ecp_playback" + schedule("_ecpseq_notify", 0, 1, icbid, 0) + turnoff +endin + +#end 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 diff --git a/site/udo/scss/persistence.udo b/site/udo/scss/persistence.udo new file mode 100755 index 0000000..075b06a --- /dev/null +++ b/site/udo/scss/persistence.udo @@ -0,0 +1,366 @@ +#ifndef UDO_SCSS_PERSISTENCE +#define UDO_SCSS_PERSISTENCE ## +/* + SONICS Category Sequencer System + Persistence: saving and loading state to database and FS + + Designed for use with an API host to/from which callbacks and JSON states can be exchanged + + Requires JSON opcodes + https://git.1bpm.net/csound-json + + This file is part of the SONICS UDO collection by Richard Knight 2022 + License: GPL-2.0-or-later + http://1bpm.net +*/ +#include "sequencing_melodic_persistence.udo" +#include "table_tools.udo" +#include "pgdb.udo" + +opcode scss_json2tab, 0, ii + iJson, ifn xin + if (jsontype(iJson) == 4) then + iarray[] jsonarrval iJson + copya2ftab iarray, ifn + endif +endop + +opcode scss_json2tab, 0, iiS + iJsonTop, ifn, Skey xin + iJson = jsonget(iJsonTop, Skey) + scss_json2tab(iJson, ifn) + jsondestroy(iJson) +endop + +opcode scss_tabarr2json, 0, iSi[][] + iJson, Srelname, ifns[][] xin + iJarrTop = jsonloads("[]") + index = 0 + while (index < lenarray(ifns, 1)) do + iJarrSub = jsonloads("[]") + index2 = 0 + while (index2 < lenarray(ifns, 2)) do + iJtable = jsonloads(tab_serialise(ifns[index][index2])) + jsonptradd iJarrSub, sprintf("/%d", jsonsize(iJarrSub)), iJtable + index2 += 1 + od + index += 1 + jsonptradd iJarrTop, sprintf("/%d", jsonsize(iJarrTop)), iJarrSub + jsondestroy(iJarrSub) + od + jsoninsert iJson, Srelname, iJarrTop + jsondestroy(iJarrTop) +endop + + +opcode scss_json2tabarr, i[][], iSi[][] + iJsonTop, Srelname, ifns[][] xin + iJson = jsonget(iJsonTop, Srelname) + isize = jsonsize(iJson) + index = 0 + while (index < isize) do + iJsonSub = jsonget(iJson, index) + isize2 = jsonsize(iJsonSub) + index2 = 0 + while (index2 < isize2) do + iJtable = jsonget(iJsonSub, index2) + + iarray[] jsonarrval iJsonSub + copya2ftab iarray, ifns[index][index2] + ;tab_unserialise(jsondumps(iJtable, 0), ifns[index][index2]) + jsondestroy(iJtable) + index2 += 1 + od + jsondestroy(iJsonSub) + index += 1 + od + jsondestroy(iJson) + xout ifns +endop + + +/* + Set parameters based on a JSON object with key as channel name and value as channel value to be set + + scss_setparamstate iJsonInput + + iJsonInput JSON object of channel values +*/ +opcode scss_setparamstate, 0, i + iJsonInput xin + Skeys[] jsonkeys iJsonInput + index = 0 + while (index < lenarray(Skeys)) do + Sparam = Skeys[index] + chnset jsongetval:i(iJsonInput, Sparam), Sparam + index += 1 + od +endop + + + +/* + Get parameters from a SCSS definition JSON object and append current values to iJsonOutput using channel prefix Sprefix + + _scss_getparams iJsonInput, iJsonOutput, Sprefix + + iJsonInput object to evaluate; should have an array with key "parameters" featuring objects which have a key "name" specifying parameter name + iJsonOutput output object to add values to (key is channel name, value is current value) + Sprefix parameter prefix to use for channel name +*/ +opcode _scss_getparams, 0, iiS + iJsonInput, iJsonOutput, Sprefix xin + SparameterPointer = "/parameters" + if (jsonptrhas(iJsonInput, SparameterPointer) == 1) then + iJsonParameters = jsonptr(iJsonInput, SparameterPointer) + iparamsize = jsonsize(iJsonParameters) + indexparam = 0 + while (indexparam < iparamsize) do + SparamName jsonptrval iJsonParameters, sprintf("/%d/name", indexparam) + Sparam = sprintf("%s_%s", Sprefix, SparamName) + jsoninsertval iJsonOutput, Sparam, chnget:i(Sparam) + indexparam += 1 + od + jsondestroy(iJsonParameters) + endif +endop + + +/* + Get all registered parameters in giscss_stateJson, and get current channel values, + returning an object with key as channel name and value as channel value + + iJsonOutput scss_getparamstate + + iJsonOutput JSON object of channel values +*/ +opcode scss_getparamstate, i, 0 + iJsonOutput = jsoninit() + + + ; global parameters + _scss_getparams(giscss_stateJson, iJsonOutput, "scss") + + ; categories + iJsonCategories = jsonptr(giscss_stateJson, "/categories") + icatnum = jsonsize(iJsonCategories) + jsondestroy(iJsonCategories) + index = 0 + while (index < icatnum) do + iJsonCategory = jsonptr(giscss_stateJson, sprintf("/categories/%d", index)) + Scategory jsonptrval iJsonCategory, "/name" + + ; category parameters + _scss_getparams(iJsonCategory, iJsonOutput, Scategory) + + ; items/instruments + iJsonItems = jsonptr(iJsonCategory, "/items") + iinstrnum = jsonsize(iJsonItems) + indexItem = 0 + while (indexItem < iinstrnum) do + iJsonItem = jsonptr(iJsonItems, sprintf("/%d", indexItem)) + Sinstrument = jsongetval(iJsonItem, "name") + _scss_getparams(iJsonItem, iJsonOutput, Sinstrument) + jsondestroy(iJsonItem) + indexItem += 1 + od + jsondestroy(iJsonItems) + jsondestroy(iJsonCategory) + index += 1 + od + xout iJsonOutput +endop + + + +/* + Get sequencing state as JSON object + + iJsonSeq scss_getseqstate + + iJsonSeq object of sequencing data +*/ +opcode scss_getseqstate, i, 0 + iJsonSeq = jsoninit() + scss_tabarr2json(iJsonSeq, "giscss_stfn_trig", giscss_stfn_trig) + scss_tabarr2json(iJsonSeq, "giscss_stfn_dur", giscss_stfn_dur) + scss_tabarr2json(iJsonSeq, "giscss_stfn_params", giscss_stfn_params) + + Skeys[] fillarray "giscss_st_size", "giscss_st_slots", "giscss_st_paramnumber" ; giscss_stfn_temp", "giscss_stfn_param_temp" + ivalues[] fillarray giscss_st_size, giscss_st_slots, giscss_st_paramnumber ; giscss_stfn_temp giscss_stfn_param_temp + jsoninsertval(iJsonSeq, Skeys, ivalues) + xout iJsonSeq +endop + + + +/* + Set sequencing state from JSON object + + scss_setseqstate iJsonSeq + + iJsonSeq object of sequencing data +*/ +opcode scss_setseqstate, 0, i + iJsonSeq xin + + giscss_stfn_trig scss_json2tabarr iJsonSeq, "giscss_stfn_trig", giscss_stfn_trig + giscss_stfn_dur scss_json2tabarr iJsonSeq, "giscss_stfn_dur", giscss_stfn_dur + giscss_stfn_params scss_json2tabarr iJsonSeq, "giscss_stfn_params", giscss_stfn_params + + giscss_st_size = jsongetval:i(iJsonSeq, "giscss_st_size") + giscss_st_slots = jsongetval:i(iJsonSeq, "giscss_st_slots") + giscss_st_paramnumber = jsongetval:i(iJsonSeq, "giscss_st_paramnumber") +endop + + + +opcode scss_getstate, i, ppppp + igetsequencing, igetparameters, igetmelstate, igetinstrstate, igetcatenabled xin + + iJson = jsoninit() + + if (igetsequencing == 1) then + iJsonSequencing = scss_getseqstate() + jsoninsert(iJson, "sequencing", iJsonSequencing) + jsondestroy(iJsonSequencing) + endif + + if (igetparameters == 1) then + iJsonParameters = scss_getparamstate() + jsoninsert(iJson, "parameters", iJsonParameters) + jsondestroy(iJsonParameters) + endif + + if (igetmelstate == 1) then + iJsonMelstate = mel_getstate_json() + jsoninsert(iJson, "melstate", iJsonMelstate) + jsondestroy(iJsonMelstate) + endif + + + if (igetinstrstate == 1) then + iJinstrState = jsonloads(tab_serialise(giscss_instrState)) + jsoninsert(iJson, "giscss_instrState", iJinstrState) + jsondestroy(iJinstrState) + endif + + if (igetcatenabled == 1) then + iJcatEnabled = jsonloads(tab_serialise(giscss_catEnabled)) + jsoninsert(iJson, "giscss_catEnabled", iJcatEnabled) + jsondestroy(iJcatEnabled) + endif + + xout iJson +endop + + + +opcode scss_setstate, 0, ippppp + iJson, isetsequencing, isetparameters, isetmelstate, isetinstrstate, isetcatenabled xin + + if (isetsequencing == 1 && jsonptrhas(iJson, "/sequencing") == 1) then + iJsonSequencing = jsonget(iJson, "sequencing") + scss_setseqstate(iJsonSequencing) + jsondestroy(iJsonSequencing) + endif + + if (isetparameters == 1 && jsonptrhas(iJson, "/parameters") == 1) then + iJsonParameters = jsonget(iJson, "parameters") + scss_setparamstate(iJsonParameters) + jsondestroy(iJsonParameters) + endif + + if (isetmelstate == 1 && jsonptrhas(iJson, "/melstate") == 1) then + iJsonMelstate = jsonget(iJson, "melstate") + mel_setstate_json(iJsonMelstate) + jsondestroy(iJsonMelstate) + endif + + if (isetinstrstate == 1 && jsonptrhas(iJson, "/giscss_instrState") == 1) then + iJinstrState = jsonget(iJson, "giscss_instrState") + endif + + if (isetcatenabled == 1 && jsonptrhas(iJson, "/giscss_catEnabled") == 1) then + iJcatEnabled = jsonget(iJson, "giscss_catEnabled") + endif + +endop + + + +/* + Get the current values of all registered parameters, returning callback ID to host and an object with key "parameters" + containing keys as the channel names and values as the channel values + + p4 callback ID +*/ +instr scss_getparamstate + icbid = p4 + iJson = jsoninit() + iJsonState = scss_getparamstate() + jsoninsert iJson, "parameters", iJsonState + jsoninsertval iJson, "cbid", icbid + jsoninsertval iJson, "status", "complete" + io_sendstring("callback", jsondumps(iJson, 0)) + jsondestroy(iJsonState) + jsondestroy(iJson) + turnoff +endin + + + +/* + Save state to database + + p4 callback ID + p5 reference name of the state to save; blank is accepted and saves as SCSS_NAME +*/ +instr scss_savestate_db + icbid = p4 + Sreference = strcat("$SCSS_NAME||", strget(p5)) + + ; save state values + iJsonState = scss_getstate() + Squery = sprintf("DELETE FROM savejson WHERE name = '%s' AND unit = 'scss_state'; INSERT INTO savejson (name, data, unit, created) VALUES ('%s', '%s', 'scss_state', current_timestamp)",\ + Sreference, Sreference, jsondumps(iJsonState, 0)\ + ) + dbexec gidb, Squery + jsondestroy(iJsonState) + + + ; return callback ID to host + iJson = jsoninit() + jsoninsertval iJson, "cbid", icbid + jsoninsertval iJson, "status", "complete" + io_sendstring("callback", jsondumps(iJson, 0)) + turnoff +endin + + + + +/* + Load parameter state from database, returning JSON to host as in the instrument scss_getparamstate + + p4 callback ID + p5 reference name of the state to load; blank is accepted and loads as SCSS_NAME +*/ +instr scss_loadstate_db + icbid = p4 + Sreference = strcat("$SCSS_NAME||", strget(p5)) + + ; load parameter values + Squery = sprintf("SELECT data::text FROM savejson WHERE unit = 'scss_state' AND name = '%s'", Sreference) + Sresult dbscalar gidb, Squery + iJson = jsonloads(Sresult) + scss_setstate(iJson) + jsondestroy(iJson) + + ; returns state data and callback ID to host + schedule("scss_getparamstate", 0, 1, icbid) + turnoff +endin + + +#end diff --git a/site/udo/scss/scss_persistence_test.csd b/site/udo/scss/scss_persistence_test.csd new file mode 100755 index 0000000..372ba76 --- /dev/null +++ b/site/udo/scss/scss_persistence_test.csd @@ -0,0 +1,100 @@ + + +-odac + + +sr = 48000 +ksmps = 64 +nchnls = 2 +0dbfs = 1 +seed 0 + +#define SCSS_NAME #default# +#include "scss/base.udo" + + + + +icategory = scss_registercategory("sine", "play_sine1") + +scss_registerinstr(icategory, ({{ + { + "name": "play_sine1", + "parameters": [ + {"name": "frequency", "default": 440, "max": 880, "min": 220} + ] + } +}})) +instr play_sine1 + icategoryid = p4 ; just used by scss_seqparam(iparamnum) + itimeindex = p5 + + kamp line 0.2, p3, 0 + a1 oscil kamp, chnget:i("play_sine1_frequency") + scss_catout(a1, a1) +endin + + +scss_registerinstr(icategory, ({{ + { + "name": "play_sine2", + "parameters": [ + {"name": "frequency", "default": 4400, "max": 8800, "min": 220} + ] + } +}})) +instr play_sine2 + kamp line 0, p3, 0.2 + a1 oscil kamp, 880 + scss_catout(a1, a1) +endin + + +icategory = scss_registercategory("bass", "play_bass1") + + +scss_registerinstr(icategory, ({{ + { + "name": "play_bass1", + "parameters": [ + {"name": "wobble", "default": 0.5, "max": 1, "min": 0.1} + ] + } +}})) +instr play_bass1 + kamp line 0.2, p3, 0 + a1 oscil kamp, 220 + scss_catout(a1, a1) +endin + + +scss_registerinstr(icategory, ({{ + { + "name": "play_bass2" + } +}})) +instr play_bass2 + kamp line 0.2, p3, 0 + a1 oscil kamp, 110 + scss_catout(a1, a1) +endin + + + +instr 1 + ;Squery = {{select f_savejsontest('{"ass":1}')}} + ;prints Squery + ;dbexec gidb, Squery + ;schedule("scss_savestate_db", 0, 1, 123, "test") + ;iJp1 = scss_getstate(1,1,1,1,1) + ;Sd1 = jsondumps(iJp1) + ;prints Sd1 + schedule("scss_savestate_db", 0, 1) +endin + + + +i1 0.1 1 + + + \ No newline at end of file diff --git a/site/udo/scss/scss_test.csd b/site/udo/scss/scss_test.csd new file mode 100755 index 0000000..72a3bd2 --- /dev/null +++ b/site/udo/scss/scss_test.csd @@ -0,0 +1,125 @@ + + +-odac +-m0 +-M0 + + +sr = 48000 +ksmps = 64 +nchnls = 2 +0dbfs = 1 +seed 0 + +#include "wavetables.udo" + +#define SCSS_MIDI ## + + +#define SCSS_BOOT_INSTRUMENT #run_shit# +#include "scss/base.udo" + + + +icategory = scss_registercategory("sine", "play_sine1") + +scss_registerinstr(icategory, ({{ + { + "name": "play_sine1", + "parameters": [ + {"name": "frequency", "default": 440, "max": 880, "min": 220} + ] + } +}})) +instr play_sine1 + icategoryid = p4 ; just used by scss_seqparam(iparamnum) + itimeindex = p5 + + kamp line 0.2, p3, 0 + a1 oscil kamp, chnget:i("play_sine1_frequency") + scss_catout(a1, a1) +endin + + +scss_registerinstr(icategory, ({{ + { + "name": "play_sine2", + "parameters": [ + {"name": "frequency", "default": 4400, "max": 8800, "min": 220} + ] + } +}})) +instr play_sine2 + kamp line 0, p3, 0.2 + a1 oscil kamp, 880, gifnSquare + scss_catout(a1, a1) +endin + + +icategory = scss_registercategory("bass", "play_bass1") + + +scss_registerinstr(icategory, ({{ + { + "name": "play_bass1", + "parameters": [ + {"name": "wobble", "default": 0.5, "max": 1, "min": 0.1} + ] + } +}})) +instr play_bass1 + kamp line 0.2, p3, 0 + a1 oscil kamp, 220 + scss_catout(a1, a1) +endin + + +scss_registerinstr(icategory, ({{ + { + "name": "play_bass2" + } +}})) +instr play_bass2 + kamp line 0.2, p3, 0 + a1 oscil kamp, 110 + scss_catout(a1, a1) +endin + + +instr run_shit + prints "run shit\n" + index1 = 0 + while (index1 < lenarray(giscss_stfn_trig, 1)) do + index2 = 0 + while (index2 < lenarray(giscss_stfn_trig, 2)) do + seq_randtable giscss_stfn_trig[index1][index2] + seq_randtable giscss_stfn_dur[index1][index2] + index2 += 1 + od + index1 += 1 + od + + + prints "OK then\n\n\n" +endin + +instr test_change1 + prints "change 1\n" + kval line 120, p3, 160 + chnset kval, "scss_bpm" +endin + + +instr test_change2 + prints "change 2\n" + kval line 160, p3, 100 + chnset kval, "scss_bpm" +endin + + + + + +f0 z + + \ No newline at end of file diff --git a/site/udo/scss/seqtable.udo b/site/udo/scss/seqtable.udo new file mode 100755 index 0000000..99f586b --- /dev/null +++ b/site/udo/scss/seqtable.udo @@ -0,0 +1,196 @@ +#ifndef UDO_SCSS_SEQTABLES +#define UDO_SCSS_SEQTABLES ## + + + + +/* +; temp scratch replacement tables +iscratchnum = 8 +gistfn_scratch[] init iscratchnum +index = 0 +while (index < iscratchnum) do + gistfn_scratch[index] = ftgen(0, 0, -16, -2, 0) + index += 1 +od +*/ + + + + + +; statics +/* +gistfn_all ftgen 0, 0, -giscss_seqtable_size, -2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +gistfn_offbeat ftgen 0, 0, -giscss_seqtable_size, -2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0 +gistfn_onbeat ftgen 0, 0, -giscss_seqtable_size, -2, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 +gisfn_2ndbeat ftgen 0, 0, -giscss_seqtable_size, -2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 +*/ + + +/* +gSseq_names[] fillarray "Kick", "Snare", "Clap", "Hat", "Hat2", "Bass", "Sine", "OPL" +giscss_stfn_src[][] init lenarray(gSseq_names), 4 +index = 0 +while (index < lenarray(giscss_stfn_src, 1)) do + index2 = 0 + while (index2 < lenarray(giscss_stfn_src, 2)) do + giscss_stfn_src[index][index2] = ftgen(0, 0, -giscss_seqtable_size, -2, 0) + index2 += 1 + od + index += 1 +od +*/ + +; automation tables for arbitrary assignment +giscss_fn_automation[] init 8 + +giscss_stfn_trig[][] init 1, 1 +giscss_stfn_dur[][] init 1, 1 +giscss_stfn_params[][] init 1, 1 + +giscss_st_size = -1 +giscss_st_slots = -1 +giscss_stfn_temp = -1 +giscss_stfn_param_temp = -1 +giscss_st_paramnumber = -1 +opcode scss_st_boot, 0, ijj + icategorynum, iseqtable_size, islots xin + iseqtable_size = (iseqtable_size == -1) ? 16 : iseqtable_size + islots = (islots == -1) ? 4 : islots + iparamnumber = 4 + + giscss_st_size = iseqtable_size + giscss_st_slots = islots + giscss_st_paramnumber = iparamnumber + giscss_stfn_temp = ftgen(0, 0, -iseqtable_size, -2, 0) + giscss_stfn_param_temp = ftgen(0, 0, -(iseqtable_size*iparamnumber), -2, 0) + + giscss_stfn_trig[][] init icategorynum, islots + giscss_stfn_dur[][] init icategorynum, islots + giscss_stfn_params[][] init icategorynum, islots + + index1 = 0 + while (index1 < icategorynum) do + index2 = 0 + while (index2 < islots) do + giscss_stfn_trig[index1][index2] = ftgen(0, 0, -iseqtable_size, -2, 0) + giscss_stfn_dur[index1][index2] = ftgen(0, 0, -iseqtable_size, -2, 0) + giscss_stfn_params[index1][index2] = ftgen(0, 0, -(iseqtable_size*iparamnumber), -2, 0) + index2 += 1 + od + index1 += 1 + od + + ; automation tables + iautomationtablesize = 1024 + index = 0 + while (index < lenarray(giscss_fn_automation)) do + giscss_fn_automation[index] = ftgen(0, 0, -iautomationtablesize, -7, 0) + index += 1 + od +endop + + + + +; called from category instrument to get parameter +opcode scss_seqparam, i, i + iparam xin + icategoryid = p4 + itimeindex = p5 + xout table:i((iparam * giscss_st_paramnumber) + itimeindex, giscss_stfn_params[icategoryid][0]) +endop + + + + +opcode seqtable_copyin, 0, iijjj + icategoryid, islot, ifntrig, ifndur, ifnparam xin + + if (ifntrig != -1) then + tableicopy giscss_stfn_trig[icategoryid][islot], ifntrig + endif + + if (ifndur != -1) then + tableicopy giscss_stfn_dur[icategoryid][islot], ifndur + endif + + if (ifnparam != -1) then + tableicopy giscss_stfn_params[icategoryid][islot], ifnparam + endif +endop + + + + +opcode scss_st_swap, 0, ii + icategoryid, islot xin + + ; triggers + tableicopy giscss_stfn_temp, giscss_stfn_trig[icategoryid][0] + tableicopy giscss_stfn_trig[icategoryid][0], giscss_stfn_trig[icategoryid][islot] + tableicopy giscss_stfn_trig[icategoryid][islot], giscss_stfn_temp + + ; durations + tableicopy giscss_stfn_temp, giscss_stfn_dur[icategoryid][0] + tableicopy giscss_stfn_dur[icategoryid][0], giscss_stfn_dur[icategoryid][islot] + tableicopy giscss_stfn_dur[icategoryid][islot], giscss_stfn_temp + + ; parameters + tableicopy giscss_stfn_param_temp, giscss_stfn_params[icategoryid][0] + tableicopy giscss_stfn_params[icategoryid][0], giscss_stfn_params[icategoryid][islot] + tableicopy giscss_stfn_params[icategoryid][islot], giscss_stfn_param_temp + +endop + + + +opcode scss_st_swapall, 0, p + isrcslot xin + index = 0 + while (index < lenarray(giscss_stfn_trig, 1)) do + scss_st_swap(index, isrcslot) + index += 1 + od +endop + + +; host call instrument +instr seqtable_swap + imode = p4 ; 0 = all, 1 = specified category id + isrcslot = p5 ; source index in giscss_stfn_src (eg 1 to 3) + icategoryid = p6 ; category ID to replace by, if mode is 1 + + if (imode == 0) then + scss_st_swapall isrcslot + else + scss_st_swap icategoryid, isrcslot + endif + turnoff +endin + + + + + + +/* + Add table numbers with names to a specified key in a target JSON object + +opcode seqtable_appendjson, 0, i + iJsontarget xin + + iJson = jsoninit() + index = 0 + while (index < lenarray(gSseq_names)) do + jsoninsertval(iJson, gSseq_names[index], getrow(giscss_stfn_src, index)) + index += 1 + od + + jsoninsert(iJsontarget, "trigtables", iJson) + jsondestroy(iJson) +endop +*/ +#end + -- cgit v1.2.3