diff options
Diffstat (limited to 'site/udo/scss')
-rwxr-xr-x | site/udo/scss/base.udo | 1046 | ||||
-rwxr-xr-x | site/udo/scss/elasticlip.udo | 823 | ||||
-rwxr-xr-x | site/udo/scss/elasticlip_arranger.udo | 213 | ||||
-rwxr-xr-x | site/udo/scss/elasticlip_sequencer.udo | 133 | ||||
-rwxr-xr-x | site/udo/scss/mixer/_effects.udo | 247 | ||||
-rwxr-xr-x | site/udo/scss/mixer/base.udo | 253 | ||||
-rwxr-xr-x | site/udo/scss/mixer/test.csd | 41 | ||||
-rwxr-xr-x | site/udo/scss/persistence.udo | 366 | ||||
-rwxr-xr-x | site/udo/scss/scss_persistence_test.csd | 100 | ||||
-rwxr-xr-x | site/udo/scss/scss_test.csd | 125 | ||||
-rwxr-xr-x | site/udo/scss/seqtable.udo | 196 |
11 files changed, 3543 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
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 @@ +<CsoundSynthesizer>
+<CsOptions>
+-odac
+</CsOptions>
+<CsInstruments>
+sr = 48000
+ksmps = 64
+nchnls = 2
+0dbfs = 1
+seed 0
+
+#include "scss/mixer/base.udo"
+
+instr test_boot
+ schedule("mx_boot", 0, 1) ; defaults: 12 channels
+ schedule("test_set", 0.5, 1)
+endin
+
+instr test_set
+ schedule("mx_alter_insert", 0.1, 1, 12, 0, 0) ; reverb on return 1 fx 1 (chan 12)
+ schedule("mx_alter_insert", 0, 1, 12, 1, 3) ; ring mod on return 1 fx 0 (chan 12)
+ chnset 1, "mxsend0_0" ; send chan 0 to reverb
+ schedule("test_sound", 0.5, 50) ; test sound
+ schedule("mx_getstate", 1, 1) ; test state output
+endin
+
+
+instr test_sound
+ prints "\n\ntest sound\n\n"
+ aL, aR diskin2 "d:/temp/drive.wav", 1, 0, 1
+ bus_mix("mxchan0", aL, aR)
+
+ kfreq = abs:k(oscil:k(2200, 1)) + 220
+ chnset kfreq, "fxi_freq_12_1"
+endin
+
+</CsInstruments>
+<CsScore>
+i"test_boot" 0 60
+</CsScore>
+</CsoundSynthesizer>
\ No newline at end of file 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 @@ +<CsoundSynthesizer>
+<CsOptions>
+-odac
+</CsOptions>
+<CsInstruments>
+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
+
+</CsInstruments>
+<CsScore>
+i1 0.1 1
+
+</CsScore>
+</CsoundSynthesizer>
\ 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 @@ +<CsoundSynthesizer>
+<CsOptions>
+-odac
+-m0
+-M0
+</CsOptions>
+<CsInstruments>
+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
+
+
+
+</CsInstruments>
+<CsScore>
+f0 z
+</CsScore>
+</CsoundSynthesizer>
\ 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
+
|