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 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1046 insertions(+) create mode 100755 site/udo/scss/base.udo (limited to 'site/udo/scss/base.udo') 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 -- cgit v1.2.3