diff options
author | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
---|---|---|
committer | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
commit | 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 (patch) | |
tree | 291bd79ce340e67affa755a8a6b4f6a83cce93ea /site/udo/scss/base.udo | |
download | apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.gz apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.bz2 apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.zip |
initial
Diffstat (limited to 'site/udo/scss/base.udo')
-rwxr-xr-x | site/udo/scss/base.udo | 1046 |
1 files changed, 1046 insertions, 0 deletions
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
|