#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