aboutsummaryrefslogtreecommitdiff
path: root/site/udo
diff options
context:
space:
mode:
Diffstat (limited to 'site/udo')
-rwxr-xr-xsite/udo/707sd.udo271
-rwxr-xr-xsite/udo/DEPRECATE_SORT_opcodes.udo211
-rwxr-xr-xsite/udo/_TESTS/feedback_test.csd41
-rwxr-xr-xsite/udo/_TESTS/mixer_emulator.csd132
-rwxr-xr-xsite/udo/_TESTS/mixer_emulator_1604a.csd144
-rwxr-xr-xsite/udo/__config__.dist.udo44
-rwxr-xr-xsite/udo/addsub.udo170
-rwxr-xr-xsite/udo/aodb.udo80
-rwxr-xr-xsite/udo/array_3d.udo132
-rwxr-xr-xsite/udo/array_tools.udo235
-rwxr-xr-xsite/udo/autorecord.udo108
-rwxr-xr-xsite/udo/bpmdetect.udo57
-rwxr-xr-xsite/udo/bsamp.udo206
-rwxr-xr-xsite/udo/bussing.udo268
-rwxr-xr-xsite/udo/bussing_quad.udo172
-rwxr-xr-xsite/udo/chop.udo715
-rwxr-xr-xsite/udo/chord_detect.udo154
-rwxr-xr-xsite/udo/chords.udo455
-rwxr-xr-xsite/udo/convolutiondb.udo110
-rwxr-xr-xsite/udo/cs81z.udo208
-rwxr-xr-xsite/udo/csv.udo75
-rwxr-xr-xsite/udo/delays.udo42
-rwxr-xr-xsite/udo/experimental.udo41
-rwxr-xr-xsite/udo/feedback.udo164
-rwxr-xr-xsite/udo/fftconvolve.udo116
-rwxr-xr-xsite/udo/fnml/clay_workings.csd58
-rwxr-xr-xsite/udo/fnml/instrument_automel.udo89
-rwxr-xr-xsite/udo/fnml/instrument_gchord1.udo260
-rwxr-xr-xsite/udo/fnml/instrument_portchord.udo132
-rwxr-xr-xsite/udo/fnml/instrument_sineblips.udo81
-rwxr-xr-xsite/udo/fnml/instrument_tikbank.udo78
-rwxr-xr-xsite/udo/fnml/instrument_tikclay.udo118
-rwxr-xr-xsite/udo/fnml/instrument_vocal.udo73
-rwxr-xr-xsite/udo/fnml/transition_click.udo143
-rwxr-xr-xsite/udo/fnml/transition_mburn.udo186
-rwxr-xr-xsite/udo/fnml/transition_snare.udo140
-rwxr-xr-xsite/udo/fnml/transition_snare_preSOUNDDB.udo133
-rwxr-xr-xsite/udo/fnml/transition_test.csd39
-rwxr-xr-xsite/udo/fnml/transitional/base.udo141
-rwxr-xr-xsite/udo/frequency_tools.udo375
-rwxr-xr-xsite/udo/fx_autoglitch.udo470
-rwxr-xr-xsite/udo/fx_autoglitchbeat.udo168
-rwxr-xr-xsite/udo/host_platform.udo45
-rwxr-xr-xsite/udo/host_tools.udo69
-rwxr-xr-xsite/udo/interop.udo178
-rwxr-xr-xsite/udo/interop.web.udo172
-rwxr-xr-xsite/udo/json.udo562
-rwxr-xr-xsite/udo/lagdetect.udo59
-rwxr-xr-xsite/udo/legacy/sequencing_melodic.udo831
-rwxr-xr-xsite/udo/legacy/sequencing_melodic_persistence.udo246
-rwxr-xr-xsite/udo/mfcc_match.udo178
-rwxr-xr-xsite/udo/midi.udo65
-rwxr-xr-xsite/udo/midimap.udo247
-rwxr-xr-xsite/udo/oprepare.udo93
-rwxr-xr-xsite/udo/pgdb.udo313
-rwxr-xr-xsite/udo/pvs_fsegproc.udo135
-rwxr-xr-xsite/udo/pvs_fulltabproc.udo197
-rwxr-xr-xsite/udo/pvs_tabproc.udo640
-rwxr-xr-xsite/udo/pvs_tools.udo31
-rwxr-xr-xsite/udo/quad.udo79
-rwxr-xr-xsite/udo/sample_level.udo90
-rwxr-xr-xsite/udo/sampling.udo77
-rwxr-xr-xsite/udo/scss/base.udo1046
-rwxr-xr-xsite/udo/scss/elasticlip.udo823
-rwxr-xr-xsite/udo/scss/elasticlip_arranger.udo213
-rwxr-xr-xsite/udo/scss/elasticlip_sequencer.udo133
-rwxr-xr-xsite/udo/scss/mixer/_effects.udo247
-rwxr-xr-xsite/udo/scss/mixer/base.udo253
-rwxr-xr-xsite/udo/scss/mixer/test.csd41
-rwxr-xr-xsite/udo/scss/persistence.udo366
-rwxr-xr-xsite/udo/scss/scss_persistence_test.csd100
-rwxr-xr-xsite/udo/scss/scss_test.csd125
-rwxr-xr-xsite/udo/scss/seqtable.udo196
-rwxr-xr-xsite/udo/sequencing.udo329
-rwxr-xr-xsite/udo/sequencing_melodic.udo807
-rwxr-xr-xsite/udo/sequencing_melodic_persistence.udo275
-rwxr-xr-xsite/udo/sequencing_melodic_persistence.web.udo213
-rwxr-xr-xsite/udo/sequencing_melodic_portamento.udo310
-rwxr-xr-xsite/udo/sequencing_scheduled.udo286
-rwxr-xr-xsite/udo/sequencing_table.udo370
-rwxr-xr-xsite/udo/sound_db.udo152
-rwxr-xr-xsite/udo/sound_melsys.udo215
-rwxr-xr-xsite/udo/sound_sdb.udo271
-rwxr-xr-xsite/udo/sounddb.udo270
-rwxr-xr-xsite/udo/soundfont.udo68
-rwxr-xr-xsite/udo/soundfonts/Rhodes/Crysrhod.sf2bin0 -> 212560 bytes
-rwxr-xr-xsite/udo/soundfonts/Rhodes/Galaxy_Electric_Pianos.sf2bin0 -> 30299302 bytes
-rwxr-xr-xsite/udo/soundfonts/Rhodes/JR_elepiano.sf2bin0 -> 470228 bytes
-rwxr-xr-xsite/udo/soundfonts/Rhodes/KR-O5R-Operator.sf2bin0 -> 23758082 bytes
-rwxr-xr-xsite/udo/soundfonts/Rhodes/jRhodes3c-stereo.sf2bin0 -> 32793738 bytes
-rwxr-xr-xsite/udo/soundfonts/Rhodes/vibra.sf2bin0 -> 201844 bytes
-rwxr-xr-xsite/udo/soundxdb.udo201
-rwxr-xr-xsite/udo/soundxdb_extract.udo67
-rwxr-xr-xsite/udo/spectral_sampler.udo126
-rwxr-xr-xsite/udo/spectral_transforms.udo358
-rwxr-xr-xsite/udo/string_tools.udo375
-rwxr-xr-xsite/udo/synth_drums.udo229
-rwxr-xr-xsite/udo/synth_instruments.udo78
-rwxr-xr-xsite/udo/tab2wav.udo151
-rwxr-xr-xsite/udo/table_tools.udo244
-rwxr-xr-xsite/udo/tabproc.udo217
-rwxr-xr-xsite/udo/tempo_tools.udo72
-rwxr-xr-xsite/udo/transient_detect.udo216
-rwxr-xr-xsite/udo/twigs/checkpointing.udo120
-rwxr-xr-xsite/udo/twigs/transforms.udo285
-rwxr-xr-xsite/udo/twigs/twigs.udo444
-rwxr-xr-xsite/udo/twist/automation.udo241
-rwxr-xr-xsite/udo/twist/checkpointing.udo127
-rwxr-xr-xsite/udo/twist/checkpointing_hold.udo196
-rwxr-xr-xsite/udo/twist/transform_api.udo421
-rwxr-xr-xsite/udo/twist/transforms.udo25
-rwxr-xr-xsite/udo/twist/transforms/amplitude.udo179
-rwxr-xr-xsite/udo/twist/transforms/cross_processing.udo176
-rwxr-xr-xsite/udo/twist/transforms/delay.udo72
-rwxr-xr-xsite/udo/twist/transforms/filter.udo172
-rwxr-xr-xsite/udo/twist/transforms/frequency.udo65
-rwxr-xr-xsite/udo/twist/transforms/general.udo28
-rwxr-xr-xsite/udo/twist/transforms/generate.udo363
-rwxr-xr-xsite/udo/twist/transforms/granular.udo138
-rwxr-xr-xsite/udo/twist/transforms/harmonic.udo142
-rwxr-xr-xsite/udo/twist/transforms/reverb.udo80
-rwxr-xr-xsite/udo/twist/transforms/spectral.udo642
-rwxr-xr-xsite/udo/twist/transforms/warping.udo210
-rwxr-xr-xsite/udo/twist/twist.udo1304
-rwxr-xr-xsite/udo/txt_tools.udo98
-rwxr-xr-xsite/udo/uniqueid.udo81
-rwxr-xr-xsite/udo/wavetables.udo31
-rwxr-xr-xsite/udo/wiimote.udo227
-rwxr-xr-xsite/udo/wiimote_fltk.udo200
129 files changed, 26852 insertions, 0 deletions
diff --git a/site/udo/707sd.udo b/site/udo/707sd.udo
new file mode 100755
index 0000000..679ca38
--- /dev/null
+++ b/site/udo/707sd.udo
@@ -0,0 +1,271 @@
+opcode playitem, a, ip
+ ilen, ipitch xin
+ ao[] init 266
+ ao[0] oscil linseg(0.024488557493,ilen, 0),10.7666015625*ipitch
+ ao[1] oscil linseg(0.0180192348741,ilen, 0),21.533203125*ipitch
+ ao[2] oscil linseg(1.0,ilen, 0),150.732421875*ipitch
+ ao[3] oscil linseg(0.0239778504714,ilen, 0),6718.359375*ipitch
+ ao[4] oscil linseg(0.0205609728986,ilen, 0),6459.9609375*ipitch
+ ao[5] oscil linseg(0.0267817769015,ilen, 0),5889.33105469*ipitch
+ ao[6] oscil linseg(0.0264693898081,ilen, 0),4661.93847656*ipitch
+ ao[7] oscil linseg(0.0200978314284,ilen, 0),6933.69140625*ipitch
+ ao[8] oscil linseg(0.0189626596094,ilen, 0),2045.65429688*ipitch
+ ao[9] oscil linseg(0.0394016023056,ilen, 0),3875.9765625*ipitch
+ ao[10] oscil linseg(0.0244133693747,ilen, 0),3940.57617188*ipitch
+ ao[11] oscil linseg(0.0181454434993,ilen, 0),3283.81347656*ipitch
+ ao[12] oscil linseg(0.815572982617,ilen, 0),161.499023438*ipitch
+ ao[13] oscil linseg(0.0311092972593,ilen, 0),5738.59863281*ipitch
+ ao[14] oscil linseg(0.0421340857104,ilen, 0),6363.06152344*ipitch
+ ao[15] oscil linseg(0.0214739723073,ilen, 0),1561.15722656*ipitch
+ ao[16] oscil linseg(0.0205910626124,ilen, 0),6223.09570312*ipitch
+ ao[17] oscil linseg(0.0294615941632,ilen, 0),5663.23242188*ipitch
+ ao[18] oscil linseg(0.0428432076284,ilen, 0),5813.96484375*ipitch
+ ao[19] oscil linseg(0.023357307483,ilen, 0),4705.00488281*ipitch
+ ao[20] oscil linseg(0.0204321027716,ilen, 0),613.696289062*ipitch
+ ao[21] oscil linseg(0.0187198942643,ilen, 0),9937.57324219*ipitch
+ ao[22] oscil linseg(0.0329440066468,ilen, 0),5587.86621094*ipitch
+ ao[23] oscil linseg(0.0224850052981,ilen, 0),3649.87792969*ipitch
+ ao[24] oscil linseg(0.0441226581933,ilen, 0),322.998046875*ipitch
+ ao[25] oscil linseg(0.031563261733,ilen, 0),6072.36328125*ipitch
+ ao[26] oscil linseg(0.0601866729828,ilen, 0),4382.00683594*ipitch
+ ao[27] oscil linseg(0.0430520694595,ilen, 0),5512.5*ipitch
+ ao[28] oscil linseg(0.0254528745151,ilen, 0),6330.76171875*ipitch
+ ao[29] oscil linseg(0.019312109605,ilen, 0),4952.63671875*ipitch
+ ao[30] oscil linseg(0.0480625303751,ilen, 0),4392.7734375*ipitch
+ ao[31] oscil linseg(0.0179122402451,ilen, 0),3488.37890625*ipitch
+ ao[32] oscil linseg(0.0191704394699,ilen, 0),10282.1044922*ipitch
+ ao[33] oscil linseg(0.0197171086104,ilen, 0),4877.27050781*ipitch
+ ao[34] oscil linseg(0.0255623423117,ilen, 0),6481.49414062*ipitch
+ ao[35] oscil linseg(0.0255904318115,ilen, 0),5437.13378906*ipitch
+ ao[36] oscil linseg(0.0183989713127,ilen, 0),2056.42089844*ipitch
+ ao[37] oscil linseg(0.034280387265,ilen, 0),3369.94628906*ipitch
+ ao[38] oscil linseg(0.0184363439226,ilen, 0),9097.77832031*ipitch
+ ao[39] oscil linseg(0.0273604328055,ilen, 0),2982.34863281*ipitch
+ ao[40] oscil linseg(0.0653034562041,ilen, 0),409.130859375*ipitch
+ ao[41] oscil linseg(0.0327228734714,ilen, 0),4425.07324219*ipitch
+ ao[42] oscil linseg(0.0190256745608,ilen, 0),6890.625*ipitch
+ ao[43] oscil linseg(0.0193144792275,ilen, 0),3854.44335938*ipitch
+ ao[44] oscil linseg(0.0587511829673,ilen, 0),269.165039062*ipitch
+ ao[45] oscil linseg(0.019286481335,ilen, 0),8742.48046875*ipitch
+ ao[46] oscil linseg(0.0216090547042,ilen, 0),3574.51171875*ipitch
+ ao[47] oscil linseg(0.0187803952388,ilen, 0),1410.42480469*ipitch
+ ao[48] oscil linseg(0.0243118170182,ilen, 0),5652.46582031*ipitch
+ ao[49] oscil linseg(0.024238087681,ilen, 0),5211.03515625*ipitch
+ ao[50] oscil linseg(0.0350021119004,ilen, 0),6815.25878906*ipitch
+ ao[51] oscil linseg(0.0246018118726,ilen, 0),4651.171875*ipitch
+ ao[52] oscil linseg(0.0183280746502,ilen, 0),6341.52832031*ipitch
+ ao[53] oscil linseg(0.0439988168993,ilen, 0),5480.20019531*ipitch
+ ao[54] oscil linseg(0.0185005478487,ilen, 0),75.3662109375*ipitch
+ ao[55] oscil linseg(0.0217309341919,ilen, 0),7299.75585938*ipitch
+ ao[56] oscil linseg(0.0239261103965,ilen, 0),5135.66894531*ipitch
+ ao[57] oscil linseg(0.0337920500579,ilen, 0),667.529296875*ipitch
+ ao[58] oscil linseg(0.0389469089661,ilen, 0),6180.02929688*ipitch
+ ao[59] oscil linseg(0.025599848855,ilen, 0),495.263671875*ipitch
+ ao[60] oscil linseg(0.0186056866333,ilen, 0),5620.16601562*ipitch
+ ao[61] oscil linseg(0.0277115109844,ilen, 0),5060.30273438*ipitch
+ ao[62] oscil linseg(0.0239839707807,ilen, 0),5846.26464844*ipitch
+ ao[63] oscil linseg(0.0242793120239,ilen, 0),6104.66308594*ipitch
+ ao[64] oscil linseg(0.0218570225382,ilen, 0),538.330078125*ipitch
+ ao[65] oscil linseg(0.0284524649389,ilen, 0),6847.55859375*ipitch
+ ao[66] oscil linseg(0.0234151014471,ilen, 0),355.297851562*ipitch
+ ao[67] oscil linseg(0.0236326097625,ilen, 0),6826.02539062*ipitch
+ ao[68] oscil linseg(0.0198269663231,ilen, 0),3983.64257812*ipitch
+ ao[69] oscil linseg(0.0242428628494,ilen, 0),5469.43359375*ipitch
+ ao[70] oscil linseg(0.022727584042,ilen, 0),6029.296875*ipitch
+ ao[71] oscil linseg(0.0257148865935,ilen, 0),7633.52050781*ipitch
+ ao[72] oscil linseg(0.0387341939638,ilen, 0),4834.20410156*ipitch
+ ao[73] oscil linseg(0.0234575272599,ilen, 0),5727.83203125*ipitch
+ ao[74] oscil linseg(0.0328683829317,ilen, 0),4909.5703125*ipitch
+ ao[75] oscil linseg(0.0194781594693,ilen, 0),2336.35253906*ipitch
+ ao[76] oscil linseg(0.0267544204582,ilen, 0),2583.984375*ipitch
+ ao[77] oscil linseg(0.0225099516997,ilen, 0),6998.29101562*ipitch
+ ao[78] oscil linseg(0.027822363223,ilen, 0),4511.20605469*ipitch
+ ao[79] oscil linseg(0.0316632150593,ilen, 0),5878.56445312*ipitch
+ ao[80] oscil linseg(0.0251764046343,ilen, 0),7482.78808594*ipitch
+ ao[81] oscil linseg(0.0269978702322,ilen, 0),5900.09765625*ipitch
+ ao[82] oscil linseg(0.0360164809734,ilen, 0),5490.96679688*ipitch
+ ao[83] oscil linseg(0.0189371940337,ilen, 0),4758.83789062*ipitch
+ ao[84] oscil linseg(0.0318790262703,ilen, 0),1431.95800781*ipitch
+ ao[85] oscil linseg(0.0269120366658,ilen, 0),4198.97460938*ipitch
+ ao[86] oscil linseg(0.0660414441974,ilen, 0),5803.19824219*ipitch
+ ao[87] oscil linseg(0.0183002135671,ilen, 0),9226.97753906*ipitch
+ ao[88] oscil linseg(0.02601135548,ilen, 0),4188.20800781*ipitch
+ ao[89] oscil linseg(0.059782820039,ilen, 0),301.46484375*ipitch
+ ao[90] oscil linseg(0.022680536783,ilen, 0),7891.91894531*ipitch
+ ao[91] oscil linseg(0.0331337139358,ilen, 0),3552.97851562*ipitch
+ ao[92] oscil linseg(0.0210451452407,ilen, 0),7332.05566406*ipitch
+ ao[93] oscil linseg(0.0542316337826,ilen, 0),3391.47949219*ipitch
+ ao[94] oscil linseg(0.0265953531859,ilen, 0),3273.046875*ipitch
+ ao[95] oscil linseg(0.0228255482004,ilen, 0),6772.19238281*ipitch
+ ao[96] oscil linseg(0.0204281003535,ilen, 0),4608.10546875*ipitch
+ ao[97] oscil linseg(0.0187120612285,ilen, 0),5243.33496094*ipitch
+ ao[98] oscil linseg(0.0239923645807,ilen, 0),6696.82617188*ipitch
+ ao[99] oscil linseg(0.0250861415508,ilen, 0),1421.19140625*ipitch
+ ao[100] oscil linseg(0.0180826742579,ilen, 0),1442.72460938*ipitch
+ ao[101] oscil linseg(0.0264853572248,ilen, 0),5577.09960938*ipitch
+ ao[102] oscil linseg(0.0206733216006,ilen, 0),2917.74902344*ipitch
+ ao[103] oscil linseg(0.136541640719,ilen, 0),236.865234375*ipitch
+ ao[104] oscil linseg(0.0199271384844,ilen, 0),602.9296875*ipitch
+ ao[105] oscil linseg(0.0347817284882,ilen, 0),5501.73339844*ipitch
+ ao[106] oscil linseg(0.0200376119011,ilen, 0),5996.99707031*ipitch
+ ao[107] oscil linseg(0.0179076379869,ilen, 0),2357.88574219*ipitch
+ ao[108] oscil linseg(0.0262162109904,ilen, 0),3068.48144531*ipitch
+ ao[109] oscil linseg(0.0263475044418,ilen, 0),8150.31738281*ipitch
+ ao[110] oscil linseg(0.0249585159453,ilen, 0),5986.23046875*ipitch
+ ao[111] oscil linseg(0.0485750524696,ilen, 0),3402.24609375*ipitch
+ ao[112] oscil linseg(0.0212922125085,ilen, 0),5975.46386719*ipitch
+ ao[113] oscil linseg(0.021729822868,ilen, 0),4866.50390625*ipitch
+ ao[114] oscil linseg(0.0279031353037,ilen, 0),6470.72753906*ipitch
+ ao[115] oscil linseg(0.0235197284618,ilen, 0),2906.98242188*ipitch
+ ao[116] oscil linseg(0.0205771312611,ilen, 0),5351.00097656*ipitch
+ ao[117] oscil linseg(0.0222872451849,ilen, 0),1453.49121094*ipitch
+ ao[118] oscil linseg(0.0186243772791,ilen, 0),6739.89257812*ipitch
+ ao[119] oscil linseg(0.0258420854569,ilen, 0),3606.81152344*ipitch
+ ao[120] oscil linseg(0.0315570313023,ilen, 0),473.73046875*ipitch
+ ao[121] oscil linseg(0.030144620486,ilen, 0),5275.63476562*ipitch
+ ao[122] oscil linseg(0.0325973191223,ilen, 0),4715.77148438*ipitch
+ ao[123] oscil linseg(0.0196349869952,ilen, 0),3090.01464844*ipitch
+ ao[124] oscil linseg(0.0191335517023,ilen, 0),4155.90820312*ipitch
+ ao[125] oscil linseg(0.0192294107496,ilen, 0),6158.49609375*ipitch
+ ao[126] oscil linseg(0.0460088874701,ilen, 0),333.764648438*ipitch
+ ao[127] oscil linseg(0.0290235813967,ilen, 0),6804.4921875*ipitch
+ ao[128] oscil linseg(0.0192921845337,ilen, 0),5684.765625*ipitch
+ ao[129] oscil linseg(0.0266475841315,ilen, 0),5835.49804688*ipitch
+ ao[130] oscil linseg(0.0200220024544,ilen, 0),3811.37695312*ipitch
+ ao[131] oscil linseg(0.019076150019,ilen, 0),8322.58300781*ipitch
+ ao[132] oscil linseg(0.020423256474,ilen, 0),3531.4453125*ipitch
+ ao[133] oscil linseg(0.0352261986885,ilen, 0),6503.02734375*ipitch
+ ao[134] oscil linseg(0.409247939635,ilen, 0),139.965820312*ipitch
+ ao[135] oscil linseg(0.0402794341335,ilen, 0),6169.26269531*ipitch
+ ao[136] oscil linseg(0.0408649673903,ilen, 0),6707.59277344*ipitch
+ ao[137] oscil linseg(0.0368015278912,ilen, 0),5609.39941406*ipitch
+ ao[138] oscil linseg(0.0199864332097,ilen, 0),5006.46972656*ipitch
+ ao[139] oscil linseg(0.0204053032336,ilen, 0),1550.390625*ipitch
+ ao[140] oscil linseg(0.0193863220144,ilen, 0),5049.53613281*ipitch
+ ao[141] oscil linseg(0.037020548398,ilen, 0),3800.61035156*ipitch
+ ao[142] oscil linseg(0.0231713206328,ilen, 0),5534.03320312*ipitch
+ ao[143] oscil linseg(0.0199967375178,ilen, 0),8839.37988281*ipitch
+ ao[144] oscil linseg(0.0184321995545,ilen, 0),419.897460938*ipitch
+ ao[145] oscil linseg(0.0259855927924,ilen, 0),7245.92285156*ipitch
+ ao[146] oscil linseg(0.0293177874851,ilen, 0),4414.30664062*ipitch
+ ao[147] oscil linseg(0.0225066153808,ilen, 0),4984.93652344*ipitch
+ ao[148] oscil linseg(0.0205668887003,ilen, 0),7622.75390625*ipitch
+ ao[149] oscil linseg(0.0363998155204,ilen, 0),5458.66699219*ipitch
+ ao[150] oscil linseg(0.0205970206623,ilen, 0),7062.890625*ipitch
+ ao[151] oscil linseg(0.0179174379577,ilen, 0),7127.49023438*ipitch
+ ao[152] oscil linseg(0.0183602513668,ilen, 0),2605.51757812*ipitch
+ ao[153] oscil linseg(0.0207152850401,ilen, 0),279.931640625*ipitch
+ ao[154] oscil linseg(0.0286191251909,ilen, 0),6578.39355469*ipitch
+ ao[155] oscil linseg(0.0237817929411,ilen, 0),6987.52441406*ipitch
+ ao[156] oscil linseg(0.0192771582309,ilen, 0),4360.47363281*ipitch
+ ao[157] oscil linseg(0.026170589693,ilen, 0),4263.57421875*ipitch
+ ao[158] oscil linseg(0.0178243199258,ilen, 0),3477.61230469*ipitch
+ ao[159] oscil linseg(0.0228707418404,ilen, 0),6912.15820312*ipitch
+ ao[160] oscil linseg(0.0206780373809,ilen, 0),3865.20996094*ipitch
+ ao[161] oscil linseg(0.0245943761808,ilen, 0),3596.04492188*ipitch
+ ao[162] oscil linseg(0.0252561486755,ilen, 0),3585.27832031*ipitch
+ ao[163] oscil linseg(0.0198086431044,ilen, 0),4726.53808594*ipitch
+ ao[164] oscil linseg(0.0436825336258,ilen, 0),193.798828125*ipitch
+ ao[165] oscil linseg(0.029314562715,ilen, 0),6836.79199219*ipitch
+ ao[166] oscil linseg(0.0627117148075,ilen, 0),5792.43164062*ipitch
+ ao[167] oscil linseg(0.0401688505577,ilen, 0),6276.92871094*ipitch
+ ao[168] oscil linseg(0.0207819445785,ilen, 0),5254.1015625*ipitch
+ ao[169] oscil linseg(0.0230833903696,ilen, 0),226.098632812*ipitch
+ ao[170] oscil linseg(0.0205660646014,ilen, 0),7321.2890625*ipitch
+ ao[171] oscil linseg(0.0195294599873,ilen, 0),4069.77539062*ipitch
+ ao[172] oscil linseg(0.0184385823647,ilen, 0),5092.60253906*ipitch
+ ao[173] oscil linseg(0.0340689016321,ilen, 0),3789.84375*ipitch
+ ao[174] oscil linseg(0.0195931960491,ilen, 0),5641.69921875*ipitch
+ ao[175] oscil linseg(0.0218807751789,ilen, 0),5124.90234375*ipitch
+ ao[176] oscil linseg(0.0480862376531,ilen, 0),5081.8359375*ipitch
+ ao[177] oscil linseg(0.0217184092747,ilen, 0),4242.04101562*ipitch
+ ao[178] oscil linseg(0.0904292099586,ilen, 0),258.3984375*ipitch
+ ao[179] oscil linseg(0.0203112303089,ilen, 0),3919.04296875*ipitch
+ ao[180] oscil linseg(0.157523429636,ilen, 0),247.631835938*ipitch
+ ao[181] oscil linseg(0.0239254092146,ilen, 0),4457.37304688*ipitch
+ ao[182] oscil linseg(0.0197231518279,ilen, 0),3994.40917969*ipitch
+ ao[183] oscil linseg(0.019245468363,ilen, 0),6050.83007812*ipitch
+ ao[184] oscil linseg(0.0331496627609,ilen, 0),7655.05371094*ipitch
+ ao[185] oscil linseg(0.0268933526405,ilen, 0),2648.58398438*ipitch
+ ao[186] oscil linseg(0.0251079784561,ilen, 0),452.197265625*ipitch
+ ao[187] oscil linseg(0.0362861105368,ilen, 0),4371.24023438*ipitch
+ ao[188] oscil linseg(0.0407981400396,ilen, 0),4446.60644531*ipitch
+ ao[189] oscil linseg(0.0248780408578,ilen, 0),2594.75097656*ipitch
+ ao[190] oscil linseg(0.0204840890321,ilen, 0),5415.60058594*ipitch
+ ao[191] oscil linseg(0.0297457893394,ilen, 0),312.231445312*ipitch
+ ao[192] oscil linseg(0.0608713620727,ilen, 0),366.064453125*ipitch
+ ao[193] oscil linseg(0.0267674184049,ilen, 0),3520.67871094*ipitch
+ ao[194] oscil linseg(0.0523770636586,ilen, 0),5200.26855469*ipitch
+ ao[195] oscil linseg(0.0187496536313,ilen, 0),3639.11132812*ipitch
+ ao[196] oscil linseg(0.0215001020646,ilen, 0),5770.8984375*ipitch
+ ao[197] oscil linseg(0.0304004139281,ilen, 0),3079.24804688*ipitch
+ ao[198] oscil linseg(0.0589216870188,ilen, 0),129.19921875*ipitch
+ ao[199] oscil linseg(0.0218903360585,ilen, 0),9506.90917969*ipitch
+ ao[200] oscil linseg(0.0393620744783,ilen, 0),5447.90039062*ipitch
+ ao[201] oscil linseg(0.0276663638898,ilen, 0),6061.59667969*ipitch
+ ao[202] oscil linseg(0.0938280514958,ilen, 0),376.831054688*ipitch
+ ao[203] oscil linseg(0.0189832103954,ilen, 0),3671.41113281*ipitch
+ ao[204] oscil linseg(0.0183208947159,ilen, 0),8451.78222656*ipitch
+ ao[205] oscil linseg(0.0241595131229,ilen, 0),6589.16015625*ipitch
+ ao[206] oscil linseg(0.0282636935114,ilen, 0),5749.36523438*ipitch
+ ao[207] oscil linseg(0.029299710833,ilen, 0),5867.79785156*ipitch
+ ao[208] oscil linseg(0.0393880185696,ilen, 0),5189.50195312*ipitch
+ ao[209] oscil linseg(0.030617823896,ilen, 0),8139.55078125*ipitch
+ ao[210] oscil linseg(0.113553319613,ilen, 0),398.364257812*ipitch
+ ao[211] oscil linseg(0.0214341444181,ilen, 0),6922.92480469*ipitch
+ ao[212] oscil linseg(0.0288698381448,ilen, 0),5673.99902344*ipitch
+ ao[213] oscil linseg(0.029583479587,ilen, 0),6373.828125*ipitch
+ ao[214] oscil linseg(0.0311395821121,ilen, 0),5544.79980469*ipitch
+ ao[215] oscil linseg(0.362974957824,ilen, 0),172.265625*ipitch
+ ao[216] oscil linseg(0.0197335484318,ilen, 0),8441.015625*ipitch
+ ao[217] oscil linseg(0.0241013475276,ilen, 0),8527.1484375*ipitch
+ ao[218] oscil linseg(0.0497960294492,ilen, 0),3380.71289062*ipitch
+ ao[219] oscil linseg(0.0184337342328,ilen, 0),2928.515625*ipitch
+ ao[220] oscil linseg(0.0218672213951,ilen, 0),8537.91503906*ipitch
+ ao[221] oscil linseg(0.0256893675701,ilen, 0),204.565429688*ipitch
+ ao[222] oscil linseg(0.0244861515314,ilen, 0),6567.62695312*ipitch
+ ao[223] oscil linseg(0.0367437959413,ilen, 0),484.497070312*ipitch
+ ao[224] oscil linseg(0.029611095538,ilen, 0),3413.01269531*ipitch
+ ao[225] oscil linseg(0.0337367106919,ilen, 0),3563.74511719*ipitch
+ ao[226] oscil linseg(0.03031611443,ilen, 0),6492.26074219*ipitch
+ ao[227] oscil linseg(0.0242389841672,ilen, 0),9108.54492188*ipitch
+ ao[228] oscil linseg(0.0188060722517,ilen, 0),4209.74121094*ipitch
+ ao[229] oscil linseg(0.0461808005353,ilen, 0),215.33203125*ipitch
+ ao[230] oscil linseg(0.0542027285351,ilen, 0),4812.67089844*ipitch
+ ao[231] oscil linseg(0.0196400374379,ilen, 0),9237.74414062*ipitch
+ ao[232] oscil linseg(0.0271651964235,ilen, 0),4252.80761719*ipitch
+ ao[233] oscil linseg(0.0210497318326,ilen, 0),678.295898438*ipitch
+ ao[234] oscil linseg(0.0223344785144,ilen, 0),3617.578125*ipitch
+ ao[235] oscil linseg(0.0315532812138,ilen, 0),7375.12207031*ipitch
+ ao[236] oscil linseg(0.0400231867922,ilen, 0),4801.90429688*ipitch
+ ao[237] oscil linseg(0.0197841277744,ilen, 0),3057.71484375*ipitch
+ ao[238] oscil linseg(0.0275392391425,ilen, 0),5264.86816406*ipitch
+ ao[239] oscil linseg(0.0468787737644,ilen, 0),5781.66503906*ipitch
+ ao[240] oscil linseg(0.027026054674,ilen, 0),7385.88867188*ipitch
+ ao[241] oscil linseg(0.0216249890355,ilen, 0),2497.8515625*ipitch
+ ao[242] oscil linseg(0.0248273063775,ilen, 0),4769.60449219*ipitch
+ ao[243] oscil linseg(0.0263085964636,ilen, 0),6761.42578125*ipitch
+ ao[244] oscil linseg(0.0231038918335,ilen, 0),2896.21582031*ipitch
+ ao[245] oscil linseg(0.0424332148873,ilen, 0),6266.16210938*ipitch
+ ao[246] oscil linseg(0.13736335311,ilen, 0),183.032226562*ipitch
+ ao[247] oscil linseg(0.0281326383826,ilen, 0),3262.28027344*ipitch
+ ao[248] oscil linseg(0.0288147924227,ilen, 0),6750.65917969*ipitch
+ ao[249] oscil linseg(0.132247626404,ilen, 0),387.59765625*ipitch
+ ao[250] oscil linseg(0.0278524775146,ilen, 0),6190.79589844*ipitch
+ ao[251] oscil linseg(0.0220096159961,ilen, 0),10669.7021484*ipitch
+ ao[252] oscil linseg(0.0187526638384,ilen, 0),549.096679688*ipitch
+ ao[253] oscil linseg(0.0209369029976,ilen, 0),1711.88964844*ipitch
+ ao[254] oscil linseg(0.0181602257441,ilen, 0),7235.15625*ipitch
+ ao[255] oscil linseg(0.0379563372059,ilen, 0),5071.06933594*ipitch
+ ao[256] oscil linseg(0.0574167307026,ilen, 0),290.698242188*ipitch
+ ao[257] oscil linseg(0.0227539258574,ilen, 0),6115.4296875*ipitch
+ ao[258] oscil linseg(0.022330092084,ilen, 0),4618.87207031*ipitch
+ ao[259] oscil linseg(0.0256630065971,ilen, 0),2573.21777344*ipitch
+ ao[260] oscil linseg(0.0257961368143,ilen, 0),6040.06347656*ipitch
+ ao[261] oscil linseg(0.036098734231,ilen, 0),7644.28710938*ipitch
+ ao[262] oscil linseg(0.0256870008524,ilen, 0),2347.11914062*ipitch
+ ao[263] oscil linseg(0.0285614985471,ilen, 0),4920.33691406*ipitch
+ ao[264] oscil linseg(0.0246086220738,ilen, 0),3951.34277344*ipitch
+ ao[265] oscil linseg(0.0210342122813,ilen, 0),8128.78417969*ipitch
+ xout sumarray(ao)
+endop \ No newline at end of file
diff --git a/site/udo/DEPRECATE_SORT_opcodes.udo b/site/udo/DEPRECATE_SORT_opcodes.udo
new file mode 100755
index 0000000..a942a3d
--- /dev/null
+++ b/site/udo/DEPRECATE_SORT_opcodes.udo
@@ -0,0 +1,211 @@
+; knearest nearest iarray, kvalue
+; get the nearest value to kvalue in the one-dimensional array iarray
+
+
+
+
+; aL, aR randpan asource
+; pan randomly (mono input)
+opcode randpan, aa, a
+ asource xin
+ ipan randfloat, 0, 1
+ xout asource * ipan, asource * (1 - ipan)
+endop
+
+
+; aL, aR randpan aL, aR
+; pan randomly (stereo input)
+opcode randpan, aa, aa
+ aL, aR xin
+ ipan randfloat, 0, 1
+ xout aL * ipan, aR * (1 - ipan)
+endop
+
+
+
+
+
+; Sfiltered[] directoryfiles Sdirectory, Sextension
+; get an array of files from Sdirectory with given extension Sextension (extension given without ".", and case insensitive)
+; does the same as csound's directory opcode but without false positives (ie, ".wav" would return ".wav.asd"
+opcode directoryfiles, S[], SS
+ Sdirectory, Sextension xin
+ Sextension strlower Sextension
+ Sfiles[] directory Sdirectory
+ Sfiltered[] init 1
+ index = 0
+ while (index < lenarray(Sfiles)) do
+ Sfile = Sfiles[index]
+ Sfileext fileextension Sfile
+ Sfileext strlower Sfileext
+ isame strcmp Sfileext, Sextension
+ if (isame == 0) then
+ if (index == 0) then
+ Sfiltered[index] = Sfile
+ else
+ Sfiltered[] arrayappend Sfiltered, Sfile
+ endif
+ endif
+ index += 1
+ od
+ xout Sfiltered
+endop
+
+
+; Selected randarrayext Sfiles[], Sext
+; get a random file from Sfiles[] where the extension is Sext
+; directory opcode does not do this correctly (would select file.wav.asd when ".wav" is supplied as extension)
+opcode randarrayext, S, S[]S
+ Sfiles[], Sext xin
+ Selected = "default"
+ ifindex = 0
+ while (ifindex < lenarray(Sfiles)) do
+ index random 0, lenarray(Sfiles) - 1
+ Sfile = Sfiles[index]
+ Sfileext fileextension Sfile
+ isame strcmp Sfileext, Sext
+ if (isame == 0) then
+ Selected = Sfile
+ goto return
+ endif
+ ifindex += 1
+ od
+return:
+ xout Selected
+endop
+
+
+; Spath randfile Spath, Sextension
+opcode randfile, S, SS
+ Sdirectory, Sextension xin
+ Sfiles[] directory Sdirectory
+ Spath randarrayext Sfiles, Sextension
+ xout Spath
+endop
+
+
+; aL, aR sndplay Spath, ipitch, ioffset=0
+; play a sound file with mono/stereo detection using pitch and optional offset
+opcode sndplay, aa, Sio
+ Spath, ipitch, ioffset xin
+ ichn filenchnls Spath
+ if (ichn == 1) then
+ aL diskin2 Spath, ipitch, ioffset
+ aR = aL
+ else
+ aL, aR diskin2 Spath, ipitch, ioffset
+ endif
+ xout aL, aR
+endop
+
+
+; aL, aR randplay Sbasepath, ipitch
+; play a random wav file from Sbasepath with pitch control and mono/stereo detection
+opcode randplay, aa, Si
+ Sbase, ipitch xin
+ Spath randfile Sbase, "wav"
+ aL, aR sndplay Spath, ipitch
+ xout aL, aR
+endop
+
+
+; ifn filetotable Spath, ichannel=0
+; read a sound file to a GEN01 table and return the ftable. global tracking of ftables so the same sound is not created twice
+; optional channel to read from (defaults to 0 = all channels)
+gisoundfiletablemax = 2048 ; max number of files that can be loaded to ftables
+gisoundfiletableindex = 0 ; using long variable names as they're global to reduce chance of a user collision
+gSsoundfiletables[] init gisoundfiletablemax ; maybe replace with arrayappend depending on performance
+opcode filetotable, i, So
+ Spath, ichannel xin
+ iftincrement = 20 ; ftables will start being created at this ftable number
+ ifn = 0
+ index = 0
+
+ ; check if the sound exists in a table already, if so return that f number
+ iarraymax = lenarray(gSsoundfiletables)
+ while (index < iarraymax) do
+ isame strcmp gSsoundfiletables[index], Spath
+ if (isame == 0) then
+ ifn = index + iftincrement
+ goto return
+ endif
+ index += 1
+ od
+
+ ; get file information
+ iseconds filelen Spath
+ isr filesr Spath
+ ibits filebit Spath ; just assuming 16 (4), maybe use this later if required
+ isize = round(iseconds * isr) - 1
+
+ ; create the GEN01 ftable
+ ifn = gisoundfiletableindex + iftincrement
+ ix ftgen ifn, 0, isize, 1, Spath, 0, 4, ichannel
+ gSsoundfiletables[gisoundfiletableindex] = Spath
+ gisoundfiletableindex += 1
+ goto return
+
+return:
+ xout ifn
+endop
+
+
+; isamples[], itimes[] loadallwavtotables Sfiles[], ichannel=0
+; load all wav files in Sfiles array to ftables, providing the ftable numbers in isamples[] and the file length in itimes[]
+; default channel=0, ie read all channels
+opcode filestotables, i[]i[], S[]o
+ Sfiles[], ichannel xin
+ ilen = lenarray(Sfiles)
+ isamples[] init lenarray(Sfiles)
+ itimes[] init lenarray(Sfiles)
+ index = 0
+ while (index < lenarray(Sfiles)) do
+ ifn filetotable Sfiles[index], ichannel
+ isamples[index] = ifn
+ ifilesr filesr Sfiles[index]
+ if (ichannel != 0) then
+ itimemul = 1
+ else
+ itimemul = 0.5
+ endif
+ itimes[index] = (ftlen(ifn) / ifilesr) * itimemul ; sr
+ index += 1
+ od
+ xout isamples, itimes
+endop
+
+
+; isamples[], itimes[] loadallwavtotables Spath, ichannel=0
+; load all wav files in Spath to ftables, providing the ftable numbers in isamples[] and the file length in itimes[]
+; default channel=0, ie read all channels
+opcode loadallwavtotables, i[]i[], So
+ Spath, ichannel xin
+ Sfiles[] directoryfiles Spath, "wav"
+ isamples[], itimes[] filestotables Sfiles, ichannel
+ xout isamples, itimes
+endop
+
+
+; aL, aR rndpanlpfplay iamp, ifn, iduration, kpitch, [ilpfmin=300], [ilpfmax=12000]
+; play a sound in ftable number ifn for duration iduraction with amp iamp and pitch kpitch
+; apply a random panning amount, and a random low pass filter constrained to within optional parameters ilpfmin and ilpfmax
+opcode rndpanlpfplay, aa, iiikjj
+ iamp, ifn, iduration, kpitch, ilpfmin, ilpfmax xin
+ if (ilpfmin == -1) then
+ ilpfmin = 300
+ endif
+
+ if (ilpfmax == -1) then
+ ilpfmax = 12000
+ endif
+
+ ain loscil iamp, kpitch, ifn, 1
+ ilpffreq random ilpfmin, ilpfmax
+ afilt butterlp ain, ilpffreq
+ kamp linseg 1, iduration * 0.95, 1, iduration * 0.05, 0
+ aout = afilt * kamp
+ aL, aR randpan aout
+ xout aL, aR
+endop
+
+
diff --git a/site/udo/_TESTS/feedback_test.csd b/site/udo/_TESTS/feedback_test.csd
new file mode 100755
index 0000000..2049acb
--- /dev/null
+++ b/site/udo/_TESTS/feedback_test.csd
@@ -0,0 +1,41 @@
+<CsoundSynthesizer>
+<CsOptions>
+-odac
+;-+rtmidi=alsa -Q hw:1 -M hw:1 -odac
+</CsOptions>
+<CsInstruments>
+sr = 48000
+ksmps= 1
+nchnls = 2
+0dbfs = 1
+seed 0
+
+#include "feedback.udo"
+
+instr 2
+ kp1[] init 9
+ kp2[] init 9
+ kdx = 0
+ while (kdx < lenarray(kp1)) do
+ if (random:k(0, 1) > 0.999) then
+ kp1[kdx] random 0, 0.9
+ endif
+ if (random:k(0, 1) > 0.999) then
+ kp2[kdx] random 0, 0.9
+ endif
+ kdx += 1
+ od
+
+ ao fbk_mixer2 kp1, kp2
+ outs ao, ao
+endin
+
+
+
+</CsInstruments>
+<CsScore>
+i2 0 3600
+
+
+</CsScore>
+</CsoundSynthesizer> \ No newline at end of file
diff --git a/site/udo/_TESTS/mixer_emulator.csd b/site/udo/_TESTS/mixer_emulator.csd
new file mode 100755
index 0000000..7f8fe3e
--- /dev/null
+++ b/site/udo/_TESTS/mixer_emulator.csd
@@ -0,0 +1,132 @@
+<CsoundSynthesizer>
+<CsOptions>
+-odac8
+;--env:INCDIR="D:/Documents/Csound/_UDO"
+--omacro:HOST=AUDIO
+;-m0
+</CsOptions>
+<CsInstruments>
+sr = 48000
+kr = 48000
+nchnls = 2
+0dbfs = 1
+seed 0
+
+#include "feedback.udo"
+
+gimaxeq init 1.5
+gimaxsend init 1.5
+gimaxamp init 1.5
+
+
+opcode FLchannel, ik[], ii
+ ix, ichannumber xin
+ iwidth = 100
+ iy = -50
+ kp[] init 14
+ ih1 FLbox sprintf("Channel %d", ichannumber), 1, 2, 14, iwidth*2, 50, ix, iy+50
+ kp[0], ih2 FLbutton "Low Cut", 1, 0, 2, iwidth, 30, ix, iy+100, -1
+ kp[8], ihx FLbutton "CModel", 1, 0, 2, iwidth, 30, ix+100, iy+100, -1
+
+ kp[1], ih3 FLknob "High", 0, gimaxeq, 0, 1, -1, 70, ix+15, iy+130
+ kp[2], ih4 FLknob "Mid", 0, gimaxeq, 0, 1, -1, 70, ix+15, iy+215
+ kp[3], ih5 FLknob "Low", 0, gimaxeq, 0, 1, -1, 70, ix+15, iy+300
+
+ FLsetColor 0, 0, 128, ih3
+ FLsetColor 0, 0, 128, ih4
+ FLsetColor 0, 0, 128, ih5
+ FLsetVal_i gimaxeq*0.5, ih3
+ FLsetVal_i gimaxeq*0.5, ih4
+ FLsetVal_i gimaxeq*0.5, ih5
+
+ kp[4], ih6 FLknob "Aux1", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+130
+ kp[5], ih7 FLknob "Aux2", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+215
+ kp[6], ih8 FLknob "Aux3", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+300
+ kp[7], ih9 FLknob "Aux4", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+385
+
+ FLsetColor 128, 0, 0, ih6
+ FLsetColor 128, 0, 0, ih7
+ FLsetColor 128, 0, 0, ih8
+ FLsetColor 128, 0, 0, ih9
+
+
+ kp[8], ih10 FLbutton "Pre", 1, 0, 2, 45, 70, ix+155, iy+130, -1
+ kp[9], ih11 FLbutton "Pre", 1, 0, 2, 45, 70, ix+155, iy+215, -1
+ kp[10], ih12 FLbutton "Pre", 1, 0, 2, 45, 70, ix+155, iy+300, -1
+ kp[11], ih13 FLbutton "Pre", 1, 0, 2, 45, 70, ix+155, iy+385, -1
+
+ kp[12], ih14 FLslider "Gain", gimaxamp, 0, 0, 6, -1, 50, 200, ix+25, iy+385
+
+ kp[13], ih15 FLbutton "Mute", 1, 0, 2, 70, 25, ix+80, iy+560, -1
+ ix += 200
+
+
+ xout ix, kp
+endop
+
+instr controller
+ ix = 0
+ iy = 0
+ FLpanel "FBMix", 1024, 600
+ ix, gkc1[] FLchannel ix, 1
+ ix, gkc2[] FLchannel ix, 2
+ ix, gkc3[] FLchannel ix, 3
+ ix, gkc4[] FLchannel ix, 4
+ FLpanelEnd
+ FLrun
+
+ event_i "i", "mixer", 0, p3
+endin
+
+
+
+instr mixer
+ ain1 init 0
+ ain2 init 0
+ ain3 init 0
+ ain4 init 0
+
+ aout1 = (gkc1[13] == 0) ? fbk_channel(ain1, gkc1[0], gkc1[3], gkc1[2], gkc1[1], gkc1[8]) : 0
+ aout2 = (gkc2[13] == 0) ? fbk_channel(ain2, gkc2[0], gkc2[3], gkc2[2], gkc2[1], gkc2[8]) : 0
+ aout3 = (gkc3[13] == 0) ? fbk_channel(ain3, gkc3[0], gkc3[3], gkc3[2], gkc3[1], gkc3[8]) : 0
+ aout4 = (gkc4[13] == 0) ? fbk_channel(ain4, gkc4[0], gkc4[3], gkc4[2], gkc4[1], gkc4[8]) : 0
+ ain1 = 0
+ ain2 = 0
+ ain3 = 0
+ ain4 = 0
+
+ /* sends 1 */
+ ain1 = ain1 + ((gkc1[8] == 1) ? aout1*gkc1[4] : aout1*gkc1[4]*gkc1[12])
+ ain2 = ain2 + ((gkc1[9] == 1) ? aout1*gkc1[5] : aout1*gkc1[5]*gkc1[12])
+ ain3 = ain3 + ((gkc1[10] == 1) ? aout1*gkc1[6] : aout1*gkc1[6]*gkc1[12])
+ ain4 = ain4 + ((gkc1[11] == 1) ? aout1*gkc1[7] : aout1*gkc1[7]*gkc1[12])
+
+ /* sends 2 */
+ ain1 = ain1 + ((gkc2[8] == 1) ? aout2*gkc2[4] : aout1*gkc2[4]*gkc2[12])
+ ain2 = ain2 + ((gkc2[9] == 1) ? aout2*gkc2[5] : aout1*gkc2[5]*gkc2[12])
+ ain3 = ain3 + ((gkc2[10] == 1) ? aout2*gkc2[6] : aout1*gkc2[6]*gkc2[12])
+ ain4 = ain4 + ((gkc2[11] == 1) ? aout2*gkc2[7] : aout1*gkc2[7]*gkc2[12])
+
+ /* sends 3 */
+ ain1 = ain1 + ((gkc3[8] == 1) ? aout3*gkc3[4] : aout1*gkc3[4]*gkc3[12])
+ ain2 = ain2 + ((gkc3[9] == 1) ? aout3*gkc3[5] : aout1*gkc3[5]*gkc3[12])
+ ain3 = ain3 + ((gkc3[10] == 1) ? aout3*gkc3[6] : aout1*gkc3[6]*gkc3[12])
+ ain4 = ain4 + ((gkc3[11] == 1) ? aout3*gkc3[7] : aout1*gkc3[7]*gkc3[12])
+
+ /* sends 4 */
+ ain1 = ain1 + ((gkc4[8] == 1) ? aout4*gkc4[4] : aout1*gkc4[4]*gkc4[12])
+ ain2 = ain2 + ((gkc4[9] == 1) ? aout4*gkc4[5] : aout1*gkc4[5]*gkc4[12])
+ ain3 = ain3 + ((gkc4[10] == 1) ? aout4*gkc4[6] : aout1*gkc4[6]*gkc4[12])
+ ain4 = ain4 + ((gkc4[11] == 1) ? aout4*gkc4[7] : aout1*gkc4[7]*gkc4[12])
+
+
+ aout = (aout1*gkc1[12]) + (aout2*gkc2[12]) + (aout3*gkc3[12]) + (aout4*gkc4[12])
+ aout *= 0.25
+ outs aout, aout
+endin
+
+</CsInstruments>
+<CsScore>
+i"controller" 0 3600
+</CsScore>
+</CsoundSynthesizer> \ No newline at end of file
diff --git a/site/udo/_TESTS/mixer_emulator_1604a.csd b/site/udo/_TESTS/mixer_emulator_1604a.csd
new file mode 100755
index 0000000..03ebeed
--- /dev/null
+++ b/site/udo/_TESTS/mixer_emulator_1604a.csd
@@ -0,0 +1,144 @@
+<CsoundSynthesizer>
+<CsOptions>
+-odac
+;--env:INCDIR="D:/Documents/Csound/_UDO"
+--omacro:HOST=AUDIO
+;-m0
+</CsOptions>
+<CsInstruments>
+sr = 48000
+ksmps = 8
+nchnls = 2
+0dbfs = 1
+seed 0
+
+#include "feedback.udo"
+
+gimaxeq init 2
+gimaxsend init 2
+gimaxamp init 1.5
+
+
+opcode FLchannel, ik[], ii
+ ix, ichannumber xin
+ iwidth = 100
+ iy = -50
+ kp[] init 14
+ ih1 FLbox sprintf("Channel %d", ichannumber), 1, 2, 14, iwidth*2, 50, ix, iy+50
+ kp[0], ih2 FLbutton "Low Cut", 1, 0, 2, iwidth, 30, ix, iy+100, -1
+ kp[8], ihx FLbutton "CModel", 1, 0, 2, iwidth, 30, ix+100, iy+100, -1
+
+ kp[1], ih3 FLknob "High", 0, gimaxeq, 0, 1, -1, 70, ix+15, iy+130
+ kp[2], ih4 FLknob "Mid", 0, gimaxeq, 0, 1, -1, 70, ix+15, iy+215
+ kp[3], ih5 FLknob "Low", 0, gimaxeq, 0, 1, -1, 70, ix+15, iy+300
+
+ FLsetColor 0, 0, 128, ih3
+ FLsetColor 0, 0, 128, ih4
+ FLsetColor 0, 0, 128, ih5
+ FLsetVal_i gimaxeq*0.5, ih3
+ FLsetVal_i gimaxeq*0.5, ih4
+ FLsetVal_i gimaxeq*0.5, ih5
+
+ kp[4], ih6 FLknob "Aux1", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+130
+ kp[5], ih7 FLknob "Aux2", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+215
+ ;kp[6], ih8 FLknob "Aux3", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+300
+ ;kp[7], ih9 FLknob "Aux4", 0, gimaxsend, 0, 1, -1, 70, ix+85, iy+385
+
+ FLsetColor 128, 0, 0, ih6
+ FLsetColor 128, 0, 0, ih7
+ ;FLsetColor 128, 0, 0, ih8
+ ;FLsetColor 128, 0, 0, ih9
+
+
+
+ kp[8], ih10 FLbutton "Pre", 1, 0, 2, 45, 70, ix+155, iy+130, -1
+ kp[9], ih11 FLbutton "CModel", 1, 0, 2, 45, 70, ix+155, iy+215, -1
+ ;kp[10], ih12 FLbutton "Pre", 1, 0, 2, 45, 70, ix+155, iy+300, -1
+ ;kp[11], ih13 FLbutton "Pre", 1, 0, 2, 45, 70, ix+155, iy+385, -1
+
+ kp[12], ih14 FLslider "Gain", gimaxamp, 0, 0, 6, -1, 50, 200, ix+25, iy+385
+
+ kp[13], ih15 FLbutton "Mute", 1, 0, 2, 70, 25, ix+80, iy+560, -1
+ ix += 200
+
+
+ xout ix, kp
+endop
+
+instr controller
+ ix = 0
+ iy = 0
+ FLpanel "FBMix", 1024, 600
+ ix, gkc1[] FLchannel ix, 1
+ ix, gkc2[] FLchannel ix, 2
+ ix, gkc3[] FLchannel ix, 3
+ ix, gkc4[] FLchannel ix, 4
+ FLpanelEnd
+ FLrun
+
+ event_i "i", "mixer", 0, p3
+endin
+
+
+
+instr mixer
+ icrosstalk[] fillarray 0.0001, 0.001
+ ain1 init 0
+ ain2 init 0
+ ain3 init 0
+ ain4 init 0
+
+ aout1 = fbk_channel(ain1, gkc1[0], gkc1[3], gkc1[2], gkc1[1], gkc1[9])
+ aout2 = fbk_channel(ain2, gkc2[0], gkc2[3], gkc2[2], gkc2[1], gkc2[9])
+ aout3 = fbk_channel(ain3, gkc3[0], gkc3[3], gkc3[2], gkc3[1], gkc3[9])
+ aout4 = fbk_channel(ain4, gkc4[0], gkc4[3], gkc4[2], gkc4[1], gkc4[9])
+ ain1 = 0
+ ain2 = 0
+ ain3 = 0
+ ain4 = 0
+
+ /* crosstalk */
+ ain1 += ain2 * random:k(icrosstalk[0], icrosstalk[1])
+ ain2 += (ain1 * random:k(icrosstalk[0], icrosstalk[1])) + (ain3 * random:k(icrosstalk[0], icrosstalk[1]))
+ ain3 += (ain2 * random:k(icrosstalk[0], icrosstalk[1])) + (ain4 * random:k(icrosstalk[0], icrosstalk[1]))
+ ain4 += ain3 * random:k(icrosstalk[0], icrosstalk[1])
+
+ /* sends chan 1 */
+ ain1 = ain1 + ((gkc1[8] == 1) ? aout1*gkc1[4] : aout1*gkc1[4]*gkc1[12])
+ ain2 = ain2 + aout1*gkc1[5]*gkc1[12]
+ if (gkc1[13] == 1) then
+ ain3 = ain3 + aout1
+ aout1 = 0
+ endif
+
+
+ /* sends chan 2 */
+ ain1 = ain1 + ((gkc2[8] == 1) ? aout2*gkc2[4] : aout2*gkc2[4]*gkc2[12])
+ ain2 = ain2 + aout2*gkc2[5]*gkc2[12]
+ if (gkc2[13] == 1) then
+ ain3 = ain3 + aout2
+ aout2 = 0
+ endif
+
+
+ /* sends chan 3 */
+ ain1 = ain1 + ((gkc3[8] == 1) ? aout3*gkc3[4] : aout3*gkc3[4]*gkc3[12])
+ ain2 = ain2 + aout3*gkc3[5]*gkc3[12]
+ ; ...
+
+ /* sends chan 4 */
+ ain1 = ain1 + ((gkc4[8] == 1) ? aout4*gkc4[4] : aout4*gkc4[4]*gkc4[12])
+ ain2 = ain2 + aout4*gkc4[5]*gkc4[12]
+ ; ...
+
+
+ aout = (aout1*gkc1[12]) + (aout2*gkc2[12]) + (aout3*gkc3[12]) + (aout4*gkc4[12])
+ aout *= 0.25
+ outs aout, aout
+endin
+
+</CsInstruments>
+<CsScore>
+i"controller" 0 3600
+</CsScore>
+</CsoundSynthesizer> \ No newline at end of file
diff --git a/site/udo/__config__.dist.udo b/site/udo/__config__.dist.udo
new file mode 100755
index 0000000..6acbea9
--- /dev/null
+++ b/site/udo/__config__.dist.udo
@@ -0,0 +1,44 @@
+#ifndef UDO_CONFIG
+#define UDO_CONFIG ##
+/*
+ SONICS config
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022, 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+; database: allow macro overrides from command line or pre-include etc
+#ifndef PGDB_HOST
+#define PGDB_HOST ##
+#endif
+
+#ifndef PGDB_NAME
+#define PGDB_NAME ##
+#endif
+
+#ifndef PGDB_USER
+#define PGDB_USER ##
+#endif
+
+#ifndef PGDB_PASSWORD
+#define PGDB_PASSWORD ##
+#endif
+
+
+; local AudioOrganiser database
+#ifndef AODB_PATH_WIN
+#define AODB_PATH_WIN ##
+#endif
+
+#ifndef AODB_PATH_LINUX
+#define AODB_PATH_LINUX ##
+#endif
+
+
+; FFT defaults
+giFFTsize = 1024
+giFFTwinFactor = 4
+
+#end
diff --git a/site/udo/addsub.udo b/site/udo/addsub.udo
new file mode 100755
index 0000000..b95ef0c
--- /dev/null
+++ b/site/udo/addsub.udo
@@ -0,0 +1,170 @@
+#ifndef UDO_ADDSUB
+#define UDO_ADDSUB ##
+/*
+ Stochastic additive and subtractive instruments
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Subtractive synthesiser
+ Operating between ifreq and ifreqmax, this recurses with a frequency step of *ifreqstepmult*ifreqstepmultrand and amplitude multiplier iampmult with each step
+
+ aout as_subtractive asrc, ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult
+
+ aout output audio
+ asrc source sound, can be noise or anything less
+ ifreq minimum frequency
+ ifreqmax maximum frequency
+ ifreqstepmult frequency multiplier for each step
+ ifreqstepmultrand frequency multiplier random for each step
+ iamp amplitude
+ iampmult amplitude multiplier random for each step
+*/
+opcode as_subtractive, a, aiiiiii
+ asrc, ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult xin
+ if (random(0, 1) >= 0.5) then
+ afreqmult linseg random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1)
+ else
+ afreqmult init 1
+ endif
+
+ if (random(0, 1) >= 0.5) then
+ istartmult = random(0.9, 1.1)
+ iendmult = random(0.9, 1.1)
+ if (random(0, 1) >= 0.5) then
+ afreqmult2 linseg istartmult, p3, iendmult
+ else
+ afreqmult2 expseg istartmult, p3, iendmult
+ endif
+ afreqmult *= afreqmult2
+ endif
+
+ aamp linseg random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9)
+
+ if (random(0, 1) >= 0.5) then
+ if (random(0, 1) >= 0.5) then
+ if (random(0, 1) >= 0.5) then
+ aamp2 linseg 1, p3, 0
+ else
+ aamp2 expseg 1, p3, 0.00001
+ endif
+ else
+ if (random(0, 1) >= 0.5) then
+ aamp2 linseg 0, p3, 1
+ else
+ aamp2 expseg 0.00001, p3, 1
+ endif
+ endif
+ aamp *= aamp2
+ endif
+
+ abw linseg random(1, 10), p3*0.1, random(1, 10), p3*0.1, \
+ random(1, 10), p3*0.1, random(1, 10), p3*0.1, \
+ random(1, 10), p3*0.1, random(1, 10), p3*0.1, \
+ random(1, 10), p3*0.1, random(1, 10), p3*0.1, \
+ random(1, 10), p3*0.1, random(1, 10)
+
+ amin init 50
+ amax init 22000
+ afreq = max:a(min:a(ifreq * afreqmult, amax), amin)
+
+ asub butterbp asrc, afreq, abw
+ asub butterbp asub, afreq, abw
+ asub balance asub, asrc
+ asub *= iamp * aamp
+
+ istep = ifreq * ifreqstepmult * random(1, ifreqstepmultrand)
+ if (istep <= ifreqmax) then
+ asubr as_subtractive asrc, istep, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp * iampmult, iampmult
+ asub += asubr
+ endif
+
+ xout asub
+endop
+
+
+/*
+ Additive synthesiser
+ Operating between ifreq and ifreqmax, this recurses with a frequency step of *ifreqstepmult*ifreqstepmultrand and amplitude multiplier iampmult with each step
+
+ aout as_additive ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult [, index=0]
+
+ aout output audio
+ ifreq minimum frequency
+ ifreqmax maximum frequency
+ ifreqstepmult frequency multiplier for each step
+ ifreqstepmultrand frequency multiplier random for each step
+ iamp amplitude
+ iampmult amplitude multiplier random for each step
+ index internal recursion tracking
+*/
+opcode as_additive, a, iiiiiio
+ ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult, index xin
+ if (random(0, 1) >= 0.5) then
+ kfreqmult linseg random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1), p3*0.1, \
+ random(0.9, 1.1), p3*0.1, random(0.9, 1.1)
+ else
+ kfreqmult init 1
+ endif
+
+
+ kamp linseg random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9), p3*0.1, \
+ random(0.1, 0.9), p3*0.1, random(0.1, 0.9)
+
+ if (random(0, 1) >= 0.5) then
+ if (random(0, 1) >= 0.5) then
+ if (random(0, 1) >= 0.5) then
+ kamp2 linseg 1, p3, 0
+ else
+ kamp2 expseg 1, p3, 0.00001
+ endif
+ else
+ if (random(0, 1) >= 0.5) then
+ kamp2 linseg 0, p3, 1
+ else
+ kamp2 expseg 0.00001, p3, 1
+ endif
+ endif
+ kamp *= kamp2
+ endif
+
+ if (random(0, 1) >= 0.5) then
+ istartmult = random(0.9, 1.1)
+ iendmult = random(0.9, 1.1)
+ if (random(0, 1) >= 0.5) then
+ kfreqmult2 linseg istartmult, p3, iendmult
+ else
+ kfreqmult2 expseg istartmult, p3, iendmult
+ endif
+ kfreqmult *= kfreqmult2
+ endif
+
+ aosc oscil iamp * kamp, ifreq * kfreqmult
+
+ istep = ifreq * ifreqstepmult * random(1, ifreqstepmultrand)
+ if (istep <= ifreqmax && index < 256) then ; 256 max recursion guard
+ aoscr as_additive istep, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp * iampmult, iampmult, index + 1
+ aosc += aoscr
+ endif
+ xout aosc
+endop
+
+#end
diff --git a/site/udo/aodb.udo b/site/udo/aodb.udo
new file mode 100755
index 0000000..c81c233
--- /dev/null
+++ b/site/udo/aodb.udo
@@ -0,0 +1,80 @@
+#ifndef UDO_AODB
+#define UDO_AODB ##
+/*
+ AudioOrganiser SQLite connection and tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "__config__.udo"
+#include "host_tools.udo"
+#define USING_AODB ##
+
+if (gihost_type == 0) then
+ giaodb dbconnect "sqlite", "$AODB_PATH_WIN"
+else
+ giaodb dbconnect "sqlite", "$AODB_PATH_LINUX"
+endif
+
+opcode _aodb_formatquery, S, Sip
+ Slike, irandomorder, inumber xin
+ xout sprintf("SELECT id, path FROM audiofile WHERE path LIKE 'd:\\Samples\\%%%s%%' %s LIMIT %d", Slike, (irandomorder == 1 ? "ORDER BY RANDOM()" : ""), inumber)
+endop
+
+
+opcode aodb_getsample, iS, Sp
+ Slike, irandomorder xin
+ Sres[][] dbarray giaodb, _aodb_formatquery(Slike, irandomorder)
+ id strtod Sres[0][0]
+ Spath = Sres[0][1]
+ xout id, Spath
+endop
+
+opcode aodb_getsample, S, Sp
+ Slike, irandomorder xin
+ id, Spath aodb_getsample Slike, irandomorder
+ xout Spath
+endop
+
+opcode aodb_loadsample, i, Spp
+ Slike, irandomorder, imono xin
+ Spath aodb_getsample Slike, irandomorder
+ ifn ftgen 0, 0, 0, 1, Spath, 0, 0, imono
+ xout ifn
+endop
+
+opcode aodb_diskinsample, aa, Sp
+ Slike, irandomorder xin
+ id, Spath aodb_getsample Slike, irandomorder
+ ichannels filenchnls Spath
+ if (ichannels == 1) then
+ aL diskin Spath, 1
+ aR = aL
+ else
+ aL, aR diskin Spath, 1
+ endif
+ xout aL, aR
+endop
+
+
+opcode aodb_getsamples, S[], Sip
+ Slike, inumber, irandomorder xin
+ Sout[] init inumber
+ Sres[][] dbarray giaodb, _aodb_formatquery(Slike, irandomorder, inumber)
+ index = 0
+ while (index < lenarray(Sres)) do
+ Sout[index] = Sres[index][1]
+ index += 1
+ od
+ xout Sout
+endop
+
+opcode aodb_getsamplebyid, S, i
+ id xin
+ Sres dbscalar giaodb, sprintf("SELECT path FROM audiofile WHERE id = %d", id)
+ xout Sres
+endop
+
+#end
diff --git a/site/udo/array_3d.udo b/site/udo/array_3d.udo
new file mode 100755
index 0000000..845ecb6
--- /dev/null
+++ b/site/udo/array_3d.udo
@@ -0,0 +1,132 @@
+/*
+ Virtual 3D array behaviour
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Initialise 3D array
+
+ ifn arr3d_init idim1, idim2, idim3
+
+ ifn resulting ftable
+ idim1 size of dimension 1
+ idim2 size of dimension 2
+ idim3 size of dimension 3
+
+*/
+opcode arr3d_init, i, iii
+ idim1, idim2, idim3 xin
+ xout ftgen(0, 0, -((idim1 * idim2 * idim3) + 3), -2, idim1, idim2, idim3)
+endop
+
+/*
+ Obtain 1D index from 3D reference
+
+ index arr3d_index ifn, idx1, idx2, idx3
+
+ index resulting index
+ idx1 dimension 1 index
+ idx2 dimension 2 index
+ idx3 dimension 3 index
+*/
+opcode arr3d_index, i, iiii
+ ifn, idx1, idx2, idx3 xin
+ i1len tab_i 0, ifn
+ i2len tab_i 1, ifn
+ i3len tab_i 2, ifn
+ ;xout ((i1len * idx2 * idx3) + (i2len * idx3) + i3len) + 3
+ xout ((idx1 * i2len * i3len) + (idx2 * i3len) + idx3) + 3
+endop
+
+/*
+ Obtain 1D index from 3D reference
+
+ kindex arr3d_index ifn, kdx1, kdx2, kdx3
+
+ kindex resulting index
+ kdx1 dimension 1 index
+ kdx2 dimension 2 index
+ kdx3 dimension 3 index
+*/
+opcode arr3d_index, k, ikkk
+ ifn, kdx1, kdx2, kdx3 xin
+ k1len tab 0, ifn
+ k2len tab 1, ifn
+ k3len tab 2, ifn
+ ;xout ((k1len * kdx2 * kdx3) + (k2len * kdx3) + k3len) + 3
+ xout ((kdx1 * k2len * k3len) + (kdx2 * k3len) + kdx3) + 3
+endop
+
+
+
+/*
+ Set 3D array value
+
+ arr3d_set ifn, idx1, idx2, idx3, ivalue
+
+ ifn ftable created with arr3d_init
+ idx1 dimension 1 index
+ idx2 dimension 2 index
+ idx3 dimension 3 index
+ ivalue value to set
+*/
+opcode arr3d_set, 0, iiiii
+ ifn, idx1, idx2, idx3, ivalue xin
+ tabw_i ivalue, arr3d_index:i(ifn, idx1, idx2, idx3), ifn
+endop
+
+/*
+ Set 3D array value
+
+ arr3d_set ifn, kdx1, kdx2, kdx3, kvalue
+
+ ifn ftable created with arr3d_init
+ kdx1 dimension 1 index
+ kdx2 dimension 2 index
+ kdx3 dimension 3 index
+ kvalue value to set
+*/
+
+opcode arr3d_set, 0, ikkkk
+ ifn, kdx1, kdx2, kdx3, kvalue xin
+ tabw kvalue, arr3d_index:k(ifn, kdx1, kdx2, kdx3), ifn
+endop
+
+
+/*
+ Get 3D array value
+
+ ivalue arr3d_get ifn, idx1, idx2, idx3
+
+ ivalue returned value
+ ifn ftable created with arr3d_init
+ idx1 dimension 1 index
+ idx2 dimension 2 index
+ idx3 dimension 3 index
+*/
+opcode arr3d_get, i, iiii
+ ifn, idx1, idx2, idx3 xin
+ xout tab_i(arr3d_index:i(ifn, idx1, idx2, idx3), ifn)
+endop
+
+
+/*
+ Get 3D array value
+
+ kvalue arr3d_get ifn, kdx1, kdx2, kdx3
+
+ kvalue returned value
+ ifn ftable created with arr3d_init
+ kdx1 dimension 1 index
+ kdx2 dimension 2 index
+ kdx3 dimension 3 index
+*/
+opcode arr3d_get, k, ikkk
+ ifn, kdx1, kdx2, kdx3 xin
+ xout tab:k(arr3d_index:k(ifn, kdx1, kdx2, kdx3), ifn)
+endop
+
diff --git a/site/udo/array_tools.udo b/site/udo/array_tools.udo
new file mode 100755
index 0000000..7934614
--- /dev/null
+++ b/site/udo/array_tools.udo
@@ -0,0 +1,235 @@
+#ifndef UDO_ARRAYTOOLS
+#define UDO_ARRAYTOOLS ##
+/*
+ Array tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+giassocarrayvalues[] init 1
+gSassocarraykeys[] init 1
+
+
+/*
+ Get a random value from an array
+
+ ivalue arr_random iarray[]
+ kvalue arr_random iarray[]
+ Svalue arr_random Sarray[]
+
+ ivalue selected value
+ kvalue selected value
+ iarray[] array to evaluate
+ Sarray[] string array to evaluate
+*/
+opcode arr_random, i, i[]
+ iarray[] xin
+ ivalue = iarray[round(random(0, lenarray(iarray) - 1))]
+ xout ivalue
+endop
+
+opcode arr_random, S, S[]
+ Sarray[] xin
+ Svalue = Sarray[round(random(0, lenarray(Sarray) - 1))]
+ xout Svalue
+endop
+
+opcode arr_random, k, i[]
+ iarray[] xin
+ kvalue = iarray[round:k(random:k(0, lenarray(iarray) - 1))]
+ xout kvalue
+endop
+
+
+
+/*
+ Append to global associative array
+*/
+opcode asarr_append, 0, Si
+ Skey, ivalue xin
+ index = 0
+ ilen = lenarray(giassocarrayvalues)
+ Snewkeys[] init (ilen + 1)
+ inewvals[] init (ilen + 1)
+ while (index < ilen) do
+ Snewkeys[index] = gSassocarraykeys[index]
+ inewvals[index] = giassocarrayvalues[index]
+ index += 1
+ od
+ Snewkeys[index] = Skey
+ inewvals[index] = ivalue
+
+ giassocarrayvalues = inewvals
+ gSassocarraykeys = Snewkeys
+endop
+
+
+opcode asarr_get, i, S
+ Skey xin
+ index = 0
+ ivalue = -1
+ ilen = lenarray(giassocarrayvalues)
+ while (index < ilen) do
+ isame strcmp Skey, gSassocarraykeys[index]
+ if (isame == 0) then
+ ivalue = giassocarrayvalues[index]
+ goto output
+ endif
+ index += 1
+ od
+output:
+ xout ivalue
+endop
+
+
+
+/*
+ Get the nearest value to kvalue in the one-dimensional array iarray, which should be sorted
+
+ knearest nearest iarray, kvalue
+
+ knearest the nearest value found
+
+*/
+opcode arr_nearest, k, i[]k
+ iarray[], kvalue xin
+ knearest = 0
+ kindex = 0
+
+ while (kindex < lenarray(iarray)) do
+ ktest = iarray[kindex]
+ if (abs(kvalue - ktest) < abs(kvalue - knearest)) then
+ knearest = ktest
+ endif
+ kindex += 1
+ od
+ xout knearest
+endop
+
+
+
+; Snew[] arrayappend Sarray, Svalue
+; extend a S array with a given value
+opcode arr_append, S[], S[]S
+ Sarray[], Svalue xin
+ index = 0
+ ilen = lenarray(Sarray)
+ Snew[] init (ilen + 1)
+ while (index < ilen) do
+ Snew[index] = Sarray[index]
+ index += 1
+ od
+ Snew[index] = Svalue
+ xout Snew
+endop
+
+
+; inew[] arrayappend iarray, ivalue
+; extend an i array with a given value
+opcode arr_append, i[], i[]i
+ iarray[], ivalue xin
+ index = 0
+ ilen = lenarray(iarray)
+ inew[] init (ilen + 1)
+ while (index < ilen) do
+ inew[index] = iarray[index]
+ index += 1
+ od
+ inew[index] = ivalue
+ xout inew
+endop
+
+
+; array, asfloat
+opcode arr_serialise, S, i[]p
+ iarray[], iasfloat xin
+ ilen = lenarray(iarray)
+ index = 0
+ SprintfChar = (iasfloat == 1) ? "%f" : "%d"
+ Sout = "["
+ while (index < ilen) do
+ if (index != 0) then
+ Sout = strcat(Sout, ",")
+ endif
+ Sout = strcat(Sout, sprintf(SprintfChar, iarray[index]))
+ index += 1
+ od
+ Sout = strcat(Sout, "]")
+ xout Sout
+endop
+
+
+; array
+opcode arr_serialise, S, S[]
+ Sarray[] xin
+ ilen = lenarray(Sarray)
+ index = 0
+ Sout = "["
+ while (index < ilen) do
+ if (index != 0) then
+ Sout = strcat(Sout, ",")
+ endif
+ Sout = strcat(Sout, sprintf("\"%s\"", Sarray[index]))
+ index += 1
+ od
+ Sout = strcat(Sout, "]")
+ xout Sout
+endop
+
+
+opcode arr_unserialise, S[], S
+ Sdata xin
+ ilen = strlen(Sdata)
+ ichar = 0
+ inarray = 0
+ instring = 0
+ iitems = 0
+ while (ichar < ilen) do
+ Schar = strsub(Sdata, ichar, ichar + 1)
+ if (instring == 0 && strcmp(Schar, "[") == 0) then
+ inarray = 1
+ elseif (inarray == 1 && instring == 0 && strcmp(Schar, "]") == 0) then
+ inarray = 0
+ elseif (inarray == 1 && instring == 0 && strcmp(Schar, ",") == 0) then
+ iitems += 1
+ elseif (inarray == 1 && strcmp(Schar, "\"") == 0) then
+ instring = 1 - instring
+ endif
+ ichar += 1
+ od
+
+ Sarray[] init iitems + 1
+ ichar = 0
+ inarray = 0
+ instring = 0
+ istringstart = -1
+ index = 0
+ while (ichar < ilen) do
+ Schar = strsub(Sdata, ichar, ichar + 1)
+ if (instring == 0 && strcmp(Schar, "[") == 0) then
+ inarray = 1
+ elseif (inarray == 1 && instring == 0 && strcmp(Schar, "]") == 0) then
+ inarray = 0
+ elseif (inarray == 1 && instring == 0 && strcmp(Schar, ",") == 0) then
+ index += 1
+ elseif (inarray == 1 && strcmp(Schar, "\"") == 0) then
+ if (instring == 0) then
+ instring = 1
+ istringstart = ichar + 1
+ else
+ instring = 0
+ Sarray[index] = strsub(Sdata, istringstart, ichar)
+ istringstart = -1
+ endif
+ endif
+ ichar += 1
+ od
+
+ xout Sarray
+endop
+
+
+
+#end
diff --git a/site/udo/autorecord.udo b/site/udo/autorecord.udo
new file mode 100755
index 0000000..c9f65a2
--- /dev/null
+++ b/site/udo/autorecord.udo
@@ -0,0 +1,108 @@
+#ifndef UDO_AUTORECORD
+#define UDO_AUTORECORD ##
+/*
+ Automatic sound file recorder using the monitor opcode
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "host_tools.udo"
+
+#ifndef AUTORECORD_PATH
+if (gihost_type == 0) then
+ gSautorecordPath = "d:/temp"
+else
+ gSautorecordPath = "/tmp"
+endif
+#else
+gSautorecordPath = "$AUTORECORD_PATH"
+#end
+
+
+/*
+ Auto file recorder
+ usage - either in score:
+
+ i"udo_autorecord" 0 5
+
+ or in instr globals:
+
+ autorecord 5
+
+*/
+
+#define MONTHIF(MS'MI) #
+ isame strcmp "$MS", Smonth
+ if (isame == 0) then
+ Smonth = $MI
+ goto monthdone
+ endif
+#
+
+opcode autorecord, 0, aap
+ aL, aR, istereo xin
+ itim date
+ Stim dates itim
+ Sday strsub Stim, 8, 10
+ Shour strsub Stim, 11, 13
+ Smin strsub Stim, 14, 16
+ Ssec strsub Stim, 17, 19
+ Syear strsub Stim, 20, 24
+ Smonth strsub Stim, 4, 7
+
+ ichr strchar Sday
+ if (ichr == 32) then
+ Sday2 strsub Sday, 1, 2
+ Sday strcat "0", Sday2
+ endif
+
+ $MONTHIF(Jan'"01")
+ $MONTHIF(Feb'"02")
+ $MONTHIF(Mar'"03")
+ $MONTHIF(Apr'"04")
+ $MONTHIF(May'"05")
+ $MONTHIF(Jun'"06")
+ $MONTHIF(Jul'"07")
+ $MONTHIF(Aug'"08")
+ $MONTHIF(Sep'"09")
+ $MONTHIF(Oct'"10")
+ $MONTHIF(Nov'"11")
+ $MONTHIF(Dec'"12")
+
+monthdone:
+ /*
+ Smkdir = "mkdir -p "
+ if (gihost_type != 0) then
+ Smkdir = strcat(Smkdir, "-p ")
+ endif
+ ; make day dir
+ Sdir sprintf "/%s%s%s", Syear, Smonth, Sday
+ prints Sdir
+ Sdaydir strcat Sbasedir, Sdir
+ Scmd strcat Smkdir, Sdaydir
+ ires system_i 1, Scmd
+ Sfile sprintf "/%s%s%s.wav", Shour, Smin, Ssec
+ Spath strcat Sdaydir, Sfile
+ */
+ Spath sprintf "%s/%s-%s-%s.%s-%s-%s.wav", gSautorecordPath, Syear, Smonth, Sday, Shour, Smin, Ssec
+ prints sprintf("\n----------------------------------------------------------------------------------------------------------\n Autorecording to %s\n----------------------------------------------------------------------------------------------------------\n", Spath)
+ if (istereo == 1) then
+ fout Spath, 14, aL, aR
+ else
+ fout Spath, 14, aL
+ endif
+endop
+
+opcode autorecord, 0, a
+ ain xin
+ autorecord ain, ain, 0
+endop
+
+instr autorecord
+ aL, aR monitor
+ autorecord aL, aR
+endin
+
+#end \ No newline at end of file
diff --git a/site/udo/bpmdetect.udo b/site/udo/bpmdetect.udo
new file mode 100755
index 0000000..6eb0f60
--- /dev/null
+++ b/site/udo/bpmdetect.udo
@@ -0,0 +1,57 @@
+#ifndef UDO_BPMDETECT
+#define UDO_BPMDETECT ##
+
+/*
+ BPM detection from trigger
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Detect a BPM given a stream of pulses
+
+ kdone, kbpm bpmdetect kpulse [, imaxpulses, imintime]
+
+ kdone outputs 1 when a new BPM has been ascertained
+ kbpm the detected BPM
+ kpulse trigger input
+ imaxpulses number of pulses to consider: will output BPM after this many
+ imintime minimum time between beats to normalise signal
+*/
+opcode bpmdetect, kk, kjj
+ kpulse, imaxpulses, imintime xin
+ imax = (imaxpulses == -1) ? 4 : imaxpulses
+ imintime = (imintime == -1) ? 0.2 : imintime
+ kcycle init 0
+ ktimesum init 0
+ ktime timeinsts
+ klasttime init 0
+ kbpm init 0
+ kdone init 0
+ if (kpulse == 1 && klasttime != 0) then
+ kelapsed = ktime - klasttime
+ if (kelapsed > imintime) then
+ ktimesum += kelapsed
+ klasttime = ktime
+ kcycle += 1
+ endif
+ elseif (klasttime == 0) then
+ klasttime = ktime
+ endif
+
+ if (kcycle >= imax) then
+ kcycle = 0
+ kbpm = 60 / (ktimesum / imax)
+ ktimesum = 0
+ kdone = 1
+ else
+ kdone = 0
+ endif
+ xout kdone, kbpm
+endop
+
+#end
+
diff --git a/site/udo/bsamp.udo b/site/udo/bsamp.udo
new file mode 100755
index 0000000..a31cede
--- /dev/null
+++ b/site/udo/bsamp.udo
@@ -0,0 +1,206 @@
+#ifndef UDO_BSAMP
+#define UDO_BSAMP ##
+/*
+ Live buffer sampling and glitch out playback
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+#include "bussing.udo"
+#include "tab2wav.udo"
+
+#ifndef BSAMP_BUFFERNUM
+#define BSAMP_BUFFERNUM #8#
+#end
+
+#ifndef BSAMP_CHANNELS
+#define BSAMP_CHANNELS #1#
+#end
+
+#ifndef BSAMP_BUFFERLENGTH
+#define BSAMP_BUFFERLENGTH #8#
+#end
+
+#ifndef BSAMP_IOPROCRATE
+#define BSAMP_IOPROCRATE #10#
+#end
+
+#ifndef BSAMP_IOBLOCKSIZE
+#define BSAMP_IOBLOCKSIZE #2048#
+#end
+
+
+gibsamp_buffernum = $BSAMP_BUFFERNUM
+gibsamp_channels = $BSAMP_CHANNELS
+gibsamp_buffers[][] init gibsamp_buffernum, gibsamp_channels
+gibsamp_bufferused[] init gibsamp_buffernum
+
+
+isize = sr * $BSAMP_BUFFERLENGTH
+index = 0
+while (index < gibsamp_buffernum) do
+ indexchan = 0
+ while (indexchan < gibsamp_channels) do
+ gibsamp_buffers[index][indexchan] = ftgen(0, 0, isize, 2, 0)
+ gibsamp_bufferused[index] = isize
+ indexchan += 1
+ od
+ index += 1
+od
+
+instr _bsamp_setbufferusedsize
+ ibufferindex = p4
+ ilens = p5
+ gibsamp_bufferused[ibufferindex] = round(ilens * sr)
+ turnoff
+endin
+
+
+opcode bsamp_save, k, iSjj
+ ibufferindex, Spath, iprocrate, iblocksize xin
+
+ if (gibsamp_channels == 2) then
+ SpathL = sprintf("%s.L", Spath)
+ SpathR = sprintf("%s.R", Spath)
+ else
+ SpathL = Spath
+ SpathR = Spath
+ endif
+
+ kdoneL tab2wav gibsamp_buffers[ibufferindex][0], SpathL, gibsamp_bufferused[ibufferindex], $BSAMP_IOPROCRATE, $BSAMP_IOBLOCKSIZE
+ if (gibsamp_channels == 2) then
+ kdoneR tab2wav gibsamp_buffers[ibufferindex][1], SpathR, gibsamp_bufferused[ibufferindex], $BSAMP_IOPROCRATE, $BSAMP_IOBLOCKSIZE
+ else
+ kdoneR init 1
+ endif
+
+ xout (kdoneL & kdoneR)
+endop
+
+
+opcode bsamp_load, k, iSjj
+ ibufferindex, Spath, iprocrate, iblocksize xin
+ SpathR = sprintf("%s.R", Spath)
+ if (filevalid(SpathR) == 1) then
+ SpathL = sprintf("%s.L", Spath)
+ else
+ SpathL = Spath
+ SpathR = Spath
+ endif
+
+ gibsamp_bufferused[ibufferindex] = min(round(filelen(SpathL) * sr), ftlen(gibsamp_buffers[ibufferindex][0]))
+
+ kdoneL wav2tab SpathL, gibsamp_buffers[ibufferindex][0], 0, $BSAMP_IOPROCRATE, $BSAMP_IOBLOCKSIZE
+ if (gibsamp_channels == 2) then
+ kdoneR wav2tab SpathR, gibsamp_buffers[ibufferindex][1], 1, $BSAMP_IOPROCRATE, $BSAMP_IOBLOCKSIZE
+ else
+ kdoneR init 1
+ endif
+
+ xout (kdoneL & kdoneR)
+endop
+
+
+instr bsamp_record
+ ibufferindex = p4
+ Sbus = strget(p5)
+
+
+ ifnL = gibsamp_buffers[ibufferindex][0]
+ if (gibsamp_channels == 2) then
+ ifnR = gibsamp_buffers[ibufferindex][1]
+ else
+ ifnR = -1
+ endif
+
+ ilen = ftlen(ifnL)
+ apos lphasor 1
+
+ aL, aR bus_tap Sbus
+
+ tablew aL, apos, ifnL, 0, 0, 1
+ if (gibsamp_channels == 2) then
+ tablew aR, apos, ifnR, 0, 0, 1
+ endif
+
+ ktimes timeinsts
+
+ kreleasing init 0
+ if (release:k() == 1 && kreleasing == 0) then
+ kreleasing = 1
+ if (ktimes > $BSAMP_BUFFERLENGTH) then
+ ktimes = ktimes % $BSAMP_BUFFERLENGTH
+ if (ktimes == 0) then
+ ktimes = $BSAMP_BUFFERLENGTH
+ endif
+ endif
+ schedulek("_bsamp_setbufferusedsize", 0, 1, ibufferindex, ktimes)
+ endif
+endin
+
+
+opcode bsamp_clear, 0, i
+ ibufferindex xin
+ gibsamp_bufferused[ibufferindex] = 0
+ ftset gibsamp_buffers[ibufferindex][0], 0
+ if (gibsamp_channels == 2) then
+ ftset gibsamp_buffers[ibufferindex][1], 0
+ endif
+endop
+
+
+opcode bsamp_play, aa, ikkOo
+ ibufferindex, kreadstart, ktriglen, kreverse, iratiotimes xin
+
+ ifnL = gibsamp_buffers[ibufferindex][0]
+ if (gibsamp_channels == 2) then
+ ifnR = gibsamp_buffers[ibufferindex][1]
+ else
+ ifnR = -1
+ endif
+
+ ilen = gibsamp_bufferused[ibufferindex] ; ftlen(ifn)
+ ilens = ilen / sr
+
+
+ areadstart = upsamp(kreadstart)
+ if (iratiotimes == 0) then
+ areadstart = areadstart / ilens
+ else
+ ktriglen *= ilens
+ endif
+
+ klenchanger metro 1 / ktriglen
+ async upsamp klenchanger
+
+ irate = (1 / ((ilen) / sr))
+ apos, a_ syncphasor irate, async
+
+ ;aamp triglinseg klenchanger, 0, 0.001, 1, 3600, 1
+ ;kamp loopseg 0.01, klenchanger, 0, 0, 0.01, 1, 3600, 1
+ ;aamp = upsamp(kamp)
+ aamp init 1
+
+ apos += areadstart
+ apos *= ilen
+
+ if (kreverse == 1) then
+ apos = ilen - apos
+ endif
+
+ aL tablei apos, ifnL, 0, 0, 1
+ aL *= aamp
+ if (gibsamp_channels == 2) then
+ aR tablei apos, ifnR, 0, 0, 1
+ aR *= aamp
+ else
+ aR = aL
+ endif
+
+ xout aL, aR
+endop
+
+#end
diff --git a/site/udo/bussing.udo b/site/udo/bussing.udo
new file mode 100755
index 0000000..7ac40b8
--- /dev/null
+++ b/site/udo/bussing.udo
@@ -0,0 +1,268 @@
+#ifndef UDO_BUSSING
+#define UDO_BUSSING ##
+/*
+ Bus handling
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#ifdef BUS_MAINMIXER
+gkmastervolume init 1
+#end
+
+/*
+ Get the stereo L and R names for a singular bus name
+
+ SnameL, SnameR bus_name Sbus
+
+ SnameL left bus identifier
+ SnameR right bus identifier
+
+ Sbus bus name
+*/
+opcode bus_name, SS, S
+ Sbus xin
+ xout sprintf("%sL", Sbus), sprintf("%sR", Sbus)
+endop
+
+
+/*
+ Read from a stereo bus, but do not clear it
+
+ aL, aR bus_tap Sbus
+
+ aL left channel
+ aR right channel
+ Sbus bus name
+*/
+opcode bus_tap, aa, S
+ Sbus xin
+ SbusL, SbusR bus_name Sbus
+ aL chnget SbusL
+ aR chnget SbusR
+ xout aL, aR
+endop
+
+
+/*
+ Read from a mono bus (or left channel of a stereo bus), but do not clear it
+
+ aout bus_tap Sbus
+
+ aout output audio
+ Sbus bus name
+*/
+opcode bus_tap, a, S
+ Sbus xin
+ SbusL, S_ bus_name Sbus
+ aout chnget SbusL
+ xout aout
+endop
+
+
+/*
+ Read from a stereo bus, and then clear the bus
+
+ aL, aR bus_read Sbus
+
+ aL left channel
+ aR right channel
+ Sbus bus name
+*/
+opcode bus_read, aa, S
+ Sbus xin
+ SbusL, SbusR bus_name Sbus
+ aL chnget SbusL
+ aR chnget SbusR
+ chnclear SbusL
+ chnclear SbusR
+ xout aL, aR
+endop
+
+/*
+ Read from a mono bus (or left channel of a stereo bus), and then clear the bus
+
+ aout bus_read Sbus
+
+ aout output audio
+ Sbus bus name
+*/
+opcode bus_read, a, S
+ Sbus xin
+ SbusL, S_ bus_name Sbus
+ aout chnget SbusL
+ chnclear SbusL
+ xout aout
+endop
+
+
+/*
+ Clear a stereo bus
+
+ bus_clear Sbus
+
+ Sbus bus name
+*/
+opcode bus_clear, 0, S
+ Sbus xin
+ SbusL, SbusR bus_name Sbus
+ chnclear SbusL
+ chnclear SbusR
+endop
+
+
+/*
+ Clear a mono bus (or left channel of a stereo bus)
+
+ bus_clear_mono Sbus
+
+ Sbus bus name
+*/
+opcode bus_clear_mono, 0, S
+ Sbus xin
+ SbusL, S_ bus_name Sbus
+ chnclear SbusL
+endop
+
+
+/*
+ Set to a stereo bus
+
+ bus_set Sbus, aL, aR
+
+ Sbus bus name
+ aL left channel
+ aR right channel
+*/
+opcode bus_set, 0, Saa
+ Sbus, aL, aR xin
+ SbusL, SbusR bus_name Sbus
+ chnset aL, SbusL
+ chnset aR, SbusR
+endop
+
+
+/*
+ Set to a mono bus (or left channel of a stereo bus)
+
+ bus_set Sbus, ain
+
+ Sbus bus name
+ ain input audio
+*/
+opcode bus_set, 0, Sa
+ Sbus, ain xin
+ SbusL, S_ bus_name Sbus
+ chnset ain, SbusL
+endop
+
+
+/*
+ Mix to a stereo bus
+
+ bus_mix Sbus, aL, aR
+
+ Sbus bus name
+ aL left channel
+ aR right channel
+*/
+opcode bus_mix, 0, Saa
+ Sbus, aL, aR xin
+ SbusL, SbusR bus_name Sbus
+ chnmix aL, SbusL
+ chnmix aR, SbusR
+endop
+
+
+/*
+ Mix to a mono bus (or left channel of a stereo bus)
+
+ bus_mix Sbus, ain
+
+ Sbus bus name
+ aL left channel
+ aR right channel
+*/
+opcode bus_mix, 0, Sa
+ Sbus, ain xin
+ SbusL, S_ bus_name Sbus
+ chnmix ain, SbusL
+endop
+
+
+/*
+ Mix to master bus
+
+ bus_masterout aL, aR
+
+ aL left channel
+ aR right channel
+*/
+opcode bus_masterout, 0, aa
+ aL, aR xin
+ chnmix aL, "mainL"
+ chnmix aR, "mainR"
+endop
+
+
+/*
+ Mix mono signal to master bus, equally across left and right
+
+ bus_masterout ain
+
+ ain input audio
+*/
+opcode bus_masterout, 0, a
+ ain xin
+ chnmix ain, "mainL"
+ chnmix ain, "mainR"
+endop
+
+
+/*
+ Record a bus to a wave file
+
+ bus_record Sbus, Spath
+
+ Sbus bus name
+ Spath path to file to record to
+*/
+opcode bus_record, 0, SS
+ Sbus, Spath xin
+ aL, aR bus_tap Sbus
+ fout Spath, 14, aL, aR
+endop
+
+
+/*
+ Record a mono bus (or left channel of a stereo bus) to a wave file
+
+ bus_record_mono Sbus, Spath
+
+ Sbus bus name
+ Spath path to file to record to
+*/
+opcode bus_record_mono, 0, SS
+ Sbus, Spath xin
+ asig bus_tap Sbus
+ fout Spath, 14, asig
+endop
+
+
+
+/*
+ Main output: anything mixed to the "main" bus goes to main outputs
+*/
+#ifdef BUS_MAINMIXER
+instr _mainmixer
+ aL, aR bus_read "main"
+ aL = aL * gkmastervolume
+ aR = aR * gkmastervolume
+ outs aL, aR
+endin
+schedule("_mainmixer", 0, -1)
+#end
+
+#end
diff --git a/site/udo/bussing_quad.udo b/site/udo/bussing_quad.udo
new file mode 100755
index 0000000..b857012
--- /dev/null
+++ b/site/udo/bussing_quad.udo
@@ -0,0 +1,172 @@
+#ifndef UDO_BUSSINGQUAD
+#define UDO_BUSSINGQUAD ##
+/*
+ Quad bus handling
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2023, 2025
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Get the quad left/right/front/rear names for a singular bus name
+
+ SnameLF, SnameRF, SnameLR, SnameRR qbus_name Sbus
+
+ SnameLF left front bus identifier
+ SnameRF right front bus identifier
+ SnameLR left rear bus identifier
+ SnameRR right rear bus identifier
+
+ Sbus bus name
+*/
+opcode qbus_name, SSSS, S
+ Sbus xin
+ xout sprintf("%sLF", Sbus), sprintf("%sRF", Sbus), sprintf("%sLR", Sbus), sprintf("%sRR", Sbus)
+endop
+
+
+/*
+ Read from a quad bus, but do not clear it
+
+ aLF, aRF, aLR, aRR qbus_tap Sbus
+
+ aLF left front channel
+ aRF right front channel
+ aLR left rear channel
+ aRR right rear channel
+
+ Sbus bus name
+*/
+opcode qbus_tap, aaaa, S
+ Sbus xin
+ SbusLF, SbusRF, SbusLR, SbusRR qbus_name Sbus
+ aLF chnget SbusLF
+ aRF chnget SbusRF
+ aLR chnget SbusLR
+ aRR chnget SbusRR
+ xout aLF, aRF, aLR, aRR
+endop
+
+/*
+ Read from a quad bus, and then clear the bus
+
+ aLF, aRF, aLR, aRR qbus_read Sbus
+
+ aLF left front channel
+ aRF right front channel
+ aLR left rear channel
+ aRR right rear channel
+
+ Sbus bus name
+*/
+opcode qbus_read, aaaa, S
+ Sbus xin
+ SbusLF, SbusRF, SbusLR, SbusRR qbus_name Sbus
+ aLF chnget SbusLF
+ aRF chnget SbusRF
+ aLR chnget SbusLR
+ aRR chnget SbusRR
+ chnclear SbusLF
+ chnclear SbusRF
+ chnclear SbusLR
+ chnclear SbusRR
+ xout aLF, aRF, aLR, aRR
+endop
+
+
+/*
+ Set to a quad bus
+
+ qbus_set Sbus, aLF, aRF, aLR, aRR
+
+ Sbus bus name
+ aLF left front channel
+ aRF right front channel
+ aLR left rear channel
+ aRR right rear channel
+*/
+opcode qbus_set, 0, Saaaa
+ Sbus, aLF, aRF, aLR, aRR xin
+ SbusLF, SbusRF, SbusLR, SbusRR qbus_name Sbus
+ chnset aLF, SbusLF
+ chnset aRF, SbusRF
+ chnset aLR, SbusLR
+ chnset aRR, SbusRR
+endop
+
+
+/*
+ Mix to a quad bus
+
+ qbus_mix Sbus, aLF, aRF, aLR, aRR
+
+ Sbus bus name
+ aLF left front channel
+ aRF right front channel
+ aLR left rear channel
+ aRR right rear channel
+
+*/
+opcode qbus_mix, 0, Saaaa
+ Sbus, aLF, aRF, aLR, aRR xin
+ SbusLF, SbusRF, SbusLR, SbusRR qbus_name Sbus
+ chnmix aLF, SbusLF
+ chnmix aRF, SbusRF
+ chnmix aLR, SbusLR
+ chnmix aRR, SbusRR
+endop
+
+
+/*
+ Mix to master bus
+
+ qbus_masterout aLF, aRF, aLR, aRR
+
+ aLF left front channel
+ aRF right front channel
+ aLR left rear channel
+ aRR right rear channel
+*/
+opcode qbus_masterout, 0, aaaa
+ aLF, aRF, aLR, aRR xin
+ chnmix aLF, "mainLF"
+ chnmix aRF, "mainRF"
+ chnmix aLR, "mainLR"
+ chnmix aRR, "mainRR"
+endop
+
+
+/*
+ Record a bus to a wave file
+
+ qbus_record Sbus, Spath
+
+ Sbus bus name
+ Spath path to file to record to
+*/
+opcode qbus_record, 0, SS
+ Sbus, Spath xin
+ aLF, aRF, aLR, aRR qbus_tap Sbus
+ fout Spath, 14, aLF, aRF, aLR, aRR
+endop
+
+
+; TODO: is this really used?
+;gkqmastervolume init 1
+/*
+ Main output: anything mixed to the "main" bus goes to main outputs
+
+instr _qmainmixer
+ aLF, aRF, aLR, aRR qbus_read "main"
+ aLF *= gkqmastervolume
+ aRF *= gkqmastervolume
+ aLR *= gkqmastervolume
+ aRR *= gkqmastervolume
+ outq aLF, aRF, aLR, aRR
+endin
+alwayson "_qmainmixer"
+*/
+
+#end
diff --git a/site/udo/chop.udo b/site/udo/chop.udo
new file mode 100755
index 0000000..cf8d2f3
--- /dev/null
+++ b/site/udo/chop.udo
@@ -0,0 +1,715 @@
+#ifndef UDO_CHOP
+#define UDO_CHOP ##
+/*
+ chop: init-time table editing
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023, 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "/uniqueid.udo"
+
+opcode chop_mktemp, ii, ij
+ isize, imono xin
+ ifnL ftgen 0, 0, isize, 2, 0
+ if (imono < 1) then
+ ifnR ftgen 0, 0, isize, 2, 0
+ endif
+ xout ifnL, ifnR
+endop
+
+opcode chop_mktemp, i, i
+ isize xin
+ ifn ftgen 0, 0, -isize, -2, 0
+ xout ifn
+endop
+
+
+
+opcode chop_zerostartend, ii, i
+ ifn xin
+ ilen = ftlen(ifn)
+ istart = -1
+ iend = -1
+ iprevsample = tab_i(0, ifn)
+ index = 1
+ while (index < ilen) do
+ isample = tab_i(index, ifn)
+ if ((isample > 0 && iprevsample < 0) || (isample < 0 && iprevsample > 0)) then
+ istart = index
+ goto foundstart
+ endif
+ iprevsample = isample
+ index += 1
+ od
+
+foundstart:
+ index = ilen - 2
+ iprevsample = tab_i(ilen - 1, ifn)
+ while (index >= 0) do
+ isample = tab_i(index, ifn)
+ if ((isample > 0 && iprevsample < 0) || (isample < 0 && iprevsample > 0)) then
+ iend = index
+ goto foundend
+ endif
+ iprevsample = isample
+ index -= 1
+ od
+
+foundend:
+ xout istart, iend
+endop
+
+
+opcode _chop_assamples, iii, iii
+ ichopstart, ichoplen, iassamples xin
+ if (iassamples == 1) then
+ ichopstartsamp = ichopstart
+ ichopendsamp = ichopstart + ichoplen
+ ichoplensamp = ichoplen
+ else
+ ichopstartsamp = round(ichopstart * sr)
+ ichopendsamp = round((ichopstart + ichoplen) * sr)
+ ichoplensamp = round(ichoplen * sr)
+ endif
+ xout ichopstartsamp, ichopendsamp, ichoplensamp
+endop
+
+
+
+
+;ftslice better?
+opcode chop_cut, iiii, iiiij
+ ichopstart, ichoplen, ifnL, ifnR, iassamples xin
+ ichopstartsamp, ichopendsamp, ichoplensamp _chop_assamples ichopstart, ichoplen, iassamples
+
+ ilen = ftlen(ifnL)
+ inewlen = ilen - ichoplensamp
+ if (ifnR == -1) then
+ icutL chop_mktemp round(ichoplensamp)
+ ifntempL chop_mktemp round(inewlen)
+ else
+ icutL, icutR chop_mktemp round(ichoplensamp)
+ ifntempL, ifntempR chop_mktemp round(inewlen)
+ endif
+
+ ireadpos = 0
+ iwritepos = 0
+ ichopwritepos = 0
+ while (ireadpos + 1 < ilen) do
+ ivalL = tab_i(ireadpos, ifnL)
+ if (ifnR != -1) then
+ ivalR = tab_i(ireadpos, ifnR)
+ endif
+
+ if (ireadpos >= ichopstartsamp && ireadpos < ichopendsamp) then
+ tabw_i ivalL, ichopwritepos, icutL
+ if (ifnR != 1) then
+ tabw_i ivalR, ichopwritepos, icutR
+ endif
+ ichopwritepos += 1
+ else
+ tabw_i ivalL, iwritepos, ifntempL
+ if (ifnR != 1) then
+ tabw_i ivalR, iwritepos, ifntempR
+ endif
+ iwritepos += 1
+ endif
+ ireadpos += 1
+ od
+
+ ftfree ifnL, 0
+ if (ifnR != -1) then
+ ftfree ifnR, 0
+ endif
+
+ xout icutL, icutR, ifntempL, ifntempR
+endop
+
+
+opcode chop_cut, ii, iiij
+ ichopstart, ichoplen, ifn, iassamples xin
+ ifnCut, i_, ifnNew, i_ chop_cut ichopstart, ichoplen, ifn, -1, iassamples
+ xout ifnCut, ifnNew
+endop
+
+
+opcode _chop_copy, ii, iiiij
+ ichopstart, ichoplen, ifnL, ifnR, iassamples xin
+ ichopstartsamp, ichopendsamp, ichoplensamp _chop_assamples ichopstart, ichoplen, iassamples
+ if (ifnL > 0) then
+ ifntempL chop_mktemp round(ichoplensamp)
+ ftslicei ifnL, ifntempL, ichopstartsamp, ichopendsamp
+ endif
+ if (ifnR > 0) then
+ ifntempR chop_mktemp round(ichoplensamp)
+ ftslicei ifnR, ifntempR, ichopstartsamp, ichopendsamp
+ endif
+ xout ifntempL, ifntempR
+endop
+
+opcode chop_copy, i, iiij
+ ichopstart, ichoplen, ifn, iassamples xin
+ ifn, i_ _chop_copy ichopstart, ichoplen, ifn, -1, iassamples
+ xout ifn
+endop
+
+opcode chop_copy, ii, iiiij
+ ichopstart, ichoplen, ifnL, ifnR, iassamples xin
+ ifntempL, ifntempR _chop_copy ichopstart, ichoplen, ifnL, ifnR, iassamples
+ xout ifntempL, ifntempR
+endop
+
+
+
+opcode _chop_operation, 0, iiiiipoop ; should only be run in one k-cycle
+ ifnSource, ifnTarget, isourceStart, isourceEnd, itargetStart, iterations, itargetIncrement, imix, iteration xin
+ ilen = (isourceEnd - isourceStart)
+ ilens = ilen / sr
+ ikcycles = ilen / ksmps
+ kcount = 0
+ while (kcount < ikcycles) do
+ aposSource linseg isourceStart, ilens, isourceEnd
+ aposTarget linseg itargetStart, ilens, (itargetStart + ilen) - 1
+ asig table3 aposSource, ifnSource
+ if (imix == 1) then
+ asig += table3:a(aposTarget, ifnTarget)
+ endif
+ tablew asig, aposTarget, ifnTarget
+ kcount += 1
+ od
+ if (iteration < iterations) then
+ _chop_operation ifnSource, ifnTarget, isourceStart, isourceEnd, itargetStart + itargetIncrement, iterations, itargetIncrement, imix, iteration + 1
+ endif
+endop
+
+
+opcode chop_pastek, kiii, ijijipo
+ ifnSrcL, ifnSrcR, ifnDestL, ifnDestR, istart, inumber, imix xin
+ inumber = round(inumber)
+ kdone init 0
+ ifnanalysis = (ifnDestL != -1) ? ifnDestL : ifnDestR
+ idestlen = ftlen(ifnanalysis)
+ ipastelen = ftlen((ifnSrcL != -1) ? ifnSrcL : ifnSrcR)
+ itotalpastelen = ipastelen * inumber
+ iextended = 1
+ if (imix == 0) then
+ inewlen = round(idestlen + itotalpastelen)
+ else
+ if (itotalpastelen > idestlen - istart) then
+ inewlen = round(istart + itotalpastelen)
+ else
+ inewlen = idestlen
+ iextended = 0
+ endif
+ endif
+ ktimek timeinstk
+ if (ktimek == 1) then
+ if (ifnDestL != -1) then
+ if (iextended == 1) then
+ ifnNewL chop_mktemp inewlen
+ ftfree ifnDestL, 1
+ else
+ ifnNewL = ifnDestL
+ endif
+ if (imix == 0) then
+ _chop_operation ifnDestL, ifnNewL, 0, istart, 0
+ _chop_operation ifnSrcL, ifnNewL, 0, ipastelen, istart, inumber, ipastelen
+ _chop_operation ifnDestL, ifnNewL, istart + 1, idestlen, istart + itotalpastelen + 1
+ else
+ if (iextended == 1) then
+ _chop_operation ifnDestL, ifnNewL, 0, idestlen, 0
+ endif
+ _chop_operation ifnSrcL, ifnNewL, 0, ipastelen, istart, inumber, ipastelen, imix
+ endif
+ else
+ ifnNewL = -1
+ endif
+
+ if (ifnDestR != -1) then
+ if (iextended == 1) then
+ ifnNewR chop_mktemp inewlen
+ ftfree ifnDestR, 1
+ else
+ ifnNewR = ifnDestR
+ endif
+ if (imix == 0) then
+ _chop_operation ifnDestR, ifnNewR, 0, istart, 0
+ _chop_operation ifnSrcR, ifnNewR, 0, ipastelen, istart, inumber, ipastelen
+ _chop_operation ifnDestR, ifnNewR, istart + 1, idestlen, istart + itotalpastelen + 1
+ else
+ if (iextended == 1) then
+ _chop_operation ifnDestR, ifnNewR, 0, idestlen, 0
+ endif
+ _chop_operation ifnSrcR, ifnNewR, 0, ipastelen, istart, inumber, ipastelen, imix
+ endif
+ else
+ ifnNewR = -1
+ endif
+ else
+ kdone = 1
+ endif
+
+ xout kdone, ifnNewL, ifnNewR, itotalpastelen
+endop
+
+
+opcode chop_pastek, kiii, ijijip
+ ifnSrcL, ifnSrcR, ifnDestL, ifnDestR, istart, inumber xin
+ inumber = round(inumber)
+ kdone init 0
+ ifnanalysis = (ifnDestL != -1) ? ifnDestL : ifnDestR
+ ipastelen = ftlen((ifnSrcL != -1) ? ifnSrcL : ifnSrcR)
+ itotalpastelen = ipastelen * inumber
+ inewlen = round(ftlen(ifnanalysis) + itotalpastelen)
+ ktimek timeinstk
+ if (ktimek == 1) then
+ if (ifnDestL != -1) then
+ ifnNewL chop_mktemp inewlen
+ _chop_operation ifnDestL, ifnNewL, 0, istart, 0
+ _chop_operation ifnSrcL, ifnNewL, 0, ipastelen, istart, inumber, ipastelen
+ _chop_operation ifnDestL, ifnNewL, istart + 1, ftlen(ifnDestL), istart + itotalpastelen + 1
+ ftfree ifnDestL, 1
+ else
+ ifnNewL = -1
+ endif
+ if (ifnDestR != -1) then
+ ifnNewR chop_mktemp inewlen
+ _chop_operation ifnDestR, ifnNewR, 0, istart, 0
+ _chop_operation ifnSrcR, ifnNewR, 0, ipastelen, istart, inumber
+ _chop_operation ifnDestR, ifnNewR, istart + 1, ftlen(ifnDestR), istart + itotalpastelen + 1
+ ftfree ifnDestR, 1
+ else
+ ifnNewR = -1
+ endif
+ else
+ kdone = 1
+ endif
+
+ xout kdone, ifnNewL, ifnNewR, itotalpastelen
+endop
+
+
+opcode chop_setsilencek, k, iijjJ
+ istart, idellen, ifnL, ifnR, ktrig xin
+ kdone init 0
+ ktimek timeinstk
+
+ if (kdone == 0 && (ktrig == -1 && ktimek == 1 || ktrig == 1)) then
+ ilens = idellen / sr
+ ikcycles = idellen / ksmps
+ kcount = 0
+ while (kcount < ikcycles) do
+ apos linseg istart, ilens, istart + idellen
+ anull init 0
+ if (ifnL != -1) then
+ tablew dcblock(anull), apos, ifnL
+ endif
+ if (ifnR != -1) then
+ tablew dcblock(anull), apos, ifnR
+ endif
+ kcount += 1
+ od
+ kdone = 1
+ endif
+
+ xout kdone
+endop
+
+
+opcode chop_deletek, kii, iijj
+ istart, idellen, ifnL, ifnR xin
+ kdone init 0
+
+ ktimek timeinstk
+
+ if (ktimek == 1) then
+ if (ifnL != -1) then
+ ifnNewL chop_mktemp round(ftlen(ifnL) - idellen)
+ _chop_operation ifnL, ifnNewL, 0, istart, 0
+ _chop_operation ifnL, ifnNewL, istart + idellen, ftlen(ifnL), istart
+ ftfree ifnL, 1
+ else
+ ifnNewL = -1
+ ifnCutL = -1
+ endif
+ if (ifnR != -1) then
+ ifnNewR chop_mktemp round(ftlen(ifnR) - idellen)
+ _chop_operation ifnR, ifnNewR, 0, istart, 0
+ _chop_operation ifnR, ifnNewR, istart + idellen, ftlen(ifnR), istart
+ ftfree ifnR, 1
+ else
+ ifnNewR = -1
+ ifnCutR = -1
+ endif
+ else
+ kdone = 1
+ endif
+
+ xout kdone, ifnNewL, ifnNewR
+endop
+
+
+opcode chop_trimk, kii, iijj
+ istart, itrimlen, ifnL, ifnR xin
+ kdone init 0
+ ktimek timeinstk
+ if (ktimek == 1) then
+ if (ifnL != -1) then
+ ifnNewL chop_mktemp round(itrimlen)
+ _chop_operation ifnL, ifnNewL, istart, istart + itrimlen, 0
+ ftfree ifnL, 1
+ else
+ ifnNewL = -1
+ endif
+ if (ifnR != -1) then
+ ifnNewR chop_mktemp round(itrimlen)
+ _chop_operation ifnR, ifnNewR, istart, istart + itrimlen, 0
+ ftfree ifnR, 1
+ else
+ ifnNewR = -1
+ endif
+ else
+ kdone = 1
+ endif
+ xout kdone, ifnNewL, ifnNewR
+endop
+
+
+opcode chop_cutk, kiiii, iijj
+ istart, icutlen, ifnL, ifnR xin
+ kdone init 0
+
+ ktimek timeinstk
+
+ if (ktimek == 1) then
+ if (ifnL != -1) then
+ ifnNewL chop_mktemp round(ftlen(ifnL) - icutlen)
+ ifnCutL chop_mktemp round(icutlen)
+ _chop_operation ifnL, ifnNewL, 0, istart, 0
+ _chop_operation ifnL, ifnCutL, istart, istart + icutlen, 0
+ _chop_operation ifnL, ifnNewL, istart + icutlen, ftlen(ifnL), istart
+ ftfree ifnL, 1
+ else
+ ifnNewL = -1
+ ifnCutL = -1
+ endif
+ if (ifnR != -1) then
+ ifnNewR chop_mktemp round(ftlen(ifnR) - icutlen)
+ ifnCutR chop_mktemp round(icutlen)
+ _chop_operation ifnR, ifnNewR, 0, istart, 0
+ _chop_operation ifnR, ifnCutR, istart, istart + icutlen, 0
+ _chop_operation ifnR, ifnNewR, istart + icutlen, ftlen(ifnR), istart
+ ftfree ifnR, 1
+ else
+ ifnNewR = -1
+ ifnCutR = -1
+ endif
+ else
+ kdone = 1
+ endif
+
+ xout kdone, ifnCutL, ifnCutR, ifnNewL, ifnNewR
+endop
+
+
+
+opcode chop_copyk, kii, iiij
+ istart, ilen, ifnL, ifnR xin
+ kdone init 0
+
+ ktimek timeinstk
+
+ if (ktimek == 1) then
+ if (ifnL != -1) then
+ ifntempL chop_mktemp round(ilen)
+ _chop_operation ifnL, ifntempL, istart, istart + ilen, 0
+ else
+ ifntempL = -1
+ endif
+ if (ifnR != -1) then
+ ifntempR chop_mktemp round(ilen)
+ _chop_operation ifnR, ifntempR, istart, istart + ilen, 0
+ else
+ ifntempR = -1
+ endif
+ else
+ kdone = 1
+ endif
+ xout kdone, ifntempL, ifntempR
+endop
+
+
+
+
+opcode chop_move, ii, iiiii
+ ichopstart, ichoplen, ichopdest, ifnL, ifnR xin
+ ifnchopL, ifnchopR chop_copy ichopstart, ichoplen, ifnL, ifnR
+
+ ichopdestsamp = round(ichopdest * sr)
+ ichopendsamp = round((ichopdest + ichoplen) * sr)
+ ilen = ftlen(ifnL)
+ ifntempdestL, ifntempdestR chop_mktemp ilen
+
+ ilastvalL = 0
+ ilastvalR = 0
+ iwritemode = 0
+ ireadposmain = 0
+ ireadposchop = 0
+ iwritepos = 0
+ while (iwritepos < ilen) do
+ if (iwritepos >= ichopdestsamp && iwritepos + 1 < ichopendsamp) then
+ ivalL = tab_i(ireadposchop, ifnchopL)
+ ivalR = tab_i(ireadposchop, ifnchopR)
+ if (iwritemode == 0) then
+ if (ireadposchop + 1 <= ichopendsamp) then
+ ivalR = (ilastvalR + tab_i(ireadposchop + 1, ifnchopL)) * 0.5
+ ivalR = (ilastvalR + tab_i(ireadposchop + 1, ifnchopR)) * 0.5
+ endif
+ iwritemode = 1
+ endif
+ ireadposchop += 1
+ else
+ ivalL = tab_i(ireadposmain, ifnL)
+ ivalR = tab_i(ireadposmain, ifnR)
+ if (iwritemode == 1) then
+ if (ireadposmain + 1 <= ilen) then
+ ivalR = (ilastvalR + tab_i(ireadposmain + 1, ifnL)) * 0.5
+ ivalR = (ilastvalR + tab_i(ireadposmain + 1, ifnR)) * 0.5
+ endif
+ iwritemode = 0
+ endif
+ ireadposmain += 1
+ endif
+ ilastvalL = ivalL
+ ilastvalR = ivalR
+ tabw_i ivalL, iwritepos, ifntempdestL
+ tabw_i ivalR, iwritepos, ifntempdestR
+ iwritepos += 1
+ od
+
+ ftfree ifnchopL, 0
+ ftfree ifnchopR, 0
+ ftfree ifnL, 0
+ ftfree ifnR, 0
+ xout ifntempdestL, ifntempdestR
+endop
+
+
+
+
+opcode _chop_paste, iii, iiiiiii
+ ifnSrcL, ifnSrcR, ifnDestL, ifnDestR, istartsamp, inumber, itimevarratio xin
+ imonosrc = (ifnSrcR == -1) ? 1 : 0
+ imonodest = (ifnDestR == -1) ? 1 : 0
+
+ inumber = round(inumber)
+ ipastelen = ftlen(ifnSrcL) * inumber
+ inewlen = round(ftlen(ifnDestL) + ipastelen)
+ ifnNewL, ifnNewR chop_mktemp inewlen, imonodest
+
+ inum = 0
+ inputsamps = ftlen(ifnDestL)
+ ichoplensamps = ftlen(ifnSrcL)
+
+ if (itimevarratio != 0) then
+ itimevarratio = random(0.1, itimevarratio)
+ endif
+ ipastetime = istartsamp + (ichoplensamps * itimevarratio)
+ ipastepos = int(ipastetime)
+ ireadposorig = 0
+ iwritepos = 0
+ ireadchoppos = 0
+ idonepaste = 0
+print inumber
+ while (iwritepos < inewlen) do
+ if (idonepaste == 1 || iwritepos <= ipastepos) then
+ if (ireadposorig < inputsamps) then
+ ivalL = tab_i(ireadposorig, ifnDestL)
+ if (imonosrc == 0) then
+ ivalR = tab_i(ireadposorig, ifnDestR)
+ else
+ ivalL = ivalR
+ endif
+
+ if (imonodest == 1) then
+ tabw_i ivalL, iwritepos, ifnNewL
+ else
+ tabw_i ivalL, iwritepos, ifnNewL
+ tabw_i ivalR, iwritepos, ifnNewR
+ endif
+ endif
+ ireadposorig += 1
+ else
+ ivalL = tab_i(ireadchoppos, ifnSrcL)
+ if (imonosrc == 0) then
+ ivalR = tab_i(ireadchoppos, ifnSrcR)
+ else
+ ivalL = ivalR
+ endif
+
+ if (imonodest == 1) then
+ tabw_i ivalL, iwritepos, ifnNewL
+ else
+ tabw_i ivalL, iwritepos, ifnNewL
+ tabw_i ivalR, iwritepos, ifnNewR
+ endif
+
+ if (ireadchoppos + 1 >= ichoplensamps) then
+ ipastetime += (ichoplensamps * random(0, itimevarratio))
+ ipastepos = ipastetime
+print ipastepos
+ if (inum + 1 < inumber) then
+ inum += 1
+print inum
+ else
+ idonepaste = 1
+ endif
+ ireadchoppos = 0
+ else
+ ireadchoppos += 1
+ endif
+ endif
+
+ iwritepos += 1
+ od
+
+ ftfree ifnDestL, 0
+ ftfree ifnDestR, 0
+ xout ifnNewL, ifnNewR, ipastelen
+endop
+
+opcode chop_paste, ii, iiiii
+ ifnSrc, ifnDest, istartsamp, inumber, itimevarratio xin
+ ifnNew, i_, ipastelen _chop_paste ifnSrc, -1, ifnDest, -1, istartsamp, inumber, itimevarratio
+ xout ifnNew, ipastelen
+endop
+
+opcode chop_paste, iii, iiiiiii
+ ifnSrcL, ifnSrcR, ifnDestL, ifnDestR, istartsamp, inumber, itimevarratio xin
+ ifnNewL, ifnNewR, ipastelen _chop_paste ifnSrcL, ifnSrcR, ifnDestL, ifnDestR, istartsamp, inumber, itimevarratio
+ xout ifnNewL, ifnNewR, ipastelen
+endop
+
+
+
+
+/*
+opcode chop_pastek, kiii, ijijip
+ ifnSrcL, ifnSrcR, ifnDestL, ifnDestR, istart, inumber xin
+ inumber = round(inumber)
+ iktime = 1 / kr
+ if (ifnDestL != -1) then
+ ipastelen = ftlen(ifnSrcL) ;;;;;;;;;;;;;;;;;;;;;;;TODOTODOTODOTODO;TODO;;;;* inumber
+ inewlen = round(ftlen(ifnDestL) + ipastelen)
+ ifnNewL chop_mktemp inewlen
+
+ ; paste beginning from original
+ Schannel = sprintf("choppaste%d", uniqueid())
+ schedule("_chop_operation", 0, -1, ifnDestL, ifnNewL, 0, istart, 0, Schannel)
+ kdone1L chnget Schannel
+
+ ; paste buffer
+ Schannel = sprintf("choppaste%d", uniqueid())
+ schedule("_chop_operation", iktime, -1, ifnSrcL, ifnNewL, 0, ipastelen, istart, Schannel)
+ kdone2L chnget Schannel
+
+ ; paste remainder from original
+ Schannel = sprintf("choppaste%d", uniqueid())
+ ipasteend = istart + ipastelen
+ schedule("_chop_operation", iktime * 2, -1, ifnDestL, ifnNewL, istart + 1, ftlen(ifnDestL), istart + ipastelen + 1, Schannel)
+ kdone3L chnget Schannel
+
+ ftfree ifnDestL, 1
+ else
+ kdone1L init 1
+ kdone2L init 1
+ kdone3L init 1
+ ifnNewL = -1
+ endif
+
+ if (ifnDestR != -1) then
+ ipastelen = ftlen(ifnSrcR) ;;;;;;;;;;;;;;;;;;;;;;;TODOTODOTODOTODO;TODO;;;;* inumber
+ inewlen = round(ftlen(ifnDestR) + ipastelen)
+ ifnNewR chop_mktemp inewlen
+
+ Schannel = sprintf("choppaste%d", uniqueid())
+ schedule("_chop_operation", iktime * 3, -1, ifnDestR, ifnNewR, 0, istart, 0, Schannel)
+ kdone1R chnget Schannel
+
+ Schannel = sprintf("choppaste%d", uniqueid())
+ schedule("_chop_operation", iktime * 4, -1, ifnSrcR, ifnNewR, 0, ipastelen, istart, Schannel)
+ kdone2R chnget Schannel
+
+ Schannel = sprintf("choppaste%d", uniqueid())
+ schedule("_chop_operation", iktime * 5, -1, ifnDestR, ifnNewR, istart + 1, ftlen(ifnDestR), istart + ipastelen + 1, Schannel)
+ kdone3R chnget Schannel
+
+ ftfree ifnDestR, 1
+ else
+ kdone1R init 1
+ kdone2R init 1
+ kdone3R init 1
+ ifnNewR = -1
+ endif
+
+ xout (kdone1L & kdone2L & kdone3L & kdone1R & kdone2R & kdone3R), ifnNewL, ifnNewR, ipastelen
+endop
+*/
+
+opcode chop_copypaste, ii, iiiiii
+ ichopstart, ichoplen, inumber, itimevarratio, ifnL, ifnR xin
+ inumber = round(inumber)
+ ifnchopL, ifnchopR chop_copy ichopstart, ichoplen, ifnL, ifnR
+
+ inewlen = round(ftlen(ifnL) + (ichoplen * inumber * sr))
+ ifntempL, ifntempR chop_mktemp inewlen
+
+ inum = 0
+ inputsamps = ftlen(ifnL)
+ ichoplensamps = ftlen(ifnchopL)
+ ipastetime = ichopstart + (ichoplen * random(0, itimevarratio))
+ ipastepos = round(ipastetime * sr)
+ ireadposorig = 0
+ iwritepos = 0
+ ireadchoppos = 0
+ idonepaste = 0
+
+ while (iwritepos < inewlen) do
+ if (idonepaste == 1 || iwritepos <= ipastepos) then
+ if (ireadposorig < inputsamps) then
+ tabw_i tab_i(ireadposorig, ifnL), iwritepos, ifntempL
+ tabw_i tab_i(ireadposorig, ifnR), iwritepos, ifntempR
+ endif
+ ireadposorig += 1
+ else
+ tabw_i tab_i(ireadchoppos, ifnchopL), iwritepos, ifntempL
+ tabw_i tab_i(ireadchoppos, ifnchopR), iwritepos, ifntempR
+
+ if (ireadchoppos + 1 >= ichoplensamps) then
+ ipastetime += (ichoplen * random(0, itimevarratio))
+ ipastepos = round(ipastetime * sr)
+ if (inum + 1 < inumber) then
+ inum += 1
+ else
+ idonepaste = 1
+ endif
+ ireadchoppos = 0
+ else
+ ireadchoppos += 1
+ endif
+ endif
+
+ iwritepos += 1
+ od
+
+ ftfree ifnchopL, 0
+ ftfree ifnchopR, 0
+ ftfree ifnL, 0
+ ftfree ifnR, 0
+ xout ifntempL, ifntempR
+endop
+
+
+#end
diff --git a/site/udo/chord_detect.udo b/site/udo/chord_detect.udo
new file mode 100755
index 0000000..da72027
--- /dev/null
+++ b/site/udo/chord_detect.udo
@@ -0,0 +1,154 @@
+#ifndef UDO_CHORDDETECT
+#define UDO_CHORDDETECT ##
+/*
+ Polyphonic note tracking
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+
+gichorddetect_ampthresh = 0.01
+
+/*
+ Internal opcode
+ Set up pvsbin recursively for each bin up to imaxbin, and add a confirmation to inotefn for the nearest MIDI note detected
+
+ _polydetect_bin fsig, imaxbin, inotefn [, ibin=1]
+
+ fsig the signal from pvsanal to perform pitch detection on
+ imaxbin maximum number of bins in the fsig which denotes the level of recursion required
+ inotefn ftable to store note confirmations in
+ ibin current bin for pvsbin to examine
+*/
+opcode _polydetect_bin, 0, fiip
+ fsig, imaxbin, inotefn, ibin xin
+ kamp, kfreq pvsbin fsig, ibin
+
+ if (kamp > gichorddetect_ampthresh) then
+ knote = ftom(kfreq, 1)
+ tablew table:k(knote, inotefn) + 1, knote, inotefn
+ endif
+
+ if (ibin + 1 < imaxbin) then
+ _polydetect_bin fsig, imaxbin, inotefn, ibin + 1
+ endif
+endop
+
+
+/*
+ Internal opcode
+ Rank the notes in inotefn and output the detected MIDI notes along with a certainty ratio
+
+ kout[], kcertainty[] _polydetect_ranknotes inotefn, imaxnotes
+
+ kout[] array of detected MIDI note numbers, of size imaxnotes
+ kcertainty[] array of detected note certainty ratios, of size imaxnotes
+ inotefn ftable with note confirmations
+ imaxnotes maximum number of notes to look for, ie polyphony level
+*/
+opcode _polydetect_ranknotes, k[]k[], ii
+ inotefn, imaxnotes xin
+ ilen = ftlen(inotefn)
+ kout[] init imaxnotes
+
+ kcertainty[] init imaxnotes
+ ktotal = 0
+ kindex = 0
+ kmax = 0
+ knonzeronum = 0
+ while (kindex < ilen) do
+ kval = tab:k(kindex, inotefn)
+ if (kval > 0) then
+ ktotal += kval
+ knonzeronum += 1
+ endif
+ kindex += 1
+ od
+
+ if (knonzeronum != 0) then
+ kavg = ktotal / knonzeronum
+ knum = 0
+ kindex = 0
+ while (kindex < ilen) do
+ kval = tab:k(kindex, inotefn)
+ kmax = max:k(kmax, kval)
+ if (kval > kavg) then
+ knum += 1
+ if (knum >= imaxnotes) then
+ goto writeout
+ endif
+ endif
+ kindex += 1
+ od
+ else
+ goto complete
+ endif
+
+writeout:
+ kindex = 0
+ kwriteindex = 0
+ while (kindex < ilen) do
+ kval = tab:k(kindex, inotefn)
+ if (kval > kmax/2) then ;kavg) then
+ kout[kwriteindex] = kindex
+ kcertainty[kwriteindex] = kval / kmax
+ kwriteindex += 1
+ if (kwriteindex >= imaxnotes) then
+ goto complete
+ endif
+ endif
+ kindex += 1
+ od
+complete:
+ xout kout, kcertainty
+endop
+
+
+/*
+ Detect the nearest MIDI notes in an audio signal to an arbitrary level of polyphony
+
+ kchanged, kout[], kcertainty[] polydetect ain, imaxnotes, iupdateksmps
+
+ kchanged trigger output as 1 if the detected notes has changed from the last output
+ kout[] array of detected MIDI note numbers, of size imaxnotes
+ kcertainty[] array of detected note certainty ratios, of size imaxnotes
+ ain input audio to examine
+ imaxnotes maximum number of notes to look for, ie polyphony level
+ iupdateksmps analysis window size in ksmps
+*/
+opcode polydetect, kk[]k[], aii
+ ain, imaxnotes, iupdateksmps xin
+ ir = 1024
+ inotefn ftgentmp 0, 0, 128, 2, 0
+ ktimek timeinstk
+
+ fsig pvsanal ain, ir, ir/4, ir, 1
+ _polydetect_bin fsig, ir/8, inotefn ; quarter of spectrum
+
+ klast[] init imaxnotes
+ kchanged = 0
+
+ if (ktimek % iupdateksmps == 0) then
+ kout[], kcertainty[] _polydetect_ranknotes inotefn, imaxnotes
+
+ kcompare[] cmp klast, "==", kout
+
+ if (sumarray(kcompare) == lenarray(kcompare)) then
+ kchanged = 0
+ else
+ kchanged = 1
+ klast = kout
+ endif
+
+ ftset inotefn, k(0)
+ endif
+ xout kchanged, kout, kcertainty
+endop
+
+
+
+
+#end
diff --git a/site/udo/chords.udo b/site/udo/chords.udo
new file mode 100755
index 0000000..87e8775
--- /dev/null
+++ b/site/udo/chords.udo
@@ -0,0 +1,455 @@
+#ifndef UDO_CHORDS
+#define UDO_CHORDS ##
+/*
+ Chord interval data and harmonic formation opcodes
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+; chord names
+gSchords[] fillarray "Augmented",
+ "Augmented 11th",
+ "Augmented major 7th",
+ "Augmented 7th",
+ "Augmented 6th",
+ "Diminished",
+ "Diminished major 7th",
+ "Diminished 7th",
+ "Dominant",
+ "Dominant 11th",
+ "Dominant minor 9th",
+ "Dominant 9th",
+ "Dominant parallel",
+ "Dominant 7th",
+ "Dominant 7th b5",
+ "Dominant 13th",
+ "Dream",
+ "Elektra",
+ "Farben",
+ "Harmonic 7th",
+ "Augmented 9th",
+ "Leadingtone",
+ "Lydian",
+ "Major",
+ "Major 11th",
+ "Major 7th",
+ "Major 7th sharp 11th",
+ "Major 6th",
+ "Major 9th",
+ "Major 13th",
+ "Mediant",
+ "Minor",
+ "Minor 11th",
+ "Minor major 7th",
+ "Minor 9th",
+ "Minor 7th",
+ "Half diminished 7th",
+ "Minor 6th",
+ "Minor 13th",
+ "Mu",
+ "Mystic",
+ "Neapolitan",
+ "Ninth augmented 5th",
+ "Ninth b5th",
+ "Northern lights",
+ "Napoleon hexachord",
+ "Petrushka",
+ "Power",
+ "Psalms",
+ "Secondary dominant",
+ "Secondary leadingtone",
+ "Secondary supertonic",
+ "Sevensix",
+ "7th b9",
+ "7th suspension 4",
+ "Sixth 9th",
+ "Suspended",
+ "Subdominant",
+ "Subdominant parallel",
+ "Submediant",
+ "Subtonic",
+ "Supertonic",
+ "So what",
+ "Thirteenth b9th",
+ "Thirteenth b9th b5th",
+ "Tonic counter parallel",
+ "Tonic",
+ "Tonic parallel",
+ "Tristan",
+ "Viennese trichord 1",
+ "Viennese trichord 2",
+ "Rix1",
+ "Rix2",
+ "Rix3",
+ "Rix4 Major",
+ "Rix4 Minor"
+
+; octave and note names
+gSoctaves[] fillarray "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
+gSnotenames[] fillarray "C", "C#", "D", "D#", "E", "F", "F#", "G", "A", "Asharp", "B"
+
+; chord interval definitions with index in gichordfns corresponding to names in gSchords
+gichordfns = ftgen(0, 0, -76, -2, 0)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 8), 0, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 10, 2, 6), 1, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 8, 11), 2, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 8, 10), 3, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 6, 8), 4, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 6), 5, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 11), 6, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 9), 7, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 8, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 10, 2, 5), 9, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 1), 10, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 2), 11, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 12, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 10), 13, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 6, 10), 14, gichordfns)
+tabw_i(ftgen(0, 0, -7, -2, 0, 4, 7, 10, 2, 5, 9), 15, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 5, 6, 7), 16, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 7, 9, 1, 4), 17, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 8, 11, 4, 9), 18, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 10), 19, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 3), 20, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 6), 21, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 11, 6), 22, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 23, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 11, 2, 5), 24, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 11), 25, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 11, 6), 26, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 9), 27, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 11, 2), 28, gichordfns)
+tabw_i(ftgen(0, 0, -7, -2, 0, 4, 7, 11, 2, 6, 9), 29, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 30, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 31, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 3, 7, 10, 2, 5), 32, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 7, 11), 33, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 3, 7, 10, 2), 34, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 7, 10), 35, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 10), 36, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 7, 9), 37, gichordfns)
+tabw_i(ftgen(0, 0, -7, -2, 0, 3, 7, 10, 2, 5, 9), 38, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 2, 4, 7), 39, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 6, 10, 4, 9, 2), 40, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 1, 5, 8), 41, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 8, 10, 2), 42, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 6, 10, 2), 43, gichordfns)
+tabw_i(ftgen(0, 0, -11, -2, 1, 2, 8, 0, 3, 6, 7, 10, 11, 4, 7), 44, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 1, 4, 5, 8, 9), 45, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 1, 4, 6, 7, 10), 46, gichordfns)
+tabw_i(ftgen(0, 0, -2, -2, 0, 7), 47, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 48, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 49, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 6), 50, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 51, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 9, 10), 52, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 1), 53, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 5, 7, 10), 54, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 9, 2), 55, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 5, 7), 56, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 57, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 58, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 59, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 60, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 61, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 5, 10, 3, 7), 62, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 10, 1, 9), 63, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 6, 10, 1, 9), 64, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 65, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 66, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 67, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 10), 68, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 1, 6), 69, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 6, 7), 70, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 5, 9), 71, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 6, 8), 72, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 5, 7), 73, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 1, 5, 8), 74, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 1, 5, 7), 75, gichordfns)
+
+
+/*
+ Get chord intervals array by index
+ intervals[] chordintervalsbyindex index
+
+ intervals[] intervals for the chord obtained from gichordfns
+ index index in gichordfns to retrieve, corresponding to gSchords names
+*/
+opcode chordintervalsbyindex, i[], i
+ index xin
+ intervals[] tab2array table:i(index, gichordfns)
+ xout intervals
+endop
+
+
+/*
+ Get chord intervals array by index
+ kintervals[] chordintervalsbyindex kindex
+
+ kintervals[] intervals for the chord obtained from gichordfns
+ kindex index in gichordfns to retrieve, corresponding to gSchords names
+*/
+opcode chordintervalsbyindex, k[], k
+ kindex xin
+ kintervals[] init 99 ; TODO : FIX AROUND THIS??
+ copyf2array kintervals, table:k(kindex, gichordfns)
+ ;kintervals[] tab2array
+ xout kintervals
+endop
+
+
+/*
+ Get index of chord name
+ index chordindexbyname Schord
+
+ index index in gichordfns and gSchords
+ Schord chord name as in gSchords
+*/
+opcode chordindexbyname, i, S
+ Schord xin
+ index = 0
+ while (index < lenarray(gSchords)) do
+ if (strcmp(gSchords[index], Schord) == 0) then
+ igoto done
+ endif
+ index += 1
+ od
+ index = 0
+done:
+ xout index
+endop
+
+
+
+/*
+ Get index of chord name
+ index chordindexbyname Schord
+
+ kindex index in gichordfns and gSchords
+ Schord chord name as in gSchords
+*/
+opcode chordindexbyname, k, S
+ Schord xin
+ kindex = 0
+ while (kindex < lenarray:k(gSchords)) do
+ if (strcmpk(gSchords[kindex], Schord) == 0) then
+ kgoto done
+ endif
+ kindex += 1
+ od
+ kindex = 0
+done:
+ xout kindex
+endop
+
+
+/*
+ Get chord intervals by name: return the array from gichordfns that corresponds to the gSchords entry
+ intervals[] chordintervals Schord
+
+ intervals[] intervals for the chord obtained from gichordfns
+ Schord chord name as in gSchords
+*/
+opcode chordintervals, i[], S
+ Schord xin
+ index chordindexbyname Schord
+ intervals[] chordintervalsbyindex index
+ xout intervals
+endop
+
+
+/*
+ Get chord intervals by name: return the array from gichordfns that corresponds to the gSchords entry
+ kintervals[] chordintervals Schord
+
+ kintervals[] intervals for the chord obtained from gichordfns
+ Schord chord name as in gSchords
+*/
+opcode chordintervals, k[], S
+ Schord xin
+ kindex chordindexbyname Schord
+ kintervals[] chordintervalsbyindex kindex
+ xout kintervals
+endop
+
+
+
+
+/*
+ Get the midi note numbers or hz for a chord named Schord using inote as the root midi note number
+ inotes[] chordmidi Schord, inote, [iashz=0]
+
+ inotes[] midi note numbers or hz
+ Schord chord name as in gSchords
+ inote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidi, i[], Sio
+ Schord, inote, iashz xin
+ intervals[] chordintervals Schord
+ index = 0
+ while (index < lenarray:i(intervals)) do
+ ivalue = intervals[index] + inote
+ intervals[index] = (iashz == 1) ? cpsmidinn:i(ivalue) : ivalue
+ index += 1
+ od
+ xout intervals
+endop
+
+
+/*
+ Get the midi note numbers or hz for a chord named Schord using knote as the root midi note number
+ knotes[] chordmidi Schord, knote, [iashz=0]
+
+ knotes[] midi note numbers or hz
+ Schord chord name as in gSchords
+ knote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidi, k[], Sko
+ Schord, knote, iashz xin
+ kintervals[] chordintervals Schord
+ kindex = 0
+ while (kindex < lenarray:k(kintervals)) do
+ kvalue = kintervals[kindex] + knote
+ kintervals[kindex] = (iashz == 1) ? cpsmidinn:k(kvalue) : kvalue
+ kindex += 1
+ od
+ xout kintervals
+endop
+
+
+
+/*
+ Get the midi note numbers or hz for a chord from gichordfns by index, using inote as the root midi note number
+ inotes[] chordmidibyindex index, inote, [iashz=0]
+
+ inotes[] midi note numbers or hz
+ index chord index as in gichordfns
+ inote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidibyindex, i[], iio
+ indexc, inote, iashz xin
+ intervals[] chordintervalsbyindex indexc
+ index = 0
+ while (index < lenarray:i(intervals)) do
+ ivalue = intervals[index] + inote
+ intervals[index] = (iashz == 1) ? cpsmidinn:i(ivalue) : ivalue
+ index += 1
+ od
+ xout intervals
+endop
+
+
+
+/*
+ Get the midi note numbers or hz for a chord from gichordfns by index, using knote as the root midi note number
+ knotes[] chordmidibyindex kindex, knote, [iashz=0]
+
+ knotes[] midi note numbers or hz
+ kindex chord index as in gichordfns
+ knote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidibyindex, k[], kko
+ kindexc, knote, iashz xin
+ kintervals[] chordintervalsbyindex kindexc
+ kindex = 0
+ while (kindex < lenarray:k(kintervals)) do
+ kvalue = kintervals[kindex] + knote
+ kintervals[kindex] = (iashz == 1) ? cpsmidinn:k(kvalue) : kvalue
+ kindex += 1
+ od
+ xout kintervals
+endop
+
+
+/*
+ Insert midi note numbers or hz for a chord into a table at k-rate, with the first index set as the length, as used by sequencing_melodic.udo
+
+ chordmidibyindextof ifn, kindex, knote, iashz
+
+ ifn table to set values in
+ kindex chord index as in gichordfns
+ knote root midi note number
+ kcentadd cent add factor (1 = full semitone)
+
+*/
+opcode chordmidibyindextof, 0, ikkO
+ ifn, kindexc, knote, kcentadd xin
+ kintervalfn = table:k(kindexc, gichordfns)
+ klen = tableng:k(kintervalfn)
+ tablewkt klen, 0, ifn
+ kindex = 0
+ while (kindex < klen) do
+ tablewkt tablekt:k(kindex, kintervalfn)+knote+kcentadd, kindex+1, ifn
+ kindex += 1
+ od
+endop
+
+
+/*
+ Insert midi note numbers or hz for a chord into a table at init time, with the first index set as the length, as used by sequencing_melodic.udo
+
+ chordmidibyindextof ifn, kindex, knote, iashz
+
+ ifn table to set values in
+ index chord index as in gichordfns
+ inote root midi note number
+ icentadd cent add factor (1 = full semitone)
+
+*/
+opcode chordmidibyindextof, 0, iiio
+ ifn, indexc, inote, icentadd xin
+ intervalfn = table:i(indexc, gichordfns)
+ ilen = tableng:i(intervalfn)
+ tablew ilen, 0, ifn
+ index = 0
+ while (index < ilen) do
+ tablew table:i(index, intervalfn)+inote+icentadd, index+1, ifn
+ index += 1
+ od
+endop
+
+
+/*
+ LEGACY SUPPORT: possibly deprecated
+ Get the note frequencies for a chord named Schord using inote as the root midi note number
+ inotes[] chordmidicps Schord, inote
+
+ inotes[] note frequencies in hz
+ Schord chord name as in gSchords
+ inote root midi note number
+*/
+opcode chordmidicps, i[], Si
+ Schord, inote xin
+ inotes[] chordmidi Schord, inote, 1
+ xout inotes
+endop
+
+
+
+/*
+ LEGACY SUPPORT: possibly deprecated
+ Get the note frequencies for a chord from gichordfns by index, using inote as the root midi note number
+ inotes[] chordmidicpsbyindex index, inote
+
+ inotes[] note frequencies in hz
+ index chord index as in gichordfns
+ inote root midi note number
+*/
+opcode chordmidicpsbyindex, i[], ii
+ index, inote xin
+ inotes[] chordmidibyindex index, inote, 1
+ xout inotes
+endop
+
+
+
+
+#end
+
diff --git a/site/udo/convolutiondb.udo b/site/udo/convolutiondb.udo
new file mode 100755
index 0000000..55f0aef
--- /dev/null
+++ b/site/udo/convolutiondb.udo
@@ -0,0 +1,110 @@
+#ifndef UDO_CONVOLUTIONDB
+#define UDO_CONVOLUTIONDB ##
+/*
+ SQL database interface to convolution impulse usage
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+
+#include "pgdb.udo"
+#include "host_tools.udo"
+
+gSconvdbPaths[] init 1
+gSconvdbNames[] init 1
+giconvdbChannels[] init 1
+
+opcode convdb_getimpulses, 0, 0
+ Squery = {{
+ select f.id, f_localpath(%d, path), replace(basename(f.path), '.wav', '') as name, s.channels
+ from file f
+ join sound s on f.id = s.file_id
+ join filecollectionrelation fcr on fcr.file_id = f.id
+ join filecollection fc on fc.id = fcr.filecollection_id
+ where fc.name = 'Impulses'
+ order by f.path;
+ }}
+ Sres[][] dbarray gidb, sprintf(Squery, gihost_type)
+ ilen = lenarray(Sres)
+ gSconvdbPaths[] init ilen
+ gSconvdbNames[] init ilen
+ giconvdbChannels[] init ilen
+ index = 0
+ while (index < ilen) do
+ gSconvdbPaths[index] = Sres[index][1]
+ gSconvdbNames[index] = Sres[index][2]
+ giconvdbChannels[index] = strtod(Sres[index][3])
+ index += 1
+ od
+endop
+
+opcode convdb_getnames, S[], 0
+ xout gSconvdbNames
+endop
+
+opcode convdb_getindexbyname, i, S
+ Sname xin
+ index = 0
+ while (index < lenarray(gSconvdbNames)) do
+ if (strcmp(gSconvdbNames[index], Sname) == 0) then
+ goto complete
+ endif
+ index += 1
+ od
+
+complete:
+ xout index
+endop
+
+opcode convdb_randomimpulseindex, i, 0
+ xout round(random(0, lenarray(gSconvdbPaths)))
+endop
+
+opcode convdb_convolve, aa, ai
+ ain, iimpulseindex xin
+ SimpulsePath = gSconvdbPaths[iimpulseindex]
+ aL pconvolve ain, SimpulsePath, -1, 1
+ if (giconvdbChannels[iimpulseindex] == 2) then
+ aR pconvolve ain, SimpulsePath, -1, 2
+ else
+ aR = aL
+ endif
+ xout aL, aR
+endop
+
+
+opcode convdb_convolve, aa, aai
+ ainL, ainR, iimpulseindex xin
+ SimpulsePath = gSconvdbPaths[iimpulseindex]
+ aL pconvolve ainL, SimpulsePath, -1, 1
+ if (giconvdbChannels[iimpulseindex] == 2) then
+ aR pconvolve ainR, SimpulsePath, -1, 2
+ else
+ aL pconvolve ainR, SimpulsePath, -1, 1
+ endif
+ xout aL, aR
+endop
+
+
+opcode convdb_convolve, aa, aS
+ ain, Sname xin
+ impulseindex convdb_getindexbyname Sname
+ aL, aR convdb_convolve ain, impulseindex
+ xout aL, aR
+endop
+
+opcode convdb_convolve, aa, aaS
+ ainL, ainR, Sname xin
+ impulseindex convdb_getindexbyname Sname
+ aL, aR convdb_convolve ainL, ainR, impulseindex
+ xout aL, aR
+endop
+
+
+convdb_getimpulses()
+
+
+#end
diff --git a/site/udo/cs81z.udo b/site/udo/cs81z.udo
new file mode 100755
index 0000000..37fd829
--- /dev/null
+++ b/site/udo/cs81z.udo
@@ -0,0 +1,208 @@
+#ifndef UDO_CS81Z
+#define UDO_CS81Z ##
+
+
+/*
+ license required! GPL
+*/
+
+#include "wavetables.udo"
+gitx_AR ftgen 0, 0, 32, -2, 0,647829,452377,322874,225473,160445,112801,80602,56434,40244,28328,20297,14152,10306,7237,5231,3687,2601,1765,1417,1000,822,572,440,400,380,310,278,165,135,130,125
+gitx_D1R ftgen 0,0,32,-2, -1000,3116605,2179104,1547622,1086731,778176,542607,389089,272208,450000,137953,98004,69000,48235,34239,24524,36000,27000,13859,5774,4387,3254,2040,1573,955,925,575,475,200,125,1,1
+gitx_D2R ftgen 0,0,32,-2, -1000,3101310,2168831,1551896,1084546,771475,541448,387275,270054,192173,134025,96252,67545,47431,34174,24459,17359,11987,8775,6000,4302,2885,2136,1415,1000,700,677,355,331,254,1,1
+gitx_RR ftgen 0,0,16,-2,0,1559542,779813,386094,192749,97322,48481,24041,11808,6038,2957,1570,858,350,118,1
+gitx_D1L ftgen 0,0,16,-2, 0,0.007943,0.01122,0.015849,0.022387,0.031623,0.044668,0.063096,0.089125,0.125893,0.177828,0.251189,0.358922,0.506991,0.716143,1
+gitx_alg[][] init 13, 10 ; ALGORITHMIC MATRIX CONNECTIONS
+gitx_alg fillarray 1,0,0,1,0,1,0, 0,0,0,
+ 1,0,0,1,1,0,0, 0,0,0,
+ 1,0,1,1,0,0,0, 0,0,0,
+ 1,1,0,0,0,1,0, 0,0,0,
+ 1,0,0,0,0,1,0, 0,1,0,
+ 0,0,1,0,1,1,0, 1,1,0,
+ 0,0,0,0,0,1,0, 1,1,0,
+ 0,0,0,0,0,0,0, 1,1,1,
+ 1,0,0,0,0,0,0, 0,0,0, ; only OP2 -> OP1
+ 0,0,0,0,0,0,0, 0,0,0, ; only OP1
+ 1,0,0,0,1,0,0, 0,0,0, ; OP4 -> OP2 -> OP1
+ 1,0,0,0,1,0,1, 0,0,0, ; FD(OP4) -> OP2 -> OP1
+ 1,0,0,1,1,0,1, 0,0,0 ; LATELY
+
+opcode _tx_envelope, k, iiiii
+ iAR,iD1R,iD1L,iD2R,iRR xin
+ iAR table iAR, gitx_AR
+ iD1R table iD1R, gitx_D1R
+ iD1L table iD1L, gitx_D1L
+ iD2R table iD2R, gitx_D2R
+ iRR table iRR, gitx_RR
+ iAR /= 96000
+ iD1R /= 96000
+ iD2R /= 96000
+ iRR /= 96000
+ kRR = iRR
+
+ xtratim iRR
+ kRel release
+
+ kEnv init 0
+ kSta init 0
+
+ kAdd transeg 0, 0.01, -8 ,1
+
+ if kRel > 0 kgoto Release
+ if kSta != 0 goto Next
+
+ kEnv += 1/(iAR*kr)
+ kEnv limit kEnv, 0, 1
+ if kEnv != 1 goto Out
+ kSta = 1
+
+Next:
+ if kSta != 1 goto Next2
+ if iD1R >= 0 goto Next1
+ goto Out
+Next1:
+ kEnv -= (1/(iD1R*kr))
+ kEnv limit kEnv, iD1L, 1
+ if kEnv != iD1L goto Out
+ kSta = 2
+Next2:
+ if iD2R >= 0 goto Next3
+ goto Out
+Next3:
+ kEnv -= 1/(iD2R*kr)
+ kEnv limit kEnv, 0, iD1L
+ goto Out
+Release:
+ kEnv -= 1/(kRR*kr)
+ kEnv limit kEnv, 0, 1
+Out:
+ xout kEnv^6.6*kAdd
+endop
+
+
+opcode _tx_lp, a, a
+ ;setksmps 1
+ aL xin
+ aD0 init 0
+ aD1 init 0
+ iA1 = -0.5100490981424427
+ iB0 = 1
+ iB1 = 1
+ aD2 = aD1
+ aD1 = aD0
+ aD0 = aL-aD1*iA1
+ aout = aD0*iB0+aD1*iB1
+ xout aout*0.24497545092877862
+endop
+
+opcode _tx_hp, a, a
+ ;setksmps 1
+ aL xin
+ aD0 init 0
+ aD1 init 0
+ iA1 = -0.99869495948492626
+ iB0 = 1
+ iB1 = -1
+ aD2 = aD1
+ aD1 = aD0
+ aD0 = aL-aD1*iA1
+ aout=aD0*iB0+aD1*iB1
+ xout aout*0.99934747974246307
+endop
+
+opcode _tx_filter, a, a
+ ain xin
+ xout _tx_lp(_tx_hp(ain))
+endop
+
+
+; instr tx_tbconstruct ; TABLE CONSTRUCTOR from 81z-hold
+
+
+opcode _tx_op, a, akiiiiiik
+ ;setksmps 1
+ ; iKVS - veloc sensivity level 0...7
+ aMod, kCarFreq,i1,i2,i3,i4,i5, iWave,kamp xin
+ aCarFreq = a(kCarFreq)
+ kEnv _tx_envelope i1,i2,i3,i4,i5
+ aPhase phasor aCarFreq
+ aCar tablei aPhase+aMod, iWave, 1, 0, 1
+ xout aCar * kEnv * kamp
+endop
+
+
+
+
+
+opcode tx_synth, a, k[]k[]ii[]k[]k[]i[]i[]i[]i[]i[]
+ kfreqs[], kamps[], ialgorithm, iwaves[], kindexmod[], kfdbk[], iattack[], id1rel[], id1lev[], id2rel[], id2lev[] xin
+
+ aOP4 init 0
+
+ ; ===4 OP ======
+ ; OP parameters:
+ ; mod in, car freq X, att, D1 rel, D1 lev, D2 rel, rel, Wave,KVS
+ aOP1 init 0
+ ain4 = aOP4*gitx_alg[ialgorithm][6]*kfdbk[3]
+
+ aOP4 _tx_op ain4,.5*kfreqs[0], iattack[0], id1rel[0], id1lev[0], id2rel[0], id2lev[0], iwaves[0],kamps[0]
+
+
+ aOP4 = aOP4*kindexmod[2]
+
+ ain3 = aOP4*gitx_alg[ialgorithm][5]*kfdbk[2]
+ aOP3 _tx_op ain3, kfreqs[1], iattack[1], id1rel[1], id1lev[1], id2rel[1], id2lev[1], iwaves[1],kamps[1] ;0.996
+ aOP3 = aOP3*kindexmod[1]
+
+ ain2 = (aOP3*gitx_alg[ialgorithm][3] + aOP4*gitx_alg[ialgorithm][4])*kfdbk[1]
+ aOP2 _tx_op ain2,kfreqs[2], iattack[2], id1rel[2], id1lev[2], id2rel[2], id2lev[2], iwaves[2],kamps[2]
+ aOP2 = aOP2*kindexmod[0]
+
+ ain1 = (aOP2*gitx_alg[ialgorithm][0] + aOP3*gitx_alg[ialgorithm][1] + aOP4*gitx_alg[ialgorithm][2])*kfdbk[0]
+ aOP1 _tx_op ain1,kfreqs[3], iattack[3], id1rel[3], id1lev[3], id2rel[3], id2lev[3], iwaves[3],kamps[3]
+
+ a0 = aOP1 + aOP2*gitx_alg[ialgorithm][7] + aOP3*gitx_alg[ialgorithm][8] + aOP4*gitx_alg[ialgorithm][9]
+ xout _tx_filter(a0)
+endop
+
+/*
+instr 1
+ if (gicount > 20) then
+ gifreqs[] chordmidicpsbyindex int(random(0, lenarray(gSchords)-1)), int(random:i(55, 70))
+ gicount = 0
+ else
+ gicount += 1
+ endif
+ imults[] fillarray 0.25, 0.5, 1, 1.5, 2
+
+ ibasefreq = gifreqs[int(random(0, lenarray(gifreqs)-1))]
+ iwaves[] fillarray wavetable_random(), wavetable_random(), wavetable_random(), wavetable_random()
+ kindexmod[] fillarray random(0, 3), random(0, 3), random(0, 3)
+ kfdbk[] fillarray random(0, 3), random(0, 3), random(0, 3), random(0, 3)
+ kfreqs[] fillarray ibasefreq*imults[int(random(0, lenarray(imults)-1))], ibasefreq*imults[int(random(0, lenarray(imults)-1))], ibasefreq*imults[int(random(0, lenarray(imults)-1))], ibasefreq*imults[int(random(0, lenarray(imults)-1))]
+ kamps[] fillarray random(0.5, 2), random(0.5, 2), random(0.5, 2), random(0.5, 2)
+ iattack[] fillarray random(0, 31), random(0, 31), random(0, 31), random(0, 31)
+ id1rel[] fillarray random(0, 31), random(0, 31), random(0, 31), random(0, 31)
+ id1lev[] fillarray random(0, 31), random(0, 31), random(0, 31), random(0, 31)
+ id2rel[] fillarray random(0, 15), random(0, 15), random(0, 15), random(0, 15)
+ id2lev[] fillarray random(0, 15), random(0, 15), random(0, 15), random(0, 15)
+
+ a1 tx_synth kfreqs, kamps, 4, iwaves, kindexmod, kfdbk, iattack, id1rel, id1lev, id2rel, id2lev
+
+ kamp linseg 1, p3*0.8, 1, p3*0.2, 0
+ kpan line random(0, 1), p3, random(0, 1)
+ outs a1*kamp*0.1*kpan, a1*kamp*0.1*(1-kpan)
+endin
+
+
+instr sched
+ krate random 0.2, 5
+ k1 metro krate
+ schedkwhen k1, 0, 0, 1, random:k(0, 2), random:k(1, 6)
+endin
+*.
+*/
+
+
+#end
+
diff --git a/site/udo/csv.udo b/site/udo/csv.udo
new file mode 100755
index 0000000..1829c0b
--- /dev/null
+++ b/site/udo/csv.udo
@@ -0,0 +1,75 @@
+#ifndef UDO_CSV
+#define UDO_CSV ##
+/*
+ CSV parsing and writing
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+opcode csv_parseline, i[], Si
+ Sline, ilen xin
+ iout[] init ilen
+ index = 0
+ irun = 1
+ while (irun == 1) do
+ ipos strindex Sline, ","
+ if (ipos == -1) then
+ irun = 0
+ iout[index] = strtod(Sline)
+ else
+ Stemp strsub Sline, 0, ipos
+ Sline strsub Sline, ipos+1
+ iout[index] = strtod(Stemp)
+ index += 1
+ endif
+ od
+ xout iout
+endop
+
+opcode csv_formline, S, i[]j
+ iarray[], iformat xin
+ if (iformat == -1) then
+ Sformat = "%f"
+ else
+ Sformat = "%d"
+ endif
+ Sout = ""
+ ilen = lenarray(iarray)
+ index = 0
+ while (index < ilen) do
+ Sout strcat Sout, sprintf(Sformat, iarray[index])
+ if (index < ilen - 1) then
+ Sout strcat Sout, ","
+ endif
+ index += 1
+ od
+ Sout strcat Sout, "\n"
+ xout Sout
+endop
+
+
+; form from table
+opcode csv_formline, S, ij
+ ifn, iformat xin
+ if (iformat == -1) then
+ Sformat = "%f"
+ else
+ Sformat = "%d"
+ endif
+ Sout = ""
+ ilen = ftlen(ifn)
+ index = 0
+ while (index < ilen) do
+ Sout strcat Sout, sprintf(Sformat, table:i(index, ifn))
+ if (index < ilen - 1) then
+ Sout strcat Sout, ","
+ endif
+ index += 1
+ od
+ Sout strcat Sout, "\n"
+ xout Sout
+endop
+
+#end
diff --git a/site/udo/delays.udo b/site/udo/delays.udo
new file mode 100755
index 0000000..948e3cb
--- /dev/null
+++ b/site/udo/delays.udo
@@ -0,0 +1,42 @@
+#ifndef UDO_DELAYS
+#define UDO_DELAYS ##
+/*
+ Delay lines
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Multitap delay with random oscillated repitching and feedback
+ aout delay_wild ain, imindelay, imaxdelay
+
+ aout delayed signal
+ ain input signal
+ imindelay minimum delay time
+ imaxdelay maximum delay time
+*/
+opcode delay_wild, a, aoo
+ ain, imindelay, imaxdelay xin
+ if (imindelay == 0) then
+ imindelay = 0.2
+ endif
+ if (imaxdelay == 0) then
+ imaxdelay = 0.6
+ endif
+ adb delayr imaxdelay
+ ad1 deltap abs(oscili:k(random:i(imindelay, imaxdelay), random:i(0.01, 0.2)))
+ ad2 deltap abs(oscili:k(random:i(imindelay, imaxdelay), random:i(0.01, 0.2)))
+ delayw ain + (ad1 * random:i(0.1, 0.3)) + (ad2 * random:i(0.1, 0.3))
+
+ adb delayr imaxdelay
+ ad3 deltap abs(oscili:k(random:i(imindelay, imaxdelay), random:i(0.01, 0.2)))
+ ad4 deltap abs(oscili:k(random:i(imindelay, imaxdelay), random:i(0.01, 0.2)))
+ delayw ad2 + (ad3 * random:i(0, 0.3)) + (ad4 * random:i(0, 0.3))
+ xout ad3+ad4
+endop
+
+
+#end \ No newline at end of file
diff --git a/site/udo/experimental.udo b/site/udo/experimental.udo
new file mode 100755
index 0000000..71a9d88
--- /dev/null
+++ b/site/udo/experimental.udo
@@ -0,0 +1,41 @@
+#ifndef UDO_EXPERIMENTAL
+#define UDO_EXPERIMENTAL ##
+
+/*
+ Experimental tonal balance of two signals
+
+ aoutput balancetonal ain, aincomparator
+
+ aoutput balanced signal
+ ain signal to apply changes to
+ aincomparator signal to 'extract' frequency contour from
+*/
+opcode balancetonal, a, aa
+ ain, ainc xin
+ aouts[] init 16
+
+ aouts[0] balance butterbp(ain, 100, 200), butterbp(ainc, 100, 200) ; 0 - 200
+ aouts[1] balance butterbp(ain, 400, 400), butterbp(ainc, 400, 400) ; 200 - 600
+ aouts[2] balance butterbp(ain, 800, 400), butterbp(ainc, 800, 400) ; 600 - 1000
+ aouts[3] balance butterbp(ain, 1200, 400), butterbp(ainc, 1200, 400) ; 1000 - 1400
+ aouts[4] balance butterbp(ain, 1700, 600), butterbp(ainc, 1700, 600) ; 1400 - 2000
+ aouts[5] balance butterbp(ain, 2400, 800), butterbp(ainc, 2400, 800) ; 2000 - 2800
+ aouts[6] balance butterbp(ain, 3200, 800), butterbp(ainc, 3200, 800) ; 2800 - 3600
+ aouts[7] balance butterbp(ain, 4200, 1200), butterbp(ainc, 4200, 1200) ; 3600 - 4800
+ aouts[8] balance butterbp(ain, 5400, 1200), butterbp(ainc, 5400, 1200) ; 4800 - 6000
+ aouts[9] balance butterbp(ain, 7000, 2000), butterbp(ainc, 7000, 2000) ; 6000 - 8000
+ aouts[10] balance butterbp(ain, 9000, 2000), butterbp(ainc, 9000, 2000) ; 8000 - 10000
+ aouts[11] balance butterbp(ain, 11000, 2000), butterbp(ainc, 11000, 2000) ; 10000 - 12000
+ aouts[12] balance butterbp(ain, 14000, 4000), butterbp(ainc, 14000, 4000) ; 12000 - 16000
+ aouts[13] balance butterbp(ain, 18000, 4000), butterbp(ainc, 18000, 4000) ; 16000 - 20000
+ aouts[14] balance butterhp(ain, 20000), butterhp(ainc, 20000)
+
+ aout sumarray aouts
+ xout aout
+endop
+
+
+
+
+
+#end
diff --git a/site/udo/feedback.udo b/site/udo/feedback.udo
new file mode 100755
index 0000000..5f4b7d8
--- /dev/null
+++ b/site/udo/feedback.udo
@@ -0,0 +1,164 @@
+#ifndef UDO_FEEDBACK
+#define UDO_FEEDBACK ##
+/*
+ No-input mixer modelling
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+opcode _arrayfilter, a, ai[]i[]o
+ ain, ifreq[], igain[], index xin
+ aout pareq ain, ifreq[index], igain[index], 0.775, 0
+ if (index < lenarray(ifreq) - 1) then
+ aout _arrayfilter aout, ifreq, igain, index+1
+ endif
+ xout aout
+endop
+
+
+opcode _fbk_componentmodel, a, a
+ a1 xin
+ ifreqs[] fillarray 30, 60, 100, 300, 600, 1200, 2400, 4800, 8000, 10000, 12000, 16000
+ igains[] fillarray 0.4, 0.7, 1, 0.4, 0.6, 0.96, 0.4, 0.7, 0.32, 0.7, 0.8, 0.9
+ a1 _arrayfilter a1, ifreqs, igains
+ xout a1
+endop
+
+opcode _fbk_componentmodel2, a, a
+ a1 xin
+ iminfreq = 20
+
+ adel delayr 1/iminfreq
+ acomb0 deltapi 1/1000
+
+ delayw a1
+
+ aout = a1 - acomb0
+ xout aout
+endop
+
+/*
+ Simulated mixer channel
+ aout fbk_channel ain, klowcut, keqlow, keqmid, keqhigh, kcompmodel
+
+ aout channel output
+
+ ain channel input
+ klowcut low cut toggle (1 is on)
+ keqlow eq low (0 to 1)
+ keqmid eq mid (0 to 1)
+ keqhigh eq high (0 to 1)
+ kcompmodel component model (0 is off, 1 is model components)
+
+*/
+opcode fbk_channel, a, akkkkk
+ a1, klowCut, keqlow, keqmid, keqhigh, kcompmodel xin
+ a1 += noise(0.01, 0)
+ ;a1 dcblock a1
+ a1 butterhp a1, 0.1
+
+ if (klowCut == 1) then
+ a1 butterhp a1, 75
+ endif
+
+
+ a1 pareq a1, 80, keqlow*5, 0.7, 1
+ a1 pareq a1, 2500, keqmid*5, 0.7, 0
+ a1 pareq a1, 12000, keqhigh*5, 0.7, 2
+ ;a1 dam a1, 0.99, 0.9, 0.9, 0.01, 0.01
+ ;a1 limit a1, -0.5, 0.5
+ a1 tanh a1
+ /*krms rms a1
+ if (krms > 1) then
+ a1 = a1 * (1/krms)
+ endif
+*/
+ if (kcompmodel == 1) then
+ a1 _fbk_componentmodel a1
+ endif
+ xout a1
+endop
+
+
+/*
+ params
+ 0 low cut
+ 1 eq low
+ 2 eq mid
+ 3 eq high
+ 4 component model
+ 5 prefade aux on/off
+ 6 aux 1 send
+ 7 aux 2 send
+ 8 mix level
+*/
+opcode fbk_mixer2, a, k[]k[]
+ kparam1[], kparam2[] xin
+ ain1 init 0
+ ain2 init 0
+ aout1 fbk_channel ain1, kparam1[0], kparam1[1], kparam1[2], kparam1[3], kparam1[4]
+ aout2 fbk_channel ain2, kparam2[0], kparam2[1], kparam2[2], kparam2[3], kparam2[4]
+ ain1 = 0
+ ain2 = 0
+ if (kparam1[5] == 1) then
+ ain1 += aout1*kparam1[6]
+ else
+ ain1 += aout1*kparam1[6]*kparam1[8]
+ endif
+ ain2 += aout1*kparam1[7]*kparam1[8]
+
+ if (kparam2[5] == 1) then
+ ain2 += aout1*kparam2[7]
+ else
+ ain2 += aout1*kparam2[7]*kparam2[8]
+ endif
+ ain1 += aout2*kparam2[6]*kparam2[8]
+
+ aout = (aout1*kparam1[8]) + (aout2*kparam2[8])
+ aout pareq aout, 18000, 0.4, 0.7
+ xout aout
+endop
+
+
+
+
+
+opcode fbk_mixer4, a, k[]k[]k[]k[]
+ kparam1[], kparam2[], kparam3[], kparam4[] xin
+ ain1 init 0
+ ain2 init 0
+ ain3 init 0
+ ain4 init 0
+ aout1 fbk_channel ain1, kparam1[0], kparam1[1], kparam1[2], kparam1[3], kparam1[4]
+ aout2 fbk_channel ain2, kparam2[0], kparam2[1], kparam2[2], kparam2[3], kparam2[4]
+ aout3 fbk_channel ain3, kparam3[0], kparam3[1], kparam3[2], kparam3[3], kparam3[4]
+ aout4 fbk_channel ain4, kparam4[0], kparam4[1], kparam4[2], kparam4[3], kparam4[4]
+ ain1 = 0
+ ain2 = 0
+ ain3 = 0
+ ain4 = 0
+ if (kparam1[5] == 1) then
+ ain1 += aout1*kparam1[6]
+ else
+ ain1 += aout1*kparam1[6]*kparam1[8]
+ endif
+ ain2 += aout1*kparam1[7]*kparam1[8]
+
+ if (kparam2[5] == 1) then
+ ain2 += aout1*kparam2[7]
+ else
+ ain2 += aout1*kparam2[7]*kparam2[8]
+ endif
+ ain1 += aout2*kparam2[6]*kparam2[8]
+
+ aout = (aout1*kparam1[8]) + (aout2*kparam2[8])
+ aout pareq aout, 18000, 0.4, 2, 0.7
+ xout aout
+endop
+
+
+#end
+
diff --git a/site/udo/fftconvolve.udo b/site/udo/fftconvolve.udo
new file mode 100755
index 0000000..fe17319
--- /dev/null
+++ b/site/udo/fftconvolve.udo
@@ -0,0 +1,116 @@
+#ifndef UDO_FFTCONVOLVE
+#define UDO_FFTCONVOLVE ##
+/*
+ Block based FFT convolution
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Per FFT block convolution
+
+ aout blockconvolve ain1, ain2, [ifftsize, ihopsize]
+
+ aout convolved output
+ ain1 input 1
+ ain2 input 2
+ ifftsize FFT size DEFAULT(256)
+ ihopsize FFT hop size DEFAULT(256)
+*/
+opcode blockconvolve, a, aajj
+ idopow = 0 ; use pow on mags
+
+ ain1, ain2, ifftsize, ihopsize xin
+
+ if (ihopsize == -1) then
+ ihopsize = 256
+ endif
+
+ if (ifftsize == -1) then
+ ifftsize = 256
+ endif
+
+
+ setksmps 1
+ iolaps = ifftsize / ihopsize
+ ibw = sr / ifftsize
+
+ kcnt init 0
+ krow init 0
+
+ kOla1[] init ifftsize
+ kOla2[] init ifftsize
+
+ kIn1[] init ifftsize
+ kIn2[] init ifftsize
+
+ kOut[][] init iolaps, ifftsize ; output buffers
+
+ if (kcnt == ihopsize) then
+
+ kWin1[] window kIn1, krow * ihopsize
+ kWin2[] window kIn2, krow * ihopsize
+ kSpec1[] rfft kWin1
+ kSpec2[] rfft kWin2
+
+ kmags1[] mags kSpec1
+ kmags2[] mags kSpec2
+ kmagMult[] = kmags1 * kmags2
+
+ kphs1[] phs kSpec1
+ kphs2[] phs kSpec2
+ kphsMult[] = kphs1 * kphs2
+
+
+ ; pow thing doesn't sound that good
+ if (idopow == 1) then
+ kindex = 0
+ kpows[] init lenarray(kmagMult)
+ while (kindex < lenarray(kmagMult)) do
+ kpows[kindex] pow kmagMult[kindex], 0.5
+ kindex += 1
+ od
+ else
+ kpows[] = kmagMult
+ endif
+
+ kSpec1 pol2rect kpows, kphsMult
+
+
+ ; IFFT + window
+ kRow[] rifft kSpec1
+ kWin1 window kRow, krow * ihopsize
+
+ ; place it on out buffer
+ kOut setrow kWin1, krow
+
+ ; zero the ola buffer
+ kOla1 = 0
+
+ ; overlap add
+ ki = 0
+ until (ki == iolaps) do
+ kRow getrow kOut, ki
+ kOla1 = kOla1 + kRow
+ ki += 1
+ od
+
+ ; update counters
+ krow = (krow + 1) % iolaps
+ kcnt = 0
+ endif
+
+ ; shift audio in/out of buffers
+ kIn1 shiftin ain1
+ kIn2 shiftin ain2
+ aout shiftout kOla1
+ xout aout / iolaps
+
+ ; increment counter
+ kcnt += ksmps
+endop
+
+#end
diff --git a/site/udo/fnml/clay_workings.csd b/site/udo/fnml/clay_workings.csd
new file mode 100755
index 0000000..e11e64b
--- /dev/null
+++ b/site/udo/fnml/clay_workings.csd
@@ -0,0 +1,58 @@
+<CsoundSynthesizer>
+<CsOptions>
+-odac
+</CsOptions>
+<CsInstruments>
+sr = 44100
+kr = 4410
+nchnls = 2
+0dbfs = 1
+seed 0
+
+#include "sounddb.udo"
+#include "uniqueid.udo"
+#include "bussing.udo"
+
+gicoll_clay[], gicollclay_id sounddb_getcollection "Clay.Hit"
+
+instr play_clay_roll
+ instanceid = uniqueid()
+ icollsize = lenarray(gicoll_clay)
+ ifreqmax = 100
+ kmetrofreq expseg ifreqmax*0.4, p3*0.3, ifreqmax*0.8, p3*0.4, ifreqmax, p4*0.4, ifreqmax*0.5
+ kmetro metro kmetrofreq
+ if (kmetro == 1) then
+ kdbid = gicoll_clay[round:k(random:k(0, icollsize - 1))]
+ schedulek "_play_clay_hit", random(0, 1/kmetrofreq), 1, kdbid, instanceid
+ endif
+
+ kamp expseg 0.001, p3*0.4, 1, p3*0.1, 1, p3*0.4, 0.001
+
+ aL, aR bus_read sprintf("clay_hit%d", instanceid)
+ aL pareq aL, 15000, 4, 0.6
+ aR pareq aR, 15000, 4, 0.6
+ aL *= kamp
+ aR *= kamp
+ outs aL, aR
+endin
+
+
+
+instr _play_clay_hit
+ idbid = p4
+ instanceid = p5
+ ifn = gisounddb[idbid][0]
+ idur = gisounddb[idbid][2]
+ p3 = idur
+
+ aL, aR loscil 0.2, 1, ifn, 1
+ ipan = random(0, 1)
+ bus_mix(sprintf("clay_hit%d", instanceid), aL*ipan, aR*(1-ipan))
+endin
+
+
+</CsInstruments>
+<CsScore>
+i"play_clay_roll" 0 6
+</CsScore>
+</CsoundSynthesizer> \ No newline at end of file
diff --git a/site/udo/fnml/instrument_automel.udo b/site/udo/fnml/instrument_automel.udo
new file mode 100755
index 0000000..437acc0
--- /dev/null
+++ b/site/udo/fnml/instrument_automel.udo
@@ -0,0 +1,89 @@
+#ifndef UDO_FNMI_AUTOMEL
+#define UDO_FNMI_AUTOMEL ##
+
+#include "sequencing_table.udo"
+#include "sequencing_melodic.udo"
+#include "bussing.udo"
+
+instr _play_fnmi_automel1
+ icollectionid = p4
+ Sbus = p5
+ ifreqmult = p6
+ inote = mel_randomnote()
+ if (random(0, 1) >= 0.5) then
+ inote += 12
+ endif
+
+ idowaveset = (random(0, 1) >= 0.8) ? 1 : 0
+ if (idowaveset == 1) then
+ if (random(0, 1) >= 0.6) then
+ p3 *= random(1.2, 1.8)
+ endif
+ endif
+
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+ ifn, ichannels, iduration, irmsnorm sounddb_get ifileid
+
+ ireadtype = round(random(0, 2))
+
+ if (ireadtype == 0) then
+ aL, aR loscil 1, ipitchratio * ifreqmult, ifn, 1
+ else
+ atime line 0, p3, iduration * random(0.5, 1)
+ if (ireadtype == 1) then
+ imincerfftsize = pow(2, round(random(8, 11)))
+ aL, aR mincer atime, random(0.7, 1.1), ipitchratio * ifreqmult, ifn, 0, imincerfftsize
+ elseif (ireadtype == 2) then
+ iwsize = random(441, 4410)
+ aL, aR sndwarpst 1, atime, ipitchratio * ifreqmult * (ftsr(ifn) / sr), ifn, 0, iwsize, iwsize * 0.1, 4, gifnHalfSine, 1
+ endif
+ endif
+ aamp linseg 1, p3*0.9, 1, p3*0.1, 0
+
+ if (random(0, 1) >= 0.6) then
+ aL, aR ringmod1 aL, aR, cpsmidinn(inote) * 2
+ endif
+
+ aL *= aamp
+ aR *= aamp
+ if (idowaveset == 1) then
+ kwaveseta line 0, p3, round(random(2, 5))
+ aL waveset aL, kwaveseta
+ aR waveset aR, kwaveseta
+ endif
+ if (random(0, 1) >= 0.7) then
+ aL distort aL, 0.5, gifnSine
+ aR distort aR, 0.5, gifnSine
+ aL *= 0.4
+ aR *= 0.4
+ endif
+
+ bus_mix(Sbus, aL, aR)
+endin
+
+/*
+ aL, aR automel1 icollectionid1, kchance=1, kdivisions=4, icollectionid2=icollectionid1, kfreqmult=1
+*/
+opcode automel1, aa, iVJjJ
+ icollectionid1, kchance, kdivisions, icollectionid2, kfreqmult xin
+ kdivisions = (kdivisions == -1) ? 4 : kdivisions
+ kfreqmult = (kfreqmult == -1) ? 1 : kfreqmult
+ kcollectionid init icollectionid1
+ Sbus = sprintf("fnmautomel%d", uniqueid())
+ ktrig seq_table gifn_tabseq_all, 0, 4, kchance
+ instrnum = nstrnum("_play_fnmi_automel1") + uniquefrac()
+ if (ktrig == 1 && active:k(instrnum) == 0) then
+ if (icollectionid2 != -1) then
+ if (random:k(0, 1) >= 0.5) then
+ kcollectionid = icollectionid1
+ else
+ kcollectionid = icollectionid2
+ endif
+ endif
+ schedulek(instrnum, 0, gkseq_quartertime * random:k(0.5, 4), kcollectionid, Sbus, kfreqmult)
+ endif
+ aL, aR bus_read Sbus
+ xout aL, aR
+endop
+
+#end
diff --git a/site/udo/fnml/instrument_gchord1.udo b/site/udo/fnml/instrument_gchord1.udo
new file mode 100755
index 0000000..e8c5905
--- /dev/null
+++ b/site/udo/fnml/instrument_gchord1.udo
@@ -0,0 +1,260 @@
+#ifndef UDO_FNMI_GCHORD1
+#define UDO_FNMI_GCHORD1 ##
+/*
+ Portamento glitch-out textural chord player
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+#include "wavetables.udo"
+#include "sequencing_melodic_portamento.udo"
+#include "sounddb.udo"
+#include "bussing.udo"
+#include "frequency_tools.udo"
+#include "uniqueid.udo"
+
+/*
+ sounddb glitchy chord player
+ aL, aR fnmi_gchord1 icollectionid, iattacktime, ireleasetime, icompressmode, kchangechance [, ipitchratio=1, ireadtype=0, ireloadtime=10]
+ aL, aR fnmi_gchord1 Scollection, iattacktime, ireleasetime, icompressmode, kchangechance [, ipitchratio=1, ireadtype=0, ireloadtime=10]
+
+ aL, aR audio output
+
+ icollectionid sounddb collection ID to use
+ Scollection sounddb collection name to use
+ iattacktime start fade in time
+ ireleasetime fade out time on host instrument note end
+ icompressmode 0 = none ; 1 = harshwall ; 2 = normal
+ kchangechance glitchy item change rate chance (1 = every quarter beat)
+ ipitchratio default pitch augmentation ratio
+ ireadtype 0 = sndwarp ; 1 = mincer
+ ireloadtime seconds between reloads of subinstruments to ensure variation in source sound
+*/
+
+opcode fnmi_gchord1, aa, iiiikpoj
+ icollectionid, iattacktime, ireleasetime, icompressmode, kchangechance, ipitchratio, ireadtype, ireloadtime xin
+ ilen = p3
+ ireloadtime = (ireloadtime == -1) ? 10 : ireloadtime
+ instanceid = uniqueid()
+
+ iusedinstruments[] uniqueinstrnums "_fnmi_gchord1_notehold", ftlen(gimel_freqs)
+
+ ; set up notehold instruments
+ index = 0
+ while (index < lenarray(iusedinstruments)) do
+ schedule iusedinstruments[index], 0, ilen, index, icollectionid, ireleasetime, instanceid, ipitchratio, ireadtype
+ index += 1
+ od
+
+
+ ; reload random notehold instrument at periodic intervals (ie to change source sound)
+ klastchangetime init 0
+ ktime timeinsts
+ if (ktime - klastchangetime > ireloadtime) then
+ kindex = round:k(random(0, lenarray(iusedinstruments)-1))
+ kinstrument = iusedinstruments[kindex]
+ turnoff2 kinstrument, 4, 1
+ schedulek kinstrument, ireleasetime*0.5, ilen-ktime, kindex, icollectionid, ireleasetime, instanceid, ipitchratio, ireadtype
+ klastchangetime = ktime
+ endif
+
+
+ ; if host instrument of opcode ends, turn off all notehold instances
+ kreleasing init 0
+ if (release:k() == 1 && kreleasing == 0) then
+ kreleasing = 1
+ kindex = 0
+ while (kindex < lenarray(iusedinstruments)) do
+ turnoff2 iusedinstruments[kindex], 4, 1
+ kindex += 1
+ od
+ endif
+
+ ; if at end of host instrument note length, add release time for relevant fade out
+ if (lastcycle:k() == 1) then
+ xtratim ireleasetime
+ endif
+
+
+ ; trigger for variations in individual notehold instruments
+ idivisions = 4
+ as, aps syncphasor -(gkseq_beathz*idivisions), a(gkseq_beat)
+ ktrig trigger k(as), 0.1, 0
+ chnset ktrig, sprintf("fnmi_gchord1_qtrig%d", instanceid)
+
+
+ ; 'global' change chance for the notehold instruments
+ chnset kchangechance, sprintf("fnmi_gchord1_changechance%d", instanceid)
+
+ ; feed from the notehold instruments
+ aL, aR bus_read sprintf("fnmi_gchord1_out%d", instanceid)
+
+ if (icompressmode == 1) then
+ acomp noise 0.2, 0.4
+ aL balance aL, acomp
+ aR balance aL, acomp
+ elseif (icompressmode == 2) then
+ aL compress aL, aL, -5, 40, 40, 6, 0, 0.1, 0
+ aR compress aR, aR, -5, 40, 40, 6, 0, 0.1, 0
+ aL *= 30
+ aR *= 30
+ endif
+
+ aL dcblock aL
+ aR dcblock aR
+
+ iattacktime = max(0.00001, iattacktime) ; can't be 0 for linseg
+ kamp linsegr 0, iattacktime, 1, ilen - iattacktime, 1, ireleasetime, 0
+
+ xout aL*kamp, aR*kamp
+endop
+
+; overload for named collection
+opcode fnmi_gchord1, aa, Siiikpoj
+ Scollection, iattacktime, ireleasetime, icompressmode, kchangechance, ipitchratio, ireadtype, ireloadtime xin
+ aL, aR fnmi_gchord1 sounddb_getcollectionid(Scollection), iattacktime, ireleasetime, icompressmode, kchangechance, ipitchratio, ireadtype, ireloadtime
+ xout aL, aR
+endop
+
+
+
+/*
+ Used internally by fnmi_gchord1 for sound generation and return via channel
+*/
+instr _fnmi_gchord1_notehold
+ index = p4
+ icollectionid = p5
+ ireleasetime = p6
+ instanceid = p7
+ iuserpitchratio = p8
+ ireadtype = p9
+ kamp table index, gimel_amps
+
+ aL init 0
+ aR init 0
+ if (kamp > 0) then ; all processing
+ kamp *= 0.32 ;0.05
+ kfreq table index, gimel_freqs
+ ibasenote random 30, 50
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, ibasenote
+ ifn = gisounddb[ifileid][0]
+
+ ipitchratio *= ((ireadtype == 0) ? (ftsr(ifn) / sr) : 1) * iuserpitchratio ; sr adjustment for sndwarp required
+ ilen = ftlen(ifn) / ftsr(ifn)
+
+ ; pitch lfo
+ alfo oscil 2.1, 0.15, gifnSine
+ kfreq += k(alfo)
+
+ kpitchratio = (kfreq / cpsmidinn(ibasenote)) * ipitchratio
+
+ istart = random(0, 0.1) ;* ilen
+ iend = random(istart+0.1, 0.4) ; 0.9
+
+ kreadmode init 0
+
+ if (kreadmode == 0) then
+ atime = (abs(oscil(iend-istart, random(0.001, 0.1), gifnSine, random(0, 1)))) * ilen ; TODO: don't think + istart is required here
+ elseif (kreadmode == 1) then
+ atime = (istart * ilen) + ((phasor(random(2, 10)) * (ilen * (iend - istart))))
+ else
+ atime = (istart * ilen) + ((phasor(-random(2, 10)) * (ilen * (iend - istart))))
+ endif
+
+ if (ireadtype == 0) then
+ aL, aR sndwarpst kamp, atime, interp(kpitchratio), ifn, istart, 441*random(1, 100), 44*random(1, 10), 8, gifnHalfSine, 1
+ elseif (ireadtype == 1) then
+ aL, aR mincer atime, kamp, kpitchratio, ifn, 0
+ endif
+
+ kdo_crush init 0
+ kdo_diff init 0
+ kdo_delaytuner init 0
+ kdo_ringmod init 0
+ kdelmult init 8
+ kcrushrange init 4
+ kringmodmult init 2
+ khpfreq init 150
+ kpan init random(0, 1)
+
+ if (kdo_crush == 1) then
+ kcrush = abs:k(oscil:k(kcrushrange, random(0.01, 0.3))) + kcrushrange
+ kcrushamount = abs:k(oscil:k(0.7, random(0.001, 0.2), gifnSaw, random(0, 1)))
+ aLbc, aRbc bitcrush aL, aR, kcrush
+ aL += aLbc * kcrushamount
+ aR += aRbc * kcrushamount
+ endif
+
+ if (kdo_ringmod == 1) then
+ aL, aR ringmod1 aL, aR, kfreq*kringmodmult ;portk(kfreq*kringmodmult, 0.01)
+ endif
+
+ if (kdo_delaytuner == 1) then
+ kdelaytuneramount = abs:k(oscil:k(0.5, random(0.001, 0.2), gifnSine, random(0, 1)))
+ aLdt, aRdt delaytuner aL, aR, max:k(1, kpitchratio)*kdelmult, 0.9 ; portk(kdelmult, 0.1)
+ aL += aLdt * kdelaytuneramount
+ aR += aRdt * kdelaytuneramount
+ endif
+
+ aL butterhp aL, khpfreq
+ aR butterhp aR, khpfreq
+
+ if (kdo_diff == 1) then
+ aL diff aL
+ endif
+
+ ktrig = chnget:k(sprintf("fnmi_gchord1_qtrig%d", instanceid))
+ kchangechance = chnget:k(sprintf("fnmi_gchord1_changechance%d", instanceid))
+
+ if (ktrig == 1 && random:k(0, 1) < kchangechance) then
+ if (random:k(0, 1) > 0.9) then
+ kreadmode = round:k(random:k(0, 2))
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ khpfreq = random:k(250, 2500)
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kdelmult = round:k(random:k(8, 16))
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kcrushrange = round:k(random:k(2, 64))
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kdo_crush = 1 - kdo_crush
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kdo_delaytuner = 1 - kdo_delaytuner
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kdo_ringmod = 1 - kdo_ringmod
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kringmodmult = pow:k(2, round:k(random:k(-1, 2))) ; 3 up to 8
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kpan = random:k(0, 1)
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kdo_diff = 1 - kdo_diff
+ endif
+ endif
+
+ aL *= kpan
+ aR *= (1-kpan)
+ endif ; if amp > 0
+
+ krelamp linsegr 1, p3, 1, ireleasetime, 0
+ bus_mix(sprintf("fnmi_gchord1_out%d", instanceid), aL*krelamp, aR*krelamp)
+endin
+
+#end
diff --git a/site/udo/fnml/instrument_portchord.udo b/site/udo/fnml/instrument_portchord.udo
new file mode 100755
index 0000000..fd8b682
--- /dev/null
+++ b/site/udo/fnml/instrument_portchord.udo
@@ -0,0 +1,132 @@
+#ifndef UDO_FNMI_PORTCHORD
+#define UDO_FNMI_PORTCHORD ##
+/*
+ Portamento recursive chord players
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "__config__.udo"
+#include "sequencing_melodic_persistence.udo"
+#include "sequencing_melodic_portamento.udo"
+#include "wavetables.udo"
+#include "sounddb.udo"
+
+
+/*
+ Play continuous chords from melodic sequencer with portamento, using oscil as an instrument and a specified wavetable
+
+ aL, aR portchord_wave [iwavefn=gifnSine, ifreqmult=1, ivibdepth=1, ivibrate=3, index=0]
+
+ aL, aR stereo outputs
+ iwavefn the f-table to use with oscil
+ ifreqmult frequency multiplier of the chord note frequencies to be applied
+ ivibdepth vibrato depth
+ ivibrate vibrato rate in Hz
+ index internal start index of the chord notes; could also be used to specify starting note offset
+*/
+opcode portchord_wave, aa, jpjjo
+ iwavefn, ifreqmult, ivibdepth, ivibrate, index xin
+
+ iwavefn = (iwavefn == -1) ? gifnSine : iwavefn
+ ivibdepth = (ivibdepth == -1) ? 1 : ivibdepth
+ ivibrate = (ivibrate == -1) ? 3 : ivibrate
+
+ kamp table index, gimel_amps
+ kfreq table index, gimel_freqs
+
+ klfo = oscil:k(ivibdepth, ivibrate) ;oscil:k(7, 5)
+ kfreq += klfo
+ kfreq *= ifreqmult
+
+ ;kamp portk kamp, (i(gkseq_beattime) * gimel_portamento_beatratio) ; fade out when change
+
+ aL oscil kamp*0.1, kfreq, iwavefn
+ ipan = random(0, 1)
+ aR = aL * ipan
+ aL *= (1 - ipan)
+
+ if (index + 1 < ftlen(gimel_amps)) then
+ aLx, aRx portchord_wave iwavefn, ifreqmult, ivibdepth, ivibrate, index + 1
+ aL += aLx
+ aR += aRx
+ endif
+
+ xout aL, aR
+endop
+
+
+
+/*
+ Play continuous chords from melodic sequencer with portamento, using a sounddb collection as source sounds
+
+ aL, aR portchord_sound icollectionid [, imode=1, kfreqmult=1, ifftsize=giFFTsize, index=0]
+
+ aL, aR stereo outputs
+ icollectionid collection ID from sounddb to use for the playback
+ imode 0 = read with sndwarp; 1 = read with mincer
+ kfreqmult frequency multiplier of the chord note frequencies to be applied
+ ifftsize FFT size to use when imode = 1 ; default to global setting in __config__.udo
+ index internal start index of the chord notes; could also be used to specify starting note offset
+*/
+opcode portchord_sound, aa, ipPjo
+ icollectionid, imode, kfreqmult, ifftsize, index xin
+
+ ifftsize = (ifftsize == -1) ? giFFTsize : ifftsize
+
+ inote = round(random(50, 80))
+ ibasefreq = cpsmidinn(inote)
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+
+ ifn = gisounddb[ifileid][0]
+ ichannels = gisounddb[ifileid][1]
+ idur = gisounddb[ifileid][2]
+ irmsnorm = gisounddb[ifileid][3]
+
+ kampb table index, gimel_amps
+ kfreq table index, gimel_freqs
+
+ kamp portk kampb, (i(gkseq_beattime) * gimel_portamento_beatratio) ; fade out when change
+
+ kpitch = (kfreq / ibasefreq) * ipitchratio * kfreqmult ; actual pitch adjustment
+
+ istart = random(0.05, 0.2)
+ iend = random(istart+0.1, 0.8)
+ atime = (abs(oscil(iend - istart, random(0.001, 0.1), gifnSine, random(0, 1))) + istart) * idur
+
+
+ klfo = oscil:k(random(0.0001, 0.009), random(1, 5)) + 1
+ kpitch *= klfo
+
+ if (kamp != 0) then
+ if (imode == 0) then
+ kpitch *= (ftsr(ifn) / sr) ; adjustment for sndwarp required
+
+ ;apitch interp kpitch
+ aL, aR sndwarpst kamp, atime, kpitch, ifn, istart, 4410, 441, 8, gifnHalfSine, 1
+
+ else
+ if (ichannels == 2) then
+ aL, aR mincer atime, kamp, kpitch, ifn, 0, ifftsize
+ else
+ aL mincer atime, kamp, kpitch, ifn, 0, ifftsize
+ aR = aL
+ endif
+ endif
+ endif
+
+ aL *= (1 - irmsnorm) * 0.5
+ aR *= (1 - irmsnorm) * 0.5
+
+ ; recursion for all chord parts
+ if (index + 1 < ftlen(gimel_amps)) then
+ aLx, aRx portchord_sound icollectionid, imode, kfreqmult, ifftsize, index + 1
+ aL += aLx
+ aR += aRx
+ endif
+ xout aL, aR
+endop
+
+#end
diff --git a/site/udo/fnml/instrument_sineblips.udo b/site/udo/fnml/instrument_sineblips.udo
new file mode 100755
index 0000000..5990909
--- /dev/null
+++ b/site/udo/fnml/instrument_sineblips.udo
@@ -0,0 +1,81 @@
+#ifndef UDO_FNMI_SINEBLIP
+#define UDO_FNMI_SINEBLIP ##
+/*
+ Stochastic sequenced sine blip instrument
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "bussing.udo"
+#include "sequencing_melodic.udo"
+
+
+/*
+ Randomised sine blip playback internal instrument
+*/
+instr _fnmi_sineblip
+ Sbus = p4
+ inote = mel_randomnote:i() + 12
+ if (random(0, 1) > 0.5) then
+ inote += 12
+ endif
+
+ if (random(0, 1) > 0.5) then
+ inote += 12
+ endif
+
+ if (random(0, 1) > 0.5) then
+ inote += 12
+ endif
+
+ if (random(0, 1) > 0.99) then
+ inote += 1
+ endif
+ ibasefreq = cpsmidinn(inote)
+ ifreqL = ibasefreq + random(-5, 5)
+ ifreqR = ibasefreq + random(-5, 5)
+ iampL = random(0.5, 1)
+ iampR = random(0.5, 1)
+ aL oscil iampL, ifreqL
+ aR oscil iampR, ifreqR
+
+ if (random(0, 1) > 0.5) then
+ kamp line 1, p3, 0
+ else
+ kamp linseg 1, p3*0.9, 1, p3*0.1, 0
+ endif
+ bus_mix(Sbus, aL*0.6*kamp, aR*0.6*kamp)
+endin
+
+
+/*
+ Randomised sine blip playback scheduler
+*/
+instr fnmi_sineblips
+ if (p4 == 0) then
+ Sbus = "main"
+ else
+ Sbus = p4
+ endif
+
+ if (p5 == 0) then
+ iqtime = i(gkseq_quartertime)
+ else
+ iqtime = p5
+ endif
+
+ inum = random(2, 8)
+ itimeindex = random(0, 8)
+ index = 0
+ while (index < inum) do
+ itime = seq_swingtime:i(iqtime * itimeindex, itimeindex)
+ schedule "_fnmi_sineblip", itime, random(0.05, 0.1), Sbus
+ itimeindex += random(1, 4)
+ index += 1
+ od
+ xtratim iqtime * itimeindex
+endin
+
+#end
diff --git a/site/udo/fnml/instrument_tikbank.udo b/site/udo/fnml/instrument_tikbank.udo
new file mode 100755
index 0000000..895c8b0
--- /dev/null
+++ b/site/udo/fnml/instrument_tikbank.udo
@@ -0,0 +1,78 @@
+#ifndef UDO_FNMI_TIKBANK
+#define UDO_FNMI_TIKBANK ##
+
+
+
+#include "sounddb.udo"
+#include "bussing.udo"
+#include "uniqueid.udo"
+
+
+instr _tik_play
+ ifn = p4
+ ipitch = p5
+ ipan = p6
+ instanceid = p7
+
+ aL, aR loscil 0.4, ipitch, ifn, 1
+ bus_mix(sprintf("tikaudio%d", instanceid), aL*ipan, aR*(1-ipan))
+endin
+
+
+instr _tik_item
+ ifn = p4
+ idur = p5
+ ipitch = p6
+ instanceid = p7
+ index = p8
+
+ kfreq chnget sprintf("tikitemfreq_%d_%d", instanceid, index)
+ ipan = random(0, 1)
+
+ if (kfreq != 0) then
+ kmetro metro kfreq*2, random(0, 1)
+ if (kmetro == 1) then
+ schedulek "_tik_play", 0, idur, ifn, ipitch, ipan, instanceid
+ endif
+ endif
+endin
+
+
+opcode tik_bank, iaa, i[]ipj
+ icollection[], inum, ipitch, instanceid xin
+
+ if (instanceid == -1) then
+ instanceid = uniqueid()
+ endif
+
+ iusedinstruments[] uniqueinstrnums "_tik_item", inum
+
+ index = 0
+ ifileindex = 0
+ while (index < lenarray(iusedinstruments)) do
+ idbindex = icollection[ifileindex]
+ ifn = gisounddb[idbindex][0]
+ idur = gisounddb[idbindex][2] / ipitch
+
+ schedule iusedinstruments[index], 0, p3, ifn, idur, ipitch, instanceid, index
+ if (ifileindex + 1 < lenarray(icollection)) then
+ ifileindex += 1
+ else
+ ifileindex = 0
+ endif
+ index += 1
+ od
+
+ if (release:k() == 1) then
+ kindex = 0
+ while (kindex < lenarray(iusedinstruments)) do
+ turnoff2 iusedinstruments[kindex], 4, 1
+ kindex += 1
+ od
+ endif
+
+ aL, aR bus_read sprintf("tikaudio%d", instanceid)
+ xout instanceid, aL, aR
+endop
+
+#end
diff --git a/site/udo/fnml/instrument_tikclay.udo b/site/udo/fnml/instrument_tikclay.udo
new file mode 100755
index 0000000..73a6e5d
--- /dev/null
+++ b/site/udo/fnml/instrument_tikclay.udo
@@ -0,0 +1,118 @@
+#ifndef UDO_FNMI_TIKCLAY
+#define UDO_FNMI_TIKCLAY ##
+
+#include "sounddb.udo"
+#include "bussing.udo"
+#include "wavetables.udo"
+#include "uniqueid.udo"
+#include "sequencing_melodic.udo"
+
+gitikfn_clay[], gicl_clay sounddb_getcollection "Clay.Hit"
+
+instr _tik_clay_play
+ instanceid = p4
+ ifileid = p5
+ ipitch = p6
+ iwsize = p7
+ irandw = p8
+ ipan = p9
+ iresonfreq = p10
+ idoreson = p11
+
+ ifn = gisounddb[ifileid][0]
+ idur = gisounddb[ifileid][2]
+ istart = idur * random(0, 0.1)
+ atime linseg istart, p3, idur*0.9
+ kamp linseg 1, p3*0.8, 1, p3*0.2, 0
+ aL, aR sndwarpst 1, atime, ipitch, ifn, istart, iwsize, irandw, 2, gifnHalfSine, 1
+
+ if (idoreson == 1) then
+ aLr resony aL, iresonfreq, 6, 16, 10
+ aRr resony aR, iresonfreq, 6, 16, 10
+ aL balance aLr, aL
+ aR balance aRr, aR
+ endif
+
+ ilpf = random(2000, 22050)
+ aL butterlp aL, ilpf
+ aR butterlp aR, ilpf
+ bus_mix(sprintf("tikclayaudio%d", instanceid), aL*ipan*kamp, aR*(1-ipan)*kamp)
+endin
+
+
+instr _tik_clay_item
+ instanceid = p4
+ index = p5
+ kreset init 1
+
+ kfreq = chnget:k(sprintf("tikclay_%d_%d", instanceid, index))
+
+ if (kfreq == 0) then
+ if (kreset == 0) then
+ kreset = 1
+ endif
+ kplaying = 0
+ else
+ kplaying = 1
+ endif
+
+ if (kreset == 1) then
+ kfileid = gitikfn_clay[round:k(random:k(0, lenarray:k(gitikfn_clay) - 1))]
+ kdur = random:k(0.4, 1.3) * gisounddb[kfileid][2]
+ kpitch = random:k(0.1, 2.2)
+ kwsize = random:k(44, 441)
+ krandw = kwsize / 10
+ kpan = random:k(0, 1)
+ kresonfreq = cpsmidinn:k(mel_randomnote:k()) * 2
+ if (random:k(0, 1) >= 0.5) then
+ kresonfreq *= 2
+ endif
+ kreset = 0
+ endif
+
+ if (kplaying == 1) then
+ kmetro metro kfreq, random(0, 1)
+ if (kmetro == 1) then
+ kresonchance = chnget:k(sprintf("tikclay_resonchance_%d", instanceid))
+ kdoreson = (random:k(0, 0.99999) < kresonchance) ? 1 : 0
+ schedulek "_tik_clay_play", 0, kdur, instanceid, kfileid, kpitch, kwsize, krandw, kpan, kresonfreq, kdoreson
+ endif
+ endif
+endin
+
+
+opcode tik_clay_bank, iaa, i
+ inum xin
+
+ icollection[] = gitikfn_clay
+ instanceid = uniqueid()
+
+ iusedinstruments[] uniqueinstrnums "_tik_clay_item", inum
+
+ index = 0
+ ifileindex = 0
+ while (index < lenarray(iusedinstruments)) do
+ ifileid = icollection[ifileindex]
+
+ schedule iusedinstruments[index], 0, p3, instanceid, index
+ if (ifileindex + 1 < lenarray(icollection)) then
+ ifileindex += 1
+ else
+ ifileindex = 0
+ endif
+ index += 1
+ od
+
+ if (release:k() == 1) then
+ kindex = 0
+ while (kindex < lenarray(iusedinstruments)) do
+ turnoff2 iusedinstruments[kindex], 4, 1
+ kindex += 1
+ od
+ endif
+
+ aL, aR bus_read sprintf("tikclayaudio%d", instanceid)
+ xout instanceid, aL, aR
+endop
+
+#end
diff --git a/site/udo/fnml/instrument_vocal.udo b/site/udo/fnml/instrument_vocal.udo
new file mode 100755
index 0000000..f5ac011
--- /dev/null
+++ b/site/udo/fnml/instrument_vocal.udo
@@ -0,0 +1,73 @@
+#ifndef UDO_FNMI_VOCAL
+#define UDO_FNMI_VOCAL ##
+
+#include "wavetables.udo"
+#include "sequencing_melodic.udo"
+#include "sounddb.udo"
+#include "bussing.udo"
+
+
+i_[], gifnmi_vocal_collectionid sounddb_getcollection "VocalAhh1"
+
+
+opcode fnmi_vocal_chord, aa, kpj
+ ktrig, iaddoctave, ireadattackratio xin
+ icollectionid = gifnmi_vocal_collectionid
+ ireadattackratio = (ireadattackratio == -1) ? 0.1 : ireadattackratio
+ if (ktrig == 1) then
+ klen = mel_length:k()
+ knotenum = table:k(0, gimel_current_notes)
+ kpanstep = 1 / knotenum
+ kindex = 0
+ while (kindex < knotenum) do
+ kpan = kpanstep * kindex
+ schedulek "fnmi_vocal", 0, klen, icollectionid, table:k(kindex+1, gimel_current_notes), kpan, ireadattackratio
+ if (iaddoctave == 1) then
+ schedulek "fnmi_vocal", 0, klen, icollectionid, table:k(kindex+1, gimel_current_notes) + 12, 1-kpan, ireadattackratio
+ endif
+ kindex += 1
+ od
+ endif
+ aL, aR bus_read "fnmi_vocal_chord"
+ xout aL, aR
+endop
+
+/*
+instr tester
+ ktrig = 0
+ if (gkmel_section_change == 1) then
+ ktrig = 1
+ endif
+ aL, aR fnmi_vocal_chord ktrig
+ outs aL, aR
+endin
+*/
+
+instr fnmi_vocal
+ icollectionid = p4
+ inote = p5
+ ipan = p6
+ ireadattackratio = p7
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+ ifn = gisounddb[ifileid][0]
+ ilen = gisounddb[ifileid][2]
+ idur = p3
+ kamp linseg 1, idur*0.1, 1, idur*0.5, 1, idur*0.4, 0
+ ireadrate random 0.01, 1
+
+ ivibrate = random(0.25, 4)
+ klfo oscil 0.006, ivibrate, gifnSine
+ kpitchratio = ipitchratio + klfo
+
+ atime = abs:a(oscil:a(0.68, ireadrate))
+ if (ireadattackratio != 0) then
+ atime = (delay:a(atime, idur*ireadattackratio) + linseg:a(0, idur*ireadattackratio, 0.1)) * ilen
+ endif
+
+ aL, aR mincer atime, kamp*0.2, kpitchratio, ifn, 1, 2048
+ aL butterhp aL, 340
+ aR butterhp aR, 340
+ bus_mix("fnmi_vocal_chord", aL*ipan, aR*(1-ipan))
+endin
+
+#end
diff --git a/site/udo/fnml/transition_click.udo b/site/udo/fnml/transition_click.udo
new file mode 100755
index 0000000..c39338c
--- /dev/null
+++ b/site/udo/fnml/transition_click.udo
@@ -0,0 +1,143 @@
+#ifndef UDO_TRANSITION_CLICK
+#define UDO_TRANSITION_CLICK ##
+
+#include "sequencing_scheduled.udo"
+#include "sequencing_melodic_portamento.udo"
+#include "uniqueid.udo"
+#include "bussing.udo"
+#include "sounddb.udo"
+
+
+gifnmt_clickfn[] sounddb_getcollection "Snare.Regular,Hihat.Closed"
+
+
+instr fnmt_clickplay
+ istartbeats = p4 ; number of beats before next event point as specified in iwaitmode
+ iwaitmode = p5 ; -1 = start of next bar, -2 = start of next bargroup , any other = number of total beats before event point
+ iampmode = p6 ; 0 = linear, 1 = exponential, 2 = random (randomises each individually)
+ idorelease = p7 ; do release, 0 or 1
+ idotune = p8 ; do tuning, 0 or 1
+ idorandom = p9 ; do random sound selection for each click
+ Sbus = strget(p10) ; bus to send to ; defaults to "main"
+ SonHit = strget(p11) ; instrument to call when hit point is reached
+
+
+ if (strcmp(Sbus, "") == 0) then
+ Sbus = "main"
+ endif
+
+ p3 = 600
+
+ if (iwaitmode == -1) then
+ kwaittrig = bar_lastbeatxof(istartbeats)
+ elseif (iwaitmode == -2) then
+ kwaittrig = bargroup_lastbeatxof(istartbeats)
+ else
+ kwaittrig = lastbeatxof(iwaitmode, istartbeats)
+ endif
+ if (kwaittrig == 1) then
+ schedulek "_fnmt_clickplay", 0, i(gkseq_beattime) * istartbeats, iampmode, idorelease, idotune, idorandom, Sbus, SonHit
+ turnoff
+ endif
+endin
+
+
+
+instr _fnmt_clickplay
+ imode = p4 ; 0 = linear, 1 = exponential, 2 = random (randomises each individually)
+ idorelease = p5
+ idotune = p6
+ idorandom = p7
+ Sbus = p8
+ SonHit = p9
+
+ ichannelid = uniqueid()
+ ibeattime = i(gkseq_beattime)
+ itempo = i(gkseq_tempo)
+ ireltime = (idorelease == 1) ? random(ibeattime, ibeattime*4) : 0
+ imtime = p3
+ ibeathz = itempo / 60
+
+ if (strcmp(SonHit, "") != 0) then
+ schedule(SonHit, imtime, 1)
+ endif
+
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kfreq linsegr ibeathz * round(random(4, 16)), imtime, ibeathz * round(random(4, 16)), ireltime, ibeathz * round(random(4, 16))
+ else
+ kfreq expsegr ibeathz * round(random(4, 16)), imtime, ibeathz * round(random(4, 16)), ireltime, ibeathz * round(random(4, 16))
+ endif
+
+ iminpitch = 6
+ imaxpitch = 20
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kpitch linsegr random(iminpitch, imaxpitch), imtime, random(iminpitch, imaxpitch), ireltime, random(iminpitch, imaxpitch)
+ else
+ kpitch expsegr random(iminpitch, imaxpitch), imtime, random(iminpitch, imaxpitch), ireltime, random(iminpitch, imaxpitch)
+ endif
+
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kpan linsegr random(0, 1), imtime, random(0, 1), ireltime, random(0, 1)
+ else
+ kpan expsegr random(0.0001, 1), imtime, random(0.0001, 1), ireltime, random(0.0001, 1)
+ endif
+
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kamp linsegr 0, p3, 1, ireltime, 0
+ else
+ kamp expsegr 0.0001, imtime, 1, ireltime, 0.0001
+ endif
+
+
+ if (idorandom == 1) then
+ ifn = -1
+ ichans = -1
+ idur = 1
+ else
+ ifileid = gifnmt_clickfn[round(random(0, lenarray(gifnmt_clickfn) - 1))]
+ ifn = gisounddb[ifileid][0]
+ ichans = gisounddb[ifileid][1]
+ idur = gisounddb[ifileid][2]
+ endif
+
+
+ kmetro metro kfreq
+ if (kmetro == 1) then
+ schedulek "_fnmt_clickitem", 0, idur / kpitch, ifn, kamp, kpitch, kpan, ichannelid, ichans
+ endif
+
+ aL, aR bus_read sprintf("fnmt_click%d", ichannelid)
+
+ if (idotune == 1) then
+ aL, aR mel_tune_portamento aL, aR, 0, 2
+ endif
+
+ bus_mix(Sbus, aL, aR)
+endin
+
+instr _fnmt_clickitem
+ ifn = p4
+ iamp = p5
+ ipitch = p6
+ ipan = p7
+ ichannelid = p8
+ ichans = p9
+
+ if (ifn == -1) then ; random sound
+ ifileid = gifnmt_clickfn[round(random(0, lenarray(gifnmt_clickfn) - 1))]
+
+ ifn = gisounddb[ifileid][0]
+ ichans = gisounddb[ifileid][1]
+ p3 = gisounddb[ifileid][2] / ipitch
+ endif
+
+ if (ichans == 2) then
+ a1, a_ loscil iamp, ipitch, ifn, 1
+ else
+ a1 loscil iamp, ipitch, ifn, 1
+ endif
+ aL, aR pan2 a1, ipan
+ bus_mix(sprintf("fnmt_click%d", ichannelid), aL, aR)
+endin
+
+#end
diff --git a/site/udo/fnml/transition_mburn.udo b/site/udo/fnml/transition_mburn.udo
new file mode 100755
index 0000000..f8d35f1
--- /dev/null
+++ b/site/udo/fnml/transition_mburn.udo
@@ -0,0 +1,186 @@
+#ifndef UDO_TRANSITION_MBURN
+#define UDO_TRANSITION_MBURN ##
+
+
+/*
+
+ TODO: use database loads for sounds ie
+
+
+ #include "sound_sdb.udo"
+
+ gifnmt_mbstart[][] sdb_getcollection "MBurn1.Start"
+ gifnmt_mbmid[][] sdb_getcollection "MBurn1.Middle"
+ gifnmt_mbend[][] sdb_getcollection "MBurn1.End"
+*/
+
+#include "sound_db.udo" ; for local sound loads
+#include "sequencing_scheduled.udo"
+#include "sequencing_melodic_portamento.udo" ; for tuning
+#include "wavetables.udo" ; for tuning
+#include "bussing.udo"
+#include "host_tools.udo" ; for dir_random
+#include "uniqueid.udo" ; for channels
+
+
+gifnmt_mbstart[] rdb_loaddir dir_random("2021/mBurn-New/Start")
+gifnmt_mbmid[] rdb_loaddir dir_random("2021/mBurn-New/Middle")
+gifnmt_mbend[] rdb_loaddir dir_random("2021/mBurn-New/End")
+
+
+/*
+ Play mBurn transition
+
+ p4 number of beats before next event as specified in iwaitmode
+ p5 wait mode: -1 = start of next bar, -2 = start of next bargroup , any other = number of total beats before event point (0 = immediate)
+ p6 tune mode: 0 = none, 1 = all, 2 = random
+ p7 bus name to send output to (0 = main)
+*/
+instr fnmt_mburnplay
+ istartbeats = p4
+ iwaitmode = p5
+ itunemode = p6
+
+ if (p7 == 0) then
+ Sbus = "main"
+ else
+ Sbus = p7
+ endif
+
+ p3 = 600
+
+ if (iwaitmode == -1) then
+ kwaittrig = bar_lastbeatxof(istartbeats)
+ elseif (iwaitmode == -2) then
+ kwaittrig = bargroup_lastbeatxof(istartbeats)
+ elseif (iwaitmode == 0) then
+ kwaittrig init 1
+ else
+ kwaittrig = lastbeatxof(iwaitmode, istartbeats)
+ endif
+
+ if (kwaittrig == 1) then
+ schedulek "_fnmt_mburnplay", 0, i(gkseq_beattime) * istartbeats, itunemode, Sbus
+ turnoff
+ endif
+
+endin
+
+
+instr _fnmt_mburnitem
+ ifn = p4
+ imode = p5
+ itune = p6
+ ichannel = p7
+ ireverseread = p8
+ iampmode = p9 ; ; 0 = linear amp, 1 = exponential amp, 2 = no envelope
+ isounddur = p10
+
+
+ ; start
+ if (imode == 1) then
+ if (iampmode == 0) then
+ kamp linseg 0, p3, 1
+ elseif (iampmode == 1) then
+ kamp expseg 0.00001, p3, 1
+ else
+ kamp init 1
+ endif
+
+ ; middle
+ elseif (imode == 2) then
+ if (iampmode == 2) then ; mid = no env choice really
+ kamp init 1
+ else
+ kamp linseg 1, p3*0.8, 1, p3*0.2, 0
+ endif
+
+ ; end
+ elseif (imode == 3) then
+ if (iampmode == 0) then
+ kamp linseg 1, p3, 0 ;linseg 0, p3*0.1, 1, p3*0.5, 1, p3*0.4, 0
+ elseif (iampmode == 1) then
+ kamp expseg 1, p3, 0.00001
+ else
+ kamp init 1
+ endif
+ endif
+
+ atime line 0, p3, isounddur
+
+ if (ireverseread == 1) then
+ atime = isounddur - atime
+ endif
+
+ arepitch init 1;linseg 1, p3, 0.5
+
+ iwinsize = round(random(441, 4410))
+ irandwin = iwinsize / 10
+ aL, aR sndwarpst 1, atime, arepitch, ifn, 0, iwinsize, irandwin, 4, gifnHalfSine, 1
+
+ if (itune >= 1) then
+ ;aL, aR mel_tune aL, aR, gifnSaw, 10, 512, 4
+ aL, aR mel_tune_portamento aL, aR, gifnSine, 16, 1024, 4
+ endif
+ aL = aL*kamp
+ aR = aR*kamp
+ chnmix aL, sprintf("mburn%dL", ichannel)
+ chnmix aR, sprintf("mburn%dR", ichannel)
+endin
+
+
+instr _fnmt_mburnplay
+ itunemode = p4
+ Sbus = p5
+
+ ichannelid = uniqueid()
+ idorelease = 1
+
+ ibeattime = i(gkseq_beattime)
+
+ istartdur = p3
+ imiddur = random(ibeattime, ibeattime*2)
+ ienddur = (idorelease == 1) ? random(ibeattime, ibeattime*4) : 0
+
+
+
+ istartfn[] get_sound gifnmt_mbstart[round(random(0, lenarray(gifnmt_mbstart) - 1))]
+ imidfn[] get_sound gifnmt_mbmid[round(random(0, lenarray(gifnmt_mbmid) - 1))]
+
+ if (idorelease == 1) then
+ iendfn[] get_sound gifnmt_mbend[round(random(0, lenarray(gifnmt_mbend) - 1))]
+ endif
+
+ if (itunemode == 0) then
+ itunestart = 0
+ itunemid = 0
+ ituneend = 0
+ elseif (itunemode == 1) then
+ itunestart = 1
+ itunemid = 1
+ ituneend = 1
+ elseif (itunemode == 2) then
+ itunestart = round(random(0, 1))
+ itunemid = round(random(0, 1))
+ ituneend = round(random(0, 1))
+ endif
+
+ iampmodestart = 0 ; 0 = linear amp, 1 = exponential amp
+ iampmodeend = 0 ; 0 = linear amp, 1 = exponential amp
+ ireversereadstart = round(random(0, 1))
+ ireversereadend = round(random(0, 1))
+
+ event_i "i", "_fnmt_mburnitem", 0, istartdur, istartfn[0], 1, itunestart, ichannelid, ireversereadstart, iampmodestart, istartfn[3]
+ event_i "i", "_fnmt_mburnitem", istartdur, imiddur, imidfn[0], 2, itunemid, ichannelid, 0, 1, imidfn[3]
+
+ if (idorelease == 1) then
+ event_i "i", "_fnmt_mburnitem", istartdur, ienddur, iendfn[0], 3, ituneend, ichannelid, ireversereadend, iampmodeend, iendfn[3]
+ xtratim ienddur + 3
+ endif
+
+
+ aL, aR bus_read sprintf("mburn%d", ichannelid)
+ bus_mix(Sbus, aL, aR)
+endin
+
+#end
diff --git a/site/udo/fnml/transition_snare.udo b/site/udo/fnml/transition_snare.udo
new file mode 100755
index 0000000..16ed2e2
--- /dev/null
+++ b/site/udo/fnml/transition_snare.udo
@@ -0,0 +1,140 @@
+#ifndef UDO_TRANSITION_SNARE
+#define UDO_TRANSITION_SNARE ##
+
+#include "sequencing_scheduled.udo"
+#include "sequencing_melodic_portamento.udo"
+#include "uniqueid.udo"
+#include "bussing.udo"
+#include "sounddb.udo"
+
+gifnmt_rollfndamp[] sounddb_getcollection "Snare.Dampened"
+gifnmt_rollfnregular[] sounddb_getcollection "Snare.Regular"
+gifnmt_rollfnrimhard[] sounddb_getcollection "Snare.Rim.Hard"
+gifnmt_rollfnrimsoft[] sounddb_getcollection "Snare.Rim.Soft"
+gifnmt_rollfnunrestrained[] sounddb_getcollection "Snare.Unrestrained"
+
+
+instr fnmt_rollplay
+ istartbeats = p4 ; number of beats before next event as specified in iwaitmode
+ iwaitmode = p5 ; -1 = start of next bar, -2 = start of next bargroup , any other = number of total beats before event point
+ iampmode = p6 ; 0 = linear, 1 = exponential, 2 = random (randomises each individually)
+ idorelease = p7 ; do release, 0 or 1
+ idotune = p8 ; do tuning, 0 or 1
+ idorandom = p9 ; do random sound selection for each click
+ Sbus = strget(p10) ; bus to send to ; defaults to "main"
+ SonHit = strget(p11) ; instrument to call when hit point is reached
+
+ if (strcmp(Sbus, "") == 0) then
+ Sbus = "main"
+ endif
+
+ p3 = 600
+
+ if (iwaitmode == -1) then
+ kwaittrig = bar_lastbeatxof(istartbeats)
+ elseif (iwaitmode == -2) then
+ kwaittrig = bargroup_lastbeatxof(istartbeats)
+ else
+ kwaittrig = lastbeatxof(iwaitmode, istartbeats)
+ endif
+
+ if (kwaittrig == 1) then
+ schedulek "_fnmt_rollplay1", 0, i(gkseq_beattime) * istartbeats, iampmode, idorelease, idotune, idorandom, Sbus, SonHit
+ turnoff
+ endif
+endin
+
+
+
+instr _fnmt_rollplay1
+ imode = p4 ; 0 = linear, 1 = exponential, 2 = random (randomises each individually)
+ idorelease = p5
+ idotune = p6
+ idorandom = p7
+ Sbus = p8
+ SonHit = p9
+
+ ichannelid = uniqueid()
+ if (idotune == 1) then ; TODO: separate channel for tuned ones
+ ichannelidtuned = uniqueid()
+ endif
+
+ ibeattime = i(gkseq_beattime)
+ itempo = i(gkseq_tempo)
+ ibeathz = itempo / 60
+ ireltime = (idorelease == 1) ? random(ibeattime, ibeattime*4) : 0
+ imtime = p3
+
+ if (strcmp(SonHit, "") != 0) then
+ schedule(SonHit, imtime, 1)
+ endif
+
+ krelease release
+
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kfreq linsegr ibeathz * round(random(4, 16)), imtime, ibeathz * round(random(4, 16)), ireltime, ibeathz * round(random(4, 16))
+ else
+ kfreq expsegr ibeathz * round(random(4, 16)), imtime, ibeathz * round(random(4, 16)), ireltime, ibeathz * round(random(4, 16))
+ endif
+
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kamp linsegr 0, p3, 1, ireltime, 0
+ else
+ kamp expsegr 0.0001, imtime, 1, ireltime, 0.0001
+ endif
+
+ ichancepercent = 100
+
+ kmetro metro kfreq
+ ktrig = (kmetro == 1 && random:k(0, 100) < ichancepercent) ? 1: 0
+
+ ksampleset = 1
+ if (krelease == 1) then
+ ksampleset = 0
+ endif
+
+ if (ktrig == 1) then
+ event "i", "_fnmt_rollitem", 0, 1, ksampleset, kamp, ichannelid
+ endif
+
+ aL, aR bus_read sprintf("fnmt_roll%d", ichannelid)
+
+ if (idotune == 1) then
+ aL, aR mel_tune_portamento aL, aR, 0, 16
+ aL butterhp aL, 120
+ aR butterhp aR, 120
+ endif
+
+ bus_mix(Sbus, aL, aR)
+endin
+
+
+
+instr _fnmt_rollitem
+ isampleset = p4
+ iamp = p5
+ ichannelid = p6
+ if (isampleset == 0) then
+ ifileid = gifnmt_rollfndamp[round(random(0, lenarray(gifnmt_rollfndamp) - 1))]
+
+ elseif (isampleset == 1) then
+ ifileid = gifnmt_rollfnrimhard[round(random(0, lenarray(gifnmt_rollfnrimhard) - 1))]
+
+ endif
+
+ ;index = round(random(0, gimaxes[isampleset] - 1))
+ ;ifn = gisamples[isampleset][index]
+ ;p3 = (ftlen(ifn) / ftsr(ifn)) + 0.1
+
+ p3 = gisounddb[ifileid][2]
+ ifn = gisounddb[ifileid][0]
+ if (gisounddb[ifileid][1] == 2) then
+ aL, aR loscil iamp, 1, ifn, 1
+ else
+ aL loscil iamp, 1, ifn, 1
+ aR = aL
+ endif
+ bus_mix(sprintf("fnmt_roll%d", ichannelid), aL, aR)
+endin
+
+#end
diff --git a/site/udo/fnml/transition_snare_preSOUNDDB.udo b/site/udo/fnml/transition_snare_preSOUNDDB.udo
new file mode 100755
index 0000000..70df4e2
--- /dev/null
+++ b/site/udo/fnml/transition_snare_preSOUNDDB.udo
@@ -0,0 +1,133 @@
+#ifndef UDO_TRANSITION_SNARE
+#define UDO_TRANSITION_SNARE ##
+
+#include "sequencing_scheduled.udo"
+#include "sequencing_melodic_portamento.udo"
+#include "uniqueid.udo"
+#include "bussing.udo"
+#include "sound_sdb.udo"
+
+gifnmt_rollfndamp[][] sdb_getcollection "Snare.Dampened"
+gifnmt_rollfnregular[][] sdb_getcollection "Snare.Regular"
+gifnmt_rollfnrimhard[][] sdb_getcollection "Snare.Rim.Hard"
+gifnmt_rollfnrimsoft[][] sdb_getcollection "Snare.Rim.Soft"
+gifnmt_rollfnunrestrained[][] sdb_getcollection "Snare.Unrestrained"
+
+
+instr fnmt_rollplay
+ istartbeats = p4 ; number of beats before next event as specified in iwaitmode
+ iwaitmode = p5 ; -1 = start of next bar, -2 = start of next bargroup , any other = number of total beats before event point
+ iampmode = p6 ; 0 = linear, 1 = exponential, 2 = random (randomises each individually)
+ idorelease = p7 ; do release, 0 or 1
+ idotune = p8 ; do tuning, 0 or 1
+ idorandom = p9 ; do random sound selection for each click
+
+ if (p10 == 0) then
+ Sbus = "main"
+ else
+ Sbus = p10
+ endif
+
+ p3 = 600
+
+ if (iwaitmode == -1) then
+ kwaittrig = bar_lastbeatxof(istartbeats)
+ elseif (iwaitmode == -2) then
+ kwaittrig = bargroup_lastbeatxof(istartbeats)
+ else
+ kwaittrig = lastbeatxof(iwaitmode, istartbeats)
+ endif
+
+ if (kwaittrig == 1) then
+ schedulek "_fnmt_rollplay1", 0, i(gkseq_beattime) * istartbeats, iampmode, idorelease, idotune, idorandom, Sbus
+ turnoff
+ endif
+endin
+
+
+
+instr _fnmt_rollplay1
+ imode = p4 ; 0 = linear, 1 = exponential, 2 = random (randomises each individually)
+ idorelease = p5
+ idotune = p6
+ idorandom = p7
+ Sbus = p8
+
+ ichannelid = uniqueid()
+ if (idotune == 1) then ; TODO: separate channel for tuned ones
+ ichannelidtuned = uniqueid()
+ endif
+
+ ibeattime = i(gkseq_beattime)
+ itempo = i(gkseq_tempo)
+ ibeathz = itempo / 60
+ ireltime = (idorelease == 1) ? random(ibeattime, ibeattime*4) : 0
+ imtime = p3
+
+ krelease release
+
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kfreq linsegr ibeathz * round(random(4, 16)), imtime, ibeathz * round(random(4, 16)), ireltime, ibeathz * round(random(4, 16))
+ else
+ kfreq expsegr ibeathz * round(random(4, 16)), imtime, ibeathz * round(random(4, 16)), ireltime, ibeathz * round(random(4, 16))
+ endif
+
+ if (imode == 0 || (imode == 2 && random(0, 1) >= 0.5)) then
+ kamp linsegr 0, p3, 1, ireltime, 0
+ else
+ kamp expsegr 0.0001, imtime, 1, ireltime, 0.0001
+ endif
+
+ ichancepercent = 100
+
+ kmetro metro kfreq
+ ktrig = (kmetro == 1 && random:k(0, 100) < ichancepercent) ? 1: 0
+
+ ksampleset = 1
+ if (krelease == 1) then
+ ksampleset = 0
+ endif
+
+ if (ktrig == 1) then
+ event "i", "_fnmt_rollitem", 0, 1, ksampleset, kamp, ichannelid
+ endif
+
+ aL, aR bus_read sprintf("fnmt_roll%d", ichannelid)
+
+ if (idotune == 1) then
+ aL, aR mel_tune_portamento aL, aR, 0, 16
+ aL butterhp aL, 120
+ aR butterhp aR, 120
+ endif
+
+ bus_mix(Sbus, aL, aR)
+endin
+
+
+
+instr _fnmt_rollitem
+ isampleset = p4
+ iamp = p5
+ ichannelid = p6
+ if (isampleset == 0) then
+ isound[] get_sound gifnmt_rollfndamp[round(random(0, lenarray(gifnmt_rollfndamp) - 1))][0]
+ elseif (isampleset == 1) then
+ isound[] get_sound gifnmt_rollfnrimhard[round(random(0, lenarray(gifnmt_rollfnrimhard) - 1))][0]
+ endif
+
+ ;index = round(random(0, gimaxes[isampleset] - 1))
+ ;ifn = gisamples[isampleset][index]
+ ;p3 = (ftlen(ifn) / ftsr(ifn)) + 0.1
+
+ p3 = isound[3]
+ ifn = isound[0]
+ if (isound[2] == 2) then
+ aL, aR loscil iamp, 1, ifn, 1
+ else
+ aL loscil iamp, 1, ifn, 1
+ aR = aL
+ endif
+ bus_mix(sprintf("fnmt_roll%d", ichannelid), aL, aR)
+endin
+
+#end
diff --git a/site/udo/fnml/transition_test.csd b/site/udo/fnml/transition_test.csd
new file mode 100755
index 0000000..d8c7542
--- /dev/null
+++ b/site/udo/fnml/transition_test.csd
@@ -0,0 +1,39 @@
+<CsoundSynthesizer>
+<CsOptions>
+-odac
+</CsOptions>
+<CsInstruments>
+sr = 44100
+kr = 4410
+nchnls = 2
+0dbfs = 1
+seed 0
+
+#include "fnml/transition_click.udo"
+#include "fnml/transition_snare.udo"
+#include "fnml/transition_mburn.udo"
+#include "bussing.udo"
+
+instr debug
+ a1 metronome
+ outs a1, a1
+endin
+
+
+instr master
+ ;schedule "fnmt_clickplay", 1, 1, 6, -1, 2, 1, 0, 0, "main"
+ ;schedule "fnmt_rollplay", 1, 1, 6, -1, 2, 1, 0, 0, "main"
+ schedule "fnmt_mburnplay", 1, 1, 6, -1, 0, "main"
+
+ aL, aR bus_read "clicktest"
+ aL, aR freeverb aL, aR, 0.7, 0.4
+ outs aL, aR
+endin
+
+
+</CsInstruments>
+<CsScore>
+i"master" 0 510
+i"debug" 0 501
+</CsScore>
+</CsoundSynthesizer> \ No newline at end of file
diff --git a/site/udo/fnml/transitional/base.udo b/site/udo/fnml/transitional/base.udo
new file mode 100755
index 0000000..ba40fbc
--- /dev/null
+++ b/site/udo/fnml/transitional/base.udo
@@ -0,0 +1,141 @@
+#ifndef UDO_FNML_TRANSITIONBASE
+#define UDO_FNML_TRANSITIONBASE ##
+
+instr fnm_transitionplayer
+ if (gifirsttransition == 1) then
+ StransInstrument = "fnm_trans5"
+ gifirsttransition = 0
+ else
+ StransInstrument = arr_random(gSfnm_transitionitems)
+ endif
+ schedule StransInstrument, 0, 36000
+
+ kreleasing init 0
+ if (release:k() == 1 && kreleasing == 0) then
+ turnoff2 StransInstrument, 0, 1
+ kreleasing = 1
+ endif
+ aL, aR bus_read "fnm_transition"
+ bus_mix("master", aL, aR)
+endin
+
+instr fnm_sectionplayer
+ SnoteInstrument = arr_random(gSfnm_chorditems)
+ SbassInstrument = arr_random(gSfnm_bassitems)
+ SaugInstrument = arr_random(gSfnm_augmentitems)
+ iplaybass = (random(0, 1) >= 0.44) ? 1 : 0
+ iplayaugment = (random(0, 1) >= 0.34) ? 1 : 0
+
+
+ if (random(0, 1) >= 0.5) then
+ ifadeintime = random(0.1, 3)
+ else
+ ifadeintime = 0
+ endif
+ index = 0
+ while (index < table:i(0, gimel_current_notes)) do
+ inoteaugment = (random(0, 1) >= 0.7) ? 12 : 0
+ schedule SnoteInstrument, 0, p3, table:i(index+1, gimel_current_notes) + inoteaugment, ifadeintime
+ index += 1
+ od
+ if (iplaybass == 1) then
+ ibassnoteindex = round(random(1, 3))
+ schedule SbassInstrument, 0, p3, table:i(ibassnoteindex, gimel_current_notes) - 24
+ endif
+ if (iplayaugment == 1) then
+ schedule SaugInstrument, 0, p3
+ endif
+
+ aL, aR bus_read "fnm_chordnote"
+ if (random(0, 1) > 0.6) then
+ aLc, aRc simplechorus aL, aR, 0.004, 0.001
+ aL += aLc
+ aR += aRc
+ endif
+ aL butterhp aL, 120
+ aR butterhp aR, 120
+
+ if (iplaybass == 1) then
+ aLb, aRb bus_read "fnm_chordbass"
+ aL += aLb
+ aR += aRb
+ endif
+
+ if (iplayaugment == 1) then
+ aLa, aRa bus_read "fnm_augment"
+ aL += aLa
+ aR += aRa
+ endif
+
+ kamp linseg 1, p3, 1, 0.1, 0
+ aL *= kamp
+ aR *= kamp
+
+ kduck = port(gkduck, 0.1)
+ aL *= (1 - kduck)
+ aR *= (1 - kduck)
+ bus_mix("master", aL*0.8, aR*0.8)
+
+ kreleasing init 0
+ if (release:k() == 1 && kreleasing == 0) then
+ turnoff2 SnoteInstrument, 0, 1
+ if (iplaybass == 1) then
+ turnoff2 SbassInstrument, 0, 1
+ endif
+ if (iplayaugment == 1) then
+ turnoff2 SaugInstrument, 0, 1
+ endif
+ kreleasing = 1
+ endif
+endin
+
+
+instr fnm_sectionmanager
+ gkmel_pause = 1
+
+ ksectionstart init 0.01
+ ksectionchange = 0
+ ktransitioner = chnget:k("fnm_transition")
+ kadvance = chnget:k("fnm_advance")
+ krepeat = chnget:k("fnm_repeat")
+
+ if (changed:k(ktransitioner) == 1) then
+ if (ktransitioner == 0) then
+ turnoff2 "fnm_transitionplayer", 0, 1
+ if (random:k(0, 1) > 0.5) then ; have ultimate transition
+ if (random:k(0, 1) > 0.5) then ; no other output during ultimate transition
+ turnoff2 "fnm_sectionplayer", 0, 1
+ endif
+ gkutransitiontime = random:k(0.2, 1)
+ schedulek("fnm_ultimatetransitionplayer", 0, gkutransitiontime)
+ ksectionstart = gkutransitiontime
+ else
+ gkutransitiontime = 0
+ ksectionstart = 0.01
+ endif
+ ksectionchange = 1
+ else
+ schedulek("fnm_transitionplayer", 0, 36000)
+ endif
+ endif
+
+ if (ksectionchange == 1 || (changed:k(kadvance) == 1 && kadvance == 1) || (changed:k(krepeat) == 1 && krepeat == 1)) then
+ if (krepeat == 1) then
+ ksectionstart = 0
+ endif
+ turnoff2 "fnm_sectionplayer", 0, 1
+ if (krepeat != 1) then
+ gkmel_advance_trig = 1
+ endif
+ schedulek("fnm_sectionplayer", ksectionstart, 36000)
+ endif
+
+ kreleasing init 0
+ if (release:k() == 1 && kreleasing == 0) then
+ turnoff2 "fnm_sectionplayer", 0, 1
+ kreleasing = 1
+ endif
+
+endin
+
+#end \ No newline at end of file
diff --git a/site/udo/frequency_tools.udo b/site/udo/frequency_tools.udo
new file mode 100755
index 0000000..d4f1f0a
--- /dev/null
+++ b/site/udo/frequency_tools.udo
@@ -0,0 +1,375 @@
+#ifndef UDO_FREQUENCYTOOLS
+#define UDO_FREQUENCYTOOLS ##
+/*
+ Frequency tools/effects: shifters, ring modulation, delays, chorus etc
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "wavetables.udo"
+
+
+/*
+ Mono frequency shifter with hilbert transform
+
+ aoutput freqshift1 ainput, kfrequency
+
+ aoutput output audio
+ ainput input audio
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+*/
+opcode freqshift1, a, ak
+ ain, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ areal, aimag hilbert ain
+ amod1 = areal * acos
+ amod2 = aimag * asin
+ ashift = (amod1 - amod2) * 0.7
+ xout ashift
+endop
+
+
+/*
+ Stereo frequency shifter with hilbert transform
+
+ aoutputL, aoutputR freqshift1 ainputL, ainputR, kfrequency
+
+ aoutputL output audio left
+ aoutputR output audio right
+ ainputL input audio left
+ ainputR input audio right
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+*/
+opcode freqshift1, aa, aak
+ ainL, ainR, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ arealL, aimagL hilbert ainL
+ arealR, aimagR hilbert ainR
+ amod1L = arealL * acos
+ amod2L = aimagL * asin
+ amod1R = arealR * acos
+ amod2R = aimagR * asin
+ ashiftL = (amod1L - amod2L) * 0.7
+ ashiftR = (amod1R - amod2R) * 0.7
+ xout ashiftL, ashiftR
+endop
+
+
+/*
+ Mono ring modulator with hilbert transform
+
+ aoutput ringmod1 ainput, kfrequency
+
+ aoutput output audio
+ ainput input audio
+ kfrequency modulation frequency [MIN(0) MAX(10000) DEFAULT(440)]
+*/
+opcode ringmod1, a, ak
+ ain, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ areal, aimag hilbert ain
+ amod1 = areal * acos
+ amod2 = aimag * asin
+ aupshift = (amod1 - amod2) * 0.7
+ adownshift = (amod1 + amod2) * 0.7
+ xout aupshift+adownshift
+endop
+
+/*
+ Stereo ring modulator with hilbert transform
+
+ aoutputL, aoutputR ringmod1 ainputL, ainputR, kfrequency
+
+ aoutputL output audio left
+ aoutputR output audio right
+ ainputL input audio left
+ ainputR input audio right
+ kfrequency modulation frequency [MIN(0) MAX(10000) DEFAULT(440)]
+*/
+opcode ringmod1, aa, aak
+ ainL, ainR, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ arealL, aimagL hilbert ainL
+ arealR, aimagR hilbert ainR
+ amod1L = arealL * acos
+ amod2L = aimagL * asin
+ amod1R = arealR * acos
+ amod2R = aimagR * asin
+ aupshiftL = (amod1L - amod2L) * 0.7
+ adownshiftL = (amod1L + amod2L) * 0.7
+ aupshiftR = (amod1R - amod2R) * 0.7
+ adownshiftR = (amod1R + amod2R) * 0.7
+ xout aupshiftL+adownshiftL, aupshiftR+adownshiftR
+endop
+
+
+/*
+ Mono frequency shifter with direct modulation
+
+ aoutput freqshift2 ainput, kfrequency, [kshiftmode=1]
+
+ aoutput output audio
+ ainput input audio
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+ kshiftmode shift mode [TYPE(bool) DEFAULT(1)]
+*/
+opcode freqshift2, a, akP
+ ain, kfreq, kshiftmode xin
+ isr4 = sr * 0.25
+
+ ko1frq = isr4 - (1 - kshiftmode) * kfreq
+ aqo1r oscil 1.0, ko1frq, gifnSine, 0.25 ; cosine
+ aqo1i oscil 1.0, ko1frq, gifnSine, 0.0 ; sine
+
+ ko2frq = isr4 + kshiftmode * kfreq
+ aqo2r oscil 1.0, ko2frq, gifnSine, 0.25 ; cosine
+ aqo2i oscil 1.0, ko2frq, gifnSine, 0.0 ; sine
+ awq1r = ain * aqo1r
+
+ awf1r biquad awq1r, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2r biquad awf1r, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3r biquad awf2r, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+ aw1fr = awf3r * 0.051532459925
+ awq2r = aw1fr * aqo2r
+
+ awq1i = ain * aqo1i
+ awf1i biquad awq1i, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2i biquad awf1i, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3i biquad awf2i, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+
+ aw1fi = awf3i * 0.051532459925
+ awq2i = aw1fi * aqo2i
+ aout = awq2r + awq2i
+ xout aout
+endop
+
+
+/*
+ Stereo frequency shifter with direct modulation
+
+ aoutputL, aoutputR freqshift2 ainputL, ainputR, kfrequency, [kshiftmode=1]
+
+ aoutputL output audio left
+ aoutputR output audio right
+ ainputL input audio left
+ ainputR input audio right
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+ kshiftmode shift mode [TYPE(bool) DEFAULT(1)]
+*/
+opcode freqshift2, aa, aakP
+ ainL, ainR, kfreq, kshiftmode xin
+ isr4 = sr * 0.25
+
+ ko1frq = isr4 - (1 - kshiftmode) * kfreq
+ aqo1r oscil 1.0, ko1frq, gifnSine, 0.25 ; cosine
+ aqo1i oscil 1.0, ko1frq, gifnSine, 0.0 ; sine
+ ko2frq = isr4 + kshiftmode * kfreq
+ aqo2r oscil 1.0, ko2frq, gifnSine, 0.25 ; cosine
+ aqo2i oscil 1.0, ko2frq, gifnSine, 0.0 ; sine
+
+ awq1rL = ainL * aqo1r
+ awq1rR = ainR * aqo1r
+
+ ; Left
+ awf1rL biquad awq1rL, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2rL biquad awf1rL, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3rL biquad awf2rL, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+ aw1frL = awf3rL * 0.051532459925
+ awq2rL = aw1frL * aqo2r
+
+ awq1iL = ainL * aqo1i
+ awf1iL biquad awq1iL, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2iL biquad awf1iL, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3iL biquad awf2iL, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+
+ aw1fiL = awf3iL * 0.051532459925
+ awq2iL = aw1fiL * aqo2i
+ aoutL = awq2rL + awq2iL
+
+ ; Right
+ awf1rR biquad awq1rR, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2rR biquad awf1rR, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3rR biquad awf2rR, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+ aw1frR = awf3rR * 0.051532459925
+ awq2rR = aw1frR * aqo2r
+
+ awq1iR = ainR * aqo1i
+ awf1iR biquad awq1iR, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2iR biquad awf1iR, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3iR biquad awf2iR, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+
+ aw1fiR = awf3iR * 0.051532459925
+ awq2iR = aw1fiR * aqo2i
+ aoutR = awq2rR + awq2iR
+
+ xout aoutL, aoutR
+endop
+
+
+
+/*
+ Bit depth reducer/crusher
+
+ aout bitcrush ain, [krush=16]
+
+ aout crushed signal
+ ain input signal
+ krush bits to reduce to [TYPE(int) MIN(1) MAX(128) DEFAULT(16)]
+
+*/
+opcode bitcrush, a, aJ
+ a1, krush xin
+ krush = (krush == -1) ? 16 : krush
+ a1 = round:a(a1 * krush) / krush
+ xout a1
+endop
+
+
+/*
+ Bit depth reducer/crusher (stereo)
+
+ aoutL, aoutR bitcrush ainL, ainR, [krush=16]
+
+ aoutL crushed signal left
+ aoutR crushed signal right
+ ainL input signal left
+ ainR input signal right
+ krush bits to reduce to [TYPE(int) MIN(1) MAX(128) DEFAULT(16)]
+
+*/
+opcode bitcrush, aa, aaJ
+ aL, aR, krush xin
+ krush = (krush == -1) ? 16 : krush
+ aL = round:a(aL * krush) / krush
+ aR = round:a(aR * krush) / krush
+ xout aL, aR
+endop
+
+
+
+/*
+ Resonant delay based tuner
+
+ aout delaytuner ain, kfrequency, kfeedback
+
+ aout tuned/delayed signal summed with input
+ ain input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ kfeedback feedback amount [MIN(0) MAX(1) DEFAULT(0.5)]
+*/
+opcode delaytuner, a, akk
+ ain, kfrequency, kfeedback xin
+ adump delayr 1
+ adelayed deltap (1/kfrequency)
+ delayw ain + (adelayed * kfeedback)
+ aout = ain + adelayed
+ xout aout
+endop
+
+
+/*
+ Resonant delay based tuner (stereo)
+
+ aoutL, aoutR delaytuner ainL, ainR, kfrequency, kfeedback
+
+ aoutL, aoutR tuned/delayed signal summed with input
+ ainL, ainR input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ kfeedback feedback amount [MIN(0) MAX(1) DEFAULT(0.5)]
+*/
+opcode delaytuner, aa, aakk
+ ainL, ainR, kfrequency, kfeedback xin
+ aoutL delaytuner ainL, kfrequency, kfeedback
+ aoutR delaytuner ainR, kfrequency, kfeedback
+ xout aoutL, aoutR
+endop
+
+
+/*
+ Resonant delay based tuner with hold control. When held, only outputs effected, not dry
+
+ aout glitchtuner ain, kfrequency, ktrig
+
+ aout output signal
+ ain input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ khold apply if 1, bypass if 0
+*/
+opcode glitchtuner, a, akk
+ ain, kfrequency, khold xin
+ adump delayr 1
+ adelayed deltap (1/kfrequency)
+ if (khold >= 1) then
+ aout = adelayed
+ else
+ aout = ain
+ endif
+ delayw aout
+ xout aout
+endop
+
+
+/*
+ Resonant delay based tuner with hold control (stereo). When held, only outputs effected, not dry
+
+ aout glitchtuner ain, kfrequency, ktrig
+
+ aoutL, aoutR output signal
+ ainL, ainR input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ khold apply if 1, bypass if 0
+*/
+opcode glitchtuner, aa, aakk
+ ainL, ainR, kfrequency, khold xin
+ aoutL glitchtuner ainL, kfrequency, khold
+ aoutR glitchtuner ainR, kfrequency, khold
+ xout aoutL, aoutR
+endop
+
+
+/*
+ Simple chorus
+
+ aout simplechorus ain, krate
+
+ aout output signal
+ ain input signal
+ krate delay rate in Hz
+*/
+opcode simplechorus, a, ak
+ ain, krate xin
+ alfo oscil krate, unirand(1)
+ aout vdelay3 ain, (0.01 + alfo) * 1000, 1000
+ xout aout
+endop
+
+
+/*
+ Simple chorus
+
+ aoutL, aoutR simplechorus ainL, ainR, krateL, krateR=krateL
+
+ aoutL, aoutR output signal
+ ainL, ainR input signal
+ krateL delay rate in Hz left
+ krateR delay rate in Hz right
+*/
+opcode simplechorus, aa, aakJ
+ aL, aR, krateL, krateR xin
+ krateR = (krateR == -1) ? krateL : krateR
+ alfoL oscil krateL, unirand(1)
+ alfoR oscil krateR, unirand(1)
+ aL vdelay3 aL, (0.01 + alfoL) * 1000, 1000
+ aR vdelay3 aR, (0.01 + alfoR) * 1000, 1000
+ xout aL, aR
+endop
+
+
+#end
+
diff --git a/site/udo/fx_autoglitch.udo b/site/udo/fx_autoglitch.udo
new file mode 100755
index 0000000..163110c
--- /dev/null
+++ b/site/udo/fx_autoglitch.udo
@@ -0,0 +1,470 @@
+#ifndef UDO_FXAUTOGLITCH
+#define UDO_FXAUTOGLITCH ##
+/*
+ Autoglitch effects
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022, 2024, 2025
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "pvs_tabproc.udo"
+#include "wavetables.udo"
+
+#ifndef AUTOGLITCH_WINSIZE
+#define AUTOGLITCH_WINSIZE #4410#
+#end
+
+#ifndef AUTOGLITCH_WINRAND
+#define AUTOGLITCH_WINRAND #441#
+#end
+
+
+/*
+ Internal autoglitch UDO for actual audio processing
+
+ aout _fx_autoglitch_audio ain, awritepos, areadpos, ibuflensamps, kdo_distortion, kdist, kporttime, kdo_ampchange, kamp, kreadmode
+
+ aout audio output
+ ain audio input
+ awritepos write position for sampler in samples
+ areadpos read position in samples
+ ibuflensamps length of buffer to be created in samples
+ kdo_distortion apply distortion
+ kdist distortion amount
+ kporttime portamento time
+ kdo_ampchange apply amplitude change
+ kamp amplitude
+ kreadmode read mode: 0 = direct table; 1 = sndwarp; 2 = mincer
+*/
+opcode _fx_autoglitch_audio, a, aaaikkkkkk
+ ain, awritepos, areadpos, ibuflensamps, kdo_distortion, kdist, kporttime, kdo_ampchange, kamp, kreadmode xin
+ ibuffer = ftgentmp(0, 0, -ibuflensamps, -2, 0)
+
+ tablew ain, awritepos, ibuffer
+
+ if (kreadmode == 0) then
+ asig table areadpos, ibuffer
+ elseif (kreadmode == 1) then
+ asig sndwarp 1, areadpos / sr, 1, ibuffer, 0, $AUTOGLITCH_WINSIZE, $AUTOGLITCH_WINRAND, 4, gifnHalfSine, 1
+ elseif (kreadmode == 2) then
+ asig mincer areadpos / sr, 1, 1, ibuffer, 1
+ endif
+
+ asig butterhp dcblock(asig), 70
+
+ if (kdo_distortion == 1 && kdist > 1) then
+ asigd distort asig, portk(kdist, kporttime), gifnSquare
+ asig balance asigd, asig
+ endif
+
+ if (kdo_ampchange == 1) then
+ asig *= portk(kamp, kporttime)
+ endif
+
+ asig butterhp dcblock(asig), 100
+ xout asig
+endop
+
+
+/*
+ Internal autoglitch UDO for control
+
+ awritepos, areadpos, ibuflensamps, kamp, kdist _fx_autoglitch_control kminratio, kchangerate, kchangechance, kporttime, ibuflens
+
+ awritepos write position for sampler in samples
+ areadpos read position in samples
+ ibuflensamps length of buffer to be created in samples
+ kamp amplitude
+ kdist distortion amount
+ kminratio minimum ratio of sound length to use (0 to 1)
+ kchangerate rate of change in Hz
+ kchangechance chance of changing at change rate (0 to 1)
+ kporttime portamento time in seconds
+ ibuflens required buffer length in seconds
+
+*/
+opcode _fx_autoglitch_control, aaikk, kkkki
+ kminratio, kchangerate, kchangechance, kporttime, ibuflens xin
+
+ kminratio = (kminratio == -1) ? 0.2 : kminratio
+ kchangerate = (kchangerate == -1) ? 0.1 : kchangerate
+ kchangechance = (kchangechance == -1) ? 0.8 : kchangechance
+ kporttime = (kporttime == -1) ? 0.2 : kporttime
+ ibuflens = (ibuflens == -1) ? 2 : ibuflens
+
+ ibuflensamps = sr * ibuflens
+ awritepos lphasor 1, 0, ibuflensamps, 1 ; ,3
+
+ kdist init 0
+ klen init random(256, ibuflensamps * 0.5)
+ kstart init random(0, ibuflensamps * 0.5)
+ kreverse init 0
+ kamp init 0
+
+ kcps = ibuflensamps / klen
+ if (kreverse == 1) then
+ kcps = 0 - kcps
+ endif
+
+ areadindex phasor kcps
+ areadpos = ((areadindex * ibuflensamps) + portk(kstart, kporttime))
+
+ kchanger metro kchangerate
+ if (kchanger == 1 && random:k(0, 1) < kchangechance) then
+ klen = random:k(50, ibuflensamps * kminratio)
+ kstart = random:k(0, ibuflensamps - klen)
+ endif
+
+ if (kchanger == 1 && random:k(0, 1) < kchangechance) then
+ kreverse = round:k(random:k(0, 1))
+ endif
+
+ if (kchanger == 1 && random:k(0, 1) < kchangechance) then
+ kdist = random:k(0, 2)
+ endif
+
+ if (kchanger == 1 && random:k(0, 1) < kchangechance) then
+ kamp = round:k(random:k(0, 1))
+ endif
+
+ xout awritepos, areadpos, ibuflensamps, kamp, kdist
+endop
+
+
+
+/*
+ Mono in, mono out autoglitch
+
+ aout fx_autoglitch ain, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, kreadmode
+
+ aout audio output
+ ain audio input
+ kminratio minimum ratio of sound length to use (0 to 1)
+ kchangerate rate of change in Hz
+ kchangechance chance of changing at change rate (0 to 1)
+ kporttime portamento time in seconds
+ kdo_distortion apply distortion
+ kdo_ampchange apply amplitude change
+ ibuflens required buffer length in seconds
+ kreadmode read mode: 0 = direct table; 1 = sndwarp; 2 = mincer
+
+*/
+opcode fx_autoglitch, a, aJJJJOOjO
+ ain, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, kreadmode xin
+
+ awritepos, areadpos, ibuflensamps, kamp, kdist _fx_autoglitch_control kminratio, kchangerate, kchangechance, kporttime, ibuflens
+ aout _fx_autoglitch_audio ain, awritepos, areadpos, ibuflensamps, kdo_distortion, kdist, kporttime, kdo_ampchange, kamp, kreadmode
+ xout aout
+endop
+
+
+/*
+ Mono in, stereo out autoglitch
+
+ aoutL, aoutR fx_autoglitch ain, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, kreadmode
+
+ aoutL left audio output
+ aoutR right audio output
+ ain audio input
+ kminratio minimum ratio of sound length to use (0 to 1)
+ kchangerate rate of change in Hz
+ kchangechance chance of changing at change rate (0 to 1)
+ kporttime portamento time in seconds
+ kdo_distortion apply distortion
+ kdo_ampchange apply amplitude change
+ ibuflens required buffer length in seconds
+ kreadmode read mode: 0 = direct table; 1 = sndwarp; 2 = mincer
+
+*/
+opcode fx_autoglitch, aa, aJJJJOOjO
+ ain, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, kreadmode xin
+
+ awriteposL, areadposL, ibuflensamps, kampL, kdistL _fx_autoglitch_control kminratio, kchangerate, kchangechance, kporttime, ibuflens
+ awriteposR, areadposR, ibuflensamps, kampR, kdistR _fx_autoglitch_control kminratio, kchangerate, kchangechance, kporttime, ibuflens
+ aL _fx_autoglitch_audio ain, awriteposL, areadposL, ibuflensamps, kdo_distortion, kdistL, kporttime, kdo_ampchange, kampL, kreadmode
+ aR _fx_autoglitch_audio ain, awriteposR, areadposR, ibuflensamps, kdo_distortion, kdistR, kporttime, kdo_ampchange, kampR, kreadmode
+ xout aL, aR
+endop
+
+
+/*
+ Stereo in, stereo out autoglitch
+
+ aoutL, aoutR fx_autoglitch ainL, ainR, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, istereounique, kreadmode
+
+ aoutL left audio output
+ aoutR right audio output
+ ainL left audio input
+ ainR right audio input
+ kminratio minimum ratio of sound length to use (0 to 1)
+ kchangerate rate of change in Hz
+ kchangechance chance of changing at change rate (0 to 1)
+ kporttime portamento time in seconds
+ kdo_distortion apply distortion
+ kdo_ampchange apply amplitude change
+ ibuflens required buffer length in seconds
+ istereounique stereo mode: 0 = left and right read the same positions from buffers; 1 = left and right are unique
+ kreadmode read mode: 0 = direct table; 1 = sndwarp; 2 = mincer
+
+*/
+opcode fx_autoglitch, aa, aaJJJJOOjjO
+ aL, aR, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, istereounique, kreadmode xin
+
+ if (istereounique == 1) then
+ awriteposL, areadposL, ibuflensamps, kampL, kdistL _fx_autoglitch_control kminratio, kchangerate, kchangechance, kporttime, ibuflens
+ awriteposR, areadposR, ibuflensamps, kampR, kdistR _fx_autoglitch_control kminratio, kchangerate, kchangechance, kporttime, ibuflens
+ aL _fx_autoglitch_audio aL, awriteposL, areadposL, ibuflensamps, kdo_distortion, kdistL, kporttime, kdo_ampchange, kampL, kreadmode
+ aR _fx_autoglitch_audio aR, awriteposR, areadposR, ibuflensamps, kdo_distortion, kdistR, kporttime, kdo_ampchange, kampR, kreadmode
+ else
+ awritepos, areadpos, ibuflensamps, kamp, kdist _fx_autoglitch_control kminratio, kchangerate, kchangechance, kporttime, ibuflens
+ aL _fx_autoglitch_audio aL, awritepos, areadpos, ibuflensamps, kdo_distortion, kdist, kporttime, kdo_ampchange, kamp, kreadmode
+ aR _fx_autoglitch_audio aR, awritepos, areadpos, ibuflensamps, kdo_distortion, kdist, kporttime, kdo_ampchange, kamp, kreadmode
+ endif
+
+ xout aL, aR
+endop
+
+
+
+/*
+ Spectral processing autoglitch
+
+ aout fx_spectralautoglitch ain, kchangerate, kchangechance, kdo_pitchalter, kporttime
+
+ aout audio output
+ ain audio input
+ kchangerate rate of change in Hz
+ kchangechance chance of changing at change rate (0 to 1)
+ kdo_pitchalter apply pitch alterations
+ kporttime portamento time in seconds
+ ifftsize fft size
+
+*/
+opcode fx_spectralautoglitch, a, akkkkj
+ ainput, kchangerate, kchangechance, kdo_pitchalter, kporttime, ifftsize xin
+
+ kdo_freeze init 0
+ kdo_average init 0
+ kdo_scramble init 0
+ kdo_scale init 0
+ kdo_shift init 0
+ kdo_blur init 0
+ kdo_delay init 0
+ kdo_bubble init 0
+ kdo_wrap init 0
+
+ kbubblechance init 0
+ kbubblestereo init 0
+ kwrapamp init 0
+ kwrapfreq init 0
+ kfreezetime init 0
+ kaveragetime init 0
+ kscale init 1
+ kshift init 0
+ kblur init 0
+ kdelaytime init 0
+ ktime init 0
+ kpos init 0
+
+ ilength = random(1, 4)
+ ir = (ifftsize == -1) ? 512 : ifftsize
+
+ finput pvsanal ainput, ir, ir/4, ir, 1
+
+ ibuffer, ktime pvsbuffer finput, ilength
+ kchange changed kpos
+ aphasor, asyncout syncphasor a(portk(ktime, kporttime) * ilength), a(kchange)
+ kphasor = k(aphasor) + (portk(kpos, kporttime) * ilength)
+ floop pvsbufread kphasor, ibuffer
+
+
+ kchanger metro kchangerate
+ if (kchanger == 1) then
+ if (random:k(0, 1) < kchangechance) then
+ ktime = random:k(0, 1)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kpos = random:k(0, 1)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kfreezetime = round:k(random:k(0, 20))
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_freeze = 1 - kdo_freeze
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kaveragetime = random:k(0, 10)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_average = 1 - kdo_average
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_scramble = 1 - kdo_scramble
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_bubble = 1 - kdo_bubble
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kbubblechance = random:k(0, 1)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kbubblestereo = round:k(random:k(0, 1))
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_wrap = 1 - kdo_wrap
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kwrapamp = round:k(random:k(0, ir / 2))
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kwrapfreq = round:k(random:k(0, ir / 2))
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kscale = random:k(0.5, 2)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_scale = 1 - kdo_scale
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kshift = random:k(-500, 100)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_shift = 1 - kdo_shift
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kblur = random:k(0, 1)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_blur = 1 - kdo_blur
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdelaytime = random:k(2, 1000)
+ endif
+
+ if (random:k(0, 1) < kchangechance) then
+ kdo_delay = 1 - kdo_delay
+ endif
+
+ endif
+
+ kready, itpv tpv_anal floop
+
+ if (kdo_scramble == 1) then
+ tpv_scramble kready, itpv, 4
+ endif
+
+ if (kdo_freeze == 1) then
+ tpv_freeze1 kready, itpv, portk(kfreezetime, kporttime)
+ endif
+
+ if (kdo_average == 1) then
+ tpv_average kready, itpv, portk(kaveragetime, kporttime)
+ endif
+
+ if (kdo_bubble == 1) then
+ tpv_bubble kready, itpv, kbubblechance, kbubblestereo
+ endif
+
+ if (kdo_wrap == 1) then
+ tpv_wrap kready, itpv, kwrapamp, kwrapfreq
+ endif
+
+ floop tpv_resynth itpv, floop
+
+ if (kdo_blur == 1) then
+ ;floop pvsblur floop, portk(kblur, kporttime), 1
+ endif
+
+ if (kdo_pitchalter == 1 && kdo_scale == 1) then
+ floop pvscale floop, portk(kscale, kporttime)
+ endif
+
+ if (kdo_pitchalter == 1 && kdo_shift == 1) then
+ floop pvshift floop, portk(kshift, kporttime), 150
+ endif
+
+ aout pvsynth floop
+
+ if (kdo_delay == 1) then
+ kdtime = (kdo_pitchalter == 1) ? portk(kdelaytime, kporttime) : kdelaytime
+ aout vdelay aout, kdtime, 1000
+ endif
+
+ xout aout * 2.5 ; is quieter
+endop
+
+
+
+/*
+ Retrigger glitcher
+
+ aout fx_retrigglitch ifn, ktriglen, areadpos[, kpitchratio=1, kapplywindowing=1, kwinfn=gifnHanning]
+
+ aout audio output
+ ifn input audio ftable (mono)
+ ktriglen retrigger length in seconds
+ areadpos read position in seconds; -1 is set randomly upon each retrigger
+ kpitchratio resample ratio; 1 = normal pitch
+ kapplywindowing amplitude enveloping: 0 = none; 1 = apply Hanning window to output
+ kwinfn window function table
+*/
+opcode fx_retrigglitch, a, ikaPPJ
+ ifn, ktriglen, areadpos, kpitchratio, kapplywindowing, kwinfn xin
+ ifnsr = ftsr(ifn)
+ ifnlen = ftlen(ifn)
+ ifnlens = ifnlen / ifnsr
+
+ kfreq = 1 / ktriglen
+ ktrig metro kfreq
+
+ if (ktrig == 1 && k(areadpos) == -1) then
+ kstart random 0, ifnlens - ktriglen
+ areadpos = a(kstart)
+ endif
+
+ areadpos = areadpos / ifnlens
+ irate = 1 / ifnlens
+ areadpos samphold areadpos, ktrig
+ async upsamp ktrig
+ apos, a_ syncphasor irate * kpitchratio, async
+ apos += areadpos
+ apos *= ifnlen
+
+ aout tablei apos, ifn
+ ;aout, a_ mincer (apos / ifnlen) * ifnlens, 1, kpitchratio, ifn, 1
+
+ if (kapplywindowing == 1) then
+ if (kwinfn == -1) then
+ kwinfn = gifnHanning
+ endif
+ aenv oscilikt 1, kfreq, kwinfn
+ aout *= aenv
+ endif
+
+ aout dcblock aout
+ xout aout
+endop
+
+
+#end
+
diff --git a/site/udo/fx_autoglitchbeat.udo b/site/udo/fx_autoglitchbeat.udo
new file mode 100755
index 0000000..a39988c
--- /dev/null
+++ b/site/udo/fx_autoglitchbeat.udo
@@ -0,0 +1,168 @@
+#ifndef UDO_FXAUTOGLITCHBEAT
+#define UDO_FXAUTOGLITCHBEAT ##
+
+/*
+ Autoglitch effects, quantised
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "pvs_tabproc.udo"
+#include "sequencing.udo"
+#include "frequency_tools.udo"
+
+
+/*
+ beat based autoglitcher in the style of dBlue Glitch
+
+ aoutL, aoutR fx_autoglitchbeat aL, aR, krandomisetrig, ipatternlength
+
+ aoutL, aoutR outputs
+ aL, aR inputs
+ krandomisetrig trigger to randomise the pattern
+ ipatterlength pattern length in semiquavers, defaults to 32
+*/
+opcode fx_autoglitchbeat, aa, aaOj
+ aL, aR, krandomisetrig, ipatternlength xin
+
+ ipatternlength = (ipatternlength == -1) ? 32 : ipatternlength
+ ifnpatterns = ftgentmp(0, 0, -ipatternlength, -7, 0)
+ ifnparams = ftgentmp(0, 0, -(ipatternlength * 2), -7, 0)
+ ifnbufferL = ftgentmp(0, 0, -44100, -7, 0)
+ ifnbufferR = ftgentmp(0, 0, -44100, -7, 0)
+ imaxeffect = 7; 5 for no tpv
+
+ kbeat init 0
+ knexteffect init 0
+ klasteffect init 0
+ kinit init 1
+ kwriting init 1
+ kparam0 init 0
+ kparam1 init 0
+ keffect init 0
+
+ if (kwriting == 1) then
+ awritepos, a_ syncphasor 1, a(gkseq_beat)
+ tablew aL, awritepos, ifnbufferL, 1
+ tablew aR, awritepos, ifnbufferR, 1
+ endif
+
+
+ if (krandomisetrig == 1 || kinit == 1) then
+ kindex = 0
+ while (kindex < ipatternlength) do
+ ksame = (kindex != 0 && random:k(0, 1) > 0.7) ? 1 : 0
+
+ if (ksame == 0) then
+ keffect = round:k(random:k(0, imaxeffect))
+ if (keffect == 0) then ; freqshift
+ kparam0 = random:k(-1000, 50)
+ elseif (keffect == 1) then ; ring mod
+ kparam0 = random:k(110, 440)
+ elseif (keffect == 2) then ; bit crush
+ kparam0 = random:k(4, 32)
+ elseif (keffect == 3 || keffect == 4 || keffect == 5) then ; retriggers and stretches
+ kparam0 = pow:k(2, round:k(random:k(1, 3))) ; division time ; was up to 4
+ if (random:k(0, 1) > 0.8) then ; reverse parameter
+ kparam1 = 1
+ else
+ kparam1 = 0
+ endif
+ endif
+ endif
+
+ tabw keffect, kindex, ifnpatterns
+ tabw kparam0, kindex, ifnparams
+ tabw kparam1, kindex + (1 * ftlen(ifnpatterns)), ifnparams
+
+ kindex += 1
+ od
+ kinit = 0
+ endif
+
+ ktime timeinsts
+
+ if (gkseq_beat == 1) then
+ kcurrenteffect tab kbeat, ifnpatterns
+ klasteffect tab (kbeat - 1 < 0) ? ipatternlength - 1 : kbeat - 1, ifnpatterns
+ kparam0 tab kbeat, ifnparams
+ kparam1 tab kbeat + (1 * ftlen(ifnpatterns)), ifnparams
+ kbeat = (kbeat + 1 < ipatternlength) ? kbeat + 1 : 0
+
+ if (kcurrenteffect != klasteffect) then
+ kwriting = 1
+ kwritestart = ktime
+ endif
+ endif
+
+
+ if (kwriting == 1 && ktime - kwritestart >= gkseq_beattime) then ; record for one beat
+ kwriting = 0
+ endif
+
+ if (kcurrenteffect == 0) then ; freqshift
+ aL, aR freqshift1 aL, aR, kparam0
+
+ elseif (kcurrenteffect == 1) then ; ring mod
+ aL, aR ringmod1 aL, aR, kparam0
+
+ elseif (kcurrenteffect == 2) then ; bit crush
+ aL, aR bitcrush aL, aR, kparam0
+
+ elseif (kcurrenteffect == 3 || kcurrenteffect == 4) then ; retriggers
+ kdivisiontime = gkseq_beattime / kparam0
+ ;kwriting vdel_k 0, kdivisiontime, 1
+ kwritelength = kdivisiontime
+ if (kparam1 == 0) then
+ aposraw = phasor(gkseq_beathz * kparam0)
+ else
+ aposraw = 1 - phasor(gkseq_beathz * kparam0)
+ endif
+
+ if (kcurrenteffect == 4) then
+ aposaug = abs:a(oscil:a(1, gkseq_beathz / 8))
+ apos = aposaug * (aposraw * kdivisiontime * ftsr(ifnbufferL))
+ else
+ apos = aposraw * kdivisiontime * ftsr(ifnbufferL)
+ endif
+
+ aL tablei apos, ifnbufferL
+ aR tablei apos, ifnbufferR
+
+ elseif (kcurrenteffect == 5) then ; stretch
+ kdivisiontime = gkseq_beattime / kparam0
+ if (kparam1 == 0) then
+ aposraw = phasor(gkseq_beathz / kparam0)
+ else
+ aposraw = 1 - phasor(gkseq_beathz / kparam0)
+ endif
+ atime = aposraw * kdivisiontime
+ kpitch init 1
+ aL mincer atime, 1, kpitch, ifnbufferL, 1
+ aR mincer atime, 1, kpitch, ifnbufferL, 1
+
+ elseif (kcurrenteffect == 6 || kcurrenteffect == 7) then
+ ir = 512
+ finputL pvsanal aL * 1.5, ir, ir/4, ir, 1
+ finputR pvsanal aR * 1.5, ir, ir/4, ir, 1
+ kready, itpvL tpv_anal finputL
+ kready, itpvR tpv_anal finputR
+
+ tpv_scramble kready, itpvL, 1
+ tpv_scramble kready, itpvR, 1
+
+ finputL tpv_resynth itpvL, finputL
+ finputR tpv_resynth itpvR, finputR
+ aL pvsynth finputL
+ aR pvsynth finputR
+
+ endif
+ xout aL, aR
+endop
+
+
+
+#end
+
diff --git a/site/udo/host_platform.udo b/site/udo/host_platform.udo
new file mode 100755
index 0000000..2b82eb7
--- /dev/null
+++ b/site/udo/host_platform.udo
@@ -0,0 +1,45 @@
+#ifndef UDO_HOSTPLATFORM
+#define UDO_HOSTPLATFORM ##
+
+/*
+ Host platform detection
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+; gihost_type: 0 = windows , 1 = linux, 2 = WASM
+
+
+#ifndef WEB
+gihost_type filevalid "/bin/ls"
+#end
+
+#ifdef WEB
+gihost_type = 2
+#end
+
+gihost_max32bitftlen = 16777216
+
+/*
+ Get temp dir as string
+*/
+opcode host_tempdir, S, 0
+ Spath = "" ; default to root assuming slash is appended to output
+ if (gihost_type == 0) then
+ Spath = "%TEMP%"
+ elseif (gihost_type == 1) then
+ Spath = "/tmp"
+ endif
+ xout Spath
+endop
+
+/*
+ Get whether the current Csound instance is 32 or 64 bit
+*/
+opcode is64bit, i, 0
+ xout (1 & 1e9+9)
+endop
+
+#end
diff --git a/site/udo/host_tools.udo b/site/udo/host_tools.udo
new file mode 100755
index 0000000..012e70d
--- /dev/null
+++ b/site/udo/host_tools.udo
@@ -0,0 +1,69 @@
+#ifndef UDO_HOSTTOOLS
+#define UDO_HOSTTOOLS ##
+
+/*
+ Host system tools for interoperability between windows/linux setups
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+#include "__config__.udo"
+#include "host_platform.udo"
+
+
+/*
+ Get the path to the shared general audio repository with a trailing slash
+ Spath _dir_audio
+
+ Spath the relevant resulting path
+*/
+opcode _dir_audio, S, 0
+ xout (gihost_type == 1) ? gSpath_baseLin : gSpath_baseWin
+endop
+
+
+/*
+ Get a full path of subdirectories in the soundfont directory
+ SfullPath dir_soundfont Spath
+
+ SfullPath the full path as specified
+
+ Sfile the last part of the path, after Soundfont/ , ie no initial slash
+*/
+opcode dir_soundfont, S, S
+ Spath xin
+ xout strcat(strcat(_dir_audio(), gSpath_soundfont), Spath)
+endop
+
+
+/*
+ Get a full path of subdirectories in the samples directory
+ SfullPath dir_samples Spath
+
+ SfullPath the full path as specified
+
+ Spath the last part of the path, after Samples/ , ie no initial slash
+*/
+opcode dir_samples, S, S
+ Spath xin
+ xout strcat(strcat(_dir_audio(), gSpath_samples), Spath)
+endop
+
+
+/*
+ Get a full path of subdirectories in the random directory
+ SfullPath dir_random Spath
+
+ SfullPath the full path as specified
+
+ Spath the last part of the path, after Random/ , ie no initial slash
+*/
+opcode dir_random, S, S
+ Spath xin
+ xout strcat(strcat(_dir_audio(), gSpath_random), Spath)
+endop
+
+
+
+#end
diff --git a/site/udo/interop.udo b/site/udo/interop.udo
new file mode 100755
index 0000000..84a3f00
--- /dev/null
+++ b/site/udo/interop.udo
@@ -0,0 +1,178 @@
+#ifndef UDO_INTEROP
+#define UDO_INTEROP ##
+/*
+ SONICS container interoperation
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+
+/*
+ Send string to channel at i-time
+
+ io_sendstring Schannel, Svalue
+
+ Schannel channel name
+ Svalue string to send
+*/
+#ifdef LOCALINTEROPINSTR
+opcode io_sendstring, 0, SS
+ Schannel, Svalue xin
+ schedule("$LOCALINTEROPINSTR", 0, 1, Schannel, Svalue)
+endop
+#else
+opcode io_sendstring, 0, SS
+ Schannel, Svalue xin
+ if (timeinstk() == -1) then
+ outvalue Schannel, Svalue
+ endif
+endop
+#end
+
+/*
+ Send value to channel at i-time
+
+ io_send Schannel, ivalue
+
+ Schannel channel name
+ ivalue value to send
+*/
+opcode io_send, 0, Si
+ Schannel, ivalue xin
+ outvalue Schannel, ivalue
+endop
+
+
+/*
+ Send value to channel at k-rate, if changed
+
+ io_send Schannel, kvalue
+
+ Schannel channel name
+ kvalue value to send
+*/
+opcode io_send, 0, Sk
+ Schannel, kvalue xin
+ if (changed:k(kvalue) == 1) then
+ outvalue Schannel, kvalue
+ endif
+endop
+
+
+instr io_callback
+ icbid = p4
+ if (qnan(p5) == 1) then
+ Sextra = sprintf(",%s", strget(p5))
+ else
+ Sextra = ""
+ endif
+ io_sendstring("callback", sprintf("{\"cbid\": %d%s}", icbid, Sextra))
+ turnoff
+endin
+
+
+instr io_compile_channel
+ icbid = p4
+ Schannel = p5
+ prints sprintf("including from channel %s \n\n", Schannel)
+ Sdata chnget Schannel
+ ires = compilestr(Sdata)
+ Sres = (ires == -1) ? "false" : "true"
+ io_sendstring("callback", sprintf("{\"cbid\": %d, \"success\": \"%s\"}", icbid, Sres))
+ turnoff
+endin
+
+/*
+ Stop instrument if playing; schedule instrument indefinitely if not playing
+ at k-rate with optional trigger (trigger resets after one iteration)
+
+ toggle_instrumentk instrnum [, ktrigger = 1]
+ toggle_instrumentk Sinstrument [, ktrigger = 1]
+
+ instrnum instrument number
+ Sinstrument instrument name to toggle
+ ktrigger perform action when 1
+*/
+opcode toggle_instrumentk, 0, iP
+ instrnum, ktrigger xin
+ if (ktrigger == 1) then
+ if (active:k(instrnum) > 0) then
+ turnoff2 instrnum, 0, 1
+ else
+ schedulek instrnum, 0, -1
+ endif
+ ktrigger = 0
+ endif
+endop
+
+; override for named instrument
+opcode toggle_instrumentk, 0, SP
+ Sinstrument, ktrigger xin
+ toggle_instrumentk(nstrnum(Sinstrument), ktrigger)
+endop
+
+
+/*
+ Stop instrument if playing; schedule instrument indefinitely if not playing, at init time
+
+ toggle_instrument instrnum
+ toggle_instrument Sinstrument
+
+ instrnum instrument number
+ Sinstrument instrument name to toggle
+*/
+opcode toggle_instrument, 0, i
+ instrnum xin
+ if (active:i(instrnum) > 0) then
+ turnoff2 instrnum, 0, 1
+ else
+ schedule instrnum, 0, -1
+ endif
+endop
+
+; override for named instrument
+opcode toggle_instrument, 0, S
+ Sinstrument xin
+ toggle_instrument(nstrnum(Sinstrument))
+endop
+
+
+
+/*
+ Stop instrument if playing; schedule instrument indefinitely if not playing, at init time
+ For host invocation.
+
+ p4 instrument number or name
+*/
+instr toggle_instrument
+ if (qnan(p4) == 1) then
+ Sinstrument = p4
+ toggle_instrument(Sinstrument)
+ else
+ instrnum = p4
+ toggle_instrument(instrnum)
+ endif
+ turnoff
+endin
+
+instr turnonoff_instrument
+ if (qnan(p4) == 1) then
+ Sinstrument = strget(p4)
+ instrnum = nstrnum(Sinstrument)
+ else
+ instrnum = p4
+ endif
+ istate = p5
+ iruntime = (p6 == 0) ? 36000 : p6
+ if (istate == 1) then
+ schedule instrnum, 0, iruntime
+ else
+ turnoff2 instrnum, 0, 1
+ endif
+endin
+
+
+#end
diff --git a/site/udo/interop.web.udo b/site/udo/interop.web.udo
new file mode 100755
index 0000000..caa802a
--- /dev/null
+++ b/site/udo/interop.web.udo
@@ -0,0 +1,172 @@
+#ifndef UDO_INTEROP
+#define UDO_INTEROP ##
+/*
+ SONICS container interoperation, web interface
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+
+/*
+ Send string to channel at i-time
+
+ io_sendstring Schannel, Svalue
+
+ Schannel channel name
+ Svalue string to send
+*/
+opcode io_sendstring, 0, SS
+ Schannel, Svalue xin
+ prints sprintf("%s %s\n", Schannel, Svalue)
+endop
+
+
+/*
+ Send value to channel at i-time
+
+ io_send Schannel, ivalue
+
+ Schannel channel name
+ ivalue value to send
+*/
+opcode io_send, 0, Si
+ Schannel, ivalue xin
+ prints sprintf("%s %f\n", Schannel, ivalue)
+endop
+
+
+/*
+ Send value to channel at k-rate, if changed
+
+ io_send Schannel, kvalue
+
+ Schannel channel name
+ kvalue value to send
+*/
+opcode io_send, 0, Sk
+ Schannel, kvalue xin
+ ktrigval init 0
+ if (changed:k(kvalue) == 1) then
+ printf "%s %f\n", ktrigval, Schannel, kvalue
+ ktrigval += 1
+ endif
+endop
+
+
+instr io_callback
+ icbid = p4
+ if (qnan(p5) == 1) then
+ Sextra = sprintf(",%s", strget(p5))
+ else
+ Sextra = ""
+ endif
+ io_sendstring("callback", sprintf("{\"cbid\": %d%s}", icbid, Sextra))
+ turnoff
+endin
+
+
+instr io_compile_channel
+ icbid = p4
+ Schannel = p5
+ prints sprintf("including from channel %s \n\n", Schannel)
+ Sdata chnget Schannel
+ ires = compilestr(Sdata)
+ Sres = (ires == -1) ? "false" : "true"
+ io_sendstring("callback", sprintf("{\"cbid\": %d, \"success\": \"%s\"}", icbid, Sres))
+ turnoff
+endin
+
+/*
+ Stop instrument if playing; schedule instrument indefinitely if not playing
+ at k-rate with optional trigger (trigger resets after one iteration)
+
+ toggle_instrumentk instrnum [, ktrigger = 1]
+ toggle_instrumentk Sinstrument [, ktrigger = 1]
+
+ instrnum instrument number
+ Sinstrument instrument name to toggle
+ ktrigger perform action when 1
+*/
+opcode toggle_instrumentk, 0, iP
+ instrnum, ktrigger xin
+ if (ktrigger == 1) then
+ if (active:k(instrnum) > 0) then
+ turnoff2 instrnum, 0, 1
+ else
+ schedulek instrnum, 0, -1
+ endif
+ ktrigger = 0
+ endif
+endop
+
+; override for named instrument
+opcode toggle_instrumentk, 0, SP
+ Sinstrument, ktrigger xin
+ toggle_instrumentk(nstrnum(Sinstrument), ktrigger)
+endop
+
+
+/*
+ Stop instrument if playing; schedule instrument indefinitely if not playing, at init time
+
+ toggle_instrument instrnum
+ toggle_instrument Sinstrument
+
+ instrnum instrument number
+ Sinstrument instrument name to toggle
+*/
+opcode toggle_instrument, 0, i
+ instrnum xin
+ if (active:i(instrnum) > 0) then
+ turnoff2 instrnum, 0, 1
+ else
+ schedule instrnum, 0, -1
+ endif
+endop
+
+; override for named instrument
+opcode toggle_instrument, 0, S
+ Sinstrument xin
+ toggle_instrument(nstrnum(Sinstrument))
+endop
+
+
+
+/*
+ Stop instrument if playing; schedule instrument indefinitely if not playing, at init time
+ For host invocation.
+
+ p4 instrument number or name
+*/
+instr toggle_instrument
+ if (qnan(p4) == 1) then
+ Sinstrument = p4
+ toggle_instrument(Sinstrument)
+ else
+ instrnum = p4
+ toggle_instrument(instrnum)
+ endif
+ turnoff
+endin
+
+instr turnonoff_instrument
+ if (qnan(p4) == 1) then
+ Sinstrument = strget(p4)
+ instrnum = nstrnum(Sinstrument)
+ else
+ instrnum = p4
+ endif
+ istate = p5
+ iruntime = (p6 == 0) ? 36000 : p6
+ if (istate == 1) then
+ schedule instrnum, 0, iruntime
+ else
+ turnoff2 instrnum, 0, 1
+ endif
+endin
+
+
+#end
diff --git a/site/udo/json.udo b/site/udo/json.udo
new file mode 100755
index 0000000..b0bdcd1
--- /dev/null
+++ b/site/udo/json.udo
@@ -0,0 +1,562 @@
+#ifndef UDO_JSON
+#define UDO_JSON ##
+/*
+ JSON formatting and parsing
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+#include "/string_tools.udo"
+#include "/array_tools.udo"
+
+
+/*
+ Create a new empty JSON string
+
+ Sjson json_init
+
+ Sout empty JSON object/string
+*/
+opcode json_init, S, 0
+ xout "{}"
+endop
+
+
+/*
+ Append raw string to JSON string
+
+ Sout json_append Sjson, StoAppend
+
+ Sout string with appendage
+ Sjson input string to append to
+ StoAppend string to append
+*/
+opcode json_append, S, SS
+ Sjson, StoAppend xin
+ ilen strlen Sjson
+
+ Sjson = (ilen < 2) ? "{" : strsub(Sjson, 0, ilen-1)
+ Sout = sprintf("%s%s%s}", Sjson, (strlen(Sjson) == 1) ? "" : ",", StoAppend)
+
+ xout Sout
+endop
+
+
+/*
+ Append JSON object string to JSON object string with key
+
+ Sout json_appendobject Sjson, Skey, Svalue
+
+ Sout string with appendage
+ Sjson input string to append to
+ Skey key for appending
+ Svalue value for appending
+*/
+opcode json_appendobject, S, SSS
+ Sjson, Skey, Svalue xin
+ Sout = json_append(Sjson, sprintf("\"%s\":%s", Skey, Svalue))
+ xout Sout
+endop
+
+
+/*
+ Append string to JSON object string with key
+
+ Sout json_appendstring Sjson, Skey, Svalue
+
+ Sout string with quoted appendage
+ Sjson input string to append to
+ Skey key for appending
+ Svalue value for appending
+*/
+opcode json_appendstring, S, SSS
+ Sjson, Skey, Svalue xin
+ Sout = json_append(Sjson, sprintf("\"%s\":\"%s\"", Skey, Svalue))
+ xout Sout
+endop
+
+
+/*
+ Append numeric value to JSON object string with key
+
+ Sout json_appendvalue Sjson, Skey, ivalue
+
+ Sout string with appendage
+ Sjson input string to append to
+ Skey key for appending
+ ivalue value for appending
+*/
+opcode json_appendvalue, S, SSi
+ Sjson, Skey, ivalue xin
+ Sformat = (frac(ivalue) == 0) ? "\"%s\":%d" : "\"%s\":%f"
+ Sout = json_append(Sjson, sprintf(Sformat, Skey, ivalue))
+ xout Sout
+endop
+
+
+/*
+ Append numeric array to JSON object string with key
+
+ Sout json_appendarray Sjson, Skey, iarray[]
+
+ Sout string with appendage
+ Sjson input string to append to
+ Skey key for appending
+ iarray[] array for appending
+*/
+opcode json_appendarray, S, SSi[]o
+ Sjson, Skey, iarray[] xin
+ Sformatted = ""
+ ilen = lenarray(iarray)
+ index = 0
+ while (index < ilen) do
+ ivalue = iarray[index]
+ Sformat = (frac(ivalue) == 0) ? "%d" : "%f"
+ Sformatted strcat Sformatted, sprintf(Sformat, ivalue)
+ if (index != ilen - 1) then
+ Sformatted strcat Sformatted, ","
+ endif
+ index += 1
+ od
+ Sout = json_appendobject(Sjson, Skey, sprintf("[%s]", Sformatted))
+ xout Sout
+endop
+
+
+
+/*
+ Append string array to JSON object string with key
+
+ Sout json_appendarray Sjson, Skey, Sarray[]
+
+ Sout string with appendage
+ Sjson input string to append to
+ Skey key for appending
+ Sarray[] array for appending
+ iomitempty leave out empty strings
+*/
+opcode json_appendarray, S, SSS[]o
+ Sjson, Skey, Sarray[], iomitempty xin
+ Sformatted = ""
+ ilen = lenarray(Sarray)
+ index = 0
+ while (index < ilen) do
+ isempty = (strcmp(Sarray[index], "") == 0) ? 1 : 0
+ if ((iomitempty == 0 && isempty == 1) || isempty == 0) then
+ Sformatted strcat Sformatted, sprintf("\"%s\",", Sarray[index])
+ endif
+ index += 1
+ od
+
+ Sout = json_appendobject(Sjson, Skey, sprintf("[%s]", strsub(Sformatted, 0, strlen(Sformatted) - 1)))
+ xout Sout
+endop
+
+
+/*
+ Append f-table to JSON object string with key
+
+ Sout json_appendtable Sjson, Skey, ifn
+
+ Sout string with appendage
+ Sjson input string to append to
+ Skey key for appending
+ ifn f-table number
+*/
+opcode json_appendtable, S, SSi
+ Sjson, Skey, ifn xin
+ Sformatted = ""
+ ilen = ftlen(ifn)
+ index = 0
+ while (index < ilen) do
+ ivalue = table:i(index, ifn)
+ Sformat = (frac(ivalue) == 0) ? "%d" : "%f"
+ Sformatted strcat Sformatted, sprintf(Sformat, ivalue)
+ if (index != ilen - 1) then
+ Sformatted strcat Sformatted, ","
+ endif
+ index += 1
+ od
+ Sout = json_appendobject(Sjson, Skey, sprintf("[%s]", Sformatted))
+ xout Sout
+endop
+
+
+
+
+/*
+ Obtain an object, array or value from a json object given a string key
+
+ itype, Stringvalue, inumericvalue json_parse Sjson, Skey
+
+ itype type of returned value: 0=string, 1=object, 2=array, 3=numeric (1 and 2 are returned as strings for further parsing)
+ Stringvalue the requested value as a string
+ inumericvalue the numeric value if itype is 3 , otherwise -1
+
+ Sjson the json to parse
+ Skey the key to lookup
+*/
+opcode json_parse, iSi, SS
+ Sjson, Skey xin
+ itype = -1
+ index = 0
+ idepth = 0
+ idepthrequest = -1
+ iobjectstart = -1
+ inval = 0
+ instring = 0
+ inrequested = 0
+ while (index < strlen(Sjson)) do
+ Schar = strsub(Sjson, index, index + 1)
+ if (strcmp(Schar, "[") == 0 && instring == 0) then
+ idepth += 1
+ if (inrequested == 1 && iobjectstart == -1) then
+ iobjectstart = index
+ endif
+
+ elseif (strcmp(Schar, "]") == 0 && instring == 0) then
+ idepth -= 1
+ if (idepthrequest == idepth) then
+ itype = 2
+ goto complete
+ endif
+
+ elseif (strcmp(Schar, "{") == 0 && instring == 0) then
+ idepth += 1
+ if (inrequested == 1 && iobjectstart == -1) then
+ iobjectstart = index
+ endif
+
+ elseif (strcmp(Schar, "}") == 0 && instring == 0) then
+ idepth -= 1
+ if (idepthrequest == idepth) then
+ itype = 1
+ goto complete
+ endif
+
+ elseif (strcmp(Schar, ":") == 0 && instring == 0) then
+ inval = 1
+ iobjectstart = index + 1
+ elseif (strcmp(Schar, ",") == 0 && instring == 0) then
+ if (inval == 1) then
+ inval = 0
+ if (inrequested == 1 && idepthrequest == idepth) then
+ index -= 1
+ itype = 3
+ goto complete
+ endif
+ endif
+ elseif (strcmp(Schar, "\"") == 0) then
+ instring = 1 - instring
+ if (instring == 1) then
+ istringstart = index + 1
+ else
+ String = strsub(Sjson, istringstart, index)
+ if (inrequested == 1 && idepthrequest == idepth) then
+ iobjectstart = istringstart
+ index -= 1
+ itype = 0
+ goto complete
+ elseif (strcmp(String, Skey) == 0) then
+ idepthrequest = idepth
+ inrequested = 1
+ endif
+ endif
+ endif
+ index += 1
+ od
+
+complete:
+
+ Stringvalue = strsub(Sjson, iobjectstart, index+1) ; +1 required?
+ if (itype == 3) then
+ ivalid, inumericvalue try_strtod strstrip(Stringvalue)
+ itype = (ivalid == 1) ? 3 : 0
+ endif
+
+
+ xout itype, Stringvalue, inumericvalue
+endop
+
+
+; itype: 0=S, 1=i
+opcode _json_getarray, S[]i[], Si
+ Sjson, itype xin
+ index = 0
+ instring = 0
+ iobjectstart = -1
+ iobjectcount = 0
+ iwriteindex = 0
+
+ while (index < strlen(Sjson)) do
+ Schar = strsub(Sjson, index, index + 1)
+ if (strcmp(Schar, ",") == 0 && instring == 0) then
+ iobjectcount += 1
+ elseif (strcmp(Schar, "\"") == 0) then
+ instring = 1 - instring
+ endif
+ index += 1
+ od
+
+ if (itype == 0) then
+ Soutput[] init iobjectcount + 1
+ ioutput[] init 1
+ else
+ Soutput[] init 1
+ ioutput[] init iobjectcount + 1
+ endif
+ index = 0
+ instring = 0
+
+ while (index < strlen(Sjson)) do
+ Schar = strsub(Sjson, index, index + 1)
+ if (strcmp(Schar, ",") == 0 && instring == 0) then
+ if (itype == 1) then
+ ioutput[iwriteindex] = strtod(strsub(Sjson, iobjectstart, index))
+ iobjectstart = index + 1
+ endif
+ iwriteindex += 1
+ elseif (strcmp(Schar, "[") == 0 && instring == 0) then
+ iobjectstart = index + 1
+ elseif (strcmp(Schar, "]") == 0 && instring == 0) then
+ if (itype == 1) then
+ ioutput[iwriteindex] = strtod(strsub(Sjson, iobjectstart, index))
+ endif
+ elseif (strcmp(Schar, "\"") == 0) then
+ if (instring == 0) then
+ iobjectstart = index + 1
+ elseif (itype == 0) then
+ Svalue = strsub(Sjson, iobjectstart, index)
+ Soutput[iwriteindex] = Svalue
+ endif
+ instring = 1 - instring
+ endif
+ index += 1
+ od
+ xout Soutput, ioutput
+endop
+
+opcode json_getstringarray, S[], S
+ Sjson xin
+ Soutput[], i_[] _json_getarray Sjson, 0
+ xout Soutput
+endop
+
+opcode json_getnumericarray, i[], S
+ Sjson xin
+ S_[], ioutput[] _json_getarray Sjson, 1
+ xout ioutput
+endop
+
+/*
+ Obtain an object, array or value from a json array given an index
+
+ itype, Stringvalue, inumericvalue json_parsearray Sjson, irequestindex
+
+ itype type of returned value: 0=string, 1=object, 2=array, 3=numeric (1 and 2 are returned as strings for further parsing)
+ Stringvalue the requested value as a string
+ inumericvalue the numeric value if itype is 3 , otherwise -1
+
+ Sjson the json to parse
+ irequestindex index in the array to obtain
+*/
+opcode json_parsearray, iSi, Si
+ Sjson, irequestindex xin
+ itype = -1
+ inmainarray = 0
+ index = 0
+ iobjectstart = 0
+ iscanindex = 0
+ instring = 0
+ idepth = 0
+ while (index < strlen(Sjson)) do
+ Schar = strsub(Sjson, index, index + 1)
+
+ if (strcmp(Schar, "[") == 0 && instring == 0) then
+ if (inmainarray == 0) then
+ inmainarray = 1
+ iobjectstart = index + 1
+ else
+ idepth += 1
+ endif
+
+ elseif (strcmp(Schar, "]") == 0 && instring == 0) then
+ if (inmainarray == 1 && idepth == 0) then
+ itype = 0
+ goto complete
+ else
+ idepth -= 1
+ itype = 2
+ endif
+
+ elseif (strcmp(Schar, "{") == 0 && inmainarray == 1 && instring == 0) then
+ idepth += 1
+
+ elseif (strcmp(Schar, "}") == 0 && inmainarray == 1 && instring == 0) then
+ idepth -= 1
+ itype = 1
+
+ elseif (strcmp(Schar, ",") == 0 && idepth == 0 && inmainarray == 1 && instring == 0) then
+ if (iscanindex == irequestindex) then
+ itype = 0
+ goto complete
+ endif
+ iobjectstart = index + 1
+ iscanindex += 1
+
+ elseif (strcmp(Schar, "\"") == 0 && idepth == 0 && inmainarray == 1 && iscanindex == irequestindex) then
+ instring = 1 - instring
+ if (instring == 1) then
+ iobjectstart = index + 1
+ else
+ itype = 0
+ goto complete
+ endif
+ endif
+
+ index += 1
+ od
+
+complete:
+
+ Stringvalue = strsub(Sjson, iobjectstart, index)
+ if (itype == 0) then
+ ivalid, inumericvalue try_strtod strstrip(Stringvalue)
+ itype = (ivalid == 1) ? 3 : 0
+ endif
+
+
+ xout itype, Stringvalue, inumericvalue
+endop
+
+
+
+/*
+ Get the length of a json array
+
+ ilength json_arraylength Sjson
+
+ ilength length determined
+ Sjson input array
+*/
+opcode json_arraylength, i, S
+ Sjson xin
+ idepth = -1
+ idepthmax = -1
+ ihasitems = 0
+ instring = 0
+ index = 0
+ itemnum = 0
+
+ while (index < strlen(Sjson)) do
+ Schar = strsub(Sjson, index, index + 1)
+ if (strcmp(Schar, "[") == 0 && instring == 0) then
+ idepth += 1
+ idepthmax += 1
+
+ elseif (strcmp(Schar, "]") == 0 && instring == 0) then
+ idepth -= 1
+ if (idepth < 0) then
+ goto complete
+ endif
+
+ elseif (strcmp(Schar, "{") == 0 && instring == 0 && idepth >= 0) then
+ idepth += 1
+ idepthmax += 1
+
+ elseif (strcmp(Schar, "}") == 0 && instring == 0 && idepth >= 0) then
+ idepth -= 1
+
+ elseif (strcmp(Schar, ",") == 0 && instring == 0 && idepth == 0) then
+ itemnum += 1
+
+ elseif (strcmp(Schar, "\"") == 0) then
+ if (idepth == 0) then
+ ihasitems = 1
+ endif
+ instring = 1 - instring
+ endif
+ index += 1
+ od
+
+complete:
+ if (idepthmax != 0 || ihasitems == 1) then
+ itemnum += 1
+ endif
+ xout itemnum
+
+endop
+
+
+
+
+/*
+ Get the keys from an object
+
+ Skeys[] json_getkeys Sjson
+
+ Skeys[] the keys found
+ Sjson input json
+*/
+opcode json_getkeys, S[], S
+ Sjson xin
+ Stemp[] init 999
+
+ itempindex init 0
+ idepth = -1
+ instring = 0
+ index = 0
+ inmainobject = 0
+ iskey = 1
+
+ while (index < strlen(Sjson)) do
+ Schar = strsub(Sjson, index, index + 1)
+ if (strcmp(Schar, "[") == 0 && instring == 0 && idepth >= 0) then
+ idepth += 1
+
+ elseif (strcmp(Schar, "]") == 0 && instring == 0 && idepth >= 0) then
+ idepth -= 1
+
+ elseif (strcmp(Schar, "{") == 0 && instring == 0) then
+ idepth += 1
+
+ elseif (strcmp(Schar, "}") == 0 && instring == 0) then
+ idepth -= 1
+ if (idepth < 0) then
+ goto complete
+ endif
+
+ elseif (strcmp(Schar, ":") == 0 && instring == 0 && idepth == 0) then
+ iskey = 0
+
+ elseif (strcmp(Schar, ",") == 0 && instring == 0 && idepth == 0) then
+ iskey = 1
+
+ elseif (strcmp(Schar, "\"") == 0 && idepth == 0) then
+ instring = 1 - instring
+ if (instring == 1) then
+ istringstart = index + 1
+ elseif (iskey == 1) then
+ Stemp[itempindex] = strsub(Sjson, istringstart, index)
+ itempindex += 1
+ endif
+ endif
+ index += 1
+ od
+
+complete:
+ Skeys[] init itempindex ;+ 1
+ index = 0
+ while (index < itempindex) do
+ Skeys[index] = Stemp[index]
+ index += 1
+ od
+
+ xout Skeys
+endop
+
+
+#end
diff --git a/site/udo/lagdetect.udo b/site/udo/lagdetect.udo
new file mode 100755
index 0000000..f4261b1
--- /dev/null
+++ b/site/udo/lagdetect.udo
@@ -0,0 +1,59 @@
+#ifndef UDO_LAGDETECT
+#define UDO_LAGDETECT ##
+/*
+ Processing lag detection
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#define LAG_DFLT_TTHRESH #0.05#
+
+
+/*
+ Detect when the CPU cannot keep up with proessing: when the realtime clock differs from Csound's clock by a specified threshold time.
+ The trigger klagging is output periodically every iautotimethreshold*2 seconds rather than continuously
+
+ klagging, ktimesincelastlag lagdetect [iautotimethreshold=LAG_DFLT_TTHRESH]
+
+ klagging trigger indicating lag has been detected in this k-cycle
+ ktimesincelastlag time in seconds sine the last lag detected
+ iautotimethreshold if realtime clock and Csound clock differ more than this number of seconds, lag is assumed
+*/
+opcode lagdetect, kk, j
+ iautotimethreshold xin
+ iautotimethreshold = (iautotimethreshold == -1) ? $LAG_DFLT_TTHRESH : iautotimethreshold
+ kstartrt init rtclock:i()
+ kclockrt rtclock
+ kstarts init times:i()
+ kclocks times
+ klag = abs:k((kclocks - kstarts) - (kclockrt - kstartrt))
+
+ klagging = 0
+ ; if time difference is above threshold and last adjustment is double threshold, reduce parameters and reset times
+ if (klag > iautotimethreshold && kclockrt - kstartrt > iautotimethreshold * 2) then
+ kstartrt = kclockrt
+ kstarts = kclocks
+ klagging = 1
+ endif
+ xout klagging, kclocks - kstarts
+endop
+
+
+/*
+ Detect when the CPU cannot keep up with proessing: when the realtime clock differs from Csound's clock by a specified threshold time
+ The trigger klagging is output periodically every iautotimethreshold*2 seconds rather than continuously
+
+ klagging lagdetect [iautotimethreshold=LAG_DFLT_TTHRESH]
+
+ klagging trigger indicating lag has been detected in this k-cycle
+ iautotimethreshold if realtime clock and Csound clock differ more than this number of seconds, lag is assumed
+*/
+opcode lagdetect, k, j
+ iautotimethreshold xin
+ klagging, ktimesincelag lagdetect iautotimethreshold
+ xout klagging
+endop
+
+#end
diff --git a/site/udo/legacy/sequencing_melodic.udo b/site/udo/legacy/sequencing_melodic.udo
new file mode 100755
index 0000000..7d3f546
--- /dev/null
+++ b/site/udo/legacy/sequencing_melodic.udo
@@ -0,0 +1,831 @@
+#ifndef UDO_MELSEQUENCING
+#define UDO_MELSEQUENCING ##
+
+/*
+ Melodic pattern sequencer base
+ Legacy, without microtonal option
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+#include "__config__.udo" ; using fftsize for tuning
+#include "chords.udo" ; chord data
+#include "sequencing.udo" ; sequencer base
+#include "interop.udo" ; for updating host with outvalue
+#include "wavetables.udo" ; for tuning
+#include "json.udo" ; used to update host
+
+; if these are set, then don't launch the manager automatically. sequencing_melodic_persistence will load accordingly
+#ifdef MEL_INITPATH
+ #define MEL_HASINIT ##
+#end
+#ifdef MEL_INITDB
+ #define MEL_HASINIT ##
+#end
+
+;-------------------------internal-globals--------------------------------------------------------------------------
+
+gimel_number init 12 ; number of melodic sections available
+
+gimel_state ftgen 0, 0, -4, -7, 0 ; state: current section, next section, current_step (gimel_number)
+gimel_chords ftgen 0, 0, -gimel_number, -7, 0 ; chord indexes from melodic.udo for each section
+gimel_notes ftgen 0, 0, -gimel_number, -7, 0 ; midi note numbers for each section
+gimel_lengths ftgen 0, 0, -gimel_number, -7, 0 ; lengths in beats for each section
+gimel_action1 ftgen 0, 0, -gimel_number, -7, 0 ; follow action 1 for each section
+gimel_action2 ftgen 0, 0, -gimel_number, -7, 0 ; follow action 2 for each section
+gimel_actionthreshold ftgen 0, 0, -gimel_number, -7, 0 ; follow action threshold - below 0.5 is action1, above is action2
+gimel_active ftgen 0, 0, -gimel_number, -7, 0 ; whether each section is active or to be ignored
+gimel_importance ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary section importance , 0 to 1
+gimel_mod1 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 1, 0 to 1
+gimel_mod2 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 2, 0 to 1
+gimel_mod3 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 3, 0 to 1
+gimel_mod4 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 4, 0 to 1
+gimel_centadd ftgen 0, 0, -gimel_number, -7, 0 ; microtonal midi note additions (0 = no change; 1 = add one semitone; 0.01 = add one cent)
+
+gimel_future ftgen 0, 0, -8, -7, 0 ; future sections: 8 in the future
+gimel_current_notes ftgen 0, 0, -13, -7, 0 ; current notes: index 0 is the length
+gimel_next_notes ftgen 0, 0, -13, -7, 0 ; next notes: index 0 is the length
+gimel_temp_random ftgen 0, 0, -gimel_number, -7, 0 ; temp storage for pattern randomisation
+
+gkmel_section_change init 0 ; section change trigger
+gkmel_section_change_due init 0 ; how many beats until next section change
+gkmel_futures_refresh_trig init 0 ; trigger to set if futures are to be recalculated
+
+; user modifiable variables
+gkmel_pause init 0 ; pause progression changes
+gkmel_advance_trig init 0 ; manual progression advance trigger
+
+
+
+; names and references for persistence and introspection: essentially the tables to be saved
+gSmel_names[] fillarray "chords", "notes", "lengths", "action1", "action2",\
+ "actionthreshold", "active", "importance", "mod1", "mod2", "mod3", "mod4"
+gimel_fns[] fillarray gimel_chords, gimel_notes, gimel_lengths, gimel_action1, gimel_action2,\
+ gimel_actionthreshold, gimel_active, gimel_importance, gimel_mod1, gimel_mod2, gimel_mod3, gimel_mod4
+
+
+
+;-----------------------------opcodes-------------------------------------------------------------------------------
+
+/*
+ Refresh the actions list: static actions and pattern references
+*/
+Smel_baseactions[] fillarray "Same", "Next", "Previous", "Random"
+gSmel_actions[] init lenarray(Smel_baseactions) + gimel_number
+index = 0
+while (index < lenarray(gSmel_actions)) do
+ if (index < 4) then
+ gSmel_actions[index] = Smel_baseactions[index]
+ else
+ gSmel_actions[index] = sprintf("Section %d", index - 3)
+ endif
+ index += 1
+od
+
+/*
+; actions: static actions and pattern references filled by _mel_refreshactions
+;gSmel_actions[] init 1
+
+opcode _mel_refreshactions, 0, 0
+
+endop
+_mel_refreshactions() ; initialise
+*/
+
+instr _mel_debug_printstate
+ index = 0
+ Sjson = json_init()
+ Sjson = json_appendtable(Sjson, "future", gimel_future)
+ while (index < lenarray(gimel_fns)) do
+ Sjson = json_appendtable(Sjson, gSmel_names[index], gimel_fns[index])
+ index += 1
+ od
+ prints Sjson
+ prints "\n\n"
+endin
+
+
+/*
+ Send JSON formatted information on current setup to API host
+*/
+instr mel_updatehost ; use p4 for channel?
+ Sjson = json_init()
+ Sjson = json_appendvalue(Sjson, "sections", gimel_number)
+ Sjson = json_appendarray(Sjson, "chordnames", gSchords)
+ Sjson = json_appendarray(Sjson, "actiontypes", gSmel_actions)
+
+ SjsonFns = json_init()
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ SjsonFns = json_appendvalue(SjsonFns, gSmel_names[index], gimel_fns[index])
+ index += 1
+ od
+ Sjson = json_appendobject(Sjson, "ftables", SjsonFns)
+
+ io_sendstring("mel_state", Sjson)
+ turnoff
+endin
+
+
+/*
+; new verison using plugin opcode
+instr mel_updatehost ; use p4 for channel?
+ iJson jsoninit
+ jsoninsertval iJson, "sections", gimel_number
+ jsoninsertval iJson, "chordnames", gSchords
+ jsoninsertval iJson, "actiontypes", gSmel_actions
+
+ iJsonFns jsoninit
+ jsoninsertval iJsonFns, gSmel_names, gimel_fns
+ jsoninsert iJson, "ftables", iJsonFns
+ io_sendstring("mel_state", jsondumps(iJson, 1))
+endin
+*/
+
+
+
+
+
+/*
+ Get modulation parameters for current section
+
+ imod1, imod2, imod3, imod4 mel_currentmod
+
+ imod1 modulation parameter 1
+ imod2 modulation parameter 2
+ imod3 modulation parameter 3
+ imod4 modulation parameter 4
+*/
+opcode mel_currentmod, iiii, 0
+ icur = table:i(0, gimel_state)
+ xout table:i(icur, gimel_mod1), table:i(icur, gimel_mod2), table:i(icur, gimel_mod3), table:i(icur, gimel_mod4)
+endop
+
+
+/*
+ Get modulation parameters for current section
+
+ kmod1, kmod2, kmod3, kmod4 mel_currentmod
+
+ kmod1 modulation parameter 1
+ kmod2 modulation parameter 2
+ kmod3 modulation parameter 3
+ kmod4 modulation parameter 4
+*/
+opcode mel_currentmod, kkkk, 0
+ kcur = table:k(0, gimel_state)
+ xout table:k(kcur, gimel_mod1), table:k(kcur, gimel_mod2), table:k(kcur, gimel_mod3), table:k(kcur, gimel_mod4)
+endop
+
+
+/*
+ Get a random midi note from the current section chord
+
+ inote mel_randomnote
+
+ inote random note from current chord
+*/
+opcode mel_randomnote, i, 0
+ ilen = table:i(0, gimel_current_notes)
+ index = round(random(1, ilen-1))
+ xout table:i(index, gimel_current_notes)
+endop
+
+
+/*
+ Get a random midi note from the current section chord
+
+ knote mel_randomnote
+
+ knote random note from current chord
+*/
+opcode mel_randomnote, k, 0
+ klen = table:k(0, gimel_current_notes)
+ kindex = round:k(random:k(1, klen-1))
+ xout table:k(kindex, gimel_current_notes)
+endop
+
+
+/*
+ Get the current section at k-rate
+
+ ksection _mel_currentsectionget
+
+ ksection current section
+*/
+opcode _mel_currentsectionget, k, 0
+ xout table:k(0, gimel_state)
+endop
+
+
+/*
+ Get the next section at k-rate
+
+ ksection _mel_nextsectionget
+
+ ksection next section
+*/
+opcode _mel_nextsectionget, k, 0
+ xout table:k(0, gimel_future)
+endop
+
+
+/*
+ Set the current section at k-rate
+
+ _mel_currentsectionset ksection
+
+ ksection current section to set
+*/
+opcode _mel_currentsectionset, 0, k
+ ksection xin
+ tablew ksection, 0, gimel_state
+endop
+
+
+/*
+ Get the current section at init time
+
+ isection _mel_currentsectionget
+
+ usection current section
+*/
+opcode _mel_currentsectionget, i, 0
+ xout table:i(0, gimel_state)
+endop
+
+
+/*
+ Get the length of the current section in seconds
+
+ iseconds mel_length
+
+ iseconds length in seconds
+*/
+opcode mel_length, i, 0
+ xout table:i(_mel_currentsectionget:i(), gimel_lengths) * i(gkseq_beattime)
+endop
+
+
+/*
+ Get the length of the current section in seconds
+
+ kseconds mel_length
+
+ kseconds length in seconds
+*/
+opcode mel_length, k, 0
+ xout table:k(_mel_currentsectionget:k(), gimel_lengths) * gkseq_beattime
+endop
+
+
+/*
+ Get the current MIDI note numbers as an array
+ inotes[] mel_currentnotes
+
+ inotes[] the note numbers
+*/
+opcode mel_currentnotes, i[], 0
+ ilen = table:i(0, gimel_current_notes)
+ iout[] init ilen
+ index = 0
+ while (index < ilen) do
+ iout[index] = table:i(index+1, gimel_current_notes)
+ index += 1
+ od
+ xout iout
+endop
+
+
+/*
+ Call Sinstrument when ktrig is fired, for each note (passed as p4) and the current section length accordingly
+ mel_eachnote Sinstrument, ktrig[, klength = mel_length:k()]
+
+ Sinstrument the instrument name to call
+ ktrig trigger to active call
+ klength duration of instrument to call, defaulting to mel_length:k()
+
+*/
+opcode mel_eachnote, 0, SkJ
+ Sinstrument, ktrig, klength xin
+ if (ktrig == 1) then
+ kdur = (klength == -1 ) ? mel_length:k() : klength
+ kindex = 0
+ while (kindex < table:k(0, gimel_current_notes)) do
+ schedulek Sinstrument, 0, kdur, table:k(kindex + 1, gimel_current_notes)
+ kindex += 1
+ od
+ endif
+endop
+
+/*
+ Get the most important entry from futures table
+
+ kbestindex, kimportance, kbeats mel_future_mostimportant
+
+ kbestindex index in gimel_future
+ kimportance the importance measure
+ kbeats number of beats until the event occurs
+*/
+opcode mel_future_mostimportant, kkk, 0
+ kindex = 0
+ kimportance = -9999
+ kbestindex = 0
+ kbeats = table:k(table:k(0, gimel_state), gimel_lengths) ; current duration base
+ while (kindex < ftlen(gimel_future)) do
+ ksection = table:k(kindex, gimel_future)
+ kimportancetemp = table:k(ksection, gimel_importance)
+ if (kimportancetemp > kimportance) then
+ kimportance = kimportancetemp
+ kbestindex = kindex
+ endif
+ kindex += 1
+ od
+
+ kindex = 0
+ while (kindex < kbestindex) do
+ kbeats += table:k(table:k(kindex, gimel_future), gimel_lengths)
+ kindex += 1
+ od
+
+ xout kbestindex, kimportance, kbeats ; * gkseq_beattime
+endop
+
+
+/*
+ Get the most important entry from futures table
+
+ ibestindex, iimportance, ibeats mel_future_mostimportant
+
+ ibestindex index in gimel_future
+ importance the importance measure
+ ibeats number of beats until the event occurs
+*/
+opcode mel_future_mostimportant, iii, 0
+ index = 0
+ importance = -9999
+ ibestindex = 0
+ ibeats = table:i(table:i(0, gimel_state), gimel_lengths) ; current duration base
+ while (index < ftlen(gimel_future)) do
+ isection = table:i(index, gimel_future)
+ importancetemp = table:i(isection, gimel_importance)
+ if (importancetemp > importance) then
+ importance = importancetemp
+ ibestindex = index
+ endif
+ index += 1
+ od
+
+ index = 0
+ while (index < ibestindex) do
+ ibeats += table:i(table:i(index, gimel_future), gimel_lengths)
+ index += 1
+ od
+ xout ibestindex, importance, ibeats ; * i(gkseq_beattime)
+endop
+
+
+
+/*
+ Calculate the next section from a given section
+
+ knext _mel_calculatenext kcurrent
+
+ knext the calculated next section index
+ kcurrent the section index to base the calculation upon
+*/
+opcode _mel_calculatenext, k, k
+ kthissection xin
+ knextsection = -1
+
+ if (random:k(0, 1) <= table:k(kthissection, gimel_actionthreshold)) then
+ knextaction = table:k(kthissection, gimel_action2)
+ else
+ knextaction = table:k(kthissection, gimel_action1)
+ endif
+
+
+ ; if current is not active, go to next ?
+ kcurrentactive = table:k(kthissection, gimel_active)
+ if (kcurrentactive == 0 && knextaction == 0) then
+ knextaction = 1
+ endif
+
+ ; same
+ if (knextaction == 0) then
+ knextsection = kthissection
+
+ ; next or previous
+ elseif (knextaction >= 1 && knextaction <= 3) then ; specified action
+ kcount = 0
+ kactive = 0
+ knextsection = kthissection
+ while (kactive == 0 && kcount < gimel_number) do ; loop until active section found or all sections checked
+
+ if (knextaction == 1) then ; next
+ if (knextsection + 1 > gimel_number - 1) then
+ knextsection = 0
+ else
+ knextsection += 1
+ endif
+
+ elseif (knextaction == 2) then ; previous
+ if (knextsection -1 < 0) then
+ knextsection = gimel_number - 1
+ else
+ knextsection -= 1
+ endif
+ endif
+
+ kactive = table:k(knextsection, gimel_active)
+ kcount += 1
+ od
+
+ ; random
+ elseif (knextaction == 3) then
+ kindex = 0
+ krandmax = 0
+ while (kindex < gimel_number) do
+ if (table:k(kindex, gimel_active) == 1) then
+ tablew kindex, krandmax, gimel_temp_random
+ krandmax += 1
+ endif
+ kindex += 1
+ od
+
+ knextsection = table:k(round(random(0, krandmax - 1)), gimel_temp_random)
+
+ ; specific section
+ elseif (knextaction >= 4) then ; specific active pattern
+ if (table:k(knextaction - 4, gimel_active) == 1) then
+ knextsection = knextaction - 4
+ else
+ knextsection = kthissection
+ endif
+ endif
+ xout knextsection
+endop
+
+
+/*
+ Set gimel_next_notes from the first entry in the futures table
+*/
+opcode _mel_setnextnotes, 0, 0
+ knext = table:k(0, gimel_future)
+ chordmidibyindextof gimel_next_notes, table:k(knext, gimel_chords), table:k(knext, gimel_notes)
+endop
+
+
+/*
+ Pop the next future entry from the futures table, move all future entries down one
+ and add a new calculated entry accordingly
+
+ kcurrent _mel_future_pop
+
+ kcurrent the current section to be used now
+*/
+opcode _mel_future_pop, k, 0
+ imax = ftlen(gimel_future)
+ kcurrent = table:k(0, gimel_future)
+
+
+ kindex = 0
+ while (kindex < imax - 1) do
+ tablew table:k(kindex + 1, gimel_future), kindex, gimel_future
+ kindex += 1
+ od
+
+ ; write new last entry
+ tablew _mel_calculatenext(table:k(kindex, gimel_future)), imax - 1, gimel_future
+
+ _mel_setnextnotes()
+
+ xout kcurrent
+endop
+
+
+/*
+ Recalculate the futures table (in the event of parameters being changed at runtime etc)
+*/
+opcode _mel_futures_refresh, 0, O
+ kindexStart xin ; usually 0, can be a start index (ie 1 leaves the first entry in place)
+ kindex = kindexStart
+ imax = ftlen(gimel_future)
+ ; TODO do first, etc
+ while (kindex < imax) do
+ if (kindex == 0) then
+ kcurrent = table:k(0, gimel_state) ; 0 ; get current, rather than 0...
+ else
+ kcurrent = table:k(kindex - 1, gimel_future)
+ endif
+
+ tablew _mel_calculatenext(kcurrent), kindex, gimel_future
+ kindex += 1
+ od
+
+ _mel_setnextnotes()
+endop
+
+
+/*
+ Set next section, for host control
+
+ p4 section number to set as next
+*/
+instr mel_setnextsection
+ isection = p4
+ if (table:i(isection, gimel_active) == 1) then
+ tablew isection, 0, gimel_future
+ gkmel_futures_refresh_trig = 2
+ endif
+ turnoff
+endin
+
+
+/*
+ Refresh the futures table, for host control
+*/
+instr mel_futures_refresh
+ gkmel_futures_refresh_trig = 1
+ turnoff
+endin
+
+
+/*
+ Randomise all section parameters
+*/
+opcode _mel_randomise, 0, 0
+ index = 0
+ iactives[] init 4 + gimel_lengths
+ iactivenum = 4
+ while (index < gimel_number) do
+ tablew round(random(0, lenarray(gSchords) - 1)), index, gimel_chords
+ tablew round(random(4, 8)), index, gimel_lengths
+ tablew round(random(48, 70)), index, gimel_notes
+ tablew random(0, 1), index, gimel_actionthreshold
+ tablew random(0, 1), index, gimel_importance
+ tablew random(0, 1), index, gimel_mod1
+ tablew random(0, 1), index, gimel_mod2
+ tablew random(0, 1), index, gimel_mod3
+ tablew random(0, 1), index, gimel_mod4
+
+
+ iactive = round(random(0, 1))
+ if (iactive == 1) then
+ iactives[iactivenum-1] = iactive
+ iactivenum += 1
+ endif
+ tablew iactive, index, gimel_active
+ index += 1
+ od
+
+ ; set next action to only active sections
+ index = 0
+ while (index < gimel_number) do
+ iaction1 = iactives[round(random(0, iactivenum))]
+ iaction2 = iactives[round(random(0, iactivenum))]
+ tablew iaction1, index, gimel_action1
+ tablew iaction2, index, gimel_action2
+ index += 1
+ od
+endop
+
+
+/*
+ Randomise all section parameters and update the host
+*/
+instr mel_randomise
+ _mel_randomise()
+ gkmel_futures_refresh_trig = 1
+ event_i "i", "mel_updatehost", 0, 1
+ turnoff
+endin
+
+
+/*
+ Pause progression, for host control
+*/
+instr mel_pause
+ gkmel_pause = p4
+ turnoff
+endin
+
+
+/*
+ Advance progression, for host control
+*/
+instr mel_advance
+ gkmel_advance_trig = 1
+ turnoff
+endin
+
+
+/*
+ Advance progression if paused, for host control
+*/
+instr mel_advanceifpaused
+ if (gkmel_pause == 1) then
+ gkmel_advance_trig = 1
+ endif
+ turnoff
+endin
+
+
+
+opcode mel_nextchangelength, k, 0
+ kcurrent = _mel_currentsectionget:k()
+ klength = table:k(kcurrent, gimel_lengths)
+
+ imaxfutures = ftlen(gimel_future)
+ kindex = 0
+ while (kindex < imaxfutures) do
+ ksection = table:k(kindex, gimel_future)
+ if (ksection != kcurrent) kgoto complete
+ klength += table:k(ksection, gimel_lengths)
+ kindex += 1
+ od
+complete:
+ xout klength
+endop
+
+/*
+ Initialise the sequencer sections; monitor for gkseq_beat triggers and change sections accordingly
+*/
+instr _mel_manager
+#ifndef MEL_HASINIT
+ _mel_randomise()
+#end
+
+ ksectionlength init 0
+ gkmel_futures_refresh_trig init 1
+
+ if (gkmel_futures_refresh_trig != 0) then
+ _mel_futures_refresh(gkmel_futures_refresh_trig - 1) ; if gkmel_futures_refresh_trig is 2, then omit first, otherwise recalculate all
+ gkmel_futures_refresh_trig = 0
+ ksectionlength = mel_nextchangelength:k()
+ endif
+
+ kstep init 0
+ gkmel_section_change = 0
+
+ kmanualadvance = 0
+ if (gkmel_advance_trig == 1) then
+ kmanualadvance = 1
+ gkmel_advance_trig = 0
+ endif
+
+ if ((gkseq_beat == 1 && gkmel_pause == 0) || kmanualadvance == 1) then
+ if (kstep == 0 || kmanualadvance == 1) then
+ kcurrent = _mel_currentsectionget:k()
+ tablecopy gimel_current_notes, gimel_next_notes
+ knew = _mel_future_pop:k()
+ _mel_currentsectionset(knew)
+
+ ; only send if actually changed
+ if (kcurrent != knew) then
+ io_send("mel_current", knew) ; send current (from next)
+ gkmel_section_change = 1
+ ksectionlength = mel_nextchangelength:k()
+ endif
+ endif
+
+ gkmel_section_change_due = ksectionlength - kstep
+
+ if (kstep < ksectionlength - 1) then ; current step < current length
+ kstep += 1
+ else
+ kstep = 0
+ endif
+
+ endif ; end each beat
+
+
+endin
+
+#ifndef MEL_HASINIT
+alwayson "_mel_manager"
+#end
+
+
+
+/*
+ Extend the current notes and convert to frequency, multiplying by powers of two to be used in mel_tune
+ ifreqs[] _mel_tune_noteprepare inotes[], imult
+
+ ifreqs[] resulting frequencies
+ inotes[] input midi note numbers
+ imult number of times to multiply note contents
+
+*/
+opcode _mel_tune_noteprepare, i[], i[]i
+ iarr[], imult xin
+ inew[] init lenarray(iarr) * imult
+ indexnew = 0
+ index = 0
+ while (index < lenarray(iarr)) do
+ ifreq = cpsmidinn(iarr[index])
+ index2 = 0
+ while (index2 < imult) do
+ if (index2 > 0) then
+ inew[indexnew] = ifreq * (2* (index2+1))
+ else
+ inew[indexnew] = ifreq
+ endif
+ index2 += 1
+ indexnew += 1
+ od
+
+ index += 1
+ od
+ xout inew
+endop
+
+
+/*
+ Create a chord with the specified frequencies
+ aout _mel_tune_chord ifreqs[] [, ifn, index]
+
+ aout resulting chord
+ ifreqs[] frequencies to play
+ ifn wavetable to play with, default = gifnSine
+ index internal index usage for recursion
+*/
+opcode _mel_tune_chord, a, i[]oo
+ ifreqs[], ifn, index xin
+ ifn = (ifn == 0) ? gifnSine : ifn
+ aout = oscil(0.1, ifreqs[index], ifn)
+ if (index < lenarray(ifreqs) - 1) then
+ aout += _mel_tune_chord(ifreqs, ifn, index + 1)
+ endif
+ xout aout
+endop
+
+
+/*
+ Stereo tuning to current melodic sequencer notes
+ aoutL, aoutR mel_tune ainL, ainR, ifn, imult [, ifftrate, ifftdiv]
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ ifn wavetable to use
+ imult multiples of harmonics to generate in tuning
+ ifftrate fft size, defaults to config default
+ ifftdiv fft window division factor (eg 4, 8, 16), defaults to config default
+*/
+opcode mel_tune, aa, aaiioo
+ aL, aR, ifn, imult, ifftrate, ifftdiv xin
+ ifftrate = (ifftrate == 0) ? giFFTsize : ifftrate
+ ifftdiv = (ifftdiv == 0) ? giFFTwinFactor : ifftdiv
+ ifreqs[] _mel_tune_noteprepare mel_currentnotes(), imult
+ fmods pvsanal _mel_tune_chord(ifreqs, ifn), ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL1 pvsanal aL, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fR1 pvsanal aR, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL2 pvsmorph fL1, fmods, 0, 1
+ fR2 pvsmorph fR1, fmods, 0, 1
+ aL1 pvsynth fL2
+ aR1 pvsynth fR2
+ idel = (ifftrate+2)/sr
+ aL1 balance aL1, delay(aL, idel)
+ aR1 balance aR1, delay(aR, idel)
+ xout aL1, aR1
+endop
+
+
+/*
+ Experimental tonal balance of two signals
+
+ aoutput balancetonal ain, aincomparator
+
+ aoutput balanced signal
+ ain signal to apply changes to
+ aincomparator signal to 'extract' frequency contour from
+*/
+opcode balancetonal, a, aa
+ ain, ainc xin
+ aouts[] init 16
+
+ aouts[0] balance butterbp(ain, 100, 200), butterbp(ainc, 100, 200) ; 0 - 200
+ aouts[1] balance butterbp(ain, 400, 400), butterbp(ainc, 400, 400) ; 200 - 600
+ aouts[2] balance butterbp(ain, 800, 400), butterbp(ainc, 800, 400) ; 600 - 1000
+ aouts[3] balance butterbp(ain, 1200, 400), butterbp(ainc, 1200, 400) ; 1000 - 1400
+ aouts[4] balance butterbp(ain, 1700, 600), butterbp(ainc, 1700, 600) ; 1400 - 2000
+ aouts[5] balance butterbp(ain, 2400, 800), butterbp(ainc, 2400, 800) ; 2000 - 2800
+ aouts[6] balance butterbp(ain, 3200, 800), butterbp(ainc, 3200, 800) ; 2800 - 3600
+ aouts[7] balance butterbp(ain, 4200, 1200), butterbp(ainc, 4200, 1200) ; 3600 - 4800
+ aouts[8] balance butterbp(ain, 5400, 1200), butterbp(ainc, 5400, 1200) ; 4800 - 6000
+ aouts[9] balance butterbp(ain, 7000, 2000), butterbp(ainc, 7000, 2000) ; 6000 - 8000
+ aouts[10] balance butterbp(ain, 9000, 2000), butterbp(ainc, 9000, 2000) ; 8000 - 10000
+ aouts[11] balance butterbp(ain, 11000, 2000), butterbp(ainc, 11000, 2000) ; 10000 - 12000
+ aouts[12] balance butterbp(ain, 14000, 4000), butterbp(ainc, 14000, 4000) ; 12000 - 16000
+ aouts[13] balance butterbp(ain, 18000, 4000), butterbp(ainc, 18000, 4000) ; 16000 - 20000
+ aouts[14] balance butterhp(ain, 20000), butterhp(ainc, 20000)
+
+ aout sumarray aouts
+ xout aout
+endop
+
+
+#end
diff --git a/site/udo/legacy/sequencing_melodic_persistence.udo b/site/udo/legacy/sequencing_melodic_persistence.udo
new file mode 100755
index 0000000..dd309af
--- /dev/null
+++ b/site/udo/legacy/sequencing_melodic_persistence.udo
@@ -0,0 +1,246 @@
+#ifndef UDO_MELSEQUENCINGPERSIST_LEGACY
+#define UDO_MELSEQUENCINGPERSIST_LEGACY ##
+/*
+ Melodic sequencer persistence: saving/loading from files and database
+ Legacy: superceded by JSON
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "pgdb.udo"
+#include "sequencing_melodic.udo"
+#include "array_tools.udo"
+#include "interop.udo"
+
+
+
+
+/*
+ Save state to file
+
+ p4 path to save to
+*/
+instr mel_savestate_fs
+ Spath = p4
+ ftsave Spath, 1,\
+ gimel_chords, gimel_notes,
+ gimel_lengths, gimel_action1,\
+ gimel_action2, gimel_actionthreshold,\
+ gimel_active, gimel_importance,\
+ gimel_mod1, gimel_mod2,\
+ gimel_mod3, gimel_mod4,\
+ gimel_state
+ turnoff
+endin
+
+
+/*
+ Load state from file
+
+ p4 path to load from
+*/
+instr mel_loadstate_fs
+ Spath = p4
+ isize = -1
+ iline = 0
+
+/* ; COMMENTED AS readfi IS NOT AVAILABLE ON LIVE COMP?!?!?! - testing if size matches etc
+
+ ; get size from first table
+ while (isize == -1 && iline != -1) do
+ Sline, iline readfi Spath
+ if (strcmp(strsub(Sline, 0, 4), "flen") == 0) then
+ isize = strtod(strsub(Sline, 6, strlen(Sline)-1))
+ isizefound = 1
+ endif
+ od
+
+ ; size not found in file
+ if (isize == -1) then
+ isize = ftlen(gimel_chords)
+
+ ; resize required
+ elseif (isize != ftlen(gimel_chords)) then
+ gimel_number = isize
+ _mel_refreshactions() ; update actions list to cater for number of patterns
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ ifn = gimel_fns[index]
+ ftfree ifn, 0
+ itemp ftgen 0, 0, -isize, -7, 0
+ index += 1
+ od
+ endif
+*/
+
+ ftload Spath, 1,\
+ gimel_chords, gimel_notes,
+ gimel_lengths, gimel_action1,\
+ gimel_action2, gimel_actionthreshold,\
+ gimel_active, gimel_importance,\
+ gimel_mod1, gimel_mod2,\
+ gimel_mod3, gimel_mod4,\
+ gimel_state
+
+ gkmel_futures_refresh_trig = 1
+ turnoff
+endin
+
+
+
+/*
+ Save state to database
+ mel_savestate_db Sname
+
+ Sname identifier to be used
+*/
+opcode mel_savestate_db, 0, S
+ Sname xin
+ pgdb_table_save Sname, "melseq", gimel_state
+
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ pgdb_table_save strcat(Sname, strcat("||", gSmel_names[index])), "melseq", gimel_fns[index]
+ index += 1
+ od
+endop
+
+
+
+; broken: underlying pgdb_table_savek not working
+opcode mel_savestate_dbk, k, S
+ Sname xin
+ ilength = lenarray(gimel_fns)
+ ktrigger init 0
+ kcomplete init 0
+ kindex init -1
+
+ if (kindex == -1) then
+ kdone pgdb_table_savek Sname, "melseq", gimel_state, -1
+ else
+ kdone pgdb_table_savek strcatk(Sname, strcatk("||", gSmel_names[kindex])), "melseq", gimel_fns[kindex], ktrigger
+ ktrigger = 0
+ endif
+
+ if (kdone == 1) then
+ if (kindex + 1 < ilength) then
+ kindex += 1
+ ktrigger = 1
+ else
+ kcomplete = 1
+ endif
+ endif
+
+
+ xout kcomplete
+endop
+
+
+; ftresize ?????
+
+/*
+ Load state from database
+ mel_loadstate_db Sname
+
+ Sname identifier to be used
+*/
+opcode mel_loadstate_db, 0, S
+ Sname xin
+ inull pgdb_table_get Sname, "melseq", gimel_state
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ StestName = sprintf("%s||%s", Sname, gSmel_names[index])
+ inull pgdb_table_get StestName, "melseq", gimel_fns[index]
+ index += 1
+ od
+ gkmel_futures_refresh_trig = 1
+endop
+
+
+
+/*
+ Get an array of the known mel states from database
+ Sdata[] mel_liststates_db
+
+ Sdata[] the state names
+*/
+opcode mel_liststates_db, S[], 0
+ Sresult[][] dbarray gidb, "SELECT distinct (string_to_array(name, '||'))[1] FROM savearray WHERE unit = 'melseq'"
+ ilen = lenarray(Sresult)
+ Sdata[] init ilen
+ index = 0
+ while (index < ilen) do
+ Sdata[index] = Sresult[index][0]
+ index += 1
+ od
+ xout Sdata
+endop
+
+
+; broken
+instr mel_savestate_dbk
+ Sname = p4
+ kdone mel_savestate_dbk Sname
+ if (kdone == 1) then
+ io_sendstring("mel_state_saved", Sname)
+ turnoff
+ endif
+endin
+
+instr mel_savestate_db
+ Sname = p4
+ mel_savestate_db Sname
+ event_i "i", "mel_hostsendstates_db", 0, 1 ; resend list of items
+ if (timeinstk() >= 3) then
+ outvalue "mel_state_saved", Sname
+ turnoff
+ endif
+ ;io_sendstring("mel_state_saved", Sname)
+ ;turnoff
+endin
+
+
+instr mel_loadstate_db
+ Sname = p4
+ mel_loadstate_db Sname
+ event_i "i", "mel_updatehost", 0, 1
+ if (timeinstk() >= 3) then
+ outvalue "mel_state_loaded", Sname
+ turnoff
+ endif
+ ;io_sendstring("mel_state_loaded", Sname)
+ ;turnoff
+endin
+
+instr mel_hostsendstates_db
+ Sjson = sprintf("{\"states\": %s}", arr_serialise(mel_liststates_db()))
+ ;io_sendstring("mel_dbstates", Sjson)
+ ;turnoff
+ if (timeinstk() >= 3) then
+ outvalue "mel_dbstates", Sjson
+ turnoff
+ endif
+endin
+
+
+; if MEL_INITPATH or MEL_INITDB is set, load the specified progression data accordingly
+#ifdef MEL_HASINIT
+instr _mel_persistence_init
+#ifdef MEL_INITPATH
+ subinstrinit "mel_loadstate_fs", "$MEL_INITPATH"
+#end
+#ifdef MEL_INITDB
+ ;mel_loadstate_db "$MEL_INITDB"
+ subinstrinit "mel_loadstate_db", "$MEL_INITDB"
+#end
+ alwayson "_mel_manager"
+ turnoff
+endin
+schedule "_mel_persistence_init", 0, 60
+
+; end MEL_HASINIT
+#end
+
+#end
diff --git a/site/udo/mfcc_match.udo b/site/udo/mfcc_match.udo
new file mode 100755
index 0000000..a87df40
--- /dev/null
+++ b/site/udo/mfcc_match.udo
@@ -0,0 +1,178 @@
+#ifndef UDO_MFCCMATCH
+#define UDO_MFCCMATCH ##
+
+#include "/wavetables.udo"
+
+; FFT size for MFCC analysis (lower = more CPU)
+gimfm_default_fftsize = 1024
+
+; Number of MFCC bands to use (^2, ideally 8, 16, 32)
+gimfm_default_mfccbands = 16
+
+; default upper and lower frequencies of range to analuse
+gimfm_default_freqrange[] fillarray 140, 19000
+
+
+opcode _mfm_checkksmps, 0, 0
+ if (ksmps & (ksmps -1) != 0) then
+ prints "\n\nERROR: MFCC matching requires ksmps to be a power of two\n\n"
+ exitnow
+ endif
+endop
+
+
+/*
+ * Calculate the Euclidean distance between a table point and an array
+ * in:
+ * icorpusdata Table containing MFCC corpus data
+ * ibands Number of bands used for MFCC analysis in corpus table
+ * kcorpusindex Start index of corpus data to compare
+ * kmatch[] Array of MFCC values to compare against
+ * out:
+ * ktotal Euclidean distance
+ */
+opcode _mfm_euclideandistance, k, ikk[]i
+ icorpusdata, kcorpusindex, kmatch[], imfccbands xin
+ ktotal = 0
+ kdx = 0
+ while (kdx < imfccbands) do
+ kcorpusval tab kcorpusindex+kdx, icorpusdata
+ ktotal += pow((kcorpusval - kmatch[kdx]), 2)
+ kdx += 1
+ od
+ xout sqrt(ktotal)
+endop
+
+
+/*
+ * Get MFCC data from an audio signal
+ * in:
+ * asig The audio signal for analysis
+ * ifreqmin=100 Optional minimum frequency for analysis
+ * ifreqmax=19000 Optional maximum frequency for analysis
+ * out:
+ * kmfcc[] Array of MFCC data with length ibands
+ * ktrig Fired when new data has been output
+ */
+opcode _mfm_getmfccs, k[]k, aiiii
+ asig, ifreqmin, ifreqmax, ifftsize, imfccbands xin
+ _mfm_checkksmps()
+ kcnt init 0
+ ibins init ifftsize/2
+ kIn[] init ifftsize
+ kIn shiftin asig
+ kcnt += ksmps
+ ktrig = 0
+ if (kcnt == ifftsize) then
+ kFFT[] = rfft(kIn)
+ kPows[] = pows(kFFT)
+ kMFB[] = log(mfb(kPows, ifreqmin, ifreqmax, imfccbands), 0)
+ kmfcc[] = dct(kMFB)
+ kcnt = 0
+ ktrig = 1
+ endif
+ xout kmfcc, ktrig
+endop
+
+/*
+ * Get nearest matching table index of an audio signal based on MFCC analysis and distance comparison
+ * in:
+ * asig The driving audio signal
+ * ifftsize FFT size for MFCC analysis
+ * ibands Number of MFCC bands to use
+ * icorpusdata Table containing MFCC corpus data
+ * out:
+ * kindex Start index of corpus audio table that best matches
+ * ktrig Fired when new match has been output
+ */
+opcode _mfm_nearest, kk, aijjjj
+ asig, icorpusdata, ifreqmin, ifreqmax, ifftsize, imfccbands xin
+ imaxitems = ftlen(icorpusdata)
+ kmfcc[], ktrig _mfm_getmfccs asig, ifreqmin, ifreqmax, ifftsize, imfccbands
+ kouttrig = 0
+ if (ktrig == 1) then
+ kcorpusindex = 0
+ kbest = 9999999
+ kbestindex = -1
+ while (kcorpusindex < imaxitems - imfccbands) do
+ kdistance _mfm_euclideandistance icorpusdata, kcorpusindex, kmfcc, imfccbands
+ if (kdistance < kbest) then
+ kbest = kdistance
+ kbestindex = kcorpusindex
+ endif
+ kcorpusindex += imfccbands
+ od
+
+ endif
+ xout (kbestindex/imfccbands)*ifftsize, ktrig
+endop
+
+
+opcode mfm_analysecorpus, ki, kijjjjjj
+ ktimek, ifn, ifreqmin, ifreqmax, ifftsize, imfccbands, ifnmaxindex, icorpustmpfn xin
+
+ ifreqmin = ((ifreqmin == -1) ? gimfm_default_freqrange[0]: ifreqmin)
+ ifreqmax = ((ifreqmax == -1) ? gimfm_default_freqrange[1]: ifreqmax)
+ ifftsize = ((ifftsize == -1) ? gimfm_default_fftsize : ifftsize)
+ imfccbands = ((imfccbands == -1) ? gimfm_default_mfccbands : imfccbands)
+ ifnmaxindex = ((ifnmaxindex == -1) ? ftlen(ifn) : ifnmaxindex)
+
+ ilen = ifnmaxindex / ftsr(ifn)
+ imaxitems = imfccbands * (ifnmaxindex / ifftsize)
+ if (icorpustmpfn == 1) then
+ icorpusdata ftgentmp 0, 0, -imaxitems, 2, 0
+ else
+ icorpusdata ftgen 0, 0, -imaxitems, 2, 0
+ endif
+ ;ktimek timeinstk
+
+ kdone init 0
+ if (ktimek == 1) then
+ kcycles = ilen*kr
+ kcount init 0
+loop:
+ ;asig loscil 1, 1, ifn, 1
+ apos lphasor 1
+ asig table3 apos, ifn
+ kdx init 0
+ kmfcc[], ktrig _mfm_getmfccs asig, ifreqmin, ifreqmax, ifftsize, imfccbands
+ if (ktrig == 1) then
+ kfb = 0
+ while (kfb < imfccbands) do
+ tabw kmfcc[kfb], kdx, icorpusdata
+ kfb += 1
+ kdx += 1
+ od
+ endif
+ loop_lt kcount, 1, kcycles, loop
+ else
+ kdone = 1
+ endif
+ xout kdone, icorpusdata
+endop
+
+
+opcode mfm_matchplay, a, aiikjjjjj
+ ain, ifn, ifndata, kstretch, ifreqmin, ifreqmax, ifftsize, imfccbands, ifnmaxindex xin
+ ifreqmin = ((ifreqmin == -1) ? gimfm_default_freqrange[0]: ifreqmin)
+ ifreqmax = ((ifreqmax == -1) ? gimfm_default_freqrange[1]: ifreqmax)
+ ifftsize = ((ifftsize == -1) ? gimfm_default_fftsize : ifftsize)
+ imfccbands = ((imfccbands == -1) ? gimfm_default_mfccbands : imfccbands)
+ ilen = ((ifnmaxindex == -1) ? ftlen(ifn) : ifnmaxindex)
+ icsr = ftsr(ifn)
+
+ kdx, ktrig _mfm_nearest ain, ifndata, ifreqmin, ifreqmax, ifftsize, imfccbands
+
+ icduration = ilen / icsr
+ icps = 1/(ilen/icsr)
+ aphs, a_ syncphasor icps*(1-kstretch), a(ktrig)
+ apos = (((aphs * ilen) + kdx) / ilen) * icduration
+
+ amatched sndwarp 0.7, apos, 1, ifn, 0, ifftsize/2, 64, 4, gifnHalfSine, 1
+ ;amatched balance amatched, delay(ain, (1/sr)*ifftsize)
+ xout amatched
+
+endop
+
+
+#end
diff --git a/site/udo/midi.udo b/site/udo/midi.udo
new file mode 100755
index 0000000..1e608b3
--- /dev/null
+++ b/site/udo/midi.udo
@@ -0,0 +1,65 @@
+#ifndef UDO_MIDI
+#define UDO_MIDI ##
+/*
+ MIDI control handler
+ Currently only handling one channel
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+gimidi_values = ftgen(0, 0, -128, -2, 0) ; scale 0 to 1 values with index correlating to MIDI CC number
+
+
+/*
+ Handle incoming MIDI messages and write to channel with scaling if defined in gSmidimap_channels and gkmidimap_values
+*/
+instr midi_handler
+ kstatus, kchan, kdata1, kdata2 midiin
+ if (kstatus == 176) then ;144 is note on 128 is note off ; 208 is aftertouch
+ tabw scale(kdata2, 1, 0, 127, 0), kdata1, gimidi_values
+#ifndef MIDI_NOTE_HANDLER_INSTRUMENT
+ endif
+#else
+ elseif (kstatus == 144) then
+ schedulek("_midi_note_handler", 0, 1, 1, kchan, kdata1, kdata2)
+ elseif (kstatus == 128) then
+ schedulek("_midi_note_handler", 0, 1, 0, kchan, kdata1, kdata2)
+ endif
+#end
+endin
+alwayson("midi_handler")
+
+
+instr _midi_note_handler
+ ionoff = p4
+ ichannel = p5
+ inote = p6
+ ivelocity = p7
+ instrnum = nstrnum("$MIDI_NOTE_HANDLER_INSTRUMENT") + (ichannel / 100) + (inote / 100000)
+ if (ionoff == 0) then
+ turnoff2 instrnum, 4, 1
+ else
+ schedule(instrnum, 0, -1, ichannel, inote, ivelocity)
+ endif
+ turnoff
+endin
+
+opcode midi_cc, k, i
+ icc xin
+ kval tab icc, gimidi_values
+ xout kval
+endop
+
+opcode midi_zeroall, 0, 0
+ index = 0
+ while (index < 128) do
+ outic 1, index, 0, 0, 1
+ tabw_i 0, index, gimidi_values
+ index += 1
+ od
+endop
+
+#end
diff --git a/site/udo/midimap.udo b/site/udo/midimap.udo
new file mode 100755
index 0000000..63be6cf
--- /dev/null
+++ b/site/udo/midimap.udo
@@ -0,0 +1,247 @@
+#ifndef UDO_MIDI
+#define UDO_MIDI ##
+/*
+ MIDI control to named channel mapper
+ Currently only handling one channel
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+#include "array_tools.udo"
+#include "table_tools.udo"
+
+gSmidimap_channels[] init 128 ; channel names with index correlating to MIDI CC number
+gimidimap_values = ftgen(0, 0, -384, -2, 0) ; min and max values for scaling, and default/saved value, with index correlating to MIDI CC number
+
+
+/*
+ Handle incoming MIDI messages and write to channel with scaling if defined in gSmidimap_channels and gkmidimap_values
+*/
+instr midimap_handler
+ kstatus, kchan, kdata1, kdata2 midiin
+ if (kstatus == 176) then
+ if (strcmpk(gSmidimap_channels[kdata1], "") != 0) then
+ kvalue = scale(kdata2, table:k(kdata1+128, gimidimap_values), table:k(kdata1, gimidimap_values), 127, 0)
+ chnset kvalue, gSmidimap_channels[kdata1]
+ ;outvalue strcmpk(gSmidimap_channels[kdata1], "_retval"), kvalue
+ endif
+#ifndef MIDI_NOTE_HANDLER_INSTRUMENT
+ endif
+#else
+ elseif (kstatus == 144) then
+ schedulek("_midi_note_handler", 0, 1, 1, kchan, kdata1, kdata2)
+ elseif (kstatus == 128) then
+ schedulek("_midi_note_handler", 0, 1, 0, kchan, kdata1, kdata2)
+ endif
+#end
+endin
+alwayson("midimap_handler")
+
+
+instr _midi_note_handler
+ ionoff = p4
+ ichannel = p5
+ inote = p6
+ ivelocity = p7
+ instrnum = nstrnum("$MIDI_NOTE_HANDLER_INSTRUMENT") + (ichannel / 100) + (inote / 100000)
+ if (ionoff == 0) then
+ turnoff2 instrnum, 4, 1
+ else
+ schedule(instrnum, 0, -1, ichannel, inote, ivelocity)
+ endif
+ turnoff
+endin
+
+
+/*
+ Register a MIDI CC to channel mapping, replacing any current mapping
+
+ p4 CC number
+ p5 channel name
+ p6 minimum value to scale to
+ p7 maximum value to scale to
+ p8 default/save value
+*/
+instr midimap_register
+ icc = p4
+ Schannel = p5
+ imin = p6
+ imax = p7
+ ivalue = p8
+ gSmidimap_channels[icc] = Schannel
+ tablew imin, icc, gimidimap_values
+ tablew imax, icc+128, gimidimap_values
+ tablew ivalue, icc+256, gimidimap_values
+ turnoff
+endin
+
+
+/*
+ Register a MIDI CC to a channel mapping, replacing any current mapping, based on user input to specify the CC number.
+ Threshold (p7) can be supplied to account for noisy devices that may emit unwanted CCs
+
+ p4 channel name
+ p5 minimum value to scale to
+ p6 maximum value to scale to
+ p7 default/save value
+ p8 how many points the CC must be moved to count as registered (defaults to 1 if not supplied)
+ p9 optional instrument name or number to call when learning completed; called with p3 = -1
+*/
+instr midimap_learn
+ Schannel = p4
+ imin = p5
+ imax = p6
+ ivalue = p7
+ ithreshold = p8
+ if (qnan(p9) == 1) then ; if onComplete provided
+ ionComplete = nstrnum(strget(p9))
+ elseif (p9 > 0) then
+ ionComplete = p9
+ else
+ ionComplete = -1
+ endif
+
+ if (ithreshold < 1) then
+ ithreshold = 1
+ endif
+ kvals[] init 128
+ prints sprintf("Learn mode: move a controller to assign to channel '%s'\n", Schannel)
+
+ kstatus, kchan, kcc, kvalue midiin
+
+ if (kstatus == 176) then
+ if (ithreshold > 1 && kvals[kcc] == 0) then
+ kvals[kcc] = kvalue
+ elseif (ithreshold == 1 || abs:k(kvals[kcc] - kvalue) >= ithreshold) then
+ gSmidimap_channels[kcc] = Schannel
+ tablew imin, kcc, gimidimap_values
+ tablew imax, kcc+128, gimidimap_values
+ tablew ivalue, kcc+256, gimidimap_values
+ printf "Controller %d assigned to channel '%s'\n", 1, kcc, Schannel
+ if (ionComplete != -1) then
+ schedulek(ionComplete, 0, -1)
+ endif
+ turnoff
+ endif
+ endif
+endin
+
+
+/*
+ Abort learning mode; turnoff midimap_learn
+*/
+instr midimap_learn_abort
+ turnoff2 "midimap_learn", 0, 0
+ turnoff
+endin
+
+
+/*
+ Set CCs and channels to default/saved value
+*/
+opcode _midimap_setvalues, 0, 0
+ icc = 0
+ while (icc < lenarray(gSmidimap_channels)) do
+ if (strcmp(gSmidimap_channels[icc], "") != 0) then
+ imin table icc, gimidimap_values
+ imax table icc+128, gimidimap_values
+ ivalue table icc+256, gimidimap_values
+ chnset ivalue, gSmidimap_channels[icc]
+ outic 1, icc, ivalue, imin, imax
+ endif
+ icc += 1
+ od
+endop
+
+
+/*
+ Gather CC values and set default/saved value
+*/
+opcode _midimap_getvalues, 0, 0
+ icc = 0
+ while (icc < lenarray(gSmidimap_channels)) do
+ if (strcmp(gSmidimap_channels[icc], "") != 0) then
+ imin table icc, gimidimap_values
+ imax table icc+128, gimidimap_values
+ ivalue ctrl7 1, icc, imin, imax
+ tablew ivalue, icc+256, gimidimap_values
+ endif
+ icc += 1
+ od
+endop
+
+
+/*
+ Save map state to file
+
+ p4 file path
+ p5 1 = save current CC values; 0 = do not save values
+*/
+instr midimap_savestate_fs
+ Sfile = p4
+ isavevalues = p5
+ if (isavevalues == 1) then
+ _midimap_getvalues()
+ endif
+ Serial = sprintf("%s\n%s", arr_serialise(gSmidimap_channels), tab_serialise(gimidimap_values))
+ fprints Sfile, Serial
+ turnoff
+endin
+
+
+/*
+ Load map state from file
+
+ p4 file path
+*/
+instr midimap_loadstate_fs
+ Sfile = p4
+read:
+ Sline, ilinenum readfi Sfile
+ if (ilinenum == 1) then
+ gSmidimap_channels arr_unserialise Sline
+ igoto read
+ elseif (ilinenum == 2) then
+ tab_unserialise Sline, gimidimap_values
+ endif
+ _midimap_setvalues()
+ turnoff
+endin
+
+
+
+#ifdef USING_DB
+/*
+ Save map state to database
+
+ p4 state name
+ p5 1 = save current CC values; 0 = do not save values
+*/
+instr midimap_savestate_db
+ Sname = p4
+ isavevalues = p5
+ if (isavevalues == 1) then
+ _midimap_getvalues()
+ endif
+ pgdb_array_save strcat(Sname, ".channels"), "midimap", gSmidimap_channels
+ pgdb_table_save strcat(Sname, ".values"), "midimap", gimidimap_values
+ turnoff
+endin
+
+
+/*
+ Load map state from database
+
+ p4 state name
+*/
+instr midimap_loadstate_db
+ Sname = p4
+ gSmidimap_channels pgdb_array_get strcat(Sname, ".channels"), "midimap"
+ i_ pgdb_table_get strcat(Sname, ".values"), "midimap", gimidimap_values
+ _midimap_setvalues()
+ turnoff
+endin
+#end ; USING_DB
+
+#end
diff --git a/site/udo/oprepare.udo b/site/udo/oprepare.udo
new file mode 100755
index 0000000..f222ad3
--- /dev/null
+++ b/site/udo/oprepare.udo
@@ -0,0 +1,93 @@
+#ifndef UDO_OPREPARE
+#define UDO_OPREPARE ##
+/*
+ Offline preparation system: record a sound in one k-cycle to a ftable for future use
+ May be used where online playback would be too CPU heavy
+ Instruments to be prepared should be prepended with src_
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+
+/*
+ Internal preparation instrument: loop through gSoprepare
+ p4 index of gSoprepare to process
+ p5 instrument name to schedule when all sounds have been prepared
+*/
+gSoprepare[] init 1 ; filled by oprepare opcode: input instrument names without src_ prepended
+giopreparedfns[] init 1 ; filled by oprepare opcode: output ftable numbers corresponding to above
+instr _oprepare
+ iprepareindex = p4
+ SonComplete = p5
+ if (iprepareindex >= lenarray(gSoprepare)) then
+ event_i "i", SonComplete, 0, 3600
+ turnoff
+ else
+ Sprepareinstr = gSoprepare[iprepareindex]
+ Srcinstr = sprintf("src_%s", Sprepareinstr)
+ ilen = 0.8
+ p3 = ilen
+ ifn ftgen 0, 0, sr*ilen, 7, 0
+ giopreparedfns[iprepareindex] = ifn
+ ktimek timeinstk
+ if (ktimek == 1) then
+ kcycles = ilen * kr
+ kcount init 0
+loop:
+ apos phasor (1/(ftlen(ifn)/sr))
+ aproc subinstr Srcinstr, 1, 0.1
+ tabw aproc, apos, ifn, 1
+ loop_lt kcount, 1, kcycles, loop
+ elseif (ktimek == 5) then
+ scoreline_i sprintf("i\"_oprepare\" 0 1 %d \"%s\"", iprepareindex+1, SonComplete)
+ turnoff
+ endif
+ endif
+
+endin
+
+
+/*
+ Start the offline preparation
+
+ oprepare Snames[], SonComplete
+
+ Snames[] list of instruments to process (instrument name without src_ prepended)
+ SonComplete instrument to be scheduled when preparation process has completed
+*/
+opcode oprepare, 0, S[]S
+ Snames[], SonComplete xin
+ gSoprepare = Snames
+ giopreparedfns[] init lenarray(Snames)
+ scoreline_i sprintf("i\"_oprepare\" 0 1 0 \"%s\"", SonComplete)
+endop
+
+
+/*
+ Get the ftable number of a specified instrument name as originally passed to oprepare
+
+ ifn oprepare_getfn Sname
+
+ ifn the ftable
+ Sname name of offline-prepared instrument
+*/
+opcode oprepare_getfn, i, S
+ Sname xin
+ ifn = -1
+ index = 0
+ while (index < lenarray(gSoprepare)) do
+ if (strcmp(gSoprepare[index], Sname) == 0) then
+ ifn = giopreparedfns[index]
+ endif
+ index += 1
+ od
+complete:
+ xout ifn
+endop
+
+
+#end
+
diff --git a/site/udo/pgdb.udo b/site/udo/pgdb.udo
new file mode 100755
index 0000000..230d74a
--- /dev/null
+++ b/site/udo/pgdb.udo
@@ -0,0 +1,313 @@
+#ifndef UDO_PGDB
+#define UDO_PGDB ##
+/*
+ PostgreSQL connection and tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "__config__.udo"
+#define USING_DB ##
+
+gidb dbconnect "postgresql", "$PGDB_HOST", "$PGDB_NAME", "$PGDB_USER", "$PGDB_PASSWORD"
+
+
+/*
+ Escape a SQL string
+
+ Soutput sqlescape Sinput
+
+ Sinput string to be escaped
+ Soutput escaped string
+
+*/
+opcode pgdb_sqlescape, S, S
+ Sdata xin
+ Sout = ""
+ ilen = strlen(Sdata)
+ index = 0
+ while (index < ilen) do
+ Schar = strsub(Sdata, index, index+1)
+ if (strcmp(Schar, "'") == 0) then
+ Sout = strcat(Sout, "''")
+ else
+ Sout = strcat(Sout, Schar)
+ endif
+ index += 1
+ od
+ xout Sout
+endop
+
+/*
+ Escape a SQL string at k-rate
+
+ Soutput sqlescape Sinput
+
+ Sinput string to be escaped
+ Soutput escaped string
+
+*/
+opcode pgdb_sqlescapek, S, S
+ Sdata xin
+ Sout = ""
+ klen = strlenk(Sdata)
+ kindex init 0
+ while (kindex < klen) do
+ Schar = strsubk(Sdata, kindex, kindex+1)
+ if (strcmpk(Schar, "'") == 0) then
+ Sout = strcatk(Sout, "''")
+ else
+ Sout = strcatk(Sout, Schar)
+ endif
+ kindex += 1
+ od
+ xout Sout
+endop
+
+
+/*
+ Save a JSON object to the database with a given name and unit
+
+ pgdb_json_save Sname, Sunit, iJson
+
+ Sname name of object
+ Sunit category of object
+ iJson JSON object
+*/
+opcode pgdb_json_save, 0, SSi
+ Sname, Sunit, iJson xin
+ Squery = sprintf("DELETE FROM savejson WHERE name = '%s' AND unit = '%s'; INSERT INTO savejson (name, unit, data, created) VALUES ('%s', '%s', '%s', current_timestamp)",\
+ Sname, Sunit, Sname, Sunit, jsondumps(iJson, 0)\
+ )
+ dbexec gidb, Squery
+endop
+
+
+/*
+ Load a JSON object from the database
+
+ iJson pgdb_json_load Sname, Sunit
+
+ iJson the JSON object if successful, otherwise -1 if the requested data does not exist
+ Sname name of saved object
+ Sunit category of saved object
+
+*/
+opcode pgdb_json_load, i, SS
+ Sname, Sunit xin
+ Squery = sprintf("SELECT data::text FROM savejson WHERE name = '%s' AND unit = '%s' UNION SELECT 'void'", Sname, Sunit)
+ Sresult dbscalar gidb, Squery
+ if (strcmp(Squery, "void") == 0) then
+ iJson = -1
+ else
+ iJson = jsonloads(Sresult)
+ endif
+ xout iJson
+endop
+
+
+
+
+
+
+/*
+ Save an array to the database with a given name
+
+ pgdb_array_save Sname, Sunit, iarray[]
+
+ Sname name of the save data
+ Sunit unit to associate save data with
+ iarray[] the array to save
+
+*/
+opcode pgdb_array_save, 0, SSi[]
+ Sname, Sunit, iarray[] xin
+ Sname = pgdb_sqlescape(Sname)
+ Sunit = pgdb_sqlescape(Sunit)
+ dbexec gidb, sprintf("DELETE FROM savearray WHERE name = '%s' AND unit = '%s'", Sname, Sunit)
+ ilen = lenarray(iarray)
+ index = 0
+ Sdata = ""
+ while (index < ilen) do
+ if (index != 0) then
+ Sdata = strcat(Sdata, ",")
+ endif
+ Sdata = strcat(Sdata, sprintf("%f", iarray[index]))
+ index += 1
+ od
+ Squery = sprintf("INSERT INTO savearray (name, data, unit, created) VALUES ('%s', array[%s], '%s', current_timestamp)", Sname, Sdata, Sunit)
+ dbexec gidb, Squery
+endop
+
+opcode pgdb_array_save, 0, SSS[]
+ Sname, Sunit, Sarray[] xin
+ Sname = pgdb_sqlescape(Sname)
+ Sunit = pgdb_sqlescape(Sunit)
+ dbexec gidb, sprintf("DELETE FROM savearray WHERE name = '%s' AND unit = '%s'", Sname, Sunit)
+ ilen = lenarray(Sarray)
+ index = 0
+ Sdata = ""
+ while (index < ilen) do
+ if (index != 0) then
+ Sdata = strcat(Sdata, ",")
+ endif
+ Sdata = strcat(Sdata, sprintf("'%s'", Sarray[index]))
+ index += 1
+ od
+ Squery = sprintf("INSERT INTO savearray (name, textdata, unit, created) VALUES ('%s', array[%s], '%s', current_timestamp)", Sname, Sdata, Sunit)
+ dbexec gidb, Squery
+endop
+
+
+/*
+ Save a ftable to the database with a given name
+
+ pgdb_table_save Sname, Sunit, ifn
+
+ Sname name of the save data
+ Sunit unit to associate save data with
+ ifn ftable number
+
+*/
+opcode pgdb_table_save, 0, SSi
+ Sname, Sunit, ifn xin
+ Sname = pgdb_sqlescape(Sname)
+ Sunit = pgdb_sqlescape(Sunit)
+ ilen = ftlen(ifn)
+ index = 0
+ Sdata = ""
+ while (index < ilen) do
+ Sdata = strcat(Sdata, sprintf("%f", table:i(index, ifn)))
+ if (index + 1 < ilen) then
+ Sdata = strcat(Sdata, ",")
+ endif
+ index += 1
+ od
+ Squery = sprintf("DELETE FROM savearray WHERE name = '%s' AND unit = '%s'; INSERT INTO savearray (name, data, unit, created) VALUES ('%s', array[%s], '%s', current_timestamp)",\
+ Sname, Sunit, Sname, Sdata, Sunit\
+ )
+ dbexec gidb, Squery
+endop
+
+
+; broken
+opcode pgdb_table_savek, k, SSkk
+ Sname, Sunit, kfn, ktrig xin
+ ;Sname pgdb_sqlescape Sname
+ ;Sunit pgdb_sqlescape Sunit
+ if (ktrig == 1) then
+ printk2 tablekt:k(1, kfn)
+ klen = tableng(kfn)
+ kindex = 0
+ Sdata = ""
+ while (kindex < klen) do
+ Sdata = strcatk(Sdata, sprintfk("%f", tablekt:k(kindex, kfn)))
+ if (kindex + 1 < klen) then
+ Sdata = strcatk(Sdata, ",")
+ endif
+ kindex += 1
+ od
+ Squery sprintfk "DELETE FROM savearray WHERE name = '%s' AND unit = '%s'; INSERT INTO savearray (name, data, unit, created) VALUES ('%s', array[%s], '%s', current_timestamp)\n",\
+ Sname, Sunit, Sname, Sdata, Sunit
+ kdone dbexec_k gidb, Squery, ktrig
+ printf Squery, ktrig
+ endif
+
+ xout kdone
+endop
+
+
+/*
+ Get an array from the database with a given name
+
+ iarray[] array_get Sname
+
+ Sname name of the save data
+ iarray[] the resulting array
+
+*/
+opcode pgdb_array_get, i[], SS
+ Sname, Sunit xin
+ Sname = pgdb_sqlescape(Sname)
+ Sunit = pgdb_sqlescape(Sunit)
+ Squery = sprintf("SELECT UNNEST(data) FROM savearray WHERE name = '%s' AND unit = '%s'", Sname, Sunit)
+ iresult[][] dbarray gidb, Squery
+ idata[] init lenarray(iresult)
+ index = 0
+
+ ; TODO : can use getcol here??
+
+ while (index < lenarray(idata)) do
+ idata[index] = iresult[index][0]
+ index += 1
+ od
+ xout idata
+endop
+
+
+opcode pgdb_array_get, S[], SS
+ Sname, Sunit xin
+ Sname = pgdb_sqlescape(Sname)
+ Sunit = pgdb_sqlescape(Sunit)
+ Squery = sprintf("SELECT UNNEST(textdata) FROM savearray WHERE name = '%s' AND unit = '%s'", Sname, Sunit)
+ Sresult[][] dbarray gidb, Squery
+ Sdata[] init lenarray(Sresult)
+ index = 0
+
+ ; TODO : can use getcol here??
+
+ while (index < lenarray(Sdata)) do
+ Sdata[index] = Sresult[index][0]
+ index += 1
+ od
+ xout Sdata
+endop
+
+
+
+/*
+ Get a ftable from the database with a given name: load to an existing table if ifnexisting not specified
+
+ ifn pgdb_table_get Sname, [ifnexisting]
+
+ Sname name of the save data
+ ifn the resulting ftable
+ ifnexisting the table to load to, if not long enough, values will be limited
+
+*/
+opcode pgdb_table_get, i, SSj
+ Sname, Sunit, ifnexisting xin
+ Sname = pgdb_sqlescape(Sname)
+ Sunit = pgdb_sqlescape(Sunit)
+ Squery = sprintf("SELECT UNNEST(data) FROM savearray WHERE name = '%s' AND unit = '%s'", Sname, Sunit)
+ iresult[][] dbarray gidb, Squery
+ if (lenarray(iresult) > 0) then
+ ifn = (ifnexisting == -1) ? ftgen(0, 0, lenarray(iresult), 7, 0) : ifnexisting
+ index = 0
+ while (index < min(lenarray(iresult), ftlen(ifn))) do
+ tablew iresult[index][0], index, ifn
+ index += 1
+ od
+ endif
+ xout ifn
+endop
+
+
+
+
+/*
+ Prewarm a relation (load to memory)
+
+ pgdb_prewarm Srelation
+
+ Srelation name of relation (ie table/view)
+*/
+opcode pgdb_prewarm, 0, S
+ Srelation xin
+ icheck dbscalar gidb, sprintf("SELECT COALESCE(pg_prewarm('%s'), 0)", Srelation)
+endop
+
+
+#end
diff --git a/site/udo/pvs_fsegproc.udo b/site/udo/pvs_fsegproc.udo
new file mode 100755
index 0000000..984be86
--- /dev/null
+++ b/site/udo/pvs_fsegproc.udo
@@ -0,0 +1,135 @@
+#ifndef UDO_PVSFSEGPROC
+#define UDO_PVSFSEGPROC ##
+/*
+ Segmented multiband frequency processing
+
+ Instruments passed to fsegproc should begin with $FSEGPROCINPUT which will
+ provide the segment audio as aL and aR, and the segment group as igroupindex
+
+
+ This file is part of the SONICS UDO collection by Richard Knight 2025
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "bussing.udo"
+
+gifsegproc_maxsessionindex = 0
+
+opcode _fsegproc_channels, SS, ii
+ isessionindex, igroupindex xin
+ Ssend = sprintf("bp%d_%d_%d", isessionindex, igroupindex, 1)
+ Sreturn = sprintf("bp%d_%d_%d", isessionindex, igroupindex, 0)
+ xout Ssend, Sreturn
+endop
+
+opcode _fsegproc_send, 0, ffiio
+ fL, fR, isessionindex, ifnsegbins, isegindex xin
+ ifnmap = tab_i(isegindex, ifnsegbins)
+ fmL pvsmaska fL, ifnmap, 1
+ fmR pvsmaska fR, ifnmap, 1
+
+ aL pvsynth fmL
+ aR pvsynth fmR
+ bus_mix sprintf("bp%d_%d", isessionindex, isegindex), aL, aR
+
+ if (isegindex + 1 < ftlen(ifnsegbins)) then
+ _fsegproc_send fL, fR, isessionindex, ifnsegbins, isegindex + 1
+ endif
+endop
+
+
+opcode _fsegproc_receive, aa, iiS[]o
+ isegments, isessionindex, Sinstrs[], igroupindex xin
+ ilen = lenarray(Sinstrs)
+ if (ilen == 1) then
+ Sinstr = Sinstrs[0]
+ elseif (ilen == isegments) then
+ Sinstr = Sinstrs[igroupindex]
+ else
+ index = min(round((ilen / isegments) * igroupindex), isegments - 1)
+ Sinstr = Sinstrs[index]
+ endif
+ aL, aR subinstr Sinstr, igroupindex, sprintf("bp%d_%d", isessionindex, igroupindex)
+ if (igroupindex + 1 < isegments) then
+ arL, arR _fsegproc_receive isegments, isessionindex, Sinstrs, igroupindex + 1
+ aL += arL
+ aR += arR
+ endif
+ xout aL, aR
+endop
+
+
+opcode _fsegproc_inner, aa, ffS[]jpj
+ fL, fR, Sinstrs[], isegments, imode, ifnmap xin
+ if (isegments == -1) then
+ isegments = lenarray(Sinstrs)
+ endif
+ isessionindex = gifsegproc_maxsessionindex
+ gifsegproc_maxsessionindex += 1
+ aL, aR _fsegproc_receive isegments, isessionindex, Sinstrs
+
+ i_, ibins, i_, i_ pvsinfo fL
+ isegmentsize = 0
+ icursegment = 0
+ if (imode == 0) then
+ isegmentsize = round(ibins / isegments)
+ endif
+ if (ifnmap == -1) then
+ ifnmap ftgentmp 0, 0, ibins, 2, 0
+ index = 0
+ indexsegment = 0
+ while (index < ftlen(ifnmap)) do
+ if (imode == 0) then
+ isegment = icursegment
+ if (indexsegment + 1 >= isegmentsize && icursegment + 1 < isegments) then
+ icursegment += 1
+ indexsegment = 0
+ else
+ indexsegment += 1
+ endif
+ else
+ isegment = round(random(0, isegments - 1))
+ endif
+ tabw_i isegment, index, ifnmap
+ index += 1
+ od
+ endif
+ ifnsegbins ftgentmp 0, 0, -isegments, -2, 0
+ index = 0
+ while (index < isegments) do
+ tabw_i ftgentmp(0, 0, -ibins, -2, 0), index, ifnsegbins
+ index += 1
+ od
+ index = 0
+ while (index < ftlen(ifnmap)) do
+ tabw_i 1, index, tab_i(tab_i(index, ifnmap), ifnsegbins)
+ index += 1
+ od
+ _fsegproc_send fL, fR, isessionindex, ifnsegbins
+ xout aL, aR
+endop
+
+/*
+ mode 0 = sequential, 1 = randomised
+*/
+opcode fsegproc, aa, ffS[]jpj
+ fL, fR, Sinstrs[], isegments, imode, ifnmap xin
+ aL, aR _fsegproc_inner fL, fR, Sinstrs, isegments, imode, ifnmap
+ xout aL, aR
+endop
+
+opcode fsegproc, aa, ffSjpj
+ fL, fR, Sinstr, isegments, imode, ifnmap xin
+ Sinstrs[] fillarray Sinstr
+ aL, aR _fsegproc_inner fL, fR, Sinstrs, isegments, imode, ifnmap
+ xout aL, aR
+endop
+
+#define FSEGPROCINPUT #
+igroupindex = p4
+Sbus = p5
+aL, aR bus_read Sbus
+#
+
+#end
diff --git a/site/udo/pvs_fulltabproc.udo b/site/udo/pvs_fulltabproc.udo
new file mode 100755
index 0000000..ccd8627
--- /dev/null
+++ b/site/udo/pvs_fulltabproc.udo
@@ -0,0 +1,197 @@
+#ifndef UDO_PVSFPROC
+#define UDO_PVSFPROC ##
+/*
+ Full table based PVS processing, for reading a complete file to a series of frames in tables
+
+ ksmps must be 64 or lower: setksmps(64) can be set in the calling instrument if required
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+opcode tpvf_destroy, 0, i
+ itpvHandle xin
+ ifnamp tab_i 2, itpvHandle
+ ifnfreq tab_i 3, itpvHandle
+ if (ifnamp > 0) then
+ ftfree ifnamp, 0
+ endif
+ if (ifnfreq > 0) then
+ ftfree ifnfreq, 0
+ endif
+ ftfree itpvHandle, 0
+endop
+
+opcode tpvf_clone, i, i
+ itpvHandle xin
+ ifnamp tab_i 2, itpvHandle
+ ifnfreq tab_i 3, itpvHandle
+ ilen = ftlen(ifnamp)
+ ifnampNew ftgen 0, 0, -ilen, -2, 0
+ ifnfreqNew ftgen 0, 0, -ilen, -2, 0
+ itpvHandleNew ftgen 0, 0, -5, -2, tab_i(0, itpvHandle), tab_i(1, itpvHandle), ifnampNew, ifnfreqNew, tab_i(4, itpvHandle)
+ tableicopy ifnampNew, ifnamp
+ tableicopy itpvHandleNew, ifnfreq
+ xout itpvHandleNew
+endop
+
+opcode tpvf_tablen, i, iii
+ ifnaudiolen, ifftsize, ifftdecimation xin
+ xout round(ifnaudiolen / (ifftsize / ifftdecimation)) * (ifftsize / 2)
+endop
+
+opcode tpvf_framecount, i, i
+ itpvHandle xin
+ ifftsize tab_i 0, itpvHandle
+ ifnamp tab_i 2, itpvHandle
+ xout ftlen(ifnamp) / (ifftsize / 2)
+endop
+
+opcode tpvf_analyse, ki, ijj
+ ifn, ifftsize, ifftdecimation xin
+ istatus = 1
+ itpvHandle = -1
+ ifftsize = (ifftsize == -1) ? 512: ifftsize
+ ifftdecimation = (ifftdecimation == -1) ? 8: ifftdecimation
+ ifnlen = ftlen(ifn)
+ ifnsr = ftsr(ifn)
+
+ ifoutlen = tpvf_tablen(ifnlen, ifftsize, ifftdecimation)
+
+ itpvHandle ftgen 0, 0, -5, -2, ifftsize, ifftdecimation, -1, -1, -1
+ ifnampTemp ftgentmp 0, 0, -(ifftsize / 2), -2, 0
+ ifnfreqTemp ftgentmp 0, 0, -(ifftsize / 2), -2, 0
+
+ ifnamp ftgen 0, 0, -ifoutlen, -2, 0
+ ifnfreq ftgen 0, 0, -ifoutlen, -2, 0
+ tabw_i ifnamp, 2, itpvHandle
+ tabw_i ifnfreq, 3, itpvHandle
+ tabw_i (ifnlen / ifnsr), 4, itpvHandle
+
+ ikcycles = ifnlen / ksmps
+ kcycle init 0
+ ktimek timeinstk
+ if (ktimek == 1) then
+ kwriteindex = 0
+ while (kcycle < ikcycles) do
+ apos lphasor 1
+ asig table3 apos, ifn
+ fsig pvsanal asig, ifftsize, ifftsize / ifftdecimation, ifftsize, 1
+ kready pvsftw fsig, ifnampTemp, ifnfreqTemp
+ if (kready == 1) then
+ kreadindex = 0
+ while (kreadindex < ftlen(ifnampTemp)) do
+ tablew tab:k(kreadindex, ifnampTemp), kwriteindex, ifnamp ; overshoots write
+ tablew tab:k(kreadindex, ifnfreqTemp), kwriteindex, ifnfreq ; overshoots write
+ kwriteindex += 1
+ kreadindex += 1
+ od
+ endif
+ kcycle += 1
+ od
+ else
+ kdone = 1
+ endif
+ xout kdone, itpvHandle
+endop
+
+opcode tpvf_resynth, aak, ijojo
+ itpvHandle, ifnBinSelection, iuseadsyn, isplitselection, istartframe xin
+ ifftsize tab_i 0, itpvHandle
+ ifftdecimation tab_i 1, itpvHandle
+ ifnamp tab_i 2, itpvHandle
+ ifnfreq tab_i 3, itpvHandle
+
+ if (ifnBinSelection > 0 && isplitselection == 1) then
+ ibslen = ftlen(ifnBinSelection)
+ ifnBinSelectionInverse ftgentmp 0, 0, -(ibslen), -2, 0
+ index = 0
+ while (index < ibslen) do
+ tabw_i -(tab_i(index, ifnBinSelection)), index, ifnBinSelectionInverse
+ index += 1
+ od
+ endif
+
+ ifnampTemp ftgentmp 0, 0, -(ifftsize / 2), -2, 0
+ ifnfreqTemp ftgentmp 0, 0, -(ifftsize / 2), -2, 0
+
+ anull init 0
+ aoutinverse = anull
+ fsig pvsanal anull, ifftsize, ifftsize / ifftdecimation, ifftsize, 1
+ kready pvsftw fsig, ifnampTemp, ifnfreqTemp
+ kwriteindex = 0
+ kreadindex init istartframe * (ifftsize / 2)
+ if (kreadindex >= ftlen(ifnamp)) then
+ kdone = 1
+ aout = anull
+ else
+ kdone = 0
+ if (kready == 1) then
+ while (kwriteindex < (ifftsize / 2)) do
+ tabw tab:k(kreadindex, ifnamp), kwriteindex, ifnampTemp
+ tabw tab:k(kreadindex, ifnfreq), kwriteindex, ifnfreqTemp
+ kreadindex += 1
+ kwriteindex += 1
+ od
+ endif
+ pvsftr fsig, ifnampTemp, ifnfreqTemp
+ if (ifnBinSelection > 0) then
+ fproc pvsmaska fsig, ifnBinSelection, 1
+ if (iuseadsyn == 1) then
+ aout pvsadsyn fproc, ifftsize / 2, 1
+ else
+ aout pvsynth fproc
+ endif
+ if (isplitselection == 1) then
+ fprocinverse pvsmaska fsig, ifnBinSelectionInverse, 1
+ if (iuseadsyn == 1) then
+ aoutinverse pvsadsyn fprocinverse, ifftsize / 2, 1
+ else
+ aoutinverse pvsynth fprocinverse
+ endif
+ endif
+ else
+ if (iuseadsyn == 1) then
+ aout pvsadsyn fsig, ifftsize / 2, 1
+ else
+ aout pvsynth fsig
+ endif
+ endif
+ endif
+ xout aout, aoutinverse, kdone
+endop
+
+opcode tpvf_resynth, ak, ijoj
+ itpvHandle, ifnBinSelection, iuseadsyn, istartframe xin
+ aout, a_, kdone tpvf_resynth itpvHandle, ifnBinSelection, iuseadsyn, istartframe
+ xout aout, kdone
+endop
+
+opcode tpvf_resynth_offline, ik, io
+ itpvHandle, iuseadsyn xin
+ ifftsize tab_i 0, itpvHandle
+ ifftdecimation tab_i 1, itpvHandle
+ ifnamp tab_i 2, itpvHandle
+ ifnfreq tab_i 3, itpvHandle
+ ifndurations tab_i 4, itpvHandle
+ ifnout ftgen 0, 0, -(round(ifndurations * sr)), -2, 0
+ ikcycles = floor(ifndurations * kr)
+ kdone = 0
+ ktimek timeinstk
+ if (ktimek == 1) then
+ kcycle = 0
+ while (kcycle < ikcycles) do
+ apos lphasor 1
+ asig, k_ tpvf_resynth itpvHandle, -1, iuseadsyn
+ tabw asig, apos, ifnout
+ kcycle += 1
+ od
+ else
+ kdone = 1
+ endif
+ xout ifnout, kdone
+endop
+
+#end
diff --git a/site/udo/pvs_tabproc.udo b/site/udo/pvs_tabproc.udo
new file mode 100755
index 0000000..dc2dc04
--- /dev/null
+++ b/site/udo/pvs_tabproc.udo
@@ -0,0 +1,640 @@
+#ifndef UDO_PVSTABPROC
+#define UDO_PVSTABPROC ##
+/*
+ Frame based PVS processing
+
+ ksmps must be 64 or lower: setksmps(64) can be set in the calling instrument if required
+
+ tpv data tables have the following indexes:
+ 0: number of channels
+ 1: amp left
+ 2: amp right
+ 3: frequency left
+ 4: frequency right
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022, 2024, 2025
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+/*
+ Make container for processing tables and information
+ itpvdata tpv_makecontainer ichannels, inumbins
+
+ itpvdata tpv information for subsequent opcodes
+ ichannels number of channels to account for
+ inumbins number of frequency bins
+*/
+opcode tpv_makecontainer, i, ii
+ ichannels, inumbins xin
+ itpv ftgentmp 0, 0, -5, -2, ichannels
+ index = 1
+ while (index < 5) do
+ if (ichannels == 1 && (index == 2 || index == 4)) then
+ ival = 0
+ else
+ ival ftgentmp 0, 0, -inumbins, -2, 0
+ endif
+ tabw_i ival, index, itpv
+ index += 1
+ od
+ xout itpv
+endop
+
+/*
+ Shorthand to get tpv data items
+ ichannels, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+
+ ichannels number of channels
+ isize bin size
+ ifnampL left amp table
+ ifnampR right amp table
+ ifnfreqL left freq table
+ ifnfreqR right freq table
+ iptvdata tpv data table
+*/
+opcode tpv_get, iiiiii, i
+ itpvdata xin
+ ifnAmpL = tab_i(1, itpvdata)
+ xout tab_i(0, itpvdata), ftlen(ifnAmpL), ifnAmpL, tab_i(2, itpvdata), tab_i(3, itpvdata), tab_i(4, itpvdata)
+endop
+
+/*
+ Analyse f-signal to table (mono)
+ kready, itpvdata tpv_anal fsrc
+
+ fsrc source f-signal
+ kready done trigger
+ iptvdata tpv analysis data
+*/
+opcode tpv_anal, ki, f
+ fsrc xin
+ ioverlap, inumbins, iwinsize, iformat pvsinfo fsrc
+ itpvdata tpv_makecontainer 1, inumbins
+ kready pvsftw fsrc, tab_i(1, itpvdata), tab_i(3, itpvdata)
+ xout kready, itpvdata
+endop
+
+/*
+ Analyse f-signal to table (stereo)
+ kready, itpvdata tpv_anal fsrcL, fsrcR
+
+ fsrcL source f-signal left
+ fsrcR source f-signal right
+ kready done trigger
+ iptvdata tpv analysis stream handles for use in other opcodes
+*/
+opcode tpv_anal, ki, ff
+ fsrcL, fsrcR xin
+ ioverlap, inumbins, iwinsize, iformat pvsinfo fsrcL
+ itpvdata tpv_makecontainer 2, inumbins
+ kreadyL pvsftw fsrcL, tab_i(1, itpvdata), tab_i(3, itpvdata)
+ kreadyR pvsftw fsrcR, tab_i(2, itpvdata), tab_i(4, itpvdata)
+ xout (kreadyL & kreadyR), itpvdata
+endop
+
+/*
+ Reform tpv data (mono). Input and output f-signals must be the same.
+
+ foutM tpv_resynth itpvdata, foutM
+
+ itpvdata tpv analysis stream handles
+ foutM f-signal to write to
+*/
+opcode tpv_resynth, f, if
+ itpvdata, foutM xin
+ pvsftr foutM, tab_i(1, itpvdata), tab_i(3, itpvdata)
+ xout foutM
+endop
+
+/*
+ Reform tpv data (stereo). Input and output f-signals must be the same.
+ foutL, foutR tpv_resynth itpvdata, foutL, foutR
+
+ itpvdata tpv analysis stream handles
+ foutL f-signal to write to left
+ foutR f-signal to write to right
+*/
+opcode tpv_resynth, ff, iff
+ itpvdata, foutL, foutR xin
+ pvsftr foutL, tab_i(1, itpvdata), tab_i(3, itpvdata)
+ pvsftr foutR, tab_i(2, itpvdata), tab_i(4, itpvdata)
+ xout foutL, foutR
+endop
+
+/*
+ Smear frames
+ tpv_smear kready, itpvdata, imaxframes, kframes, kavgfreqs, kincludeoriginal
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ imaxframes maximum frames for smearage
+ kframes frames of smearage to apply
+ kavgfreqs average frequencies as well as smearing amplitudes (bool)
+ kincludeoriginal include the original frame in output
+*/
+opcode tpv_smear, 0, kijJOP
+ kready, itpvdata, imaxframes, kframes, kavgfreqs, kincludeoriginal xin
+ imaxframes = (imaxframes == -1) ? 8 : imaxframes
+ kframes = (kframes < 1 || kframes > imaxframes) ? imaxframes: kframes
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ itpvtemps ftgentmp 0, 0, -imaxframes, -2, 0
+ index = 0
+ while (index < imaxframes) do
+ tabw_i(tpv_makecontainer(ichans, isize), index, itpvtemps)
+ index += 1
+ od
+ kindexframew init 0
+
+ if (kready == 1) then
+ ktpvfnw = tab:k(kindexframew, itpvtemps)
+ tablecopy tablekt:k(1, ktpvfnw), ifnampL
+ tablecopy tablekt:k(3, ktpvfnw), ifnfreqL
+ if (ichans == 2) then
+ tablecopy tablekt:k(2, ktpvfnw), ifnampR
+ tablecopy tablekt:k(4, ktpvfnw), ifnfreqR
+ endif
+
+ kindexframer = (kindexframew - 1 < 0) ? imaxframes - 1 : kindexframew - 1
+ kframescale = 1 / kframes
+ kindex = 0
+ while (kindex < isize) do
+ kampL = (kincludeoriginal == 1) ? tab:k(kindex, ifnampL) : 0
+ kfreqL = (kincludeoriginal == 1) ? tab:k(kindex, ifnfreqL) : 0
+ if (ichans == 2) then
+ kampR = (kincludeoriginal == 1) ? tab:k(kindex, ifnampR) : 0
+ kfreqR = (kincludeoriginal == 1) ? tab:k(kindex, ifnfreqR) : 0
+ endif
+ kindexframeabs = 0
+ while (kindexframeabs < kframes) do
+ kcurscale = (kframescale * (kframes - kindexframeabs))
+ ktpvframe tab kindexframer, itpvtemps
+ ;kampL = (kampL + tablekt:k(kindex, ktpvframe)) * 0.5
+ kampL += tablekt:k(kindex, tablekt:k(1, ktpvframe)) * kcurscale
+ if (kavgfreqs == 1) then
+ kfreqL = (kfreqL + tablekt:k(kindex, tablekt:k(3, ktpvframe))) * 0.5
+
+ endif
+ if (ichans == 2) then
+ kampR += tablekt:k(kindex, tablekt:k(2, ktpvframe)) * kcurscale
+ if (kavgfreqs == 1) then
+ kfreqR = (kfreqR + tablekt:k(kindex, tablekt:k(4, ktpvframe))) * 0.5
+ endif
+ endif
+ kindexframer = (kindexframer - 1 < 0) ? imaxframes - 1 : kindexframer - 1
+ kindexframeabs += 1
+ od
+
+ tabw kampL, kindex, ifnampL
+ if (kavgfreqs == 1) then
+ tabw kfreqL, kindex, ifnfreqL
+ endif
+ if (ichans == 2) then
+ tabw kampR, kindex, ifnampR
+ if (kavgfreqs == 1) then
+ tabw kfreqR, kindex, ifnfreqR
+ endif
+ endif
+ kindex += 1
+ od
+ if (kindexframew + 1 < imaxframes) then
+ kindexframew += 1
+ else
+ kindexframew = 0
+ endif
+ endif
+endop
+
+/*
+ Wrap spectrum
+ tpv_wrap kready, itpvdata, kwrapstart
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ kwrapAmpBin start bin for amplitude wrapping
+ kwrapFreqBin start bin for frequency wrapping
+*/
+opcode tpv_wrap, 0, kikk
+ kready, itpvdata, kwrapAmpBin, kwrapFreqBin xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ itpvdatatemp tpv_makecontainer ichans, isize
+ i_, i_, ifnampLtemp, ifnampRtemp, ifnfreqLtemp, ifnfreqRtemp tpv_get itpvdatatemp
+
+ if (kready == 1) then
+ tablecopy ifnampLtemp, ifnampL
+ tablecopy ifnfreqLtemp, ifnfreqL
+ if (ichans == 2) then
+ tablecopy ifnampRtemp, ifnampR
+ tablecopy ifnfreqRtemp, ifnfreqR
+ endif
+
+ kindex = 0
+ while (kindex < isize) do
+ kwrapAmpIndexW = (kwrapAmpBin + kindex) % isize
+ kwrapFreqIndexW = (kwrapFreqBin + kindex) % isize
+
+ tabw tab:k(kindex, ifnampLtemp), kwrapAmpIndexW, ifnampL
+ tabw tab:k(kindex, ifnfreqLtemp), kwrapFreqIndexW, ifnfreqL
+
+ if (ichans == 2) then
+ tabw tab:k(kindex, ifnampRtemp), kwrapAmpIndexW, ifnampR
+ tabw tab:k(kindex, ifnfreqRtemp), kwrapFreqIndexW, ifnfreqR
+ endif
+ kindex += 1
+ od
+ endif
+endop
+
+/*
+ Set random bin amplitudes to 0. Ported from pvtool
+ tpv_bubble kready, itpvdata, kchance, kstereounique
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ kchance chance of applying bin amplitude, between 0 and 1
+ kstereounique whether to apply the effect channel independently
+*/
+opcode tpv_bubble, 0, kikP
+ kready, itpvdata, kchance, kstereounique xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ kreplacement init 0
+ if (kready == 1) then
+ kindex = 0
+ while (kindex < isize) do
+ kapplyL = (random:k(0, 1) <= kchance) ? 1 : 0
+ if (ichans == 1) then
+ if (kapplyL == 1) then
+ tabw 0, kindex, ifnampL
+ endif
+ else
+ if (kstereounique == 0) then
+ if (kapplyL == 1) then
+ tabw kreplacement, kindex, ifnampL
+ tabw kreplacement, kindex, ifnampR
+ endif
+ else
+ if (kapplyL == 1) then
+ tabw kreplacement, kindex, ifnampL
+ endif
+ if (random:k(0, 1) <= kchance) then
+ tabw kreplacement, kindex, ifnampR
+ endif
+ endif
+ endif
+ kindex += 1
+ od
+ endif
+
+endop
+
+/*
+ Swap spectrum areas
+ tpv_swap kready, itpvdata, kampStart, kampLength, kampTarget, kfreqStart, kfreqLength, kfreqTarget [, kwrapmode = 1]
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ kampStart bin start for amplitude
+ kampLength bins length for amplitude
+ kampTarget bin target start for amplitude
+ kfreqStart bin start for frequency
+ kfreqLength bins length for frequency
+ kfreqTarget bin target start for frequency
+ kwrapmode wrap mode: 0 = limit; 1 = wrap
+*/
+opcode tpv_swap, 0, kikOkkOkP
+ kready, itpvdata, kampStart, kampLength, kampTarget, kfreqStart, kfreqLength, kfreqTarget, kwrapmode xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ itpvdatatemp tpv_makecontainer ichans, isize
+ i_, i_, ifnampLtemp, ifnampRtemp, ifnfreqLtemp, ifnfreqRtemp tpv_get itpvdatatemp
+
+ kampLength = (kampLength == 0) ? isize : kampLength
+ kfreqLength = (kfreqLength == 0) ? isize : kfreqLength
+
+ if (kready == 1) then
+ tablecopy ifnampLtemp, ifnampL
+ tablecopy ifnfreqLtemp, ifnfreqL
+ if (ichans == 2) then
+ tablecopy ifnampRtemp, ifnampR
+ tablecopy ifnfreqRtemp, ifnfreqR
+ endif
+
+ kampStartW = min:k(kampStart, kampTarget)
+ kampTargetW = max:k(kampStart, kampTarget)
+ kfreqStartW = min:k(kfreqStart, kfreqTarget)
+ kfreqTargetW = max:k(kfreqStart, kfreqTarget)
+
+ kindex = 0
+ while (kindex < isize) do
+ if (kwrapmode == 1) then
+ kampStartW = kampStartW % isize
+ kampEndW = (kampStartW + kampLength) % isize
+ kampTargetW = (kampTargetW + kindex) % isize
+
+ kfreqStartW = kfreqStartW % isize
+ kfreqEndW = (kfreqStartW + kfreqLength) % isize
+ kfreqTargetW = (kfreqTargetW + kindex) % isize
+ else
+ kampStartW = (kampStartW >= isize) ? isize - 1 : kampStart
+ kampEnd = kampStartW + kampLength
+ kampEndW = (kampEnd >= isize) ? isize - 1 : kampEnd
+ kampTargetW = (kampTargetW + kindex >= isize) ? isize - 1 : kampTargetW + kindex
+
+ kfreqStartW = (kfreqStartW >= isize) ? isize - 1 : kfreqStart
+ kfreqEnd = kfreqStartW + kfreqLength
+ kfreqEndW = (kfreqEnd >= isize) ? isize - 1 : kfreqEnd
+ kfreqTargetW = (kfreqTargetW + kindex >= isize) ? isize - 1 : kfreqTargetW + kindex
+ endif
+
+ if (kindex >= kampStartW && kindex < kampEndW) then
+ tabw tab:k(kindex, ifnampLtemp), kampTargetW, ifnampL
+ tabw tab:k(kampTargetW, ifnampLtemp), kindex, ifnampL
+ if (ichans == 2) then
+ tabw tab:k(kindex, ifnampRtemp), kampTargetW, ifnampR
+ tabw tab:k(kampTargetW, ifnampRtemp), kindex, ifnampR
+ endif
+ endif
+
+ if (kindex >= kfreqStartW && kindex < kfreqEndW) then
+ tabw tab:k(kindex, ifnfreqLtemp), kfreqTargetW, ifnfreqL
+ tabw tab:k(kfreqTargetW, ifnfreqLtemp), kindex, ifnfreqL
+ if (ichans == 2) then
+ tabw tab:k(kindex, ifnfreqRtemp), kfreqTargetW, ifnfreqR
+ tabw tab:k(kfreqTargetW, ifnfreqRtemp), kindex, ifnfreqR
+ endif
+ endif
+ kindex += 1
+ od
+ endif
+endop
+
+/*
+ Invert spectrum
+ tpv_invert kready, itpvdata, [kinvertamp, kinvertfreq]
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ kinvertamp whether to invert amp or not (1 or 0)
+ kinvertfreq whether to invert frequency or not (1 or 0)
+*/
+opcode tpv_invert, 0, kiPP
+ kready, itpvdata, kinvertamp, kinvertfreq xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ if (kready == 1) then
+ kindex = 0
+ while (kindex < isize) do
+ if (kinvertamp == 1) then
+ tabw tab:k(isize-kindex, ifnampL), kindex, ifnampL
+ endif
+
+ if (kinvertfreq == 1) then
+ tabw tab:k(isize-kindex, ifnfreqL), kindex, ifnfreqL
+ endif
+
+ if (ichans == 2) then
+ if (kinvertamp == 1) then
+ tabw tab:k(isize-kindex, ifnampR), kindex, ifnampR
+ endif
+
+ if (kinvertfreq == 1) then
+ tabw tab:k(isize-kindex, ifnfreqR), kindex, ifnfreqR
+ endif
+ endif
+
+ kindex += 1
+ od
+ endif
+endop
+
+/*
+ Filter bins with a ftable mask
+ tpv_binfilter kready, itpvdata, ifnamps
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ ifnamps ftable containing amplitude values per bin
+*/
+opcode tpv_binfilter, 0, kii
+ kready, itpvdata, ifnamps xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ if (kready == 1) then
+ kindex = 0
+ while (kindex < isize) do
+ tabw tab:k(kindex, ifnampL) * tab:k(kindex, ifnamps), kindex, ifnampL
+
+ if (ichans == 2) then
+ tabw tab:k(kindex, ifnampR) * tab:k(kindex, ifnamps), kindex, ifnampR
+ endif
+ kindex += 1
+ od
+ endif
+endop
+
+/*
+ Allow bins over or below a certain threshold; a spectral gate
+ tpv_threshold kready, itpvdata, kthreshold, kabove
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ kthreshold amplitude threshold to apply
+ kabove above or below threshold will be let through the gate (1 = above, 0 = below)
+*/
+opcode tpv_threshold, 0, kikO
+ kready, itpvdata, kthresh, kabove xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ if (kready == 1) then
+ kindex = 0
+ while (kindex < isize) do
+
+ kvalL tab kindex, ifnampL
+ if ((kabove == 0 && kvalL < kthresh) || (kabove == 1 && kvalL > kthresh)) then
+ tabw 0, kindex, ifnampL
+ tabw 0, kindex, ifnfreqL
+ endif
+
+ if (ichans == 2) then
+ kvalR tab kindex, ifnampR
+ if ((kabove == 0 && kvalR < kthresh) || (kabove == 1 && kvalR > kthresh)) then
+ tabw 0, kindex, ifnampR
+ tabw 0, kindex, ifnfreqR
+ endif
+ endif
+
+ kindex += 1
+ od
+ endif
+endop
+
+/*
+ Scramble amplitude and/or frequency
+ tpv_scramble kready, itpvdata, kstepratio, kdoamp, kdofreq
+
+ kready done trigger from tpv_anal
+ itpvdata tpv analysis stream handles
+ kstepratio partitioning ratio
+ kdoamp scramble amplitudes (bool)
+ kdofreq scramble frequencies (bool)
+*/
+opcode tpv_scramble, 0, kiJPP
+ kready, itpvdata, kstepratio, kdoamp, kdofreq xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ kstep = (kstepratio == -1) ? 1 : max:k(round:k(kstepratio * isize), 1)
+ if (kready == 1) then
+ kindex = 0
+ while (kindex < isize) do
+ kdest = int:k(random:k(kstep, isize)) - kstep
+ kindex2 = 0
+ while (kindex2 < kstep) do
+
+ if (kdoamp == 1) then
+ kval table kindex+kindex2, ifnampL, 0, 0, 1
+ kcurrentval table kdest+kindex2, ifnampL, 0, 0, 1
+ tablew (kval+kcurrentval)/2, kdest+kindex2, ifnampL, 0, 0, 1
+ if (ichans == 2) then
+ kval table kindex+kindex2, ifnampR, 0, 0, 1
+ kcurrentval table kdest+kindex2, ifnampR, 0, 0, 1
+ tablew (kval+kcurrentval)/2, kdest+kindex2, ifnampR, 0, 0, 1
+ endif
+ endif
+
+ if (kdofreq == 1) then
+ kval table kindex+kindex2, ifnfreqL, 0, 0, 1
+ kcurrentval table kdest+kindex2, ifnfreqL, 0, 0, 1
+ tablew (kval+kcurrentval)/2, kdest+kindex2, ifnfreqL, 0, 0, 1
+ if (ichans == 2) then
+ kval table kindex+kindex2, ifnfreqR, 0, 0, 1
+ kcurrentval table kdest+kindex2, ifnfreqR, 0, 0, 1
+ tablew (kval+kcurrentval)/2, kdest+kindex2, ifnfreqR, 0, 0, 1
+ endif
+ endif
+ kindex2 += 1
+ od
+ kindex += kstep
+ od
+ endif
+endop
+
+
+
+
+opcode tpv_freeze1, 0, kikPPP
+ kready, itpvdata, kfreeze, kfreezeamp, kfreezefreq, kcrossfade xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ itpvtemp tpv_makecontainer ichans, isize
+ i_, i_, ifnampLtemp, ifnampRtemp, ifnfreqLtemp, ifnfreqRtemp tpv_get itpvtemp
+
+ if (kready == 1) then
+ kindex = 0
+ while (kindex < isize) do
+ if (kfreeze >= 0) then
+ kamount min kfreeze-1, 1
+
+ if (kfreezeamp == 1) then
+ if (kcrossfade == 1) then
+ tabw ((1-kamount)*tab:k(kindex, ifnampL) + (kamount * tab:k(kindex, ifnampLtemp))), kindex, ifnampL
+ if (ichans == 2) then
+ tabw ((1-kamount)*tab:k(kindex, ifnampR) + (kamount * tab:k(kindex, ifnampRtemp))), kindex, ifnampR
+ endif
+ else
+ tabw tab:k(kindex, ifnampLtemp), kindex, ifnampL
+ if (ichans == 2) then
+ tabw tab:k(kindex, ifnampRtemp), kindex, ifnampR
+ endif
+ endif
+ endif
+
+ if (kfreezefreq == 1) then
+ if (kcrossfade == 1) then
+ tabw ((1-kamount)*tab:k(kindex, ifnfreqL) + (kamount * tab:k(kindex, ifnfreqLtemp))), kindex, ifnfreqL
+ if (ichans == 2) then
+ tabw ((1-kamount)*tab:k(kindex, ifnfreqR) + (kamount * tab:k(kindex, ifnfreqRtemp))), kindex, ifnfreqR
+ endif
+ else
+ tabw tab:k(kindex, ifnfreqLtemp), kindex, ifnfreqL
+ if (ichans == 2) then
+ tabw tab:k(kindex, ifnfreqRtemp), kindex, ifnfreqR
+ endif
+ endif
+ endif
+
+ else
+ tabw tab:k(kindex, ifnampL), kindex, ifnampLtemp
+ tabw tab:k(kindex, ifnfreqL), kindex, ifnfreqLtemp
+
+ if (ichans == 2) then
+ tabw tab:k(kindex, ifnampR), kindex, ifnampRtemp
+ tabw tab:k(kindex, ifnfreqR), kindex, ifnfreqRtemp
+ endif
+ endif
+ kindex += 1
+ od
+ endif
+endop
+
+
+
+opcode tpv_average, 0, kikPPO
+ kready, itpvdata, kmax, kavgamp, kavgfreq, ktrig xin
+ ichans, isize, ifnampL, ifnampR, ifnfreqL, ifnfreqR tpv_get itpvdata
+ itpvtemp tpv_makecontainer ichans, isize
+ i_, i_, ifnampLtemp, ifnampRtemp, ifnfreqLtemp, ifnfreqRtemp tpv_get itpvtemp
+
+ kcount init 1
+ if (kready == 1) then
+ kindex = 0
+ while (kindex < isize) do
+
+ ; store to average
+ tabw tab:k(kindex, ifnampL), kindex, ifnampLtemp
+ tabw tab:k(kindex, ifnfreqL), kindex, ifnfreqLtemp
+
+ ; read average
+ if (kavgamp == 1) then
+ tabw tab:k(kindex, ifnampLtemp) / kcount, kindex, ifnampL
+ endif
+
+ if (kavgfreq == 1) then
+ tabw tab:k(kindex, ifnfreqLtemp) / kcount, kindex, ifnfreqL
+ endif
+
+ if (ichans == 2) then
+
+ ; store to average
+ tabw tab:k(kindex, ifnampR), kindex, ifnampRtemp
+ tabw tab:k(kindex, ifnfreqR), kindex, ifnfreqRtemp
+
+ ; read average
+ if (kavgamp == 1) then
+ tabw tab:k(kindex, ifnampRtemp) / kcount, kindex, ifnampR
+ endif
+
+ if (kavgfreq == 1) then
+ tabw tab:k(kindex, ifnfreqRtemp) / kcount, kindex, ifnfreqR
+ endif
+ endif
+
+ kindex += 1
+ od
+
+ if (kcount >= kmax || ktrig == 1) then
+ kindex = 0
+ while (kindex < isize) do
+
+ ; empty
+ tabw 0, kindex, ifnampLtemp
+ tabw 0, kindex, ifnfreqLtemp
+
+ if (ichans == 2) then
+ tabw 0, kindex, ifnampRtemp
+ tabw 0, kindex, ifnfreqRtemp
+ endif
+
+ kindex += 1
+ od
+ kcount = 1
+ else
+ kcount += 1
+ endif
+ endif
+endop
+
+#end
diff --git a/site/udo/pvs_tools.udo b/site/udo/pvs_tools.udo
new file mode 100755
index 0000000..2fcbc8a
--- /dev/null
+++ b/site/udo/pvs_tools.udo
@@ -0,0 +1,31 @@
+#ifndef UDO_PVSTOOLS
+#define UDO_PVSTOOLS ##
+/*
+ Phase vocoder tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+
+opcode pvs_ifn2buffer, i, iiiii
+ ifn, ifftsize, ioverlap, iwinsize, iwinshape xin
+ ktimek timeinstk
+ if (ktimek == 1) then
+ ilen = ftlen(ifn) / ftsr(ifn)
+ kcycles = ilen * kr
+ kcount init 0
+ while (kcount < kcycles) do
+ apos lphasor 1
+ ain table3 apos, ifn
+ ffin pvsanal ain, ifftsize, ioverlap, iwinsize, iwinshape
+ ibuf, ktime pvsbuffer ffin, ilen + (ifftsize / sr)
+ kcount += 1
+ od
+ xout ibuf
+ endif
+endop
+
+#end
diff --git a/site/udo/quad.udo b/site/udo/quad.udo
new file mode 100755
index 0000000..bd9abc3
--- /dev/null
+++ b/site/udo/quad.udo
@@ -0,0 +1,79 @@
+#ifndef UDO_QUAD
+#define UDO_QUAD ##
+/*
+ Quadrophonic setup and tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2025
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+#define QUAD ##
+#include "wavetables.udo"
+vbaplsinit 2, 4, 45, 135, 225, 314 ; or 0, 90, 180, 270
+
+opcode cart2apol, ii, ii
+ ix, iy xin
+ idistance = sqrt(pow(ix, 2) + pow(iy, 2))
+ idegrees = taninv(iy / ix) * (180 / (22 / 7)) - 90
+ if (qnan(idegrees) == 1) then
+ idegrees = 0
+ endif
+ xout idegrees, idistance
+endop
+
+opcode cart2apol, kk, kk
+ kx, ky xin
+ kdistance = sqrt:k(pow:k(kx, 2) + pow:k(ky, 2))
+ kdegrees = taninv:k(ky / kx) * (180 / (22 / 7)) - 90
+ if (qnan:k(kdegrees) == 1) then
+ kdegrees = 0
+ endif
+ xout kdegrees, kdistance
+endop
+
+opcode quadify, aaaa, akO
+ ain, kangle, kbleed xin
+ a1, a2, a3, a4 vbap ain, kangle, 0, kbleed * 100
+ xout a1, a2, a3, a4
+endop
+
+opcode quadify, aaaa, aakOO
+ aL, aR, kangle, kspreadangle, kbleed xin
+ kbleed *= 100
+ aL1, aL2, aL3, aL4 vbap aL, kangle - kspreadangle, 0, kbleed
+ aR1, aR2, aR3, aR4 vbap aR, kangle + kspreadangle, 0, kbleed
+ xout aL1 + aR1, aL2 + aR2, aL3 + aR3, aL4 + aR4
+endop
+
+opcode _quadify_random_params, kk, 0
+ kangle init random(0, 359)
+ kbleed init random(0, 15)
+ ktime init random(1, 10)
+ krate = 1 / ktime
+ ktrig metro krate
+ if (ktrig == 1) then
+ ktime = random:k(1, 10)
+ kangle = random:k(0, 359)
+ kbleed = random:k(0, 15)
+ endif
+
+ kanglep portk kangle, ktime
+ kbleedp portk kbleed, ktime
+ xout kanglep, kbleedp
+endop
+
+opcode quadify_random, aaaa, a
+ ain xin
+ kangle, kbleed _quadify_random_params
+ a1, a2, a3, a4 quadify ain, kangle, kbleed
+ xout a1, a2, a3, a4
+endop
+
+opcode quadify_random, aaaa, aa
+ aL, aR xin
+ kangle, kbleed _quadify_random_params
+ a1, a2, a3, a4 quadify aL, aR, kangle, kbleed
+ xout a1, a2, a3, a4
+endop
+
+#end
diff --git a/site/udo/sample_level.udo b/site/udo/sample_level.udo
new file mode 100755
index 0000000..3aa5b1e
--- /dev/null
+++ b/site/udo/sample_level.udo
@@ -0,0 +1,90 @@
+#ifndef UDO_SAMPLELEVEL
+#define UDO_SAMPLELEVEL ##
+/*
+ Sample level block processing
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+opcode smp_hold, a, ak
+ ain, kratio xin
+ aout init 0
+ ktrig metro kr * kratio
+ if (ktrig == 1) then
+ kindex = 0
+ while (kindex < ksmps) do
+ aout[kindex] = ain[kindex]
+ kindex += 1
+ od
+ endif
+ xout aout
+endop
+
+opcode smp_rearrange, aa, iiiij
+ ichops, ichopmin, ichopmax, ifnL, ifnR xin
+ if (ichopmin > ichopmax) then
+ ichopmin = ichopmax
+ endif
+ ilen = ftlen(ifnL)
+ if (ichopmin > ilen) then
+ ichopmin = round(ilen * 0.1)
+ endif
+ if (ichopmax > ilen) then
+ ichopmax = round(ilen * 0.2)
+ endif
+ ichopfn ftgentmp 0, 0, -(ichops * 3), 2, 0
+ index = 0
+ iwriteindex = 0
+ while (index < ichops) do
+ ichoplen = round(random(ichopmin, ichopmax))
+ ichopstart = round(random(0, ilen - ichoplen))
+ tabw_i ichopstart, iwriteindex, ichopfn
+ tabw_i ichoplen, iwriteindex + 1, ichopfn
+ tabw_i round(random(0, ilen - ichoplen)), iwriteindex + 2, ichopfn
+ iwriteindex += 3
+ index += 1
+ od
+ apos lphasor 1
+ kindex1 = 0
+ kinmove init 0
+ while (kindex1 < ftlen(ichopfn)) do
+ kstart tab kindex1, ichopfn
+ klen tab kindex1 + 1, ichopfn
+ kdest tab kindex1 + 2, ichopfn
+ koffset = 0
+ kindex2 = 0
+ while (kindex2 < ksmps) do
+ if (apos[kindex2] >= kdest) then
+ kinmove = 1
+ koffset = apos[kindex2] - kdest
+ endif
+ if (apos[kindex2] >= kdest + klen) then
+ kinmove = 0
+ endif
+ if (kinmove == 1) then
+ apos[kindex2] = kstart + koffset
+ endif
+ kindex2 += 1
+ od
+ kindex1 += 3
+ od
+
+ aL table3 apos, ifnL
+ if (ifnR != -1) then
+ aR table3 apos, ifnR
+ else
+ aR = aL
+ endif
+ xout aL, aR
+endop
+
+opcode smp_rearrange, a, iiii
+ ichops, ichopmin, ichopmax, ifn xin
+ aout, a_ smp_rearrange ichops, ichopmin, ichopmax, ifn
+ xout aout
+endop
+
+#end
+
diff --git a/site/udo/sampling.udo b/site/udo/sampling.udo
new file mode 100755
index 0000000..66a571b
--- /dev/null
+++ b/site/udo/sampling.udo
@@ -0,0 +1,77 @@
+#ifndef UDO_SAMPLING
+#define UDO_SAMPLING ##
+/*
+ Samplers
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "wavetables.udo"
+opcode smp_round, a, aapPJjjj
+ ain, aread, ilen, kpitch, kamp, iwsize, irandw, ioverlap xin
+ iwsize = (iwsize == -1) ? (sr*ilen)/10 : iwsize
+ irandw = (irandw == -1) ? (sr*ilen)/100 : irandw
+ ioverlap = (ioverlap == -1) ? 4 : ioverlap
+ kamp = (kamp == -1) ? 1/ioverlap : kamp
+ ifn ftgen 0, 0, sr*ilen, 2, 0
+ andx phasor 1/ilen
+ tabw ain, andx, ifn, 1
+ aout sndwarp kamp*0.6, aread, kpitch, ifn, 0, iwsize, irandw, ioverlap, gifnHalfSine, 1
+ xout aout
+endop
+
+opcode smp_round_st, aa, aaapPJjjj
+ aL, aR, aread, ilen, kpitch, kamp, iwsize, irandw, ioverlap xin
+ iwsize = (iwsize == -1) ? (sr*ilen)/10 : iwsize
+ irandw = (irandw == -1) ? (sr*ilen)/100 : irandw
+ ioverlap = (ioverlap == -1) ? 4 : ioverlap
+ kamp = (kamp == -1) ? 1/ioverlap : kamp
+ ifnL ftgen 0, 0, sr*ilen, 2, 0
+ ifnR ftgen 0, 0, sr*ilen, 2, 0
+ andx phasor 1/ilen
+ tabw aL, andx, ifnL, 1
+ tabw aR, andx, ifnR, 1
+ aoutL sndwarp kamp*0.6, aread, kpitch, ifnL, 0, iwsize, irandw, ioverlap, gifnHalfSine, 1
+ aoutR sndwarp kamp*0.6, aread, kpitch, ifnR, 0, iwsize, irandw, ioverlap, gifnHalfSine, 1
+ xout aoutL, aoutR
+endop
+
+opcode smp_hold, a, aapPJjjj
+ ain, aread, ilen, kpitch, kamp, iwsize, irandw, ioverlap xin
+ iwsize = (iwsize == -1) ? (sr*ilen)/10 : iwsize
+ irandw = (irandw == -1) ? (sr*ilen)/100 : irandw
+ ioverlap = (ioverlap == -1) ? 4 : ioverlap
+ kamp = (kamp == -1) ? 1/ioverlap : kamp
+ istart times
+ ifn ftgen 0, 0, sr*ilen, 2, 0
+ if (times:k() < istart+ilen) then
+ andx phasor 1/ilen
+ tabw ain, andx, ifn, 1
+ endif
+ aout sndwarp kamp, aread, kpitch, ifn, 0, iwsize, irandw, ioverlap, gifnHalfSine, 1
+ xout aout
+endop
+
+opcode smp_hold_st, aa, aaapPJjjj
+ aL, aR, aread, ilen, kpitch, kamp, iwsize, irandw, ioverlap xin
+ iwsize = (iwsize == -1) ? (sr*ilen)/10 : iwsize
+ irandw = (irandw == -1) ? (sr*ilen)/100 : irandw
+ ioverlap = (ioverlap == -1) ? 4 : ioverlap
+ kamp = (kamp == -1) ? 1/ioverlap : kamp
+ istart times
+ ifnL ftgen 0, 0, sr*ilen, 2, 0
+ ifnR ftgen 0, 0, sr*ilen, 2, 0
+ if (times:k() < istart+ilen) then
+ andx phasor 1/ilen
+ tabw aR, andx, ifnL, 1
+ tabw aL, andx, ifnR, 1
+ endif
+ aoutL sndwarp kamp, aread, kpitch, ifnL, 0, iwsize, irandw, ioverlap, gifnHalfSine, 1
+ aoutR sndwarp kamp, aread, kpitch, ifnR, 0, iwsize, irandw, ioverlap, gifnHalfSine, 1
+ xout aoutL, aoutR
+endop
+
+#end
+
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
+
diff --git a/site/udo/sequencing.udo b/site/udo/sequencing.udo
new file mode 100755
index 0000000..ac4fd40
--- /dev/null
+++ b/site/udo/sequencing.udo
@@ -0,0 +1,329 @@
+#ifndef UDO_SEQUENCING
+#define UDO_SEQUENCING ##
+/*
+ Sequencing base
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+gkseq_tempo init 120 ; tempo BPM
+gkseq_beat init 0 ; trigger fired on each beat
+gkseq_beattime init 0 ; time in seconds of one beat (read only; set by BPM)
+gkseq_quartertime init 0 ; time in seconds of one quarter beat (read only; set by BPM)
+gkseq_beathz init 0 ; Hz of one beat (read only; set by BPM)
+gkseq_swing init 0.2 ; swing amount
+gkseq_on init 1
+
+/*
+ Instrument to control the main beat metronome and beat time globals
+*/
+instr _seq_manager
+ kseq_beat metro gkseq_tempo / 60
+ if (gkseq_on == 1) then
+ gkseq_beat = kseq_beat
+ endif
+ gkseq_beattime = 60 / gkseq_tempo
+ gkseq_quartertime = gkseq_beattime / 4
+ gkseq_beathz = (1 / 60) * gkseq_tempo
+endin
+;alwayson "_seq_manager" ; not available in web api
+schedule("_seq_manager", 0, -1)
+
+
+
+/*
+ Get the swung time for a given time, if appropriate. If the index given is a second 16th, time will be swung
+
+ kresult seq_swingtime ktime, kindex, kswing
+
+ kresult resulting time
+ ktime the time to consider
+ kindex beat index, beginning with 0
+ kswing the swing amount (0 to 1)
+*/
+opcode seq_swingtime, k, kkJ
+ ktime, kindex, kswing xin
+ kswing = (kswing == -1) ? gkseq_swing : kswing
+ if ((kindex+1) % 2 == 0) then
+ ktime = ktime + (gkseq_quartertime*kswing)
+ endif
+ xout ktime
+endop
+
+
+/*
+ Get the swung time for a given time, if appropriate. If the index given is a second 16th, time will be swung
+
+ iresult seq_swingtime itime, iindex, iswing
+
+ iresult resulting time
+ itime the time to consider
+ iindex beat index, beginning with 0
+ iswing the swing amount (0 to 1)
+*/
+opcode seq_swingtime, i, iij
+ itime, index, iswing xin
+ iswing = (iswing == -1) ? i(gkseq_swing) : iswing
+ if ((index+1) % 2 == 0) then
+ itime = itime + (i(gkseq_quartertime)*iswing)
+ endif
+ xout itime
+endop
+
+
+/*
+ Set the tempo in BPM
+ DEPRECATED: just use init or direct assignment to gkseq_tempo
+
+ seq_settempo ktempo
+
+ ktempo the tempo in BPM
+*/
+opcode seq_settempo, 0, k
+ ktempo xin
+ gkseq_tempo = ktempo
+endop
+
+
+/*
+ Set the tempo in BPM; typically for host control
+
+ p4 the tempo in BPM
+*/
+instr seq_settempo
+ itempo = p4
+ gkseq_tempo = itempo
+ turnoff
+endin
+
+
+
+
+/*
+ Basic sequencer: trigger an instrument on each beat
+
+ seq_basic SdespatchI
+
+ SdespatchI name of the instrument to trigger
+*/
+opcode seq_basic, 0, S
+ SdespatchI xin
+ schedkwhen gkseq_beat, 0, 0, SdespatchI, 0, 1
+endop
+
+
+
+/*
+ Basic swung sequencer: trigger an instrument on each 16th beat with
+ p4 as the cyclical index between 0 and 3
+ p5 as the total number of beats beginning at one (eg so modulus can be used to determine position etc)
+
+ seq_swing SdespatchI, kswing
+
+ SdespatchI name of the instrument to trigger
+ kswing swing amount (0 to 1)
+*/
+opcode seq_swing, 0, SJ
+ SdespatchI, kswing xin
+ kswing = (kswing == -1) ? gkseq_swing : kswing
+ kbeatnum init 1
+ if (gkseq_beat == 1) then
+ kswingamount = gkseq_quartertime*kswing
+ event "i", SdespatchI, 0, 1, 0, kbeatnum
+ event "i", SdespatchI, gkseq_quartertime+kswingamount, 1, 1, kbeatnum
+ event "i", SdespatchI, gkseq_quartertime*2, 1, 2, kbeatnum
+ event "i", SdespatchI, (gkseq_quartertime*3)+kswingamount, 1, 3, kbeatnum
+ kbeatnum += 1
+ endif
+endop
+
+
+
+/*
+ Basic array sequencer. Treat each index as a 16th, and if it is 1, trigger the despatch instrument
+
+ seq_array SdespatchI, karray[], kswing
+
+ SdespatchI name of the instrument to trigger
+ karray[] array of sequence information
+ kswing swing amount (0 to 1)
+*/
+opcode seq_array, 0, Sk[]k
+ SdespatchI, karray[], kswing xin
+ kindex init 0
+ if (gkseq_beat == 1) then
+ ktime = 0
+ kcount = 0
+ while (kcount < 4) do
+ if (karray[kindex] == 1) then
+ event "i", SdespatchI, seq_swingtime(ktime, kcount, kswing), 0.1
+ endif
+
+ if (kindex + 1 >= lenarray(karray)) then
+ kindex = 0
+ else
+ kindex += 1
+ endif
+ kcount += 1
+ ktime += gkseq_quartertime
+ od
+ endif
+endop
+
+
+/*
+ Parametric array sequencer. Treat each index of the first dimension as a 16th. The second dimension is as follows:
+ 0 trigger (1 is active)
+ 1 duration in seconds
+ 2 p4 onwards (can be as many p-fields as required to be passed to the despatch instrument)
+
+ seq_array2d SdespatchI, karray[][], kswing
+
+ SdespatchI name of the instrument to trigger
+ karray[][] 2D array of sequence information
+ kswing swing amount (0 to 1)
+*/
+opcode seq_array2d, 0, Sk[][]k
+ SdespatchI, karray[][], kswing xin
+ kindex init 0
+ if (gkseq_beat == 1) then
+ ktime = 0
+ kcount = 0
+ while (kcount < 4) do
+ if (karray[kindex][0] == 1) then
+ krow[] getrow karray, kindex
+ Scoreline sprintfk "i\"%s\" %f %f ", SdespatchI, seq_swingtime(ktime, kcount, kswing), krow[1]
+ if (lenarray(krow) > 2) then
+ kdx = 2
+ while (kdx < lenarray(krow)) do
+ Scoreline strcatk Scoreline, sprintfk("%f ", krow[kdx])
+ kdx += 1
+ od
+ endif
+ scoreline Scoreline, 1
+ endif
+
+ if (kindex + 1 >= lenarray(karray)) then
+ kindex = 0
+ else
+ kindex += 1
+ endif
+ kcount += 1
+ ktime += gkseq_quartertime
+ od
+ endif
+endop
+
+
+
+/*
+ Freak sequencer. Three iterations of generative triggers sent to despatch instruments
+
+ seq_freak SdespatchI1, SdespatchI2, SdespatchI3, kbeatdensity[], kbeatstrength[]
+
+ SdespatchI1 name of the primary instrument to trigger
+ SdespatchI2 name of the secondary instrument to trigger
+ SdespatchI3 name of the tertiary instrument to trigger
+*/
+opcode seq_freak, 0, SSSk[]k[]
+ SdespatchI1, SdespatchI2, SdespatchI3, kbeatdensity[], kbeatstrength[] xin
+
+ kbeat init 0
+ ktrig init 0
+ if (gkseq_beat == 1) then ; only on every 1st of 4 beats
+ if (kbeat == 0) then
+ ktrig = 1
+ kbeat += 1
+ elseif (kbeat == 3) then
+ kbeat = 0
+ else
+ kbeat += 1
+ endif
+
+ endif
+
+ if (ktrig == 1 && random:k(0, 1) < kbeatdensity[0]) then
+ ktrig = 0
+ ktime = 0
+ event "i", SdespatchI1, ktime, 1, random:k(kbeatstrength[0]*0.2, kbeatstrength[0])
+ kindex1 = 0
+ kitems1 random 0, 8
+ while (kindex1 < kitems1) do
+ if (random:k(0, 1) < kbeatdensity[1]) then
+ event "i", SdespatchI2, ktime, 1, random:k(kbeatstrength[1]*0.2, kbeatstrength[1])
+ endif
+ ktime2 = ktime + (gkseq_beattime/8/8)
+ kindex2 = 0
+ kitems2 random 0, 32
+ while (kindex2 < kitems2) do
+ if (random:k(0, 1) < kbeatdensity[2]) then
+ event "i", SdespatchI3, ktime2, 1, random:k(kbeatstrength[2]*0.2, kbeatstrength[2])
+ endif
+ ktime2 += gkseq_beattime/8/8
+ kindex2 += 1
+ od
+
+ ktime += gkseq_beattime/8
+ kindex1 += 1
+ od
+ endif
+endop
+
+
+/*
+ Bar and bargroup management
+
+ Each beat is counted upon a gkseq_beat trigger.
+ When the number of beats implies a bar has completed, the trigger gkseq_bar_trig is fired.
+ The position in the bar (beat number (0 to giseq_barlength - 1)) is available in gkseq_barbeat.
+
+ When the number of bars implies a bargroup has completed, the trigger gkseq_bargroup_trig is fired.
+ The position in the bargroup (bar number (0 to giseq_bargrouplength - 1)) is available in gkseq_bargroup.
+*/
+gkseq_bar_trig init 0 ; trigger
+gkseq_barbeat init -1 ; number
+giseq_barlength = 4
+
+gkseq_bargroup_trig init 0 ; trigger
+gkseq_bargroup init -1 ; number
+giseq_bargrouplength = 4
+
+instr _barmanager
+ kbarbeat init 0
+ gkseq_bar_trig = 0
+ gkseq_bargroup_trig = 0
+
+ if (gkseq_beat == 1) then
+ gkseq_barbeat = (gkseq_barbeat < (giseq_barlength-1) ? gkseq_barbeat + 1 : 0)
+ if (gkseq_barbeat == 0) then
+ gkseq_bar_trig = 1
+ gkseq_bargroup = (gkseq_bargroup < (giseq_bargrouplength-1) ? gkseq_bargroup + 1 : 0)
+ if (gkseq_bargroup == 0) then
+ gkseq_bargroup_trig = 1
+ endif
+ endif
+ endif
+endin
+;alwayson "_barmanager" ; not available in web api
+schedule("_barmanager", 0, -1)
+
+
+/*
+ metronome; first beat has a higher pitch
+
+ aout metronome [iamp=1]
+
+ aout metronome output
+ iamp optional amplitude
+*/
+opcode metronome, a, p
+ iamp xin
+ kamp loopseg gkseq_tempo/60, gkseq_beat, 0, 1, 0.2, 0, 0.8, 0, 0
+ aout oscil 0.1, (gkseq_barbeat == 0 ? 2000 : 1000)
+ xout aout * kamp * iamp
+endop
+
+
+#end
diff --git a/site/udo/sequencing_melodic.udo b/site/udo/sequencing_melodic.udo
new file mode 100755
index 0000000..d4a5205
--- /dev/null
+++ b/site/udo/sequencing_melodic.udo
@@ -0,0 +1,807 @@
+#ifndef UDO_MELSEQUENCING
+#define UDO_MELSEQUENCING ##
+
+/*
+ Melodic pattern sequencer base
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+#include "__config__.udo" ; using fftsize for tuning
+#include "chords.udo" ; chord data
+#include "sequencing.udo" ; sequencer base
+#include "interop.udo" ; for updating host with outvalue
+#include "wavetables.udo" ; for tuning
+
+; if these are set, then don't launch the manager automatically. sequencing_melodic_persistence will load accordingly
+#ifdef MEL_INITPATH
+ #define MEL_HASINIT ##
+#end
+#ifdef MEL_INITDB
+ #define MEL_HASINIT ##
+#end
+
+;-------------------------internal-globals--------------------------------------------------------------------------
+
+gimel_number init 12 ; number of melodic sections available
+
+gimel_state ftgen 0, 0, -4, -7, 0 ; state: current section, next section, current_step (gimel_number)
+gimel_chords ftgen 0, 0, -gimel_number, -7, 0 ; chord indexes from melodic.udo for each section
+gimel_notes ftgen 0, 0, -gimel_number, -7, 0 ; midi note numbers for each section
+gimel_lengths ftgen 0, 0, -gimel_number, -7, 0 ; lengths in beats for each section
+gimel_action1 ftgen 0, 0, -gimel_number, -7, 0 ; follow action 1 for each section: 0 = same, 1 = next, 2 = previous, 3 = random, 4 = specific section (section index + 4)
+gimel_action2 ftgen 0, 0, -gimel_number, -7, 0 ; follow action 2 for each section
+gimel_actionthreshold ftgen 0, 0, -gimel_number, -7, 0 ; follow action threshold - below 0.5 is action1, above is action2
+gimel_active ftgen 0, 0, -gimel_number, -7, 0 ; whether each section is active or to be ignored
+gimel_importance ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary section importance , 0 to 1
+gimel_mod1 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 1, 0 to 1
+gimel_mod2 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 2, 0 to 1
+gimel_mod3 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 3, 0 to 1
+gimel_mod4 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 4, 0 to 1
+gimel_centadd ftgen 0, 0, -gimel_number, -7, 0 ; microtonal midi note additions (0 = no change; 1 = add one semitone; 0.01 = add one cent)
+
+gimel_future ftgen 0, 0, -8, -7, 0 ; future sections: 8 in the future
+gimel_current_notes ftgen 0, 0, -13, -7, 0 ; current notes: index 0 is the length
+gimel_next_notes ftgen 0, 0, -13, -7, 0 ; next notes: index 0 is the length
+gimel_temp_random ftgen 0, 0, -gimel_number, -7, 0 ; temp storage for pattern randomisation
+
+gkmel_section_change init 0 ; section change trigger
+gkmel_section_change_due init 0 ; how many beats until next section change
+gkmel_futures_refresh_trig init 0 ; trigger to set if futures are to be recalculated
+
+; user modifiable variables
+gkmel_pause init 0 ; pause progression changes
+gkmel_advance_trig init 0 ; manual progression advance trigger
+gSmel_details = "" ; notes associated with progression included in save/load operations
+
+
+; names and references for persistence and introspection: essentially the tables to be saved
+gSmel_names[] fillarray "chords", "notes", "lengths", "action1", "action2",\
+ "actionthreshold", "active", "importance", "mod1", "mod2", "mod3", "mod4", "centadd"
+gimel_fns[] fillarray gimel_chords, gimel_notes, gimel_lengths, gimel_action1, gimel_action2,\
+ gimel_actionthreshold, gimel_active, gimel_importance, gimel_mod1, gimel_mod2, gimel_mod3, gimel_mod4, gimel_centadd
+
+
+
+;-----------------------------opcodes-------------------------------------------------------------------------------
+
+/*
+ Refresh the actions list: static actions and pattern references
+*/
+Smel_baseactions[] fillarray "Same", "Next", "Previous", "Random"
+gSmel_actions[] init lenarray(Smel_baseactions) + gimel_number
+index = 0
+while (index < lenarray(gSmel_actions)) do
+ if (index < 4) then
+ gSmel_actions[index] = Smel_baseactions[index]
+ else
+ gSmel_actions[index] = sprintf("Section %d", index - 3)
+ endif
+ index += 1
+od
+
+
+
+/*
+ Send JSON formatted information on current setup to API host
+*/
+/*
+ legacy version
+
+instr mel_updatehost ; use p4 for channel?
+ Sjson = json_init()
+ Sjson = json_appendvalue(Sjson, "sections", gimel_number)
+ Sjson = json_appendarray(Sjson, "chordnames", gSchords)
+ Sjson = json_appendarray(Sjson, "actiontypes", gSmel_actions)
+
+ SjsonFns = json_init()
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ SjsonFns = json_appendvalue(SjsonFns, gSmel_names[index], gimel_fns[index])
+ index += 1
+ od
+ Sjson = json_appendobject(Sjson, "ftables", SjsonFns)
+
+ io_sendstring("mel_state", Sjson)
+ turnoff
+endin
+*/
+
+
+/*
+ Send JSON formatted information on current setup to API host
+*/
+instr mel_updatehost
+ icbid = p4
+ iJson jsoninit
+ jsoninsertval iJson, "mel_number", gimel_number
+ jsoninsertval iJson, "chordnames", gSchords
+ jsoninsertval iJson, "actiontypes", gSmel_actions
+ jsoninsertval iJson, "cbid", icbid
+
+ iJsonFns jsoninit
+ jsoninsertval iJsonFns, gSmel_names, gimel_fns
+ jsoninsert iJson, "ftables", iJsonFns
+ jsondestroy(iJsonFns)
+ io_sendstring("callback", jsondumps(iJson, 1))
+ turnoff
+endin
+
+
+
+
+
+
+/*
+ Get modulation parameters for current section
+
+ imod1, imod2, imod3, imod4 mel_currentmod
+
+ imod1 modulation parameter 1
+ imod2 modulation parameter 2
+ imod3 modulation parameter 3
+ imod4 modulation parameter 4
+*/
+opcode mel_currentmod, iiii, 0
+ icur = table:i(0, gimel_state)
+ xout table:i(icur, gimel_mod1), table:i(icur, gimel_mod2), table:i(icur, gimel_mod3), table:i(icur, gimel_mod4)
+endop
+
+
+/*
+ Get modulation parameters for current section
+
+ kmod1, kmod2, kmod3, kmod4 mel_currentmod
+
+ kmod1 modulation parameter 1
+ kmod2 modulation parameter 2
+ kmod3 modulation parameter 3
+ kmod4 modulation parameter 4
+*/
+opcode mel_currentmod, kkkk, 0
+ kcur = table:k(0, gimel_state)
+ xout table:k(kcur, gimel_mod1), table:k(kcur, gimel_mod2), table:k(kcur, gimel_mod3), table:k(kcur, gimel_mod4)
+endop
+
+
+/*
+ Get the root midi note from the current section chord
+
+ inote mel_rootnote
+
+ inote root note from current chord
+*/
+opcode mel_rootnote, i, 0
+ xout table:i(1, gimel_current_notes)
+endop
+
+/*
+ Get a random midi note from the current section chord
+
+ inote mel_randomnote
+
+ inote random note from current chord
+*/
+opcode mel_randomnote, i, 0
+ ilen = table:i(0, gimel_current_notes)
+ index = round(random(1, ilen-1))
+ xout table:i(index, gimel_current_notes)
+endop
+
+
+/*
+ Get a random midi note from the current section chord
+
+ knote mel_randomnote
+
+ knote random note from current chord
+*/
+opcode mel_randomnote, k, 0
+ klen = table:k(0, gimel_current_notes)
+ kindex = round:k(random:k(1, klen-1))
+ xout table:k(kindex, gimel_current_notes)
+endop
+
+
+/*
+ Get the current section at k-rate
+
+ ksection _mel_currentsectionget
+
+ ksection current section
+*/
+opcode _mel_currentsectionget, k, 0
+ xout table:k(0, gimel_state)
+endop
+
+
+/*
+ Get the next section at k-rate
+
+ ksection _mel_nextsectionget
+
+ ksection next section
+*/
+opcode _mel_nextsectionget, k, 0
+ xout table:k(0, gimel_future)
+endop
+
+
+/*
+ Set the current section at k-rate
+
+ _mel_currentsectionset ksection
+
+ ksection current section to set
+*/
+opcode _mel_currentsectionset, 0, k
+ ksection xin
+ tablew ksection, 0, gimel_state
+endop
+
+
+/*
+ Get the current section at init time
+
+ isection _mel_currentsectionget
+
+ usection current section
+*/
+opcode _mel_currentsectionget, i, 0
+ xout table:i(0, gimel_state)
+endop
+
+
+/*
+ Get the length of the current section in seconds
+
+ iseconds mel_length
+
+ iseconds length in seconds
+*/
+opcode mel_length, i, 0
+ xout table:i(_mel_currentsectionget:i(), gimel_lengths) * i(gkseq_beattime)
+endop
+
+
+/*
+ Get the length of the current section in seconds
+
+ kseconds mel_length
+
+ kseconds length in seconds
+*/
+opcode mel_length, k, 0
+ xout table:k(_mel_currentsectionget:k(), gimel_lengths) * gkseq_beattime
+endop
+
+
+/*
+ Get the current MIDI note numbers as an array
+ inotes[] mel_currentnotes
+
+ inotes[] the note numbers
+*/
+opcode mel_currentnotes, i[], 0
+ ilen = table:i(0, gimel_current_notes)
+ iout[] init ilen
+ index = 0
+ while (index < ilen) do
+ iout[index] = table:i(index+1, gimel_current_notes)
+ index += 1
+ od
+ xout iout
+endop
+
+
+
+/*
+ Get the most important entry from futures table
+
+ kbestindex, kimportance, kbeats mel_future_mostimportant
+
+ kbestindex index in gimel_future
+ kimportance the importance measure
+ kbeats number of beats until the event occurs
+*/
+opcode mel_future_mostimportant, kkk, 0
+ kindex = 0
+ kimportance = -9999
+ kbestindex = 0
+ kbeats = table:k(table:k(0, gimel_state), gimel_lengths) ; current duration base
+ while (kindex < ftlen(gimel_future)) do
+ ksection = table:k(kindex, gimel_future)
+ kimportancetemp = table:k(ksection, gimel_importance)
+ if (kimportancetemp > kimportance) then
+ kimportance = kimportancetemp
+ kbestindex = kindex
+ endif
+ kindex += 1
+ od
+
+ kindex = 0
+ while (kindex < kbestindex) do
+ kbeats += table:k(table:k(kindex, gimel_future), gimel_lengths)
+ kindex += 1
+ od
+
+ xout kbestindex, kimportance, kbeats ; * gkseq_beattime
+endop
+
+
+/*
+ Get the most important entry from futures table
+
+ ibestindex, iimportance, ibeats mel_future_mostimportant
+
+ ibestindex index in gimel_future
+ importance the importance measure
+ ibeats number of beats until the event occurs
+*/
+opcode mel_future_mostimportant, iii, 0
+ index = 0
+ importance = -9999
+ ibestindex = 0
+ ibeats = table:i(table:i(0, gimel_state), gimel_lengths) ; current duration base
+ while (index < ftlen(gimel_future)) do
+ isection = table:i(index, gimel_future)
+ importancetemp = table:i(isection, gimel_importance)
+ if (importancetemp > importance) then
+ importance = importancetemp
+ ibestindex = index
+ endif
+ index += 1
+ od
+
+ index = 0
+ while (index < ibestindex) do
+ ibeats += table:i(table:i(index, gimel_future), gimel_lengths)
+ index += 1
+ od
+ xout ibestindex, importance, ibeats ; * i(gkseq_beattime)
+endop
+
+
+
+/*
+ Calculate the next section from a given section
+
+ knext _mel_calculatenext kcurrent
+
+ knext the calculated next section index
+ kcurrent the section index to base the calculation upon
+*/
+opcode _mel_calculatenext, k, k
+ kthissection xin
+ knextsection = -1
+
+ if (random:k(0, 1) <= table:k(kthissection, gimel_actionthreshold)) then
+ knextaction = table:k(kthissection, gimel_action2)
+ else
+ knextaction = table:k(kthissection, gimel_action1)
+ endif
+
+
+ ; if current is not active, go to next ?
+ kcurrentactive = table:k(kthissection, gimel_active)
+ if (kcurrentactive == 0 && knextaction == 0) then
+ knextaction = 1
+ endif
+
+ ; same
+ if (knextaction == 0) then
+ knextsection = kthissection
+
+ ; next or previous
+ elseif (knextaction >= 1 && knextaction <= 3) then ; specified action
+ kcount = 0
+ kactive = 0
+ knextsection = kthissection
+ while (kactive == 0 && kcount < gimel_number) do ; loop until active section found or all sections checked
+
+ if (knextaction == 1) then ; next
+ if (knextsection + 1 > gimel_number - 1) then
+ knextsection = 0
+ else
+ knextsection += 1
+ endif
+
+ elseif (knextaction == 2) then ; previous
+ if (knextsection -1 < 0) then
+ knextsection = gimel_number - 1
+ else
+ knextsection -= 1
+ endif
+ endif
+
+ kactive = table:k(knextsection, gimel_active)
+ kcount += 1
+ od
+
+ ; random
+ elseif (knextaction == 3) then
+ kindex = 0
+ krandmax = 0
+ while (kindex < gimel_number) do
+ if (table:k(kindex, gimel_active) == 1) then
+ tablew kindex, krandmax, gimel_temp_random
+ krandmax += 1
+ endif
+ kindex += 1
+ od
+
+ knextsection = table:k(round(random(0, krandmax - 1)), gimel_temp_random)
+
+ ; specific section
+ elseif (knextaction >= 4) then ; specific active pattern
+ if (table:k(knextaction - 4, gimel_active) == 1) then
+ knextsection = knextaction - 4
+ else
+ knextsection = kthissection
+ endif
+ endif
+ xout knextsection
+endop
+
+
+/*
+ Set gimel_next_notes from the first entry in the futures table
+*/
+opcode _mel_setnextnotes, 0, 0
+ knext = table:k(0, gimel_future)
+ chordmidibyindextof gimel_next_notes, table:k(knext, gimel_chords), table:k(knext, gimel_notes), table:k(knext, gimel_centadd)
+endop
+
+
+/*
+ Pop the next future entry from the futures table, move all future entries down one
+ and add a new calculated entry accordingly
+
+ kcurrent _mel_future_pop
+
+ kcurrent the current section to be used now
+*/
+opcode _mel_future_pop, k, 0
+ imax = ftlen(gimel_future)
+ kcurrent = table:k(0, gimel_future)
+
+
+ kindex = 0
+ while (kindex < imax - 1) do
+ tablew table:k(kindex + 1, gimel_future), kindex, gimel_future
+ kindex += 1
+ od
+
+ ; write new last entry
+ tablew _mel_calculatenext(table:k(kindex, gimel_future)), imax - 1, gimel_future
+
+ _mel_setnextnotes()
+
+ xout kcurrent
+endop
+
+
+/*
+ Recalculate the futures table (in the event of parameters being changed at runtime etc)
+*/
+opcode _mel_futures_refresh, 0, O
+ kindexStart xin ; usually 0, can be a start index (ie 1 leaves the first entry in place)
+ kindex = kindexStart
+ imax = ftlen(gimel_future)
+ ; TODO do first, etc
+ while (kindex < imax) do
+ if (kindex == 0) then
+ kcurrent = table:k(0, gimel_state) ; 0 ; get current, rather than 0...
+ else
+ kcurrent = table:k(kindex - 1, gimel_future)
+ endif
+
+ tablew _mel_calculatenext(kcurrent), kindex, gimel_future
+ kindex += 1
+ od
+
+ _mel_setnextnotes()
+endop
+
+
+/*
+ Set next section, for host control
+
+ p4 section number to set as next
+*/
+instr mel_setnextsection
+ isection = p4
+ if (table:i(isection, gimel_active) == 1) then
+ tablew isection, 0, gimel_future
+ gkmel_futures_refresh_trig = 2
+ endif
+ turnoff
+endin
+
+
+/*
+ Refresh the futures table, for host control
+*/
+instr mel_futures_refresh
+ gkmel_futures_refresh_trig = 1
+ turnoff
+endin
+
+
+/*
+ Randomise all section parameters
+*/
+opcode _mel_randomise, 0, 0
+ index = 0
+ iactives[] init 4 + gimel_lengths
+ iactivenum = 4
+ while (index < gimel_number) do
+ tablew round(random(0, lenarray(gSchords) - 1)), index, gimel_chords
+ tablew round(random(4, 8)), index, gimel_lengths
+ tablew round(random(54, 70)), index, gimel_notes
+ tablew random(0, 1), index, gimel_actionthreshold
+ tablew random(0, 1), index, gimel_importance
+ tablew random(0, 1), index, gimel_mod1
+ tablew random(0, 1), index, gimel_mod2
+ tablew random(0, 1), index, gimel_mod3
+ tablew random(0, 1), index, gimel_mod4
+ tablew 0, index, gimel_centadd ; always regular to begin with
+
+
+ iactive = round(random(0, 1))
+ if (iactive == 1) then
+ iactives[iactivenum-1] = iactive
+ iactivenum += 1
+ endif
+ tablew iactive, index, gimel_active
+ index += 1
+ od
+
+ ; set next action to only active sections
+ index = 0
+ while (index < gimel_number) do
+ iaction1 = iactives[round(random(0, iactivenum))]
+ iaction2 = iactives[round(random(0, iactivenum))]
+;iaction1 = 1
+;iaction2 = 1
+ tablew iaction1, index, gimel_action1
+ tablew iaction2, index, gimel_action2
+ index += 1
+ od
+endop
+
+
+/*
+ Randomise all section parameters and update the host
+*/
+instr mel_randomise
+ icbid = p4
+ _mel_randomise()
+ gkmel_futures_refresh_trig = 1
+ event_i "i", "mel_updatehost", 0, 1, icbid
+ turnoff
+endin
+
+
+/*
+ Pause progression, for host control
+*/
+instr mel_pause
+ gkmel_pause = p4
+ turnoff
+endin
+
+
+/*
+ Advance progression, for host control
+*/
+instr mel_advance
+ gkmel_advance_trig = 1
+ turnoff
+endin
+
+
+/*
+ Advance progression if paused, for host control
+*/
+instr mel_advanceifpaused
+ if (gkmel_pause == 1) then
+ gkmel_advance_trig = 1
+ endif
+ turnoff
+endin
+
+
+/*
+ Get the length of the current progression, if there are two of the same progression consecutively, sum those
+
+ klength mel_nextchangelength
+
+ klength cumulative length taking into account consecutive same sections
+*/
+opcode mel_nextchangelength, k, 0
+ kcurrent = _mel_currentsectionget:k()
+ klength = table:k(kcurrent, gimel_lengths)
+
+ imaxfutures = ftlen(gimel_future)
+ kindex = 0
+ while (kindex < imaxfutures) do
+ ksection = table:k(kindex, gimel_future)
+ if (ksection != kcurrent) kgoto complete
+ klength += table:k(ksection, gimel_lengths)
+ kindex += 1
+ od
+complete:
+ xout klength
+endop
+
+
+
+
+/*
+ Call Sinstrument when ktrig is fired, for each note (passed as p4) and the current section length accordingly
+ mel_eachnote Sinstrument, ktrig[, klength = mel_length:k()]
+
+ Sinstrument the instrument name to call
+ ktrig trigger to active call
+ klength duration of instrument to call, defaulting to mel_length:k()
+
+*/
+opcode mel_eachnote, 0, SkJ
+ Sinstrument, ktrig, klength xin
+ if (ktrig == 1) then
+ kdur = (klength == -1 ) ? mel_nextchangelength:k() * gkseq_beattime : klength
+ kindex = 0
+ while (kindex < table:k(0, gimel_current_notes)) do
+ schedulek Sinstrument, 0, kdur, table:k(kindex + 1, gimel_current_notes)
+ kindex += 1
+ od
+ endif
+endop
+
+/*
+ Initialise the sequencer sections; monitor for gkseq_beat triggers and change sections accordingly
+*/
+instr _mel_manager
+#ifndef MEL_HASINIT
+ _mel_randomise()
+#end
+
+ ksectionlength init 0
+ gkmel_futures_refresh_trig init 1
+
+ if (gkmel_futures_refresh_trig != 0) then
+ _mel_futures_refresh(gkmel_futures_refresh_trig - 1) ; if gkmel_futures_refresh_trig is 2, then omit first, otherwise recalculate all
+ gkmel_futures_refresh_trig = 0
+ ksectionlength = mel_nextchangelength:k()
+ endif
+
+ kstep init 0
+ gkmel_section_change = 0
+
+ kmanualadvance = 0
+ if (gkmel_advance_trig == 1) then
+ kmanualadvance = 1
+ gkmel_advance_trig = 0
+ endif
+
+ if ((gkseq_beat == 1 && gkmel_pause == 0) || kmanualadvance == 1) then
+ if (kstep == 0 || kmanualadvance == 1) then
+ kcurrent = _mel_currentsectionget:k()
+ tablecopy gimel_current_notes, gimel_next_notes
+ knew = _mel_future_pop:k()
+ _mel_currentsectionset(knew)
+
+ ; only send if actually changed
+ if (kcurrent != knew) then
+ io_send("mel_current", knew) ; send current (from next)
+ gkmel_section_change = 1
+ ksectionlength = mel_nextchangelength:k()
+ endif
+ endif
+
+ gkmel_section_change_due = ksectionlength - kstep
+
+ if (kstep < ksectionlength - 1) then ; current step < current length
+ kstep += 1
+ else
+ kstep = 0
+ endif
+
+ endif ; end each beat
+
+
+endin
+
+#ifndef MEL_HASINIT
+alwayson "_mel_manager"
+#end
+
+
+
+/*
+ Extend the current notes and convert to frequency, multiplying by powers of two to be used in mel_tune
+ ifreqs[] _mel_tune_noteprepare inotes[], imult
+
+ ifreqs[] resulting frequencies
+ inotes[] input midi note numbers
+ imult number of times to multiply note contents
+
+*/
+opcode _mel_tune_noteprepare, i[], i[]i
+ iarr[], imult xin
+ inew[] init lenarray(iarr) * imult
+ indexnew = 0
+ index = 0
+ while (index < lenarray(iarr)) do
+ ifreq = cpsmidinn(iarr[index])
+ index2 = 0
+ while (index2 < imult) do
+ if (index2 > 0) then
+ inew[indexnew] = ifreq * (2* (index2+1))
+ else
+ inew[indexnew] = ifreq
+ endif
+ index2 += 1
+ indexnew += 1
+ od
+
+ index += 1
+ od
+ xout inew
+endop
+
+
+/*
+ Create a chord with the specified frequencies
+ aout _mel_tune_chord ifreqs[] [, ifn, index]
+
+ aout resulting chord
+ ifreqs[] frequencies to play
+ ifn wavetable to play with, default = gifnSine
+ index internal index usage for recursion
+*/
+opcode _mel_tune_chord, a, i[]oo
+ ifreqs[], ifn, index xin
+ ifn = (ifn == 0) ? gifnSine : ifn
+ aout = oscil(0.1, ifreqs[index], ifn)
+ if (index < lenarray(ifreqs) - 1) then
+ aout += _mel_tune_chord(ifreqs, ifn, index + 1)
+ endif
+ xout aout
+endop
+
+
+/*
+ Stereo tuning to current melodic sequencer notes
+ aoutL, aoutR mel_tune ainL, ainR, ifn, imult [, ifftrate, ifftdiv]
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ ifn wavetable to use
+ imult multiples of harmonics to generate in tuning
+ ifftrate fft size, defaults to config default
+ ifftdiv fft window division factor (eg 4, 8, 16), defaults to config default
+*/
+opcode mel_tune, aa, aaiioo
+ aL, aR, ifn, imult, ifftrate, ifftdiv xin
+ ifftrate = (ifftrate == 0) ? giFFTsize : ifftrate
+ ifftdiv = (ifftdiv == 0) ? giFFTwinFactor : ifftdiv
+ ifreqs[] _mel_tune_noteprepare mel_currentnotes(), imult
+ fmods pvsanal _mel_tune_chord(ifreqs, ifn), ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL1 pvsanal aL, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fR1 pvsanal aR, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL2 pvsmorph fL1, fmods, 0, 1
+ fR2 pvsmorph fR1, fmods, 0, 1
+ aL1 pvsynth fL2
+ aR1 pvsynth fR2
+ idel = (ifftrate+2)/sr
+ aL1 balance aL1, delay(aL, idel)
+ aR1 balance aR1, delay(aR, idel)
+ xout aL1, aR1
+endop
+
+#end
diff --git a/site/udo/sequencing_melodic_persistence.udo b/site/udo/sequencing_melodic_persistence.udo
new file mode 100755
index 0000000..0709832
--- /dev/null
+++ b/site/udo/sequencing_melodic_persistence.udo
@@ -0,0 +1,275 @@
+#ifndef UDO_MELSEQUENCINGPERSIST
+#define UDO_MELSEQUENCINGPERSIST ##
+/*
+ Melodic sequencer persistence: saving/loading from files and database
+ Requires JSON opcodes
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "pgdb.udo"
+#include "sequencing_melodic.udo"
+#include "array_tools.udo"
+#include "interop.udo"
+
+
+/*
+ Get the current state as a JSON object
+
+ iJson mel_getstate_json
+
+ iJson the JSON object containing current sequencing and progression data
+
+*/
+opcode mel_getstate_json, i, 0
+ iJson jsoninit
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ iarray[] tab2array gimel_fns[index]
+ jsoninsertval iJson, gSmel_names[index], iarray
+ index += 1
+ od
+ iarray[] tab2array gimel_state
+ jsoninsertval iJson, "state", iarray
+ jsoninsertval iJson, "details", gSmel_details
+ jsoninsertval iJson, "mel_number", gimel_number
+ jsoninsertval iJson, "seq_tempo", i(gkseq_tempo)
+ jsoninsertval iJson, "seq_swing", i(gkseq_swing)
+ xout iJson
+endop
+
+
+/*
+ Set the current sequencing and progression state
+
+ mel_setstate_json iJson
+
+ iJson JSON object containing state data
+
+*/
+opcode mel_setstate_json, 0, i
+ iJson xin
+
+ gSmel_details jsongetval iJson, "details"
+ gimel_number jsongetval iJson, "mel_number"
+ itempo jsongetval iJson, "seq_tempo"
+ iswing jsongetval iJson, "seq_swing"
+
+#ifdef MEL_INITTIME
+ gkseq_tempo init itempo
+ gkseq_swing init iswing
+#end
+
+ ; if data to be loaded has more progression items than ftables, free and generate again
+ if (gimel_number > ftlen(gimel_fns[0])) then
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ ftfree gimel_fns[index], 0
+ gimel_fns[index] = ftgen(0, 0, -gimel_number, -7, 0)
+ od
+ endif
+
+
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ iJsonSub jsonget iJson, gSmel_names[index]
+ iarray[] jsonarrval iJsonSub
+ copya2ftab iarray, gimel_fns[index]
+ jsondestroy iJsonSub
+ index += 1
+ od
+
+
+ iJsonSub jsonget iJson, "state"
+ iarray[] jsonarrval iJsonSub
+ copya2ftab iarray, gimel_state
+ jsondestroy(iJsonSub)
+
+endop
+
+
+
+/*
+ Save the sequencing and progression state to a file
+
+ p4 path to save file to
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_savestate_fs
+ Spath = p4
+ icbid = p5
+ iJson = mel_getstate_json()
+ jsondump(iJson, Spath)
+ jsondestroy(iJson)
+ if (icbid != 0) then
+ schedule("io_callback", 0, 1, icbid)
+ endif
+ turnoff
+endin
+
+
+
+opcode mel_loadstate_fs, 0, S
+ Spath xin
+ iJson jsonload Spath
+ mel_setstate_json(iJson)
+ jsondestroy(iJson)
+endop
+/*
+ Load the sequencing and progression state from a file
+
+ p4 path to load data from
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_loadstate_fs
+ Spath = p4
+ icbid = p5
+ mel_loadstate_fs Spath
+ if (icbid != 0) then
+ schedule("io_callback", 0, 1, icbid)
+ endif
+ schedule("mel_futures_refresh", 0, 1)
+ turnoff
+endin
+
+
+
+opcode mel_savestate_db, 0, S
+ Sname xin
+ iJson = mel_getstate_json()
+ pgdb_json_save Sname, "melsys", iJson
+ jsondestroy(iJson)
+endop
+/*
+ Save the sequencing and progression state to database
+
+ p4 reference name in database
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_savestate_db
+ Sname = p4
+ icbid = p5
+ mel_savestate_db Sname
+ if (icbid != 0) then
+ schedule("io_callback", 0, 1, icbid)
+ endif
+ turnoff
+endin
+
+
+
+opcode mel_loadstate_db, 0, S
+ Sname xin
+ iJson pgdb_json_load Sname, "melsys"
+ mel_setstate_json(iJson)
+ jsondestroy(iJson)
+endop
+/*
+ Load the sequencing and progression state from database
+
+ p4 reference name in database
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_loadstate_db
+ Sname = p4
+ icbid = p5
+ mel_loadstate_db Sname
+ if (icbid != 0) then
+ schedule("io_callback", 0, 1, icbid)
+ endif
+ schedule("mel_futures_refresh", 0, 1)
+ turnoff
+endin
+
+
+
+/*
+ Load the sequencing and progression state from a string channel
+
+ p4 channel name containing string representation of JSON
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_loadstate_channel
+ Schannel = p4
+ icbid = p5
+ Sdata chnget Schannel
+ iJson jsonloads Sdata
+ mel_setstate_json(iJson)
+ jsondestroy(iJson)
+ if (icbid != 0) then
+ schedule("io_callback", 0, 1, icbid)
+ endif
+ schedule("mel_futures_refresh", 0, 1)
+ turnoff
+endin
+
+
+/*
+ Just get state: interop host handles persistence
+ p4 callback ID to send data with/to
+*/
+instr mel_getstate_string
+ icbid = p4
+ iJson = mel_getstate_json()
+ jsoninsertval iJson, "cbid", icbid
+ Sjson = jsondumps(iJson, 0)
+ io_sendstring("callback", Sjson)
+ turnoff
+endin
+
+
+/*
+ Get an array of the known mel states from database
+ Sdata[] mel_liststates_db
+
+ Sdata[] the state names
+*/
+opcode mel_liststates_db, S[], 0
+ Sresult[][] dbarray gidb, "SELECT name FROM savejson WHERE unit = 'melsys'"
+ ilen = lenarray(Sresult)
+ Sdata[] init ilen
+ index = 0
+ while (index < ilen) do
+ Sdata[index] = Sresult[index][0]
+ index += 1
+ od
+ xout Sdata
+endop
+
+
+/*
+ Get a list of mel states from database and return to host with the specified callback ID
+
+ p4 callback ID
+*/
+instr mel_liststates_db
+ icbid = p4
+ iJson = jsoninit()
+ jsoninsertval(iJson, "cbid", icbid)
+ jsoninsertval(iJson, "states", mel_liststates_db())
+ io_sendstring("callback", jsondumps(iJson, 0))
+ turnoff
+endin
+
+
+; if MEL_INITPATH or MEL_INITDB is set, load the specified progression data accordingly
+#ifdef MEL_HASINIT
+instr _mel_persistence_init
+#ifdef MEL_INITPATH
+ subinstrinit "mel_loadstate_fs", "$MEL_INITPATH"
+#end
+#ifdef MEL_INITDB
+ ;mel_loadstate_db "$MEL_INITDB"
+ subinstrinit "mel_loadstate_db", "$MEL_INITDB"
+#end
+ alwayson "_mel_manager"
+ turnoff
+endin
+schedule "_mel_persistence_init", 0, 60
+
+; end MEL_HASINIT
+#end
+
+#end
diff --git a/site/udo/sequencing_melodic_persistence.web.udo b/site/udo/sequencing_melodic_persistence.web.udo
new file mode 100755
index 0000000..01d005f
--- /dev/null
+++ b/site/udo/sequencing_melodic_persistence.web.udo
@@ -0,0 +1,213 @@
+#ifndef UDO_MELSEQUENCINGPERSIST
+#define UDO_MELSEQUENCINGPERSIST ##
+/*
+ Melodic sequencer persistence: saving/loading from files and database
+ Web version
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022, 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "/sequencing_melodic.udo"
+#include "/array_tools.udo"
+#include "/table_tools.udo"
+#include "/interop.udo"
+#include "/json.udo"
+
+
+/*
+ Get the current state as a JSON object
+
+ iJson mel_getstate_json
+
+ iJson the JSON object containing current sequencing and progression data
+
+*/
+opcode mel_getstate_json, S, 0
+ Sjson = "{"
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ Sjson = strcat(Sjson, sprintf("\"%s\":%s,", gSmel_names[index], tab_serialise(gimel_fns[index])))
+ index += 1
+ od
+
+ Sjson = strcat(Sjson, sprintf("\"state\":%s,", tab_serialise(gimel_state)))
+ Sjson = strcat(Sjson, sprintf("\"details\":\"%s\",\"mel_number\":%d,\"seq_tempo\":%f,\"seq_swing\":%f}", gSmel_details, gimel_number, i(gkseq_tempo), i(gkseq_swing)))
+ xout Sjson
+endop
+
+
+/*
+ Set the current sequencing and progression state
+
+ mel_setstate_json SJson
+
+ SJson JSON string containing state data
+
+*/
+opcode mel_setstate_json, 0, S
+ Sjson xin
+
+ i_, gSmel_details, i_ json_parse Sjson, "details"
+ i_, S_, gimel_number json_parse Sjson, "mel_number"
+ i_, S_, itempo json_parse Sjson, "seq_tempo"
+ i_, S_, iswing json_parse Sjson, "seq_swing"
+
+#ifdef MEL_INITTIME
+ gkseq_tempo init itempo
+ gkseq_swing init iswing
+#end
+
+ ; if data to be loaded has more progression items than ftables, free and generate again
+ if (gimel_number > ftlen(gimel_fns[0])) then
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ ftfree gimel_fns[index], 0
+ gimel_fns[index] = ftgen(0, 0, -gimel_number, -7, 0)
+ od
+ endif
+
+
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ i_, Stringvalue, i_ json_parse Sjson, gSmel_names[index]
+ iarray[] json_getnumericarray Stringvalue
+ copya2ftab iarray, gimel_fns[index]
+ index += 1
+ od
+
+
+ i_, Stringvalue, i_ json_parse Sjson, "state"
+ iarray[] json_getnumericarray Stringvalue
+ copya2ftab iarray, gimel_state
+endop
+
+
+
+
+instr mel_savestate_fs
+ prints sprintf("%s unsupported in web UDO\n", nstrstr(p1))
+ turnoff
+endin
+
+instr mel_loadstate_fs
+ prints sprintf("%s unsupported in web UDO\n", nstrstr(p1))
+ turnoff
+endin
+
+
+/*
+ Save the sequencing and progression state to database
+
+ p4 reference name in database
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_savestate_db
+ Sname = p4
+ icbid = p5
+
+ turnoff
+endin
+
+
+
+
+
+/*
+ Load the sequencing and progression state from database
+
+ p4 reference name in database
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_loadstate_db
+ Sname = p4
+ icbid = p5
+
+ schedule("mel_futures_refresh", 0, 1)
+ turnoff
+endin
+
+
+
+/*
+ Load the sequencing and progression state from a string channel
+
+ p4 channel name containing string representation of JSON
+ p5 optional callback ID for host interop; sent on completion
+*/
+instr mel_loadstate_channel
+ Schannel = p4
+ icbid = p5
+ Sdata chnget Schannel
+ mel_setstate_json(Sdata)
+ if (icbid != 0) then
+ schedule("io_callback", 0, 1, icbid)
+ endif
+ schedule("mel_futures_refresh", 0, 1)
+ turnoff
+endin
+
+
+/*
+ Just get state: interop host handles persistence
+ p4 callback ID to send data with/to
+*/
+instr mel_getstate_string
+ icbid = p4
+ Sjson = mel_getstate_json()
+ Sjson json_appendvalue Sjson, "cbid", icbid
+ io_sendstring("callback", Sjson)
+ turnoff
+endin
+
+
+/*
+ Get an array of the known mel states from database
+ Sdata[] mel_liststates_db
+
+ Sdata[] the state names
+
+opcode mel_liststates_db, S[], 0
+ Sresult[][] dbarray gidb, "SELECT name FROM savejson WHERE unit = 'melsys'"
+ ilen = lenarray(Sresult)
+ Sdata[] init ilen
+ index = 0
+ while (index < ilen) do
+ Sdata[index] = Sresult[index][0]
+ index += 1
+ od
+ xout Sdata
+endop
+*/
+
+/*
+ Get a list of mel states from database and return to host with the specified callback ID
+
+ p4 callback ID
+*/
+instr mel_liststates_db
+ icbid = p4
+ turnoff
+endin
+
+
+; if MEL_INITPATH or MEL_INITDB is set, load the specified progression data accordingly
+#ifdef MEL_HASINIT
+instr _mel_persistence_init
+#ifdef MEL_INITPATH
+ subinstrinit "mel_loadstate_fs", "$MEL_INITPATH"
+#end
+#ifdef MEL_INITDB
+ ;mel_loadstate_db "$MEL_INITDB"
+ subinstrinit "mel_loadstate_db", "$MEL_INITDB"
+#end
+ alwayson "_mel_manager"
+ turnoff
+endin
+schedule "_mel_persistence_init", 0, 60
+
+; end MEL_HASINIT
+#end
+
+#end
diff --git a/site/udo/sequencing_melodic_portamento.udo b/site/udo/sequencing_melodic_portamento.udo
new file mode 100755
index 0000000..fe3bde2
--- /dev/null
+++ b/site/udo/sequencing_melodic_portamento.udo
@@ -0,0 +1,310 @@
+#ifndef UDO_MELSEQUENCINGPORT
+#define UDO_MELSEQUENCINGPORT ##
+
+/*
+ Extension to sequencing_melodic.udo which permits usage of k-rate frequency arrays
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "__config__.udo" ; using fftsize for tuning
+#include "sequencing_melodic.udo"
+#include "wavetables.udo"
+#include "frequency_tools.udo"
+
+
+gimel_freqs ftgen 0, 0, -12, -7, 0 ; current notes: index 0 is the length
+gimel_amps ftgen 0, 0, -12, -7, 0 ; current notes: index 0 is the length
+
+gimel_portamento_beatratio init 0.5 ; portamento time as ratio of current beat time
+gimel_linetype init 0 ; 0=pre-section, 1=post-section
+
+
+/*
+ Automate a frequency/amp line
+*/
+instr _mel_linedraw
+ index = p4
+ ifreq = p5
+ iamp = p6
+
+ icurrentfreq table index, gimel_freqs
+
+ if (icurrentfreq == 0 && ifreq != 0) then
+ tablew ifreq, index, gimel_freqs
+ elseif (ifreq != 0 && icurrentfreq != ifreq) then
+ tablew line:k(icurrentfreq, p3, ifreq), index, gimel_freqs
+ endif
+
+ icurrentamp table index, gimel_amps
+ if (icurrentamp != iamp) then
+ tablew line:k(icurrentamp, p3, iamp), index, gimel_amps
+ endif
+endin
+
+
+instr _mel_linestep_inner
+ if (timeinstk() == 1) then
+ turnoff2 "_mel_linedraw", 0, 0
+ endif
+
+ if (table:i(1, gimel_next_notes) != 0) then
+ index = 0
+ while (index < table:i(0, gimel_next_notes)) do
+ event_i "i", "_mel_linedraw", 1/kr, p3, index, cpsmidinn(table:i(index + 1, gimel_next_notes)), 1
+ index += 1
+ od
+ while (index < ftlen(gimel_freqs)) do
+ event_i "i", "_mel_linedraw", 1/kr, p3, index, 0, 0
+ index += 1
+ od
+ endif
+endin
+
+
+instr _mel_linestep
+ icurrentduration mel_length
+ ilinetime = (i(gkseq_beattime) * gimel_portamento_beatratio)
+ if (gimel_linetype == 0) then
+ inextline = icurrentduration - ilinetime
+ else
+ inextline = icurrentduration
+ endif
+ event_i "i", "_mel_linestep_inner", inextline, ilinetime
+ turnoff
+endin
+
+
+/*
+ Portamento manager: respond to gkmel_section_change trigger by calling _mel_linestep instrument
+*/
+instr _mel_linemanager
+ ; set initial freqs
+ index = 0
+ while (index < table:i(0, gimel_current_notes)) do
+ tablew cpsmidinn(table:i(index + 1, gimel_current_notes)), index, gimel_freqs
+ tablew 1, index, gimel_amps
+ index += 1
+ od
+ while (index < ftlen(gimel_freqs)) do
+ tablew 0, index, gimel_amps
+ index += 1
+ od
+
+ schedkwhen gkmel_section_change, 0, 1, "_mel_linestep", 0, 1
+endin
+
+schedule "_mel_linemanager", 0.1, 36000 ; notes not ready on 0
+;alwayson "_mel_linemanager"
+
+
+
+
+
+/*
+ Recursively create a chord to be used by mel_tune_portamento; internal use only
+
+ aout _mel_tune_chord_portamento kfreqmult, ifn, imaxmult, imult, index
+
+ aout chord output
+ kfreqmult frequency multiplier to apply to tuning
+ ifn wavetable to use
+ imaxmult multiples of harmonics to generate in tuning
+ imult internal multiplier for recursion
+ index internal index for recursion
+
+*/
+opcode _mel_tune_chord_portamento, a, kiipo
+ kfreqmult, ifn, imaxmult, imult, index xin
+
+
+ if (index + 1 > ftlen(gimel_amps)) then
+ index = 0
+ imult += 1
+ endif
+
+ aout = oscil(table:k(index, gimel_amps), kfreqmult * table:k(index, gimel_freqs) * pow:k(2, imult), ifn) * 0.1
+ ; recursion for all chord parts
+ if (imult <= imaxmult) then
+ aout += _mel_tune_chord_portamento(kfreqmult, ifn, imaxmult, imult, index + 1)
+ endif
+
+ xout aout
+endop
+
+
+
+/*
+ PVS morph tuning to current melodic sequencer notes
+ aoutL, aoutR mel_tune_portamento ainL, ainR, [ifn=gifnSine, imult=4, ifftrate=giFFTsize, ifftdiv=giFFTwinFactor, kfreqmult=1]
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ ifn wavetable to use
+ imaxmult multiples of harmonics to generate in tuning (defaults to 4)
+ ifftrate fft size, defaults to config default
+ ifftdiv fft window division factor (eg 4, 8, 16), defaults to config default
+ kfreqmult frequency multiplier to apply to tuning
+*/
+opcode mel_tune_portamento, aa, aaooooP
+ aL, aR, ifn, imaxmult, ifftrate, ifftdiv, kfreqmult xin
+ ifn = (ifn == 0) ? gifnSine : ifn
+ imaxmult = (imaxmult == 0) ? 4 : imaxmult
+ ifftrate = (ifftrate == 0) ? giFFTsize : ifftrate
+ ifftdiv = (ifftdiv == 0) ? giFFTwinFactor : ifftdiv
+ fmods pvsanal _mel_tune_chord_portamento(kfreqmult, ifn, imaxmult), ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL1 pvsanal aL, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fR1 pvsanal aR, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL2 pvsmorph fL1, fmods, 0, 1
+ fR2 pvsmorph fR1, fmods, 0, 1
+ aL1 pvsynth fL2
+ aR1 pvsynth fR2
+ idel = (ifftrate)/sr
+ aL1 balance aL1, delay(aL, idel)
+ aR1 balance aR1, delay(aR, idel)
+ xout aL1, aR1
+endop
+
+
+/*
+ Bandpass tuning for internal use only, applied to each note frequency for full spectrum bandpass
+
+ aoutL, aoutR _mel_bandpass_portamento_freqgroup ainL, ainR, kfreq, kbw, iprecise, imult
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ kfreq frequency to tune to
+ kbw bandwidth of bandpass filters
+ iprecise if 1, use two serial bandpass filters for more precision
+ imult current multiplier for recursion
+*/
+opcode _mel_bandpass_portamento_freqgroup, aa, aakkip
+ ainL, ainR, kfreq, kbw, iprecise, imult xin
+ imaxmult = 24
+
+ aoutL butterbp ainL, kfreq*imult, kbw
+ aoutR butterbp ainR, kfreq*imult, kbw
+
+ if (iprecise == 1) then
+ aoutL butterbp aoutL, kfreq*imult, kbw
+ aoutR butterbp aoutR, kfreq*imult, kbw
+ endif
+
+ if (imult <= imaxmult) then
+ aoutLrec, aoutRrec _mel_bandpass_portamento_freqgroup ainL, ainR, kfreq, kbw, iprecise, imult * 2
+ aoutL += aoutLrec
+ aoutR += aoutRrec
+ endif
+ xout aoutL, aoutR
+endop
+
+
+/*
+ Bandpass tuning to current melodic sequencer notes
+
+ aoutL, aoutR mel_bandpass_portamento ainL, ainR [, kbw=1, iprecise=0, index=0]
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ kbw bandwidth of bandpass filters
+ iprecise if 1, use two serial bandpass filters for more precision
+ index recursion index for internal use
+*/
+opcode mel_bandpass_portamento, aa, aaPoo
+ ainL, ainR, kbw, iprecise, index xin
+ kamp = table:k(index, gimel_amps)
+
+ if (kamp > 0) then
+ kfreq = table:k(index, gimel_freqs)
+ aoutL, aoutR _mel_bandpass_portamento_freqgroup ainL, ainR, kfreq, kbw, iprecise
+ aoutL *= kamp
+ aoutR *= kamp
+ else
+ aoutL = 0
+ aoutR = 0
+ endif
+
+ if (index < ftlen(gimel_amps)) then
+ aoutLr, aoutRr mel_bandpass_portamento ainL, ainR, kbw, iprecise, index + 1
+ aoutL += aoutLr
+ aoutR += aoutRr
+ endif
+ xout aoutL, aoutR
+endop
+
+
+
+/*
+ Ringmod tuning to current melodic sequencer notes
+ aoutL, aoutR mel_ringmod_portamento ainL, ainR kfreqmult, index
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ kfreqmult frequency multiplier to apply to current frequencies
+ index recursion index for internal use
+*/
+opcode mel_ringmod_portamento, aa, aaPo
+ ainL, ainR, kfreqmult, index xin
+
+ kamp = table:k(index, gimel_amps)
+
+ if (kamp > 0) then
+ aoutL, aoutR ringmod1 ainL, ainR, table:k(index, gimel_freqs) * kfreqmult
+ aoutL *= kamp
+ aoutR *= kamp
+ else
+ aoutL = 0
+ aoutR = 0
+ endif
+
+ if (index < ftlen(gimel_amps)) then
+ aoutLr, aoutRr mel_ringmod_portamento ainL, ainR, kfreqmult, index + 1
+ aoutL += aoutLr
+ aoutR += aoutRr
+ endif
+ xout aoutL, aoutR
+endop
+
+
+
+/*
+ Reson tuning to current melodic sequencer notes
+
+ aoutL, aoutR mel_reson_portamento ainL, ainR kfreqmult, index
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ kfreqmult frequency multiplier to apply to current frequencies
+ index recursion index for internal use
+*/
+opcode mel_reson_portamento, aa, aaPo
+ ainL, ainR, kfreqmult, index xin
+
+ kamp = table:k(index, gimel_amps)
+ kfreq = table:k(index, gimel_freqs)
+
+ if (kamp > 0) then
+ aoutL resony ainL, kfreq * kfreqmult, 2, 8, 10
+ aoutR resony ainR, kfreq * kfreqmult, 2, 8, 10
+ aoutL balance aoutL, ainL
+ aoutR balance aoutR, ainR
+ aoutL *= kamp
+ aoutR *= kamp
+ else
+ aoutL = 0
+ aoutR = 0
+ endif
+
+ if (index < ftlen(gimel_amps)) then
+ aoutLr, aoutRr mel_reson_portamento ainL, ainR, kfreqmult, index + 1
+ aoutL += aoutLr
+ aoutR += aoutRr
+ endif
+ xout aoutL, aoutR
+endop
+
+
+#end
+
diff --git a/site/udo/sequencing_scheduled.udo b/site/udo/sequencing_scheduled.udo
new file mode 100755
index 0000000..17cb865
--- /dev/null
+++ b/site/udo/sequencing_scheduled.udo
@@ -0,0 +1,286 @@
+#ifndef UDO_SEQUENCING_SCHEDULING
+#define UDO_SEQUENCING_SCHEDULING ##
+/*
+ Sequenced scheduling
+
+ 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"
+
+
+/*
+ Helper for sequenced scheduling calls
+*/
+opcode _seq_form_pfields, S, i[]
+ ipfields[] xin
+ Sdata = ""
+ index = 0
+ while (index < lenarray(ipfields)) do
+ Sdata strcat Sdata, sprintf("%f ", ipfields[index])
+ index += 1
+ od
+ xout Sdata
+endop
+
+/*
+ schedule a scoreline for the next beat, bar or bargroup
+*/
+instr _seq_next
+ imode = p4
+ Scoreline = p5
+
+ if ((imode == 0 && gkseq_beat == 1) || (imode == 1 && gkseq_bar_trig == 1) || (imode == 2 && gkseq_bargroup_trig == 1)) then
+ scoreline Scoreline, 1
+ turnoff
+ endif
+endin
+
+opcode _seq_next, 0, iSi[]
+ imode, Sinstrument, ipfields[] xin
+ scoreline_i sprintf("i\"_seq_next\" %d 0 3600 \"i \\\"%s\\\" %s\"", imode, Sinstrument, _seq_form_pfields(ipfields))
+endop
+
+
+instr _seq_turnoff
+ imode = p4
+ instrnum = p5
+
+ if ((imode == 0 && gkseq_beat == 1) || (imode == 1 && gkseq_bar_trig == 1) || (imode == 2 && gkseq_bargroup_trig == 1)) then
+ turnoff2 instrnum, 4, 0
+ turnoff
+ endif
+endin
+
+opcode _seq_turnoff, 0, ii
+ imode, instrnum xin
+ event_i "i", "_seq_turnoff", 0, 600, imode, instrnum
+endop
+
+
+/*
+ Schedule an instrument for the next beat
+
+ beat_next Sinstrument, ipfields[]
+
+ Sinstrument instrument name
+ ipfields[] p fields
+*/
+opcode beat_next, 0, Si[]
+ Sinstrument, ipfields[] xin
+ _seq_next 0, Sinstrument, ipfields
+endop
+
+
+
+/*
+ Schedule an instrument for the next bar
+
+ bar_next Sinstrument, ipfields[]
+
+ Sinstrument instrument name
+ ipfields[] p fields
+*/
+opcode bar_next, 0, Si[]
+ Sinstrument, ipfields[] xin
+ _seq_next 1, Sinstrument, ipfields
+endop
+
+
+
+/*
+ Schedule an instrument for the next bar group
+
+ bargroup_next Sinstrument, ipfields[]
+
+ Sinstrument instrument name
+ ipfields[] p fields
+*/
+opcode bargroup_next, 0, Si[]
+ Sinstrument, ipfields[] xin
+ _seq_next 2, Sinstrument, ipfields
+endop
+
+
+
+
+/*
+ Turn off an instrument at the next beat
+
+ beat_next_turnoff instrnum
+
+ instrnum the instrument number; fractional if required
+*/
+opcode beat_next_turnoff, 0, i
+ instrnum xin
+ _seq_turnoff 0, instrnum
+endop
+
+
+/*
+ Turn off an instrument at the next bar
+
+ next_bar_turnoff instrnum
+
+ instrnum the instrument number; fractional if required
+*/
+opcode bar_next_turnoff, 0, i
+ instrnum xin
+ _seq_turnoff 1, instrnum
+endop
+
+
+/*
+ Turn off an instrument at the next bar group
+
+ next_bargroup_turnoff instrnum
+
+ instrnum the instrument number; fractional if required
+*/
+opcode bargroup_next_turnoff, 0, i
+ instrnum xin
+ _seq_turnoff 2, instrnum
+endop
+
+
+
+
+instr bargroup_lastof
+ Scoreline = p4
+ p3 = 600
+ if (gkseq_bargroup == giseq_bargrouplength - 1) then
+ scoreline Scoreline, 1
+ turnoff
+ endif
+endin
+
+
+opcode nextbeatxof, k, i
+ ibeats xin
+ kbeatnum init ibeats
+ ktrig init 0
+ kactive init 1
+ if (gkseq_beat == 1 && kactive == 1) then
+ if (kbeatnum == 0) then
+ kactive = 0
+ ktrig = 1
+ else
+ kbeatnum -= 1
+ endif
+ endif
+ xout ktrig
+endop
+
+
+opcode lastbeatxof, k, ii
+ itotalbeats, ibeats xin
+ ktrig = 0
+ kbeatnum init itotalbeats
+ kactive init 1
+ if (kactive == 1) then
+ if (gkseq_beat == 1) then
+ if (kbeatnum - 1 == ibeats) then
+ ktrig = 1
+ kactive = 0
+ else
+ kbeatnum -= 1
+ endif
+ endif
+ endif
+
+ xout ktrig
+endop
+
+
+opcode bar_lastbeatxof, k, i
+ ibeats xin
+ kactive init 1
+ ktrig = 0
+
+ kbeats init ibeats
+ if (kactive == 1) then
+ kreducing = (kbeats > giseq_barlength) ? 1 : 0
+
+ if (kreducing == 1 && gkseq_beat == 1 && gkseq_barbeat == giseq_barlength - 1) then
+ kbeats -= giseq_barlength
+ endif
+
+ if (kreducing == 0 && gkseq_beat == 1 && (gkseq_barbeat+kbeats) == giseq_barlength) then
+ ktrig = 1
+ kactive = 0
+ endif
+ endif
+
+ xout ktrig
+endop
+
+opcode bargroup_lastbeatxof, k, io
+ ibeats, icontinuous xin
+ kactive init 1
+ ktrig = 0
+
+ ibargroupbeats = giseq_bargrouplength * giseq_barlength
+ kbeats init ibeats
+ if (kactive == 1) then
+ kreducing = (kbeats > ibargroupbeats) ? 1 : 0
+
+ if (kreducing == 1 && gkseq_bar_trig == 1 && gkseq_bargroup == giseq_bargrouplength - 1) then
+ kbeats -= ibargroupbeats
+ endif
+
+ if (kreducing == 0 && gkseq_beat == 1 && (((gkseq_bargroup)*giseq_bargrouplength)+gkseq_barbeat+kbeats) == ibargroupbeats) then
+ ktrig = 1
+
+ if (icontinuous == 0) then
+ kactive = 0
+ else
+ kbeats = ibeats
+ endif
+ endif
+
+ endif
+
+ xout ktrig
+endop
+
+
+opcode bar_lastbeatxof_launch, 0, iSi[]
+ ibeats, Sinstrument, ipfields[] xin
+ scoreline_i sprintf("i\"_bar_lastbeatxof\" 0 600 %d \"i \\\"%s\\\" %s\"", ibeats, Sinstrument, _seq_form_pfields(ipfields))
+endop
+
+
+opcode bargroup_lastbeatxof_launch, 0, iSi[]
+ ibeats, Sinstrument, ipfields[] xin
+ scoreline_i sprintf("i\"_bargroup_lastbeatxof\" 0 600 %d \"i \\\"%s\\\" %s\"", ibeats, Sinstrument, _seq_form_pfields(ipfields))
+endop
+
+
+; launch an instrument on the next relevant X beats before a bar start
+instr _bar_lastbeatxof
+ ibeats = p4
+ Scoreline = p5
+ p3 = 600
+
+ if (bar_lastbeatxof(ibeats) == 1) then
+ scoreline Scoreline, 1
+ turnoff
+ endif
+endin
+
+
+; launch an instrument on the next relevant X beats before a bar group start
+instr _bargroup_lastbeatxof
+ ibeats = p4
+ Scoreline = p5
+ p3 = 600
+
+ if (bargroup_lastbeatxof(ibeats) == 1) then
+ scoreline Scoreline, 1
+ turnoff
+ endif
+endin
+
+#end
diff --git a/site/udo/sequencing_table.udo b/site/udo/sequencing_table.udo
new file mode 100755
index 0000000..8ef40dc
--- /dev/null
+++ b/site/udo/sequencing_table.udo
@@ -0,0 +1,370 @@
+#ifndef UDO_SEQUENCING_TABLE
+#define UDO_SEQUENCING_TABLE ##
+/*
+ Table sequencing
+
+ 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"
+
+
+gifn_tabseq_all = ftgen(0, 0, -4, -2, 1, 1, 1, 1) ; 4 quarter notes to allow for swing
+
+/*
+
+TODO: write to database
+
+gistfn_kick1 ftgen 0, 0, -16, -2, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0
+gistfn_kick2 ftgen 0, 0, -16, -2, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0
+gistfn_kick3 ftgen 0, 0, -16, -2, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0
+
+*/
+
+
+/*
+ Fill a table with random boolean values, generating the table if ifn is -1
+
+ ifnout _seq_randtable_bool ilen, ifn, ichance
+
+ ifnout the resulting table
+ ilen length of table to create, if generating
+ ifn existing table or -1 to generate
+ ichance chance of random assignment: -1 defaults to 0.5, 1 is fill all values, 0 is fill no values
+*/
+opcode _seq_randtable_bool, i, iii
+ ilen, ifn, ichance xin
+ ichance = (ichance == -1) ? 0.5 : ichance
+ if (ifn == -1) then
+ ifn ftgen 0, 0, -ilen, -2, 0
+ endif
+ index = 0
+ while (index < ftlen(ifn)) do
+ ivalue = (random(0, 1) <= ichance) ? 1 : 0
+ tableiw ivalue, index, ifn
+ index += 1
+ od
+ xout ifn
+endop
+
+
+/*
+ Fill a table with random numeric values, generating the table if ifn is -1
+
+ ifnout _seq_randtable_bool ilen, ifn, ichance
+
+ ifnout the resulting table
+ ilen length of table to create, if generating
+ ifn existing table or -1 to generate
+ iminvalue minimuma value to assign
+ imaxvalue maximum value to assign
+ irounded 1 = integer values, 0 = float values
+*/
+opcode _seq_randtable_numeric, i, iiiii
+ ilen, ifn, iminvalue, imaxvalue, irounded xin
+ if (ifn == -1) then
+ ifn ftgen 0, 0, -ilen, -2, 0
+ endif
+ index = 0
+ while (index < ftlen(ifn)) do
+ ivalue = random(iminvalue, imaxvalue)
+ tableiw ((irounded == 1) ? round(ivalue) : ivalue), index, ifn
+ index += 1
+ od
+ xout ifn
+endop
+
+
+/*
+ Generate a table with random boolean values
+
+ ifn seq_randtablegen ilen, ichance
+
+ ifn the resulting table
+ ilen length of table to create
+ ichance chance of random assignment: -1 defaults to 0.5, 1 is fill all values, 0 is fill no values
+*/
+opcode seq_randtablegen, i, ij
+ ilen, ichance xin
+ ifn _seq_randtable_bool ilen, -1, ichance
+ xout ifn
+endop
+
+
+/*
+ Fill an existing table with random boolean values
+
+ seq_randtable ifn, ichance
+
+ ifn the table to fill
+ ichance chance of random assignment: -1 defaults to 0.5, 1 is fill all values, 0 is fill no values
+*/
+opcode seq_randtable, 0, ij
+ ifn, ichance xin
+ ifn _seq_randtable_bool -1, ifn, ichance
+endop
+
+
+
+/*
+ Generate a table with random numeric values
+
+ ifn seq_randtablegen_numeric ilen, iminvalue, imaxvalue, irounded
+
+ ifnout the resulting table
+ ilen length of table to create
+ iminvalue minimuma value to assign
+ imaxvalue maximum value to assign
+ irounded 1 = integer values, 0 = float values
+*/
+opcode seq_randtablegen_numeric, i, iiii
+ ilen, iminvalue, imaxvalue, irounded xin
+ ifn _seq_randtable_numeric ilen, -1, iminvalue, imaxvalue, irounded
+ xout ifn
+endop
+
+
+
+/*
+ Fill an existing table with random numeric values
+
+ seq_randtable_numeric ifn, iminvalue, imaxvalue, irounded
+
+ ifn the table to fill
+ iminvalue minimuma value to assign
+ imaxvalue maximum value to assign
+ irounded 1 = integer values, 0 = float values
+*/
+opcode seq_randtable_numeric, 0, iiii
+ ifn, iminvalue, imaxvalue, irounded xin
+ ifn _seq_randtable_numeric -1, ifn, iminvalue, imaxvalue, irounded
+endop
+
+
+/*
+ Fill an existing table with random boolean values at k-rate on receipt of a trigger
+
+ seq_randtable kfn, ktrig, kchance
+
+ kfn the table to fill
+ ktrig repopulate the table when 1
+ kchance chance of random assignment: -1 defaults to 0.5, 1 is fill all values, 0 is fill no values
+*/
+opcode seq_randtable, 0, kkV
+ kfn, ktrig, kchance xin
+ kchance = (kchance == -1) ? 0.5 : kchance
+ if (ktrig == 1) then ; && changed:k(ktrig) == 1) then
+ kindex = 0
+ while (kindex < tableng:k(kfn)) do
+ kvalue = (random:k(0, 1) <= kchance) ? 1 : 0
+ tablewkt kvalue, kindex, kfn
+ kindex += 1
+ od
+ endif
+endop
+
+
+/*
+opcode seq_table_numeric, k, i
+ ifn xin
+ ilen tableng ifn
+ kindex init 0
+ as, a_ syncphasor gkseq_beathz*4, a(gkseq_beat)
+ kt trigger k(as), 0.05, 0 ; was 0.005.. works?
+ if (kt == 1) then
+ koutvalue table kindex, ifn
+
+ if (kindex < klength - 1) then
+ kindex += 1
+ else
+ kindex = 0
+ endif
+ endif
+ koutvalue
+endop
+*/
+
+
+
+
+/*
+ Trigger and index output table sequencer
+
+ ktrig, kindex seq_table ifn [, kreset=0, kdivisions=4, kchanceon=1, kchanceoff=1, klength=ftlen(ifn), kswing=gkseq_swing, kbeathz=gkseq_beathz, inosync=0]
+ ktrig seq_table ifn [, kreset=0, kdivisions=4, kchanceon=1, kchanceoff=1, klength=ftlen(ifn), kswing=gkseq_swing, kbeathz=gkseq_beathz, inosync=0]
+
+ ktrig the sequence trigger
+ kindex current index (max = klength-1)
+
+ ifn the table containing boolean positions
+ kreset if above 0 and changed since last value, then reset sequence index to 0
+ kdivisions how many points feature in one beat
+ kchanceon chance of an on point being on (1 = always, 0 = never)
+ kchanceoff chance of an off point being off (1 = always, 0 = never)
+ klength the maximum number of points in the table to use
+ kswing the swing amount to apply
+ kbeathz trigger rate in Hz
+ inosync do not sync to the sequencer beat clock
+*/
+opcode seq_table, kk, iOJPPJJJo
+ ifn, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing, kbeathz, inosync xin
+ ilen = ftlen(ifn)
+
+ kdivisions = (kdivisions == -1) ? 4 : kdivisions
+ klength = (klength == -1) ? ilen : min(ilen, klength)
+ kswing = (kswing == -1) ? gkseq_swing : kswing
+ kbeathz = (kbeathz == -1) ? gkseq_beathz : kbeathz
+
+ kindex init 0
+ klaunchindex init 0
+
+ if (kreset >= 0 && changed:k(kreset) == 1) then
+ kindex = 0
+ endif
+
+ if (inosync == 1) then
+ kt metro kbeathz * kdivisions
+ else
+ as, a_ syncphasor kbeathz * kdivisions, a(gkseq_beat)
+ kt trigger k(as), 0.05, 0 ; was 0.005.. works?
+ endif
+ ktrigout = 0
+ if (kt == 1) then
+ ktrigout tab kindex, ifn
+ if (ktrigout == 1 && kchanceon < 1) then
+ if (random:k(0, 1) > kchanceon) then
+ ktrigout = 0
+ endif
+ elseif (ktrigout == 0 && kchanceoff < 1) then
+ if (random:k(0, 1) > kchanceoff) then
+ ktrigout = 1
+ endif
+ endif
+ klaunchindex = kindex
+ if (kindex < klength - 1) then
+ kindex += 1
+ else
+ kindex = 0
+ endif
+ endif
+
+ ktrigout = vdel_k(ktrigout, seq_swingtime(0, ((kindex-1)/kdivisions)*4, kswing), 1)
+ xout ktrigout, klaunchindex
+endop
+
+; override for single output
+opcode seq_table, k, iOJPPJJJ
+ ifn, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing, kbeathz xin
+ ktrig, kindex seq_table ifn, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing, kbeathz
+ xout ktrig
+endop
+
+
+/*
+ Numeric output table sequencer
+
+ kvalout seq_table_numeric ifntrig, ifnval [, kreset=0, kdivisions=4, kchanceon=1, kchanceoff=1, klength=ftlen(ifn), kswing=gkseq_swing]
+
+ kvalout the numeric output
+
+ ifntrig table containing boolean positions
+ ifnval table containing values to return
+ kreset if above 0 and changed since last value, then reset sequence index to 0
+ kdivisions how many points feature in one beat
+ kchanceon chance of an on point being on (1 = always, 0 = never)
+ kchanceoff chance of an off point being off (1 = always, 0 = never)
+ klength the maximum number of points in the table to use
+ kswing the swing amount to apply
+*/
+opcode seq_table_numeric, k, iiOJPPJJ
+ ifntrig, ifnval, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing xin
+
+ if (ftlen(ifnval) != ftlen(ifntrig)) then
+ prints "seq_table_numeric: ifntrig and ifnval are not the same length\n\n"
+ exitnow
+ endif
+
+ kvalout = -1
+ ktrig, kindex seq_table ifntrig, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing
+
+ if (ktrig == 1) then
+ kvalout tab kindex, ifnval
+ endif
+
+ xout kvalout
+endop
+
+
+/*
+ Table sequencer which calls the instrument with number instrnum when ifntrig is 1 for the current index.
+ The instrument is passed the following parameters:
+ p4 index of playback
+ p5 value of kp5 from opcode parameters
+
+ seq_table_scheduler instrnum, ifntrig, [ifnstart=-1, ifndur=-1, kp5=0, kreset=0, kdivisions=4, kchanceon=1, kchanceoff=1, klength=ftlen(ifn), kswing=gkseq_swing]
+
+ instrnum instrument number to schedule
+ ifntrig table containing boolean triggers per position.
+ ifnstart table containing start times (offset from current position). If -1, all instances have the start time 0
+ ifndur table containing durations per position. If -1, all instances have the duration 1
+ kp5 value to be passed to instrument as p5 if required
+ kreset if above 0 and changed since last value, then reset sequence index to 0
+ kdivisions how many points feature in one beat
+ kchanceon chance of an on point being on (1 = always, 0 = never)
+ kchanceoff chance of an off point being off (1 = always, 0 = never)
+ klength the maximum number of points in the table to use
+ kswing the swing amount to apply
+*/
+opcode seq_table_scheduler, 0, iijjOOJPPJJ
+ instrnum, ifntrig, ifnstart, ifndur, kp5, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing xin
+
+ if (ifnstart != -1 && ftlen(ifnstart) != ftlen(ifntrig)) then
+ prints "seq_table_scheduler: ifntrig and ifnstart are not the same length\n\n"
+ exitnow
+ endif
+
+ if (ifndur != -1 && ftlen(ifndur) != ftlen(ifntrig)) then
+ prints "seq_table_scheduler: ifntrig and ifndur are not the same length\n\n"
+ exitnow
+ endif
+
+ ktrig, kindex seq_table ifntrig, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing
+
+ if (ktrig == 1) then
+ schedulek instrnum,\
+ (ifnstart == -1) ? 0 : tab:k(kindex, ifnstart),\
+ (ifndur == -1) ? 1 : tab:k(kindex, ifndur),\
+ kindex, kp5
+ endif
+endop
+
+
+/*
+ Table sequencer which calls the instrument with name Sinstrument when ifntrig is 1 for the current index.
+ The instrument is passed the following parameters:
+ p4 index of playback
+ p5 value of kp5 from opcode parameters
+
+ seq_table_scheduler instrnum, ifntrig, [ifnstart=-1, ifndur=-1, kp5=0, kreset=0, kdivisions=4, kchanceon=1, kchanceoff=1, klength=ftlen(ifn), kswing=gkseq_swing]
+
+ Sinstrument instrument name to schedule
+ ifntrig table containing boolean triggers per position.
+ ifnstart table containing start times (offset from current position). If -1, all instances have the start time 0
+ ifndur table containing durations per position. If -1, all instances have the duration 1
+ kp5 value to be passed to instrument as p5 if required
+ kreset if above 0 and changed since last value, then reset sequence index to 0
+ kdivisions how many points feature in one beat
+ kchanceon chance of an on point being on (1 = always, 0 = never)
+ kchanceoff chance of an off point being off (1 = always, 0 = never)
+ klength the maximum number of points in the table to use
+ kswing the swing amount to apply
+*/
+opcode seq_table_scheduler, 0, SijjOOJPPJJ
+ Sinstrument, ifntrig, ifnstart, ifndur, kp5, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing xin
+ seq_table_scheduler nstrnum(Sinstrument), ifntrig, ifnstart, ifndur, kp5, kreset, kdivisions, kchanceon, kchanceoff, klength, kswing
+endop
+
+#end
diff --git a/site/udo/sound_db.udo b/site/udo/sound_db.udo
new file mode 100755
index 0000000..c9862fc
--- /dev/null
+++ b/site/udo/sound_db.udo
@@ -0,0 +1,152 @@
+#ifndef UDO_RUNTIMEDB
+#define UDO_RUNTIMEDB ##
+/*
+ Runtime sound database manager
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "string_tools.udo"
+
+/*
+ Runtime sound database array structure:
+ 0 ftable number
+ 1 file id
+ 2 number of channels
+ 3 duration
+*/
+gisoundsdb[][] init 99999, 4
+gisoundsdbmax init 0
+
+
+/*
+ Load a sound to the runtime sounds db array, or return the index if already loaded (by file id tracking)
+ For pgdb usage
+ index loadsound ifileid, ichannels, iduration, Spath
+
+ index sound database index
+
+ ifileid database file id, for tracking
+ ichannels number of channels in file
+ iduration duration of file
+ Spath path to the file
+*/
+opcode _rdb_loadsound, i, iiiS
+ ifileid, ichannels, iduration, Spath xin
+ index = 0
+ ifn = -1
+ ;goto loadrequired ; HACK, 32bit fail on hash
+ while (index < gisoundsdbmax) do
+ if (gisoundsdb[index][0] == 0) then ; nothing loaded at all
+ igoto loadrequired ; give up now, don't go through all of array
+ endif
+
+ if (gisoundsdb[index][1] == ifileid) then
+ igoto complete
+ endif
+ index += 1
+ od
+
+loadrequired:
+ if (ifn == -1) then
+ isize = filelen(Spath) * filesr(Spath) * filenchnls(Spath); HACK: grain cannot use deferred time: TODO: pass in samplerate and length to opcode (TODO: database needs samplerate)
+ ifn = ftgen(0, 0, isize, 1, Spath, 0, 0, 0)
+ index = gisoundsdbmax
+ gisoundsdb[index][0] = ifn
+ gisoundsdb[index][1] = ifileid
+ gisoundsdb[index][2] = ichannels
+ gisoundsdb[index][3] = iduration
+ gisoundsdbmax += 1
+ endif
+
+complete:
+ xout index
+endop
+
+
+/*
+ Load a sound to the runtime sounds db array, or return the index if already loaded (by file id tracking)
+ For direct FS usage
+ index loadsound Spath
+
+ index sound database index
+
+ Spath path to the file
+
+*/
+opcode rdb_loadsound, i, S
+ Spath xin
+ ifileid str_hash Spath
+ ichannels = filenchnls(Spath)
+ iduration = filelen(Spath)
+print iduration
+ xout _rdb_loadsound(ifileid, ichannels, iduration, Spath)
+endop
+
+
+
+/*
+ Load a directory of sounds with suffix .wav and return an array of the runtime db indexes
+ indexes[] rdb_loaddir Spath
+
+ indexes[] sound database indexes
+
+ Spath directory path
+*/
+opcode rdb_loaddir, i[], S
+ Spath xin
+ Sfiles[] directory Spath, ".wav"
+ isounds[] init lenarray(Sfiles)
+ index = 0
+ while (index < lenarray(Sfiles)) do
+ isounds[index] rdb_loadsound Sfiles[index]
+ index += 1
+ od
+ xout isounds
+endop
+
+
+
+/*
+ Get sound array from sound database
+ isound[] get_sound index
+
+ isound[] the runtime sound database entry
+
+ index sound index as provided by load_sound
+*/
+opcode get_sound, i[], i
+ index xin
+ ;xout getrow(gisoundsdb, index) ; 32bit fail!
+
+ ilen lenarray gisoundsdb, 2
+ idata[] init ilen
+ index2 = 0
+ while (index2 < ilen) do
+ idata[index2] = gisoundsdb[index][index2]
+ index2 += 1
+ od
+ xout idata
+endop
+
+
+/*
+ Get sound array from sound database
+ ifn, ifileid, inchnls, idur get_sound index
+
+ ifn ftable of sound
+ ifileid file id
+ inchnls number of channels
+ idur duration in seconds
+
+ index sound index as provided by load_sound
+*/
+opcode get_sound, iiii, i
+ index xin
+ xout gisoundsdb[index][0], gisoundsdb[index][1], gisoundsdb[index][2], gisoundsdb[index][3]
+endop
+
+
+#end
diff --git a/site/udo/sound_melsys.udo b/site/udo/sound_melsys.udo
new file mode 100755
index 0000000..588f137
--- /dev/null
+++ b/site/udo/sound_melsys.udo
@@ -0,0 +1,215 @@
+#ifndef UDO_MELSYS
+#define UDO_MELSYS ##
+
+/*
+ Melodic sampler system
+
+ Typical external usage should only refer to
+ mel_playnote
+ mel_getcollection
+
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+
+
+#include "sound_db.udo"
+#include "pgdb.udo"
+#include "host_tools.udo"
+#include "pvs_tools.udo"
+#include "bussing.udo"
+
+
+
+opcode _mel_loadobject, i[][], S[][]
+ Sres[][] xin
+ imelmap[][] init 128, 16
+ index = 0
+ while (index < lenarray(Sres)) do
+ ifileid strtod Sres[index][0]
+ ichannels strtod Sres[index][2]
+ iduration strtod Sres[index][3]
+ inote strtod Sres[index][4]
+
+ if (imelmap[inote][0] == 0) then
+ imelmap[inote][0] = 1
+ endif
+ imelmap[inote][imelmap[inote][0]] _rdb_loadsound ifileid, ichannels, iduration, Sres[index][1]
+ imelmap[inote][0] = imelmap[inote][0] + 1 ; first index keeps track of length
+
+ index += 1
+ od
+ xout imelmap
+endop
+
+
+
+
+/*
+ Get soundcollection with basic analysis information
+ isounds[][] getcollection ScollectionName
+
+ isounds[][] soundcollection object
+ ScollectionName the name of the filecollection in the database
+*/
+opcode mel_getcollection, i[][], S
+ Scollection xin
+ Sbase = {{select file_id, f_localpath(%d, path), channels, duration, note
+ from svw.analysis_basic_collectionnorm a
+ join filecollection fc on fc.id = a.filecollection_id
+ join filecollectiontype fct on fct.id = fc.type_id
+ where fc.name = '%s'
+ and fct.name = 'melsys'
+ }}
+ Squery sprintf Sbase, gihost_type, Scollection
+ Sres[][] dbarray gidb, Squery
+ idata[][] _mel_loadobject Sres
+ xout idata
+endop
+
+
+/*
+ Get all melodic soundcollection names
+ Scollections[] mel_listcollections
+
+ Scollections[] list of soundcollections
+*/
+opcode mel_listcollections, S[], 0
+ Sres[][] dbarray gidb, "select distinct fc.name from filecollection fc join filecollectiontype fct on fct.id = fc.type_id where fct.name = 'melsys'"
+ ilen = lenarray(Sres)
+ Scollections[] init ilen
+ index = 0
+ while (index < ilen) do
+ Scollections[index] = Sres[index][0]
+ index += 1
+ od
+ xout Scollections
+endop
+
+
+/*
+ Get nearest note and a ratio to alter to specific note
+*/
+opcode _mel_getnearestnote, ii, ii[][]
+ inote, imelmap[][] xin
+ iratio = 0
+ inearest = -1
+ idistance = 999
+ index = 0
+ while (index < 128) do
+ if (imelmap[index][0] > 0) then
+ if (inote > index) then
+ imeasure = inote - index
+ if (imeasure < idistance) then
+ idistance = imeasure
+ inearest = index
+ endif
+ elseif (inote < index ) then
+ imeasure = index - inote
+ if (imeasure < idistance) then
+ idistance = imeasure
+ inearest = index
+ endif
+ else
+ idistance = 0
+ inearest = inote
+ goto output
+ endif
+ endif
+ index += 1
+ od
+ iratio = cpsmidinn(inote) / cpsmidinn(inearest)
+output:
+ isoundindex = imelmap[inearest][int(random(1, imelmap[inearest][0]))]
+ xout isoundindex, iratio
+endop
+
+
+opcode mel_getrandnote, ii, ii[][]
+ inote, imelmap[][] xin
+ iout = -1
+ if (imelmap[inote][0] > 0) then
+ index = int(random(1, imelmap[inote][0]))
+ iout = imelmap[inote][index]
+ iratio = 1
+ else
+ iout, iratio _mel_getnearestnote inote, imelmap
+ endif
+ xout iout, iratio
+endop
+
+
+
+/*
+
+
+*/
+opcode _mel_playsound, 0, iSppooooo
+ index, Sbus, iamp, ipitchratio, iwhen, iusepvs, ikeepformant, ifadein, idurationoverride xin
+ Scoreline = sprintf("i\"mel_player_default\" %f 1 \"%s\" %f %f %f %d %d %d %f", iwhen, Sbus, index, iamp, ipitchratio, iusepvs, ikeepformant, ifadein, idurationoverride)
+ scoreline_i Scoreline
+endop
+
+
+opcode mel_playnote, 0, i[][]iSpooooo
+ imelmap[][], inote, Sbus, iamp, iwhen, idurationoverride, iusepvs, ikeepformant, ifadein xin
+ isoundindex, ipitchratio mel_getrandnote inote, imelmap
+ _mel_playsound isoundindex, Sbus, iamp, ipitchratio, iwhen, iusepvs, ikeepformant, ifadein, idurationoverride
+endop
+
+
+
+
+instr mel_player_default
+ Sbus = p4
+ isoundindex = p5
+ iamp = p6
+ ipitchratio = p7
+ iusepvs = p8
+ ikeepformant = p9
+ ifadein = p10
+ idurationoverride = p11
+ isound[] = get_sound(isoundindex)
+
+ if (idurationoverride == 0) then
+ p3 = isound[3] ; set duration
+ else
+ p3 = idurationoverride
+ endif
+
+
+ if (iusepvs == 1) then
+ iloscilratio = 1
+ else
+ iloscilratio = ipitchratio
+ endif
+
+ if (isound[2] == 1) then ; check channels
+ aL loscil iamp, iloscilratio, isound[0], 1
+ if (iusepvs == 1) then
+ aL pvscaler aL, ipitchratio, ikeepformant
+ endif
+ aR = aL
+ else
+ aL, aR loscil iamp, iloscilratio, isound[0], 1
+ if (iusepvs == 1) then
+ aL pvscaler aL, ipitchratio, ikeepformant
+ aR pvscaler aR, ipitchratio, ikeepformant
+ endif
+ endif
+
+ if (ifadein == 1) then
+ kenv linseg 0, p3*0.4, 1, p3*0.5, 1, p3*0.1, 0
+ else
+ kenv linseg 1, p3*0.9, 1, p3*0.1, 0
+ endif
+
+ bus_mix(Sbus, aL*kenv, aR*kenv)
+endin
+
+
+#end
diff --git a/site/udo/sound_sdb.udo b/site/udo/sound_sdb.udo
new file mode 100755
index 0000000..82fe009
--- /dev/null
+++ b/site/udo/sound_sdb.udo
@@ -0,0 +1,271 @@
+#ifndef UDO_SOUNDSDB
+#define UDO_SOUNDSDB ##
+/*
+ SQL database interface to sound object management
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+#include "sound_db.udo"
+#include "pgdb.udo"
+#include "bussing.udo"
+#include "host_tools.udo"
+#include "pvs_tools.udo"
+
+/*
+ soundcollection object structure: 2D array with first dimension as sound index and second as
+ 0 runtime sound db index
+ 1 rms normalised over all collections
+ 2 average pitch hz
+ 3 average centroid hz
+ 4 match distance; optional depending on returning opcode
+*/
+
+
+
+;gisdb_data[][] init 99999, 4
+
+/*
+ Internal: format a standard soundcollection database result as soundcollection object
+ isounds[][] _load_sdbobject Sresult[][], [ihasdistance]
+
+ isounds[][] soundcollection object
+ Sresult[][] the raw database result string array
+ ihasdistance whether to include the additional distance dimension
+
+*/
+
+opcode _sdb_loadobject, i[][], S[][]o
+ Sres[][], ihasdistance xin
+ iarraylength = lenarray(Sres)
+ idata[][] init iarraylength, 4 + ihasdistance
+ index = 0
+ while (index < lenarray(Sres)) do
+ ifileid strtod Sres[index][0]
+ ichannels strtod Sres[index][2]
+ iduration strtod Sres[index][3]
+ idata[index][0] _rdb_loadsound ifileid, ichannels, iduration, Sres[index][1]
+ idata[index][1] strtod Sres[index][4] ; rmsnormal
+ idata[index][2] strtod Sres[index][5] ; pitch
+ idata[index][3] strtod Sres[index][6] ; centroid
+ if (ihasdistance == 1) then
+ idata[index][4] strtod Sres[index][7]
+ endif
+ index += 1
+ od
+ xout idata
+endop
+
+
+/*
+ Get a string array of all current filecollections
+ Scollections[] sdb_listcollections
+
+ Scollections[] list of collections
+*/
+opcode sdb_listcollections, S[], 0
+ Sres[][] dbarray gidb, "select name from filecollection"
+ ilen = lenarray(Sres)
+ Scollections[] init ilen
+ index = 0
+ while (index < ilen) do
+ Scollections[index] = Sres[index][0]
+ index += 1
+ od
+ xout Scollections
+endop
+
+
+/*
+ Get soundcollection(s) with basic analysis information
+ isounds[][] sdb_getcollection ScollectionName
+
+ isounds[][] soundcollection object
+ ScollectionName the name of the filecollection in the database or comma separated list of collections
+*/
+
+opcode sdb_getcollection, i[][], S
+ Scollection xin
+ Sbase = {{select file_id, f_localpath(%d, path), channels, duration, rmsnormal, pitch, centroid
+ from svw.analysis_basic_collectionnorm a
+ join filecollection fc on fc.id = a.filecollection_id
+ where %s
+ }}
+
+ if (strindex(Scollection, ",") > 0) then
+ Sclause = "(1=2"
+ index = 1
+ Stemp = Scollection
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ Sclause strcat Sclause, sprintf(" or fc.name='%s'", strsub(Stemp, 0, index))
+ Stemp strsub Stemp, index+1
+ else
+ Sclause strcat Sclause, sprintf(" or fc.name='%s'", Stemp)
+ endif
+ od
+ Sclause strcat Sclause, ")"
+ else
+ Sclause = sprintf("fc.name = '%s'", Scollection)
+ endif
+
+ Squery sprintf Sbase, gihost_type, Sclause
+ Sres[][] dbarray gidb, Squery
+ idata[][] _sdb_loadobject Sres
+ xout idata
+endop
+
+
+
+/*
+ Bubble sort a soundcollection 'object' by a specified analysis element
+ iordered[][] orderby isounds[][], ielement, [isortorder=0]
+
+ iordered[][] the sorted soundcollection 'object'
+ isounds[][] the input soundcollection 'object'
+ ielement which analysis element to order by:
+ 0 duration
+ 1 rms
+ 2 pitch
+ 3 centroid
+ 4 distance, if collection is provided from a matching opcode
+ isortorder optional sort order:
+ 0 descending
+ 1 ascending
+
+*/
+opcode sdb_orderby, i[][], i[][]io
+ idata[][], ielement, iordering xin
+ ilen = lenarray(idata)
+ index = 0
+ while (index < ilen) do
+ index2 = 0
+ while (index2 < ilen) do
+ ; duration requires an obnoxious looking cross array reference
+ if (\
+ (ielement == 0 && ( \
+ (iordering == 0 && gisoundsdb[idata[index][0]][3] > gisoundsdb[idata[index2][0]][3]) \
+ || (iordering == 1 && gisoundsdb[idata[index][0]][3] < gisoundsdb[idata[index2][0]][3]) \
+ )) || ( \
+ (iordering == 0 && idata[index][ielement] > idata[index2][ielement]) \
+ ||(iordering == 1 && idata[index][ielement] < idata[index2][ielement]) \
+ ) \
+ ) then
+ iswap[] getrow idata, index
+ iswap2[] getrow idata, index2
+ idata setrow iswap2, index
+ idata setrow iswap, index2
+ endif
+ index2 += 1
+ od
+ index += 1
+ od
+ xout idata
+endop
+
+
+
+
+
+
+/*
+ Filter a soundcollection object with min and max parameters of basic analysis data applied
+ ifiltered[][], ivalid subselect isounds[][],
+ [iminduration, imaxdur, iminrms, imaxrms, iminpitch, imaxpitch, imincent, imaxcent]
+
+ ifiltered[][] the new soundcollection 'object' with sounds limited to scope specified
+ ivalid 1 if records returned, 0 if the returned array is effectively empty
+ isounds[][] the input soundcollection 'object' to be filtered
+ iminduration optional minimum duration
+ imaxduration optional maximum duration
+ iminrms optional minimum rms
+ imaxrms optional maximum rms
+ iminpitch optional minimum pitch
+ imaxpitch optional maximum pitch
+ imincent optional minimum centroid
+ imaxcent optional maximum centroid
+
+*/
+opcode sdb_subselect, i[][]i, i[][]iii
+ idata[][], ielement, imin, imax xin
+
+ indexes[] init lenarray(idata)
+ imaxindex = 0
+ index = 0
+
+ while (index < lenarray(idata)) do
+ if ( \
+ idata[index][ielement] >= imin && idata[index][ielement] <= imax \
+ ) then
+ indexes[imaxindex] = index
+ imaxindex += 1
+ endif
+ index += 1
+ od
+
+ if (imaxindex == 0) then
+ ifiltered[][] init 1, 1
+ ivalid = 0
+ else
+ ifiltered[][] init imaxindex, 7
+ ivalid = 1
+ index = 0
+ while (index < imaxindex) do ; TODO setrow here?????
+ index2 = 0
+ while (index2 < 7) do
+ ifiltered[index][index2] = idata[indexes[index]][index2]
+ index2 += 1
+ od
+ index += 1
+ od
+ endif
+ xout ifiltered, ivalid
+endop
+
+
+
+
+opcode _sdb_playsound, 0, iSppoooo
+ index, Sbus, iamp, ihz, iwhen, iusepvs, ikeepformant, ifadein xin
+ Scoreline = sprintf("i\"sdb_player_default\" %f 1 \"%s\" %d %f %f %d %d %d", iwhen, Sbus, index, iamp, ihz, iusepvs, ikeepformant, ifadein)
+ scoreline_i Scoreline
+endop
+
+
+opcode sdb_playsound, 0, i[][]iSpooooo
+ isounds[][], index, Sbus, iamp, iwhen, ihz, iusepvs, ikeepformant, ifadein xin
+ _sdb_playsound isounds[index][0], Sbus, iamp, ihz, iwhen, iusepvs, ikeepformant, ifadein
+endop
+
+
+opcode sdb_playrandom, 0, i[][]Spooooo
+ isounds[][], Sbus, iamp, iwhen, ihz, iusepvs, ikeepformant, ifadein xin
+ index = round(random(0, lenarray(isounds)-1))
+ _sdb_playsound isounds[index][0], Sbus, iamp, ihz, iwhen, iusepvs, ikeepformant, ifadein
+endop
+
+
+instr sdb_player_default
+ Sbus = p4
+ isoundindex = p5
+ iamp = p6
+ ihz = p7
+ iusepvs = p8
+ ikeepformant = p9
+ ifadein = p10
+ isound[] = get_sound(isoundindex)
+
+ p3 = isound[3]
+
+ aL, aR loscil iamp, 1, isound[0], 1
+
+ kenv linseg 1, p3*0.9, 1, p3*0.1, 0
+
+ bus_mix(Sbus, aL*kenv, aR*kenv)
+endin
+
+#end
diff --git a/site/udo/sounddb.udo b/site/udo/sounddb.udo
new file mode 100755
index 0000000..91f6f10
--- /dev/null
+++ b/site/udo/sounddb.udo
@@ -0,0 +1,270 @@
+#ifndef UDO_SOUNDDB
+#define UDO_SOUNDDB ##
+/*
+ SQL database interface to sound object management.
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+; if XDB extract has been loaded, don't use database
+#ifdef XDB_SET
+#include "soundxdb.udo"
+#else
+
+#include "pgdb.udo"
+#include "host_tools.udo"
+
+; set max number of files for global array allocation
+imaxindex dbscalar gidb, "SELECT MAX(id)+1 FROM file"
+gisounddb[][] init imaxindex, 4
+
+
+/*
+ Load file to gisounddb: to be used internally and passed parameters from database
+
+ _sounddb_loadfile ifileid, Spath, ichannels, iduration, irmsnorm, isamplerate [, imono=0]
+
+ ifileid database file ID, corresponds to index of gisounddb
+ Spath path to load sound file from
+ ichannels number of channels
+ iduration sound duration
+ irmsnorm normalisation factor
+ isamplerate sample rate
+ imono whether to load sound as mono
+*/
+opcode _sounddb_loadfile, 0, iSiiiio
+ ifileid, Spath, ichannels, iduration, irmsnorm, isamplerate, imono xin
+ if (imono == 1) then
+ ichannels = 1
+ iloadchan = 1
+ else
+ iloadchan = 0
+ endif
+ isize = iduration * isamplerate * ichannels; HACK: grain cannot use deferred time: TODO: pass in samplerate and length to opcode (TODO: database needs samplerate)
+ ifn = ftgen(0, 0, isize, 1, Spath, 0, 0, iloadchan)
+ gisounddb[ifileid][0] = ifn
+ gisounddb[ifileid][1] = ichannels
+ gisounddb[ifileid][2] = iduration
+ gisounddb[ifileid][3] = irmsnorm
+endop
+
+
+/*
+ Get file details for a give file ID
+
+ ifn, ichannels, iduration, irmsnorm sounddb_get ifileid
+
+ ifn ftable number containing sound
+ ichannels number of channels in file
+ iduration duration of file in seconds
+ irmsnorm RMS normalisation factor
+ ifileid file ID to look up
+*/
+opcode sounddb_get, iiii, i
+ ifileid xin
+ xout gisounddb[ifileid][0], gisounddb[ifileid][1], gisounddb[ifileid][2], gisounddb[ifileid][3]
+endop
+
+
+/*
+ Load files to gisounddb if not already loaded, to be passed a 2D string array as returned from a database query. Returns the file IDs in an array
+
+ ifileids[] _sounddb_loadobject SqueryResult[][] [, imono=0]
+
+ ifileids[] database file IDs, which also correspond to indexes in gisounddb
+ SqueryResult[][] query result from database with each row containing file ID, path, channels, duration, RMS normalisation factor and samplerate
+ imono whether to load all sounds as mono
+*/
+opcode _sounddb_loadobject, i[], S[][]o
+ Sres[][], imono xin
+ iarraylength = lenarray(Sres)
+ idata[] init iarraylength
+ index = 0
+ while (index < iarraylength) do
+ ifileid strtod Sres[index][0] ; fileid
+ idata[index] = ifileid
+
+ if (gisounddb[ifileid][0] == 0) then ; load required
+ _sounddb_loadfile ifileid, Sres[index][1], strtod(Sres[index][2]), strtod(Sres[index][3]), strtod(Sres[index][4]), strtod(Sres[index][5]), imono
+ endif
+ index += 1
+ od
+ xout idata
+endop
+
+
+/*
+ Load a sound to gisounddb if not already loaded, based on a specified query using f_nearestnote.
+ Return the file ID and the result of column 6, which is the ratio to the nearest pitch requested.
+ Used internally by the sounddb_mel_nearestnote opcodes which select one row
+
+ ifileid, ipitchratio _sounddb_mel_nearestnote_inner Squery
+
+ ifileid file ID
+ ipitchratio pitch ratio to note requested
+ Squery query to evaluate
+*/
+opcode _sounddb_mel_nearestnote_inner, ii, S
+ Squery xin
+ Sres[][] dbarray gidb, Squery
+ ifileid strtod Sres[0][0]
+
+ if (gisounddb[ifileid][0] == 0) then ; load required
+ _sounddb_loadfile ifileid, Sres[0][1], strtod(Sres[0][2]), strtod(Sres[0][3]), strtod(Sres[0][4]), strtod(Sres[0][5])
+ endif
+ xout ifileid, strtod(Sres[0][6])
+endop
+
+
+; nearest note query base
+#define SOUNDDB_NNQUERYBASE #SELECT file_id, f_localpath(%d, path), channels, duration, rmsnormal, samplerate, pitchratio FROM f_nearestnote#
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote Scollection, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ Scollection collection name
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, Si
+ Scollection, inote xin
+ ifileid, ipitchratio _sounddb_mel_nearestnote_inner sprintf("$SOUNDDB_NNQUERYBASE (%f, '%s')", gihost_type, inote, Scollection)
+ xout ifileid, ipitchratio
+endop
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ icollectionid collection ID
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, ii
+ icollectionid, inote xin
+ ifileid, ipitchratio _sounddb_mel_nearestnote_inner sprintf("$SOUNDDB_NNQUERYBASE (%f, %d)", gihost_type, inote, icollectionid)
+ xout ifileid, ipitchratio
+endop
+
+
+/*
+ List file collections
+
+ Sout[][] list of file collection name and type
+*/
+opcode sounddb_listcollections, S[][], 0
+ Squery = "select fc.name, fct.name from filecollection fc join filecollectiontype fct on fc.type_id = fct.id"
+ Sout[][] dbarray gidb, Squery
+ xout Sout
+endop
+
+
+/*
+
+*/
+opcode sounddb_getbypathpart, i[], S
+ SpathPart xin
+ Sbase = {{select file_id, f_localpath(%d, path), channels, duration, rmsnormal, samplerate
+ from svw.analysis_basic_collectionnorm
+ where path ilike '%s'
+ }}
+ Squery = sprintf(Sbase, gihost_type, SpathPart)
+ Sres[][] dbarray gidb, Squery
+ idata[] _sounddb_loadobject Sres
+ xout idata
+endop
+
+
+/*
+ Get the collection ID and file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[], icollectionid sounddb_getcollection Scollection [, imono=0]
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ icollectionid collection ID
+ Scollection collection name
+ imono whether to load all sounds in mono
+*/
+opcode sounddb_getcollection, i[]i, So
+ Scollection, imono xin
+ Sbase = {{select file_id, f_localpath(%d, path), channels, duration, rmsnormal, samplerate, fc.id
+ from svw.analysis_basic_collectionnorm a
+ join filecollection fc on fc.id = a.filecollection_id
+ where %s
+ }}
+
+ if (strindex(Scollection, ",") > 0) then
+ Sclause = "(1=2"
+ index = 1
+ Stemp = Scollection
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ Sclause strcat Sclause, sprintf(" OR fc.name='%s'", strsub(Stemp, 0, index))
+ Stemp strsub Stemp, index+1
+ else
+ Sclause strcat Sclause, sprintf(" OR fc.name='%s'", Stemp)
+ endif
+ od
+ Sclause strcat Sclause, ")"
+ else
+ Sclause = sprintf("fc.name = '%s'", Scollection)
+ endif
+
+ Squery sprintf Sbase, gihost_type, Sclause
+ Sres[][] dbarray gidb, Squery
+ idata[] _sounddb_loadobject Sres, imono
+ icollectionid = strtod(Sres[0][6])
+ xout idata, icollectionid
+endop
+
+
+/*
+ Get the ID of a filecollection by name
+
+ icollectionid sounddb_getcollectionid Scollection [, ipreload = 0, imono = 0]
+
+ icollectionid collection ID
+ Scollection collection name
+ ipreload preload the files to gisounddb
+ imono whether to load all sounds in mono
+*/
+opcode sounddb_getcollectionid, i, Soo
+ Scollection, ipreload, imono xin
+ if (ipreload == 0) then
+ icollectionid = dbscalar(gidb, sprintf("SELECT id FROM filecollection WHERE name = '%s'", Scollection))
+ else
+ i_[], icollectionid sounddb_getcollection Scollection, imono
+ endif
+ xout icollectionid
+endop
+
+/*
+ Get the file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[] sounddb_getcollection Scollection [, imono = 0]
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ Scollection collection name
+ imono whether to load all sounds in mono
+*/
+opcode sounddb_getcollection, i[], So
+ Scollection, imono xin
+ idata[], icollectionid sounddb_getcollection Scollection, imono
+ xout idata
+endop
+
+; end of XDB_SET
+#end
+
+#end
diff --git a/site/udo/soundfont.udo b/site/udo/soundfont.udo
new file mode 100755
index 0000000..6c60b99
--- /dev/null
+++ b/site/udo/soundfont.udo
@@ -0,0 +1,68 @@
+#ifndef UDO_SOUNDFONT
+#define UDO_SOUNDFONT ##
+/*
+ Sound font tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+#include "host_tools.udo"
+
+; load soundfonts
+isf_temp[] fillarray \
+ sfload(dir_soundfont("Rhodes/Galaxy_Electric_Pianos.sf2")),\
+ sfload(dir_soundfont("Rhodes/vibra.sf2")),\
+ sfload(dir_soundfont("Rhodes/JR_elepiano.sf2")),\
+ sfload(dir_soundfont("Rhodes/Crysrhod.sf2")),\
+ sfload(dir_soundfont("Rhodes/KR-O5R-Operator.sf2"))
+
+; prepare rhodes presets
+gisf_rhodes[] fillarray \
+ sfpreset(4, 2, isf_temp[0], 0),\
+ sfpreset(5, 2, isf_temp[0], 1),\
+ sfpreset(4, 3, isf_temp[0], 2),\
+ sfpreset(5, 3, isf_temp[0], 3),\
+ sfpreset(4, 4, isf_temp[0], 4),\
+ sfpreset(5, 4, isf_temp[0], 5),\
+ sfpreset(0, 0, isf_temp[1], 6),\
+ sfpreset(2, 0, isf_temp[1], 7),\
+ sfpreset(0, 0, isf_temp[2], 8 ),\
+ sfpreset(0, 0, isf_temp[3], 9),\
+ sfpreset(0, 0, isf_temp[4], 10)
+
+; rhodes gains for each preset index as above
+gisf_rhodes_gain[] fillarray 1.6, 5, 1, 1, 1, 1, 1, 1, 1, 1.5, 1
+gisf_rhodes_noteaugment[] fillarray 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0
+
+/*
+ Play rhodes soundfont
+ aL, aR sf_rhodes index, inote, [ivel]
+
+ index index in gisf_rhodes, 0 to 10
+ inote midi note number
+ ivel velocity, 0 to 1, default=1
+*/
+opcode sf_rhodes, aa, iip
+ index, inote, ivel xin
+ aL, aR sfplay3 ivel, inote + gisf_rhodes_noteaugment[index], gisf_rhodes_gain[index]/30000, 1, gisf_rhodes[index]
+ xout aL, aR
+endop
+
+
+/*
+ Play random rhodes soundfont from gisf_rhodes
+ aL, aR sf_rhodes inote, [ivel]
+
+ inote midi note number
+ ivel velocity, 0 to 1, default=1
+*/
+opcode sf_rhodes_random, aa, ip
+ inote, ivel xin
+ aL, aR sf_rhodes round(random(0, lenarray(gisf_rhodes)-1)), inote, ivel
+ xout aL, aR
+endop
+
+#end \ No newline at end of file
diff --git a/site/udo/soundfonts/Rhodes/Crysrhod.sf2 b/site/udo/soundfonts/Rhodes/Crysrhod.sf2
new file mode 100755
index 0000000..2fc86b4
--- /dev/null
+++ b/site/udo/soundfonts/Rhodes/Crysrhod.sf2
Binary files differ
diff --git a/site/udo/soundfonts/Rhodes/Galaxy_Electric_Pianos.sf2 b/site/udo/soundfonts/Rhodes/Galaxy_Electric_Pianos.sf2
new file mode 100755
index 0000000..9f92285
--- /dev/null
+++ b/site/udo/soundfonts/Rhodes/Galaxy_Electric_Pianos.sf2
Binary files differ
diff --git a/site/udo/soundfonts/Rhodes/JR_elepiano.sf2 b/site/udo/soundfonts/Rhodes/JR_elepiano.sf2
new file mode 100755
index 0000000..23cf6e5
--- /dev/null
+++ b/site/udo/soundfonts/Rhodes/JR_elepiano.sf2
Binary files differ
diff --git a/site/udo/soundfonts/Rhodes/KR-O5R-Operator.sf2 b/site/udo/soundfonts/Rhodes/KR-O5R-Operator.sf2
new file mode 100755
index 0000000..99c3782
--- /dev/null
+++ b/site/udo/soundfonts/Rhodes/KR-O5R-Operator.sf2
Binary files differ
diff --git a/site/udo/soundfonts/Rhodes/jRhodes3c-stereo.sf2 b/site/udo/soundfonts/Rhodes/jRhodes3c-stereo.sf2
new file mode 100755
index 0000000..cacd81c
--- /dev/null
+++ b/site/udo/soundfonts/Rhodes/jRhodes3c-stereo.sf2
Binary files differ
diff --git a/site/udo/soundfonts/Rhodes/vibra.sf2 b/site/udo/soundfonts/Rhodes/vibra.sf2
new file mode 100755
index 0000000..7c1c415
--- /dev/null
+++ b/site/udo/soundfonts/Rhodes/vibra.sf2
Binary files differ
diff --git a/site/udo/soundxdb.udo b/site/udo/soundxdb.udo
new file mode 100755
index 0000000..70160d6
--- /dev/null
+++ b/site/udo/soundxdb.udo
@@ -0,0 +1,201 @@
+#ifndef UDO_SOUNDXDB
+#define UDO_SOUNDXDB ##
+/*
+ SQL database extract interface to sound object management.
+ File containing extract definitions must be included before this.
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+#ifndef XDB_SET
+prints "Database extract not defined; cannot continue.\n\n\n"
+exitnow
+#end
+
+
+#ifndef XDB_MINNOTE
+#define XDB_MINNOTE #0#
+#end
+
+
+
+
+
+/*
+ Get the ID of a filecollection by name
+
+ icollectionid sounddb_getcollectionid Scollection
+
+ icollectionid collection ID
+ Scollection collection name
+*/
+opcode sounddb_getcollectionid, i, S
+ Scollection xin
+ index = 0
+ while (index < lenarray(gSxdb_collections)) do
+ if (strcmp(gSxdb_collections[index], Scollection) == 0) then
+ igoto complete
+ endif
+ index += 1
+ od
+ index = -1
+complete:
+ xout index
+endop
+
+
+
+/*
+ Get the file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[] sounddb_getcollection Scollection
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ Scollection collection name
+*/
+opcode sounddb_getcollection, i[], S
+ Scollection xin
+ idata[], icollectionid sounddb_getcollection Scollection
+ xout idata
+endop
+
+
+
+
+
+/*
+ Get the collection ID and file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[], icollectionid sounddb_getcollection Scollection
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ icollectionid collection ID
+ Scollection collection name
+*/
+opcode sounddb_getcollection, i[]i, S
+ Scollection xin
+
+ itotalsize = 0
+ if (strindex(Scollection, ",") > 0) then
+ index = 1
+ Stemp = Scollection
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ icollectionid = sounddb_getcollectionid(strsub(Stemp, 0, index))
+ itotalsize += ftlen(gixdb_collectionsfn[icollectionid])
+ Stemp strsub Stemp, index+1
+ else
+ icollectionid = sounddb_getcollectionid(Stemp)
+ itotalsize += ftlen(gixdb_collectionsfn[icollectionid])
+ endif
+ od
+
+ idata[] init itotalsize
+ iwriteindex = 0
+ index = 1
+ Stemp = Scollection
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ icollectionid = sounddb_getcollectionid(strsub(Stemp, 0, index))
+ ifn = gixdb_collectionsfn[icollectionid]
+ ireadindex = 0
+ while (ireadindex < ftlen(ifn)) do
+ idata[iwriteindex] table ireadindex, ifn
+ ireadindex += 1
+ iwriteindex += 1
+ od
+ Stemp strsub Stemp, index+1
+ else
+ icollectionid = sounddb_getcollectionid(Stemp)
+ ifn = gixdb_collectionsfn[icollectionid]
+ ireadindex = 0
+ while (ireadindex < ftlen(ifn)) do
+ idata[iwriteindex] table ireadindex, ifn
+ ireadindex += 1
+ iwriteindex += 1
+ od
+ endif
+ od
+
+ else
+ icollectionid = sounddb_getcollectionid(Scollection)
+ idata[] tab2array gixdb_collectionsfn[icollectionid]
+ igoto complete
+ endif
+
+complete:
+ xout idata, icollectionid
+endop
+
+
+
+
+/*
+ gixdb_pitchreference
+ has 127 * 4 entries. get start and end indexes for gixdb_pitchnotes by
+ index = (inote + (icollectionid * 127)) * 2
+
+ then values
+ iminindex table index, gixdb_pitchreference
+ imaxindex table index+1, gixdb_pitchreference
+
+ then get the actual files which are between min and max indexes eg
+
+ iselected = round(random(iminindex, imaxindex))
+ ifileid table iselected, gixdb_pitchnotes
+ ipitchadjust table iselected, gixdb_pitchadjust
+
+
+
+*/
+
+
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ icollectionid collection ID
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, ii
+ icollectionid, inote xin
+ irefindex = ((inote - $XDB_MINNOTE) + tab_i(icollectionid, gixdb_pitchrefoffset)) * 2
+ iselected = round(random(tab_i(irefindex, gixdb_pitchreference), tab_i(irefindex+1, gixdb_pitchreference)))
+ ifileid tab_i iselected, gixdb_pitchnotes
+ ipitchratio tab_i iselected, gixdb_pitchadjust
+
+ xout ifileid, ipitchratio
+endop
+
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote Scollection, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ Scollection collection name
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, Si
+ Scollection, inote xin
+ icollectionid = sounddb_getcollectionid(Scollection)
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+ xout ifileid, ipitchratio
+endop
+
+
+
+#end
diff --git a/site/udo/soundxdb_extract.udo b/site/udo/soundxdb_extract.udo
new file mode 100755
index 0000000..64899c8
--- /dev/null
+++ b/site/udo/soundxdb_extract.udo
@@ -0,0 +1,67 @@
+#ifndef UDO_SOUNDDB_XPORT
+#define UDO_SOUNDDB_XPORT ##
+/*
+ SQL database extraction to be used with soundxdb.udo
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+#include "pgdb.udo"
+
+
+/*
+ Extract UDO definition from database for specified collection(s), to be used with soundxdb.udo.
+ Permits using functionality of sounddb.udo without database connectivity.
+ The file output includes an include of soundxdb.udo, so only the resulting file needs to be included.
+
+ soundxdb_extract Sfile, Scollections [, iminnote=0]
+
+ Sfile file path to extract to
+ Scollections collection name, or multiple comma-separated (no whitespace) collection names
+ iminnote the minimum MIDI note number to extract (higher can save output space/text usage; lower notes (eg < 25) are not usually used)
+*/
+opcode soundxdb_extract, 0, SSo
+ Sfile, Scollections, iminnote xin
+
+ Sclause = ""
+ if (strindex(Scollections, ",") > 0) then
+ index = 1
+ Stemp = Scollections
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ Sclause strcat Sclause, sprintf("'%s',", strsub(Stemp, 0, index))
+ Stemp strsub Stemp, index+1
+ else
+ Sclause strcat Sclause, sprintf("'%s'", Stemp)
+ endif
+ od
+ else
+ Sclause = sprintf("'%s'", Scollections)
+ endif
+
+ Squery = sprintf("SELECT f_xdb_export(%d, %s)", iminnote, Sclause)
+
+ iwritelines = 50
+ Scache = ""
+ Sres[][] dbarray gidb, Squery
+ index = 0
+ while (index < lenarray(Sres)) do
+ Scache strcat Scache, Sres[index][0]
+ Scache strcat Scache, "\n"
+
+ if (index % iwritelines == 0) then
+ fprints Sfile, Scache
+ Scache = ""
+ endif
+ index += 1
+ od
+ fprints Sfile, Scache
+ Scache = ""
+endop
+
+
+#end
diff --git a/site/udo/spectral_sampler.udo b/site/udo/spectral_sampler.udo
new file mode 100755
index 0000000..04f1f4d
--- /dev/null
+++ b/site/udo/spectral_sampler.udo
@@ -0,0 +1,126 @@
+#ifndef UDO_SPECTRALSAMPLER
+#define UDO_SPECTRALSAMPLER ##
+/*
+ Spectral sampler
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ ; pvs buffer handle and length storage
+gipvsBuffers[] init 8
+gipvsBufferLengths[] init lenarray(gipvsBuffers)
+
+; record to a spectral sampling buffer
+; ibuffer spectralsamplerecord ain, iduration, ifftsize
+opcode spectralsamplerrecord, i, aio
+ ain, iduration, ifftsize xin
+ if (ifftsize == 0) then
+ ifftsize = 1024
+ endif
+ kamp linseg 0, iduration * 0.01, 1, iduration * 0.98, 1, iduration * 0.01, 0
+ ain1 = ain * kamp
+ ilength = iduration + (ifftsize / sr)
+ fanal pvsanal ain1, ifftsize, ifftsize/4, ifftsize, 1
+ ibuffer, ktim pvsbuffer fanal, ilength
+ xout ibuffer
+endop
+
+
+; play back from a spectral sampling buffer
+; aL, aR spectralsamplerplay ibuffer, ilength, ktime, kpos
+opcode spectralsamplerplay, aa, iikk
+ ibuffer, ilength, ktime, kpos xin
+ kchange changed kpos
+ aphasor, asyncout syncphasor a(ktime * ilength), a(kchange)
+ kphasor = k(aphasor) + (kpos * ilength)
+ fL pvsbufread kphasor, ibuffer
+ fR pvsbufread kphasor*0.95, ibuffer
+ aL pvsynth fL
+ aR pvsynth fR
+ xout aL, aR
+endop
+
+
+*/
+; ABOVE ARE LEGACY AND TO BE DEPRECATED
+
+
+/*
+ Spectral sampling and playback
+
+ Can't sub-opcode the f in the a for some reason, doesn't work - so some duplication here
+*/
+
+opcode spectralsampler, f, fkkio
+ fanal, ktime, kpos, ilength, icontinuous xin
+
+ ksampling init 1
+ if (icontinuous == 1 || ksampling == 1) then
+ ibuffer, ktime pvsbuffer fanal, ilength
+ if (icontinuous == 0 && timeinsts() >= ilength) then
+ ksampling = 0
+ endif
+ endif
+
+ kchange changed kpos
+ aphasor, asyncout syncphasor a(ktime * ilength), a(kchange)
+ kphasor = k(aphasor) + (kpos * ilength)
+ fread pvsbufread kphasor, ibuffer
+ xout fread
+endop
+
+
+
+opcode spectralsampler, a, akkioo
+ ain, ktime, kpos, ilength, ifftsize, icontinuous xin
+
+ ifftsize = (ifftsize == 0) ? 1024 : ifftsize
+ ksampling init 1
+ if (icontinuous == 1 || ksampling == 1) then
+ fanal pvsanal ain, ifftsize, ifftsize/4, ifftsize, 1
+ ibuffer, ktime pvsbuffer fanal, ilength
+ if (icontinuous == 0 && timeinsts() >= ilength) then
+ ksampling = 0
+ endif
+ endif
+
+ kchange changed kpos
+ aphasor, asyncout syncphasor a(ktime * ilength), a(kchange)
+ kphasor = k(aphasor) + (kpos * ilength)
+ fread pvsbufread kphasor, ibuffer
+ aout pvsynth fread
+ xout aout
+endop
+
+
+
+opcode spectralsampler, aa, aakkioo
+ ainL, ainR, ktime, kpos, ilength, ifftsize, icontinuous xin
+
+ ifftsize = (ifftsize == 0) ? 1024 : ifftsize
+ ksampling init 1
+ if (icontinuous == 1 || ksampling == 1) then
+ fanalL pvsanal ainL, ifftsize, ifftsize/4, ifftsize, 1
+ fanalR pvsanal ainR, ifftsize, ifftsize/4, ifftsize, 1
+ ibufferL, ktime pvsbuffer fanalL, ilength
+ ibufferR, ktime pvsbuffer fanalR, ilength
+ if (icontinuous == 0 && timeinsts() >= ilength) then
+ ksampling = 0
+ endif
+ endif
+
+ kchange changed kpos
+ aphasor, asyncout syncphasor a(ktime * ilength), a(kchange)
+ kphasor = k(aphasor) + (kpos * ilength)
+ freadL pvsbufread kphasor, ibufferL
+ freadR pvsbufread kphasor, ibufferR
+ aoutL pvsynth freadL
+ aoutR pvsynth freadR
+ xout aoutL, aoutR
+endop
+
+#end
diff --git a/site/udo/spectral_transforms.udo b/site/udo/spectral_transforms.udo
new file mode 100755
index 0000000..7e9424f
--- /dev/null
+++ b/site/udo/spectral_transforms.udo
@@ -0,0 +1,358 @@
+#ifndef UDO_SPECTRALTRANSFORMS
+#define UDO_SPECTRALTRANSFORMS ##
+/*
+ Spectral transforms, mostly recursive using pvsbin
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "/wavetables.udo"
+
+/*
+ Validate start and end bins against a f-sig, and check/set common variables
+ If start and end are <= 1, treat them as ratios and calculate accordingly
+
+ istartbin, iendbin, ibin, kportamento, ifnwave _spc_validatebins fsig [, istart=0, iend=1, kportamento=-1, ifnwave=-1]
+
+ istartbin resulting bin number
+ iendbin resulting bin number
+ ibin initial bin to use
+ kportamento portamento in/out
+ ifnwave wave f-table in/out
+ fsig f-sig to validate maximum bin number against
+ istart start bin number or ratio
+ iend end bin number or ratio
+*/
+opcode _spc_validatebins, iiiki, fopJj
+ fsig, istart, iend, kportamento, ifnwave xin
+ ioverlap, inumbins, iwinsize, iformat pvsinfo fsig
+
+ if (istart <= 1 && iend <= 1) then ; is ratio
+ istartbin = max(round(inumbins * istart), 1)
+ iendbin = round(inumbins * iend)
+ else ; is absolute bin number
+ istartbin = max(1, istart)
+ iendbin = min(inumbins, iend)
+ endif
+ ibin = istartbin
+ kportamento = (kportamento == -1) ? 0.02 : kportamento
+ ifnwave = (ifnwave == -1) ? gifnSine : ifnwave
+
+ xout istartbin, iendbin, ibin, kportamento, ifnwave
+endop
+
+
+/*
+ Spectral delay
+
+ aout spc_delay fsig, kdeltime, kdeladd [, istart=0, iend=1, kfreqmod=1, kportamento=0.01, ifnwave=-1, ibin=-1]
+
+ aout audio output
+ fsig input signal
+ kdeltime initial delay time in seconds
+ kdeladd increment delay by this with each bin increment or decrement
+ istart absolute bin number or ratio of the start bin, top end bins may be quite low, around a ratio of 0.1
+ iend absolute bin number or ratio of the end bin
+ kfreqmod frequency modulation ratio in resynthesis
+ kportamento portamento time for amp and frequency
+ ifnwave f-table to use for the oscillator, default is sine
+ ibin bin tracking used internally, should not be set
+*/
+opcode spc_delay, a, fkkopPJjj
+ fsig, kdeltime, kdeladd, istart, iend, kfreqmod, kportamento, ifnwave, ibin xin
+ if (ibin == -1) then
+ istart, iend, ibin, kportamento, ifnwave _spc_validatebins fsig, istart, iend, kportamento, ifnwave
+ endif
+
+ idirection = (istart < iend) ? 1 : -1
+ kamp, kfreq pvsbin fsig, ibin
+ imaxdelay = 1000
+ aout oscili portk(kamp, kportamento), portk(kfreq, kportamento) * kfreqmod, ifnwave
+ kdel = min:k(imaxdelay, kdeltime + (kdeladd * (((idirection == 1) ? ibin - istart : istart - ibin) + 1)) * 1000)
+ aout vdelay aout, a(kdel), imaxdelay
+
+ if ((idirection == 1 && ibin + 1 < iend) || (idirection == -1 && ibin - 1 >= iend)) then
+ arecurse spc_delay fsig, kdeltime, kdeladd, istart, iend, kfreqmod, kportamento, ifnwave, ibin + idirection
+ aout += arecurse
+ endif
+
+ xout aout
+endop
+
+
+/*
+ Spectral incremental shift
+
+ aout spc_shift fsig, kfreqincr [, istart=0, iend=1, kfreqmod=1, kportamento=0.01, ifnwave=-1, ibin=-1]
+
+ aout audio output
+ fsig input signal
+ kfreqincr frequency increment for each bin, may be positive or negative
+ istart absolute bin number or ratio of the start bin, top end bins may be quite low, around a ratio of 0.1
+ iend absolute bin number or ratio of the end bin
+ kfreqmod frequency modulation ratio in resynthesis
+ kportamento portamento time for amp and frequency
+ ifnwave f-table to use for the oscillator, default is sine
+ ibin bin tracking used internally, should not be set
+*/
+opcode spc_shift, a, fkopPJjj
+ fsig, kfreqincr, istart, iend, kfreqmod, kportamento, ifnwave, ibin xin
+ if (ibin == -1) then
+ istart, iend, ibin, kportamento, ifnwave _spc_validatebins fsig, istart, iend, kportamento, ifnwave
+ endif
+
+ idirection = (istart < iend) ? 1 : -1
+ kamp, kfreq pvsbin fsig, ibin
+ kfreqadd = (((idirection == 1) ? ibin - istart : istart - ibin) + 1) * kfreqincr
+ aout oscili portk(kamp, kportamento), portk(kfreq + kfreqadd, kportamento) * kfreqmod, ifnwave
+
+ if ((idirection == 1 && ibin + 1 < iend) || (idirection == -1 && ibin - 1 >= iend)) then
+ arecurse spc_shift fsig, kfreqincr, istart, iend, kfreqmod, kportamento, ifnwave, ibin + idirection
+ aout += arecurse
+ endif
+
+ xout aout
+endop
+
+
+/*
+ Spectral gate
+
+ aout spc_gate fsig, kthresh [, khold=0, istart=0, iend=1, kfreqmod=1, kportamento=0.01, ifnwave=-1, ibin=-1]
+
+ aout audio output
+ fsig input signal
+ kthresh threshold amplitude for gating
+ khold hold amplitudes rather than decaying them
+ istart absolute bin number or ratio of the start bin, top end bins may be quite low, around a ratio of 0.1
+ iend absolute bin number or ratio of the end bin
+ kfreqmod frequency modulation ratio in resynthesis
+ kportamento portamento time for amp and frequency
+ ifnwave f-table to use for the oscillator, default is sine
+ ibin bin tracking used internally, should not be set
+*/
+opcode spc_gate, a, fkOopPJjj
+ fsig, kthresh, khold, istart, iend, kfreqmod, kportamento, ifnwave, ibin xin
+ if (ibin == -1) then
+ istart, iend, ibin, kportamento, ifnwave _spc_validatebins fsig, istart, iend, kportamento, ifnwave
+ endif
+
+ idirection = (istart < iend) ? 1 : -1
+ klastamp init 0
+ klastfreq init 0
+ kamp, kfreq pvsbin fsig, ibin
+
+ if (kamp > kthresh) then
+ klastamp = kamp
+ klastfreq = kfreq
+ else
+ kamp = (khold == 1) ? klastamp : 0
+ kfreq = klastfreq
+ endif
+ aout oscili portk(kamp, kportamento), portk(kfreq, kportamento) * kfreqmod, ifnwave
+
+ if ((idirection == 1 && ibin + 1 < iend) || (idirection == -1 && ibin - 1 >= iend)) then
+ arecurse spc_gate fsig, kthresh, khold, istart, iend, kfreqmod, kportamento, ifnwave, ibin + idirection
+ aout += arecurse
+ endif
+
+ xout aout
+endop
+
+
+
+/*
+ Spectral granular resynthesis 1
+ Internal opcode
+*/
+opcode _spc_grain1_inner, a, ikkpopPJJOJjpp
+ ipbuf, ktime, kgraindur, ilayers, istart, iend, kfreqmod, kfreqrand, kdurrand, kpitchrand, kportamento, ifnwave, ibin, ibingrain xin
+ idirection = (istart < iend) ? 1 : -1
+ kchange metro 1 / kgraindur;/ 1
+ if (kchange == 1) then
+ if (kfreqrand > 1) then
+ kfreqrandv random 1 / kfreqrand, kfreqrand
+ elseif (kfreqrand == 1) then
+ kfreqrandv = 1
+ else
+ kfreqrandv random 0.5, 2
+ endif
+
+ if (kdurrand > 1) then
+ kdurrandv random 1 / kdurrand, kdurrand
+ elseif (kdurrand == 1) then
+ kdurrandv = 1
+ else
+ kdurrandv random 0.5, 2
+ endif
+
+ if (kpitchrand > 1) then
+ kpitchrandv random 1 / kpitchrand, kpitchrand
+ else
+ kpitchrandv = 1
+ endif
+ endif
+
+ kreadfreq = (1 / kgraindur) * kfreqrandv ;5 + random:k(1, 5) ;random(10, 20) ;10 + random:k(0, 5)
+ kenv = abs:k(oscili:k(1, kreadfreq, gifnHanning, random(0, 1)))
+ areadpos phasor kreadfreq, random(0, 1)
+
+ kreadtime = ktime + (k(areadpos) * kgraindur * kdurrandv) ;ktime + random(0, 0.6) ;portk(ktime + random:k(0, 0.6), kportamento)
+ fsig pvsbufread kreadtime, ipbuf
+ kamp, kfreq pvsbin fsig, ibin
+ kfreq *= kpitchrandv
+ kamp *= kenv
+
+ aout oscil portk(kamp, kportamento), portk(kfreq, kportamento) * kfreqmod, ifnwave
+ if (ibingrain < ilayers) then
+ arecurse _spc_grain1_inner ipbuf, ktime, kgraindur, ilayers, istart, iend, kfreqmod, kfreqrand, kdurrand, kpitchrand, kportamento, ifnwave, ibin, ibingrain + 1
+ aout += arecurse
+ elseif ((idirection == 1 && ibin + 1 < iend) || (idirection == -1 && ibin - 1 >= iend)) then
+ arecurse _spc_grain1_inner ipbuf, ktime, kgraindur, ilayers, istart, iend, kfreqmod, kfreqrand, kdurrand, kpitchrand, kportamento, ifnwave, ibin + idirection
+ aout += arecurse
+ endif
+ xout aout
+endop
+
+
+/*
+ Spectral granular resynthesis 1
+
+ aout spc_grain1 ifn, ktime, kgraindur [, ifftsize=512, ilayers=1, istart=0, iend=1, kfreqmod=1, kfreqrand=1, kdurrand=1, kpitchrand=0, kportamento=0.01, ifnwave=-1]
+
+ aout audio output
+ ifn f-table containing mono source sound
+ ktime read time in seconds
+ kgraindur grain duration in seconds
+ ifftsize fft size for the input
+ ilayers number of overlapping layers of grains to use
+ istart absolute bin number or ratio of the start bin, top end bins may be quite low, around a ratio of 0.1
+ iend absolute bin number or ratio of the end bin
+ kfreqmod frequency modulation ratio in resynthesis
+ kfreqrand read frequency random variation ratio
+ kdurrand grain duration random variation ratio
+ kpitchrand grain pitch random variation ratio
+ kportamento portamento time for amp and frequency
+ ifnwave f-table to use for the oscillator, default is sine
+*/
+opcode spc_grain1, a, ikkjpopPJJOJj
+ ifn, ktime, kgraindur, ifftsize, ilayers, istart, iend, kfreqmod, kfreqrand, kdurrand, kpitchrand, kportamento, ifnwave xin
+ ifftsize = (ifftsize == -1) ? 512 : ifftsize
+ ktimek timeinstk
+ ilen = ftlen(ifn) / ftsr(ifn)
+ ikcycles = ilen * kr
+ kcount init 0
+ if (ktimek == 1) then
+ while (kcount < ikcycles) do
+ apos linseg 0, ilen, ftlen(ifn)
+ asig table3 apos, ifn
+ fsig pvsanal asig, ifftsize, ifftsize/4, ifftsize, 1
+ ipbuf, k_ pvsbuffer fsig, ilen
+ kcount += 1
+ od
+ else
+ istartbin, iendbin, ibin, kportamento, ifnwave _spc_validatebins fsig, istart, iend, kportamento, ifnwave
+ aout _spc_grain1_inner ipbuf, ktime, kgraindur, ilayers, istartbin, iendbin, kfreqmod, kfreqrand, kdurrand, kpitchrand, kportamento, ifnwave, ibin
+ endif
+ xout aout
+endop
+
+/*
+ Alter the phase of FFT values
+
+ aout spc_phasemash ain, kphasemode, kphasevalue, ifftsize
+
+ aout audio output
+ ain audio input
+ kphasemode phase mode: 0 = multiply phase with kphasevalue; 1 = replace phase with kphasevalue
+ kphasevalue phase value, between -pi and +pi
+ ifftsize fft size
+*/
+opcode spc_phasemash, a, akkj
+ ain1, kphasemode, kphasevalue, ifftsize xin
+ ifftsize = (ifftsize == -1) ? 512 : ifftsize
+ ihopsize = ksmps
+ iolaps = (ifftsize / ihopsize)
+ ibw = (sr / ifftsize)
+ kcnt init 0
+ krow init 0
+ kOla1[] init ifftsize
+ kIn1[] init ifftsize
+ kOut[][] init iolaps, ifftsize
+
+ if (kcnt == ihopsize) then
+ kdx = 0
+ kWin1[] window kIn1, krow * ihopsize
+ kSpec1[] rfft kWin1
+ kmags1[] mags kSpec1
+ kphs1[] phs kSpec1 ;init lenarray(kmags1) ;phs kSpec1
+ kindex = 0
+ while (kindex < lenarray(kphs1)) do
+ if (kindex > 0) then
+ if (kphasemode == 0) then
+ kphs1[kindex] = kphs1[kindex] * kphasevalue
+ else
+ kphs1[kindex] = kphasevalue
+ endif
+ endif
+ kindex += 1
+ od
+
+ kSpec1 pol2rect kmags1, kphs1
+ kRow[] rifft kSpec1
+ kWin1 window kRow, krow * ihopsize
+ kOut setrow kWin1, krow
+
+ kOla1 = 0
+ ki = 0
+ until (ki == iolaps) do
+ kRow getrow kOut, ki
+ kOla1 = kOla1 + kRow
+ ki += 1
+ od
+ krow = (krow + 1) % iolaps
+ kcnt = 0
+ endif
+
+ kIn1 shiftin ain1
+ aout shiftout kOla1
+ aouter = aout / iolaps
+ kcnt += ksmps
+ xout aouter
+endop
+
+
+/*
+opcode spc_attack, a, fkOopPJjj
+ fsig, kthresh, khold, istart, iend, kfreqmod, kportamento, ifnwave, ibin xin
+ if (ibin == -1) then
+ istart, iend, ibin, kportamento, ifnwave _spc_validatebins fsig, istart, iend, kportamento, ifnwave
+ endif
+
+ idirection = (istart < iend) ? 1 : -1
+ klastamp init 0
+ klastfreq init 0
+ kamp, kfreq pvsbin fsig, ibin
+
+ if (kamp > kthresh) then
+ klastamp = kamp
+ klastfreq = kfreq
+ else
+ kamp = (khold == 1) ? klastamp : 0
+ kfreq = klastfreq
+ endif
+ aout oscil portk(kamp, kportamento), portk(kfreq, kportamento) * kfreqmod, ifnwave
+
+ if ((idirection == 1 && ibin + 1 < iend) || (idirection == -1 && ibin - 1 >= iend)) then
+ arecurse spc_gate fsig, kthresh, khold, istart, iend, kfreqmod, kportamento, ifnwave, ibin + idirection
+ aout += arecurse
+ endif
+
+ xout aout
+endop
+*/
+
+
+#end
diff --git a/site/udo/string_tools.udo b/site/udo/string_tools.udo
new file mode 100755
index 0000000..c2c14c0
--- /dev/null
+++ b/site/udo/string_tools.udo
@@ -0,0 +1,375 @@
+#ifndef UDO_STRINGTOOLS
+#define UDO_STRINGTOOLS ##
+/*
+ String processing tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022, 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+#include "/host_platform.udo"
+
+/*
+ Replace character with another character or sequence of characters
+ Soutput str_replacechar Sinput, Sfrom, Sto
+
+ Soutput string with replacements
+ Sinput input string
+ Sfrom character to replace
+ Sto string or character to substitute
+*/
+opcode str_replacechar, S, SSS
+ Sinput, Sfrom, Sto xin
+ Soutput = ""
+ index = 0
+ while (index < strlen(Sinput)) do
+ Schar = strsub(Sinput, index, index + 1)
+ if (strcmp(Sfrom, Schar) == 0) then
+ Soutput = strcat(Soutput, Sto)
+ else
+ Soutput = strcat(Soutput, Schar)
+ endif
+ index += 1
+ od
+ xout Soutput
+endop
+
+
+/*
+ String alphabetical bubble sort
+ Sout[] srt_bubblestr Sin[]
+
+ Sout[] sorted array
+ Sin[] array to sort
+*/
+opcode srt_bubblestr, S[], S[]
+ Sin[] xin
+ Stemp = ""
+ ilen = lenarray(Sin)
+ index1 = 0
+ while (index1 < ilen-1) do
+ index2 = 0
+ while (index2 < ilen-1-index1) do
+ if (strcmp(Sin[index2], Sin[index2+1]) > 0) then
+ Stemp = Sin[index2]
+ Sin[index2] = Sin[index2+1]
+ Sin[index2+1] = Stemp
+ endif
+
+ index2 += 1
+ od
+ index1 += 1
+ od
+ xout Sin
+endop
+
+
+/*
+ Polynomial rolling hash
+ ihash str_hash Sstring
+
+ ihash the resulting hash value
+ Sstring string to be hashed
+*/
+opcode str_hash, i, S
+ Sin xin
+ ip = 31
+ im = 1e9+7 ; was 1e9+9, changed for 32bit
+ ipp = 1
+ ihash = 0
+ index = 0
+ while (index < strlen(Sin)) do
+ ihash = (ihash + ((strchar(strsub(Sin, index)) - 97) + 1) * ipp) ; changed from *ip to *ipp ??? due to collisions. not fully checked
+ ipp = (ipp * ip) % im
+ index += 1
+ od
+ xout ihash
+endop
+
+
+/*
+ Split separated string to array
+ Sitems[] str_split Sinput, Separator
+
+ Sitems[] the processed items
+ Sinput string to split
+ Separator separator to split on
+*/
+opcode str_split, S[], SS
+ Sin, Sep xin
+ Stemp = Sin
+ itemnum = 1
+ index = 1
+ while (index > 0) do
+ index strindex Stemp, Sep
+ if (index > 0) then
+ Stemp strsub Stemp, index+1
+ itemnum += 1
+ endif
+ od
+ Sout[] init itemnum
+
+ iwindex = 0
+ Stemp = Sin
+ index = 1
+ while (index > 0) do
+ index strindex Stemp, Sep
+ if (index > 0) then
+ Sout[iwindex] strsub Stemp, 0, index
+ Stemp strsub Stemp, index+1
+ else
+ Sout[iwindex] = Stemp
+ endif
+ iwindex += 1
+ od
+
+ xout Sout
+endop
+
+
+/*
+ Print ftable contents
+ printtable ifn
+
+ ifn ftable number to print
+*/
+opcode printtable, 0, i
+ ifn xin
+ index = 0
+ while (index < ftlen(ifn)) do
+ print table:i(index, ifn)
+ index += 1
+ od
+endop
+
+
+
+/*
+ Store string to an exising table as ascii characters
+ str2tab String, ifn
+
+ String string to store
+ ifn table to store to
+*/
+opcode str2tab, 0, Si
+ String, ifn xin
+ index = 0
+ while (index < strlen(String)) do
+ ival strchar String, index
+ tabw_i ival, index, ifn
+ index += 1
+ od
+ tabw_i -99, index, ifn
+endop
+
+
+/*
+ Create a table and store a string in it as ascii characters
+ ifn str2newtab String
+
+ ifn new table number
+ String string to store
+*/
+opcode str2newtab, i, S
+ String xin
+ ifn ftgen 0, 0, strlen(String)+1, 7, 0
+ str2tab String, ifn
+ xout ifn
+endop
+
+
+/*
+ Obtain a string from a table containing ascii characters
+ String tab2str ifn
+
+ String retrieved string
+ ifn table number
+*/
+opcode tab2str, S, i
+ ifn xin
+ index = 0
+ Sval = ""
+ ival = 0
+ while (ival != -99) do
+ ival tab_i index, ifn
+ if (ival == -99) igoto done
+ Sval strcat Sval, sprintf("%c", ival)
+ index += 1
+ od
+done:
+ xout Sval
+endop
+
+
+/*
+ Obtain file extension converted to lowercase (anything past the last dot)
+ Sextension fileextension Sfile
+
+ Sfile path or filename
+ Sextension the extension in lower case
+*/
+opcode fileextension, S, S
+ Sfile xin
+ ilastdot strrindex Sfile, "."
+ if (ilastdot == -1) then
+ Sextension = "none"
+ goto return
+ endif
+ ilen strlen Sfile
+ Sextension strsub Sfile, ilastdot + 1, ilen
+ Sextension strlower Sextension
+ goto return
+return:
+ xout Sextension
+endop
+
+
+/*
+ Print a string array with each value separated by a newline
+
+ str_printarray Sarray[]
+
+ Sarray[] the array to print
+*/
+opcode str_printarray, 0, S[]
+ Sarray[] xin
+ ilen = lenarray(Sarray)
+ index = 0
+ while (index < ilen) do
+ prints sprintf("%s\n", Sarray[index])
+ index += 1
+ od
+endop
+
+
+/*
+ Test if a string is numeric
+
+ isnumeric str_isnumeric Svalue
+
+ isnumeric 0 if not numeric, 1 if numeric
+ Svalue string to test
+*/
+opcode str_isnumeric, i, S
+ Svalue xin
+ isnumeric = 1
+ ihaspoint = 0
+ index = 0
+ while (index < strlen(Svalue)) do
+ ichar = strchar(Svalue, index)
+
+ ; allow only one decimal point
+ if (ichar == 46) then
+ if (ihaspoint == 1) then
+ isnumeric = 0
+ goto complete
+ else
+ ihaspoint = 1
+ endif
+
+ ; not a number or decimal place
+ elseif (!(ichar >= 48 && ichar <= 57)) then
+ isnumeric = 0
+ goto complete
+ endif
+ index += 1
+ od
+complete:
+ xout isnumeric
+endop
+
+
+
+/*
+ Convert a string to float if numeric
+
+ ivalid, ivalue try_strtod Svalue
+
+ ivalid 0 if the input is not numeric, 1 if conversion is successful
+ ivalue converted value, or -1 if the input is not numeric
+*/
+opcode try_strtod, ii, S
+ Svalue xin
+ if (str_isnumeric(Svalue) == 0) then
+ ivalid = 0
+ ivalue = -1
+ else
+ ivalid = 1
+ ivalue = strtod(Svalue)
+ endif
+ xout ivalid, ivalue
+endop
+
+
+/*
+ Get the basename from a file path separated by slashes
+
+ Sbasename str_basename Spath
+
+ Sbasename the filename
+ Spath the path
+*/
+opcode str_basename, S, S
+ Spath xin
+ Sbasename = strsub(Spath, strrindex(Spath, "/") + 1)
+ xout Sbasename
+endop
+
+
+/*
+ Get a random alphanumeric string
+
+ Sout str_random [ilen=10]
+
+ Sout the random string
+ ilen length of string
+*/
+opcode str_random, S, j
+ ilen xin
+ Sout = ""
+ ilen = (ilen == -1) ? 10 : ilen
+ index = 0
+ while (index < ilen) do
+ irange = round(random(0, 2))
+ if (irange == 0) then
+ ichar random 48, 57
+ elseif (irange == 1) then
+ ichar random 65, 90
+ elseif (irange == 2) then
+ ichar random 97, 122
+ endif
+ Sout = strcat(Sout, sprintf("%c", ichar))
+ index += 1
+ od
+ xout Sout
+endop
+
+/*
+ Get a random filename
+
+ Sout str_randomfilename Sext [,ilen=10]
+
+ Sout the random filename
+ Sext file extension
+ ilen length of string
+*/
+opcode str_randomfilename, S, Sj
+ Sext, ilen xin
+ Sfile str_random ilen
+ xout strcat(Sfile, sprintf(".%s", Sext))
+endop
+
+/*
+ Get a random filename in the host temporary directory
+
+ Sout str_randomtempfilename Sext [,ilen=10]
+*/
+opcode str_randomtempfilename, S, Sj
+ Sext, ilen xin
+ Sfile str_randomfilename Sext, ilen
+ xout sprintf("%s/%s", host_tempdir(), Sfile)
+endop
+
+#end
diff --git a/site/udo/synth_drums.udo b/site/udo/synth_drums.udo
new file mode 100755
index 0000000..3d2ea10
--- /dev/null
+++ b/site/udo/synth_drums.udo
@@ -0,0 +1,229 @@
+#ifndef UDO_SYNTHDRUMS
+#define UDO_SYNTHDRUMS ##
+/*
+ Synth drum generators
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "wavetables.udo"
+
+opcode kick909, a, 0
+ idur = p3
+ ifreq = 200
+ idct = .0001
+ aampenv expseg 1, .08, .3, .4, .001
+ adclick expseg .001, idct, 1, idur-(idct*2), 1, idct, .001
+ aosc1 oscil 1, ifreq*aampenv
+ xout aosc1*aampenv*adclick
+endop
+
+
+/*
+ Clap derived from an instrument by Istvan Varga (2002)
+*/
+opcode clap1, a, jj
+ idec, ifreq xin
+ ibpfrq = (ifreq == -1) ? 1046.5 : ifreq
+ kbpbwd port ibpfrq*0.25, 0.03, ibpfrq*4.0 ;bandpass filter bandwidth
+ idec = (idec == -1) ? 0.5 : idec ;decay time
+
+ a1 = 1.0
+ a1x delay1 a1
+ a1 = a1 - a1x
+ a2 delay a1, 0.011
+ a3 delay a1, 0.023
+ a4 delay a1, 0.031
+
+ a1 tone a1, 60.0
+ a2 tone a2, 60.0
+ a3 tone a3, 60.0
+ a4 tone a4, 1.0 / idec
+
+ aenv1 = a1 + a2 + a3 + a4*60.0*idec
+ aout unirand 2.0
+ aout = aenv1 * (aout - 1.0)
+ aout butterbp aout, ibpfrq, kbpbwd
+ xout aout * 120
+endop
+
+
+/*
+ Rim shot derived from an instrument by Istvan Varga (2002)
+*/
+opcode rim1, a, 0
+ icps = 440.0*exp(log(2.0)*(57.0-69.0)/12.0)
+ acps expon icps, 0.0025, icps * 0.5
+ acps = acps + icps
+ iamp = 1
+ a1a phasor acps, 0.0
+ a1b phasor acps, 0.5
+ afmenv expon 1.0, 0.02, 0.5
+ a1 = (a1a-a1b)*6.0*afmenv
+ acps = acps*(1.0+a1)
+ a0 oscil3 1.0, acps
+ a1 unirand 2.0
+ a1 tone a1-1.0, 2000
+ a0 = a0 + a1*0.1
+ aenv expon 1.0, 0.005, 0.5
+
+ a0 limit 4.0*iamp*a0*aenv, -1.0, 1.0
+ a0 table3 a0*4096.0, gifnSine, 0, 0, 1
+
+ kffrq expseg 20000, 0.07, 100, 1, 100
+
+ a0x tone a0, 4000
+ a0y = a0 - a0x
+ a0x delay a0y, 0.0002
+ a0 = a0 - a0x*4.0
+ a0 pareq a0, kffrq, 0, 0.7071, 2
+
+ a_ linseg 1, p3-0.1, 1, 0.025, 0, 1, 0
+ a0 = a0*a_
+
+ xout a0
+endop
+
+
+/*
+ Snare derived from an instrument by Istvan Varga (2002)
+*/
+opcode snare1, a, 0
+ icps0 = 150
+ icps1 = 2.0 * icps0
+ kcps port icps0, 0.007, icps1
+ kcpsx = kcps * 1
+ kfmd port 0.0, 0.01, 0.7
+ aenv1 expon 1.0, 0.03, 0.5
+ kenv2 port 1.0, 0.008, 0.0
+ aenv2 interp kenv2
+ aenv3 expon 1.0, 0.025, 0.5
+
+ a_ oscili 1.0, kcps, gifnSine
+ a1 oscili 1.0, kcps * (1.0 + a_*kfmd), gifnSine
+ a_ oscili 1.0, kcpsx, gifnSine
+ a2 oscili 1.0, kcpsx * (1.0 + a_*kfmd), gifnSine
+
+ a3 unirand 2.0
+ a3 = a3 - 1.0
+ a3 butterbp a3, 5000, 7500
+ a3 = a3 * aenv2
+
+ a0 = a1 + a2*aenv3 + a3*1.0
+ a0 = a0 * aenv1
+ xout a0
+endop
+
+
+/*
+ RK kick1
+*/
+opcode kick1, a, 0
+ /* p3 should be between 0.1 and 0.3 really */
+ kax linseg 500, p3*0.05, 200, p3*0.1, 40, p3*0.8, 20
+ kenv linseg 1,p3*0.2,0.6,p3*0.2,0.4,p3*0.2,0.1,p3*0.2,0
+ kpit linseg 300, p3*0.2, 200, p3*0.4, 80, p3*0.2, 50, p3*0.2, 30
+ a1 oscil 0.6, kax*1
+ a2 oscil 0.85,kpit*0.4
+ a3 oscil 0.95,kpit*0.2
+ an noise 0.3,-0.1
+ ax1 oscil 0.05, 200
+ ax2 oscil 0.02, 900
+ kaxenv linseg 0.2, p3*0.02, 0
+ akx = (ax1*ax2)*kaxenv
+ aout = (a1+a2+a3)*0.25
+ amix = ((aout*kenv)+akx)
+ adist distort amix, 0.3, gifnSine
+ aeq1 pareq adist, 90, 0, 1.4
+ aeq2 pareq aeq1, 60, 1.8, 0.9
+ aout pareq aeq2, 9000, 2.8, 0.5
+ xout aout
+endop
+
+
+/*
+ RK kick2, a bit 808 ish unless harsh mode is on
+*/
+opcode kick2, a, oo
+ iharsh, itune xin
+ if (iharsh == 1) then
+ idist = 0.5
+ idistwave = gifnSquare
+ else
+ idist = 0.1
+ idistwave = gifnSine
+ endif
+ xtratim 0.1
+ krelease release
+ ktune init itune
+ kmul transeg 0.2, p3*0.5, -15, 0.01, p3*0.5, 0, 0
+ kbend transeg 0.5, 1.2, -4, 0, 1, 0, 0
+ asig gbuzz 0.5, 50*octave(ktune)*semitone(kbend), 20, 1, kmul, gifnCosine
+ aenv transeg 1, p3-0.004, -6, 0
+ gkpump = k(aenv)
+ aatt linseg 0, 0.004, 1
+ asig = asig*aenv*aatt
+ aenv linseg 1, 0.07, 0
+ acps expsega 400, 0.07, 0.001, 1, 0.001
+ aimp oscili aenv, acps*octave(ktune*0.25)
+ amix = ((asig*0.7)+(aimp*0.35))
+ gkpump = min(rms(amix) * 7, 1)
+ aout1 pareq amix, 100, 0.01, 0.7
+ aout1 distort aout1, idist, idistwave
+ aout1 pareq aout1, 50, 2, 0.4
+ aout1 pareq aout1, 190, 0.7, 0.6
+ aout1 pareq aout1, 4900, 2, 0.7
+ aout1 pareq aout1, 8900, 2, 0.7
+ xout aout1
+endop
+
+
+
+opcode hihat1, a, 0
+ idel = random(0.001, 0.02)
+ xtratim idel
+ kfreq line 300, p3, 100
+ aenv expsega .1, .0005, 1, p3 - .0005, .01
+ asqr1 oscil 1, kfreq, gifnSquare, -1
+ asqr2 oscil 1, kfreq*1.4471, gifnSaw, -1
+ asqr3 oscil 1, kfreq*1.6170, gifnSaw, -1
+ asqr4 oscil 1, kfreq*1.9265, gifnSaw, -1
+ asqr5 oscil 1, kfreq*2.5028, gifnSaw, -1
+ asqr6 oscil 1, kfreq*2.6637, gifnSaw, -1
+ a808 sum asqr1, asqr2, asqr3, asqr4, asqr5, asqr6
+ a808 butterhp a808, 4270
+ a808 butterhp a808, 4270
+ aout = a808 * aenv * 0.5
+ aout delay aout, idel
+ xout aout
+endop
+
+
+
+opcode hihat2, aa, 0
+ idel1 = random(0.005, 0.02)
+ idel2 = random(0.005, 0.02)
+ xtratim(max(idel1, idel2))
+
+ kfreq line 800, p3, 300
+ aenv expsega .1, .0005, 1, p3 - .0005, .01
+ asqr1 oscil 1, kfreq, gifnSquare, -1
+ asqr2 oscil 1, kfreq*1.4471, gifnSquare, -1
+ asqr3 oscil 1, kfreq*1.6170, gifnSquare, -1
+ asqr4 oscil 1, kfreq*1.9265, gifnSquare, -1
+ asqr5 oscil 1, kfreq*2.5028, gifnSaw, -1
+ asqr6 oscil 1, kfreq*2.6637, gifnSaw, -1
+ a808 sum asqr1, asqr2, asqr3, asqr4, asqr5, asqr6
+ a808 butterhp a808, 3270
+ a808 butterhp a808, 3270
+ aout = a808 * aenv * 0.5
+ aL delay aout, idel1
+ aR delay aout, idel2
+ xout aL, aR
+endop
+
+
+#end
+
diff --git a/site/udo/synth_instruments.udo b/site/udo/synth_instruments.udo
new file mode 100755
index 0000000..a5ec34c
--- /dev/null
+++ b/site/udo/synth_instruments.udo
@@ -0,0 +1,78 @@
+#ifndef UDO_SYNTHINSTRUMENTS
+#define UDO_SYNTHINSTRUMENTS ##
+/*
+ Standard regular wave function tables
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "wavetables.udo"
+
+opcode synth_fmbass1, aa, k
+ kfreq xin
+ k1 linseg 2.1, p3*0.2, 2, p3*0.8, 2
+ k2 line 10, p3, 0
+ iamp random 500, 800
+ kamp linseg iamp*0.001, p3*0.1, 0.5, p3*0.9, 0
+ aL foscil 0.8, kfreq, 1, k1, k2, gifnSine
+ aR foscil 0.8, kfreq, 1.9, k1, k2, gifnSine
+ aL1 pareq aL, 440, 0.4, 0.7
+ aR1 pareq aR, 440, 0.4, 0.7
+ xout aL1, aR1
+endop
+
+
+opcode synth_303, a, kjJ
+ kfreq, ifilter, kdistortion xin
+ ifilter = (ifilter == -1) ? 90 : ifilter
+ kdistortion = (kdistortion == -1) ? 1 : kdistortion
+ ifrq1 = 440 * exp(log(2) * (ifilter - 69) / 12) ; filter start freq.
+ kffrq port 0, 60/150, ifrq1
+ a1 phasor kfreq
+ a1 = 1 - 2 * a1
+ a1x butterbp a1, kffrq, kfreq
+ a1x = a1x * (2 + kffrq / kfreq)
+ a1 = a1x + a1 * 0.25 ; 0.5
+ a1 butterlp a1, kffrq
+ a1 = taninv(a1 * kdistortion)
+ keqf limit kffrq * 4, 10, sr * 0.48 ; EQ frequency
+ a1 pareq a1 * 0.4, keqf, 4.0, 1.0, 2
+ xout a1
+endop
+
+opcode synth_strings1, a, kJJjj
+ kfreq, kvibwidth, kvibrate, inoisdur, ivibdel xin
+ kvibwidth = (kvibwidth == -1) ? 0.01 : kvibwidth
+ kvibrate = (kvibrate == -1) ? 5.5 : kvibrate
+ inoisdur = (inoisdur == -1) ? 0.1 : inoisdur
+ ivibdel = (ivibdel == -1) ? 0.25 : ivibdel
+
+ kfm2 = kfreq * 3
+ kfm3 = kfreq * 4
+ kindex1 = 7.5 / log:k(kfreq)
+ kindex2 = 15 / sqrt:k(kfreq)
+ kindex3 = 1.25 / sqrt:k(kfreq)
+
+ irise = 0.1
+ idec = 0.4
+
+ kvib init 0
+ kvbctl linen 1, 0.5, p3 - ivibdel, .1
+ krnd randi 0.0075, 2
+ kvib oscili kvbctl * kvibwidth + krnd, kvibrate * kvbctl
+
+ ktrans linseg 1, inoisdur, 0, 1, 0
+ anoise randi ktrans / 4, 0.2 * kfreq
+ attack oscili anoise, 2000
+
+ amod1 oscili kfreq * (kindex1 + ktrans), kfreq
+ amod2 oscili kfm2 * (kindex2 + ktrans), kfm2
+ amod3 oscili kfm3 * (kindex3 + ktrans), kfm3
+ asig oscili 1, (kfreq + amod1 + amod2 + amod3) * (1 + kvib)
+ asig linen (asig + attack), irise, p3, idec
+ xout asig
+endop
+
+#end
diff --git a/site/udo/tab2wav.udo b/site/udo/tab2wav.udo
new file mode 100755
index 0000000..1c0b9cb
--- /dev/null
+++ b/site/udo/tab2wav.udo
@@ -0,0 +1,151 @@
+#ifndef UDO_TAB2WAV
+#define UDO_TAB2WAV ##
+
+/*
+ Table to wave to table file ease tool
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Output table to 16bit WAV file
+
+ kdone tab2wav ifn, Spath [, ilen, iprocrate, iblocksize]
+
+ kdone trigger to specify when done
+ ifn table number to use
+ Spath file path to output to
+ isamples how many samples to save, defaults to full length
+ iprocrate processing rate in processes per second, defaults to 10
+ iblocksize block size in one process cycle, defaults to 2048
+*/
+opcode tab2wav, k, iSjjj
+ ifn, Spath, isamples, iprocrate, iblocksize xin
+
+ iprocrate = (iprocrate == -1) ? 10 : iprocrate
+ iblocksize = (iblocksize == -1) ? 2048 : iblocksize
+
+ ichannels = ftchnls(ifn)
+ isr = ftsr(ifn)
+ ilenraw = (isamples == -1) ? ftlen(ifn) : isamples
+ ilen = ilenraw / ichannels
+
+ kdone init 0
+
+
+ if (kdone == 0) then
+ ; each block processing at iprocrate
+ ktrig metro iprocrate
+ if (ktrig == 1) then
+ kcount = 0
+ while (kcount < iblocksize) do
+ apos = round:a(lphasor(isr / sr)) ; rounded to deal with stereo offset if required
+
+ ; if complete, set kdone
+ if (downsamp(apos) >= ilen) then
+ kdone = 1
+ kgoto complete
+ endif
+
+ ; read and write position calcs for stereo / reverse
+ aposbase = apos * ichannels
+ aposrL = aposbase
+ aposrR = aposbase + 1
+
+ if (ichannels == 2) then
+ fout Spath, 14, table:a(aposrL, ifn), table:a(aposrR, ifn)
+ else
+ fout Spath, 14, table:a(aposrL, ifn)
+ endif
+ kcount += 1
+ od
+ endif
+
+ endif
+
+complete:
+ xout kdone
+endop
+
+
+
+
+/*
+ Load file to existing table without altering length/channels
+
+ kdone wav2tab Spath, ifn [, imonochannel=0]
+
+ kdone trigger to specify when done
+ Spath file path to load from
+ ifn table number to use
+ imonochannel if ifn is mono and Spath is stereo, 1 specifies to load from the right channel, rather than the default (0) which is left
+ iprocrate processing rate in processes per second, defaults to 10
+ iblocksize block size in one process cycle, defaults to 2048
+*/
+opcode wav2tab, k, Siojj
+ Spath, ifn, imonochannel, iprocrate, iblocksize xin
+ iprocrate = (iprocrate == -1) ? 10 : iprocrate
+ iblocksize = (iblocksize == -1) ? 2048 : iblocksize
+
+ ifilechannels = filenchnls(Spath)
+ ichannels = ftchnls(ifn)
+ isr = ftsr(ifn)
+ ilenraw = ftlen(ifn)
+ ilen = ilenraw / ichannels
+
+ kdone init 0
+
+
+ if (kdone == 0) then
+ ; each block processing at iprocrate
+ ktrig metro iprocrate
+ if (ktrig == 1) then
+ kcount = 0
+ while (kcount < iblocksize) do
+ apos = round:a(lphasor(isr / sr)) ; rounded to deal with stereo offset if required
+
+ ; if complete, set kdone
+ if (downsamp(apos) >= ilen) then
+ kdone = 1
+ kgoto complete
+ endif
+
+ if (ifilechannels == 2) then
+ aL, aR diskin Spath, 1
+ else
+ aL diskin Spath, 1
+ aR = aL
+ endif
+
+ ; read and write position calcs for stereo / reverse
+ aposbase = apos * ichannels
+ aposwL = aposbase
+ aposwR = aposbase + 1
+
+
+ if (ichannels == 2) then
+ tablew aL, aposwL, ifn
+ tablew aR, aposwR, ifn
+ else
+ if (imonochannel == 1 && ifilechannels == 2) then
+ tablew aR, aposwL, ifn
+ else
+ tablew aL, aposwL, ifn
+ endif
+ endif
+ kcount += 1
+ od
+ endif
+
+ endif
+
+complete:
+ xout kdone
+endop
+
+
+#end
+
diff --git a/site/udo/table_tools.udo b/site/udo/table_tools.udo
new file mode 100755
index 0000000..50f5997
--- /dev/null
+++ b/site/udo/table_tools.udo
@@ -0,0 +1,244 @@
+#ifndef UDO_TABLETOOLS
+#define UDO_TABLETOOLS ##
+/*
+ Table tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022, 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Normalise an audio ftable to to between -1 and 1
+
+ ftnormalise ifn
+
+ ifn ftable to analyse
+*/
+opcode ftnormalise, 0, ip
+ ifn, iscaling xin
+ imaxpos = -999
+ imaxneg = 999
+ iendpoint = 0
+ index = 0
+ while (index < ftlen(ifn)) do
+ ival table index, ifn
+ if (ival > 0 && ival > imaxpos) then
+ imaxpos = ival
+ elseif (ival < 0 && ival < imaxneg) then
+ imaxneg = ival
+ endif
+ index += 1
+ od
+
+
+ iscale = ((0.999/max(abs(imaxneg), abs(imaxpos)))) * iscaling
+ index = 0
+ while (index < ftlen(ifn)) do
+ ival table index, ifn
+ tablew ival * iscale, index, ifn
+ index += 1
+ od
+endop
+
+
+opcode tab_record, i, aj
+ ain, iduration xin
+ if (iduration == -1 || iduration > p3) then
+ iduration = p3
+ endif
+ ifn ftgen 0, 0, -(iduration * sr), -2, 0
+ apos lphasor 1
+ tablew ain, apos, ifn
+ xout ifn
+endop
+
+/*
+ Sample rate agnostic audio file loading
+*/
+opcode tab_loadaudio, iik, S
+ Sfile xin
+ kdone init 0
+ ilen = filelen(Sfile)
+ ilensamp = ilen * sr
+ ikcycles = ilen * kr
+ ichannels = filenchnls(Sfile)
+ ifnL ftgen 0, 0, ilensamp, 2, 0
+ if (ichannels == 2) then
+ ifnR ftgen 0, 0, ilensamp, 2, 0
+ else
+ ifnR = -1
+ endif
+ ktimek timeinstk
+ kcount init 0
+ if (ktimek == 1) then
+ while (kcount < ikcycles) do
+ apos linseg 0, ilen, ilensamp - 1
+ if (ichannels == 1) then
+ asig soundin Sfile
+ tablew asig, apos, ifnL
+ else
+ aL, aR soundin Sfile
+ tablew aL, apos, ifnL
+ tablew aR, apos, ifnR
+ endif
+ kcount += 1
+ od
+ else
+ kdone = 1
+ endif
+ xout ifnL, ifnR, kdone
+endop
+
+
+opcode tab_samplerateconvert, iik, ijpj
+ ifnL, ifnR, ifreeafter, isourcesr xin
+ isr = (isourcesr == -1) ? ftsr(ifnL) : isourcesr
+ kdone init 0
+ if (isr != sr) then
+ ilen = ftlen(ifnL)
+ ilens = ftlen(ifnL) / isr
+ inewlen = (sr / isr) * ilen
+ ifnnewL ftgen 0, 0, -inewlen, -2, 0
+ if (ifnR != -1) then
+ ifnnewR ftgen 0, 0, -inewlen, -2, 0
+ else
+ ifnnewR = -1
+ endif
+ ktimek timeinstk
+ ikcycles = ilens * kr
+ if (ktimek == 1) then
+ kcount = 0
+ while (kcount < ikcycles) do
+ aposw linseg 0, ilens, inewlen - 1
+ aposr linseg 0, ilens, ilen - 1
+ asig table3 aposr, ifnL
+ tablew asig, aposw, ifnnewL
+ if (ifnR != -1) then
+ asig table3 aposr, ifnR
+ tablew asig, aposw, ifnnewR
+ endif
+ kcount += 1
+ od
+ else
+ kdone = 1
+ endif
+ if (ifreeafter == 1) then
+ ftfree ifnL, 1
+ if (ifnR != -1) then
+ ftfree ifnR, 1
+ endif
+ endif
+ else
+ kdone init 1
+ ifnnewL = ifnL
+ ifnnewR = ifnR
+ endif
+ xout ifnnewL, ifnnewR, kdone
+endop
+
+opcode tab_samplerateconvert, ik, ipj
+ ifn, ifreeafter, isourcesr xin
+ ifnnew, i_, kdone tab_samplerateconvert ifn, -1, ifreeafter, isourcesr
+ xout ifnnew, kdone
+endop
+
+; table
+opcode tab_serialise, S, i
+ ifn xin
+ ilen = ftlen(ifn)
+ index = 0
+
+ Sout = "["
+ while (index < ilen) do
+ if (index != 0) then
+ Sout = strcat(Sout, ",")
+ endif
+ ivalue = table:i(index, ifn)
+ SprintfChar = (frac(ivalue) == 0) ? "%d" : "%f"
+ Sout = strcat(Sout, sprintf(SprintfChar, ivalue))
+ index += 1
+ od
+ Sout = strcat(Sout, "]")
+ xout Sout
+endop
+
+
+opcode tab_unserialise, 0, Si
+ Sdata, ifn xin
+ ilen = strlen(Sdata)
+ ichar = 0
+ inarray = 0
+ ivalstart = -1
+ index = 0
+ while (ichar < ilen) do
+ Schar = strsub(Sdata, ichar, ichar + 1)
+ if (strcmp(Schar, "[") == 0) then
+ inarray = 1
+ ivalstart = ichar + 1
+ elseif (inarray == 1 && strcmp(Schar, "]") == 0) then
+ inarray = 0
+ elseif (inarray == 1 && strcmp(Schar, ",") == 0) then
+ tablew strtod(strsub(Sdata, ivalstart, ichar)), index, ifn
+ ivalstart = ichar + 1
+ index += 1
+ endif
+ ichar += 1
+ od
+endop
+
+
+opcode tab_bubblesort, 0, i
+ ifn xin
+ itemp = 0
+ ilen = ftlen(ifn)
+ index1 = 0
+ while (index1 < ilen - 1) do
+ index2 = 0
+ while (index2 < ilen - 1 - index1) do
+ ival1 = tab_i(index2, ifn)
+ ival2 = tab_i(index2 + 1, ifn)
+ if (ival1 > ival2) then
+ itemp = ival1
+ tabw_i ival2, index2, ifn
+ tabw_i itemp, index2 + 1, ifn
+ endif
+ index2 += 1
+ od
+ index1 += 1
+ od
+endop
+
+
+opcode tab_destroy, 0, i
+ ifn xin
+ if (ftexists(ifn) == 1) then
+ ftfree ifn, 0
+ endif
+endop
+
+/*
+ get condensed table eg for waveform view
+*/
+opcode tab_overview, i, io
+ ifn, isamples xin
+ isamples = (isamples == 0) ? 256 : isamples
+ ifnlen = ftlen(ifn)
+ istep = round(ifnlen / isamples)
+ ifndata ftgen 0, 0, -isamples, 7, 0
+ index = 0
+ indexwrite = 0
+ while (index < ifnlen) do
+ ival tab_i index, ifn
+ if (!(indexwrite > isamples - 1)) then
+ tabw_i ival, indexwrite, ifndata
+ endif
+ indexwrite += 1
+ index += istep
+ od
+ xout ifndata
+endop
+
+
+#end
diff --git a/site/udo/tabproc.udo b/site/udo/tabproc.udo
new file mode 100755
index 0000000..1fd4e49
--- /dev/null
+++ b/site/udo/tabproc.udo
@@ -0,0 +1,217 @@
+#ifndef UDO_TABPROC
+#define UDO_TABPROC ##
+/*
+ JIT style table processor
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "lagdetect.udo"
+
+#define TP_DFLT_RATE #10#
+
+gitabproc_instancetrack = 0 ; internal instance tracking for channel names
+
+
+
+opcode tabproc_profiler, kkk, ij
+ instrnum, istabletime xin
+ istabletime = (istabletime == -1) ? 5 : istabletime
+
+ ichannelbase = gitabproc_instancetrack
+ gitabproc_instancetrack += 1
+
+ kblocksize init 16384
+ kprocrate init $TP_DFLT_RATE
+ kdone init 0
+
+ ; actual processing at iprocrate
+ if (kdone == 0) then
+ klagging, ktimesincelag lagdetect
+ if (klagging == 1) then
+ ; reduce block size first, then reduce processing rate
+ if (kblocksize > 128) then
+ kblocksize -= 128
+ elseif (kprocrate > 1) then
+ kprocrate -= 1
+ endif
+ endif
+
+ if (ktimesincelag >= istabletime) then
+ kdone = 1
+ kgoto complete
+ endif
+
+ ktrig metro kprocrate
+ if (ktrig == 1) then
+ kcount = 0
+ while (kcount < kblocksize) do
+ atest oscil 1, 1000
+
+ ; set channels and call subinstrument for processing
+ SchannelL = sprintf("tabproc%dL", ichannelbase)
+ SchannelR = sprintf("tabproc%dR", ichannelbase)
+ chnset atest, SchannelL
+ chnset atest, SchannelR
+ aL, aR subinstr instrnum, SchannelL, SchannelR
+ kcount += 1
+ od
+ endif
+
+ endif
+complete:
+ xout kdone, kblocksize, kprocrate
+endop
+
+
+opcode tabproc_profiler, kkk, S
+ Sinstrname xin
+ kdone, kblocksize, kprocrate tabproc_profiler nstrnum(Sinstrname)
+ xout kdone, kblocksize, kprocrate
+endop
+
+
+
+/*
+ Process a table with a subinstrument at deferred k-rate.
+ The table ifn is read and passed to the instrument Sinstrname/instrnum which should perform processing and use the outs() or out() opcode to return audio to be written to ifn.
+ Sinstrument is passed the input audio on channels with the names passed as p4 (left) and p5 (right, if the sound is stereo).
+ Hence the instrument should implement something such as
+ aL = chnget:a(strget(p4))
+ aR = chnget:a(strget(p5))
+ in order to deal with the incoming audio.
+
+ The table can be read in reverse by specifying ireverseread=1. Similarly it can be written back to in reverse with ireversewrite=1.
+
+ tabproc performs processing in chunks at a given rate in order to minimise CPU usage and allow other instruments to continue uninterrupted (without this, it
+ would consume all available CPU and block the performance until complete). The blocksize iblocksize is how many k-cycles are processed in each chunk, and iprocrate
+ determines the frequency at which chunks are processed. Defaults are provided but these will be best altered depending on the load of Sinstrument and the
+ available CPU.
+ iautoadjust can be set to 1 to attempt to automatically reduce iblocksize and iprocrate if it is detected that the CPU cannot keep up. This is done by comparing
+ the realtime clock against Csound's instrument time. If the difference is over iautotimethreshold, then a reduction of block size/frequency is made.
+
+
+ kdone, kpercent tabproc ifn, instrnum [, ireverseread=0, ireversewrite=0, iautoadjust=0, iblocksize=2048, iprocrate=TP_DFLT_RATE, iautotimethreshold=TP_DFLT_TTHRESH]
+ kdone, kpercent tabproc ifn, Sinstrname [, ireverseread=0, ireversewrite=0, iautoadjust=0, iblocksize=2048, iprocrate=TP_DFLT_RATE, iautotimethreshold=TP_DFLT_TTHRESH]
+
+
+ kdone output trigger when the processing has completed
+ kpercent percent of the table that has been processed
+
+ ifn table containing audio (typically GEN1 but could be any). Mono and stereo are supported
+ instrnum the instrument number to be invoked to process the table
+ Sinstrname the instrument name to be invoked to process the table
+ ireverseread set to 1 in order to read the table in reverse
+ ireversewrite set to 1 in order to write back to the table in reverse
+ iautoadjust set to 1 to enable automatic reduction of iblocksize and iprocrate if processing lag is detected
+ iblocksize number of k-cycles of audio to be processed in each run
+ iprocrate frequency at which to perform block processing
+ iautotimethreshold time lag at which to automatically adjust, if iautoadjust is specified
+
+*/
+opcode tabproc, kk, iiooojjj
+ ifn, instrnum, ireverseread, ireversewrite, iautoadjust, iblocksize, iprocrate, iautotimethreshold xin
+ kblocksize init (iblocksize == -1) ? 2048 : iblocksize
+ kprocrate init ((iprocrate == -1) ? $TP_DFLT_RATE : iprocrate)
+ iautotimethreshold = (iautotimethreshold == -1) ? $LAG_DFLT_TTHRESH : iautotimethreshold
+
+ ichannelbase = gitabproc_instancetrack
+ gitabproc_instancetrack += 1
+
+ ichannels = ftchnls(ifn)
+ isr = ftsr(ifn)
+ ilenraw = ftlen(ifn)
+ ilen = ilenraw / ichannels
+
+ kpos init 0
+ kpercent = 0
+ kdone init 0
+
+
+ if (kdone == 0) then
+ ; auto adjust block size to account when cpu can't keep up
+ if (iautoadjust == 1 && lagdetect:k(iautotimethreshold) == 1) then
+ if (kblocksize > 128) then
+ kblocksize -= 128
+ elseif (kprocrate > 1) then
+ kprocrate -= 1
+ endif
+ endif
+
+
+ ; each block processing at iprocrate
+ ktrig metro kprocrate
+ if (ktrig == 1) then
+ kcount = 0
+ while (kcount < kblocksize) do
+ apos = round:a(lphasor(isr / sr)) ; rounded to deal with stereo offset if required
+
+ ; if complete, set kdone
+ if (downsamp(apos) >= ilen) then ;? >= ilenraw) then
+ kpercent = 100
+ kdone = 1
+ kgoto complete
+ endif
+
+ ; read and write position calcs for stereo / reverse
+ aposbase = apos * ichannels
+ if (ireverseread == 1) then
+ aposrR = ilen - aposbase
+ aposrL = (ichannels == 2) ? aposrR + 1 : aposrR
+ else
+ aposrL = aposbase
+ aposrR = aposbase + 1
+ endif
+
+ if (ireversewrite == 1) then
+ aposwR = ilen - aposbase
+ aposwL = (ichannels == 2) ? aposwR + 1 : aposwR
+ else
+ aposwL = aposbase
+ aposwR = aposbase + 1
+ endif
+
+ ; read source table
+ aL table aposrL, ifn
+ if (ichannels == 2) then
+ aR table aposrR, ifn
+ else
+ aR = aL
+ endif
+
+ ; set channels and call subinstrument for processing
+ SchannelL = sprintf("tabproc%dL", ichannelbase)
+ SchannelR = sprintf("tabproc%dR", ichannelbase)
+ chnset aL, SchannelL
+ chnset aR, SchannelR
+ aL, aR subinstr instrnum, SchannelL, SchannelR
+
+ ; write back to table
+ tablew aL, aposwL, ifn
+ if (ichannels == 2) then
+ tablew aR, aposwR, ifn
+ endif
+
+ kcount += 1
+ od
+ endif
+
+ ; calculate percent complete
+ kpercent = (100 / ilenraw) * downsamp(apos)
+ endif
+
+complete:
+ xout kdone, kpercent
+endop
+
+
+; derived opcode for named instruments
+opcode tabproc, kk, iSooojjj
+ ifn, Sinstrname, ireverseread, ireversewrite, iautoadjust, iblocksize, iprocrate, iautotimethreshold xin
+ kdone, kpercent tabproc ifn, nstrnum(Sinstrname), ireverseread, ireversewrite, iautoadjust, iblocksize, iprocrate, iautotimethreshold
+ xout kdone, kpercent
+endop
+
+#end
diff --git a/site/udo/tempo_tools.udo b/site/udo/tempo_tools.udo
new file mode 100755
index 0000000..a678f7a
--- /dev/null
+++ b/site/udo/tempo_tools.udo
@@ -0,0 +1,72 @@
+#ifndef UDO_TEMPOTOOLS
+#define UDO_TEMPOTOOLS ##
+/*
+ Tempo tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+/*
+ Get the beat time (1/4 note) and quarter beat time (1/16 note) in seconds
+ for a given BPM
+
+ ibeattime, iquartertime tempotime ibpm
+
+ ibeattime the 1/4 note time in seconds
+ iquartertime the 1/16 note time in seconds
+ ibpm the beats per minute value
+*/
+opcode tempotime, ii, i
+ itempo xin
+ ibeattime = 60.0 / itempo
+ iquartertime = ibeattime * .25
+ xout ibeattime, iquartertime
+endop
+
+
+/*
+ Get the swung time (when iquarterindex is 1 or 3) for a given time
+ Using iquartertime as the 1/16 beat time and iswing as multiplier of
+ iquartertime to add to the time (iswing should be less than one)
+
+ itime swinger iquarterindex, iquartertime, itime, iswing
+
+ itime the time with swing applied
+ iquarterindex the quarter beat index MIN(0) MAX(3)
+ iquartertime time of one 1/16th beat
+ itime the normal/quantised time
+ iswing the swing amount MIN(0) MAX(1)
+
+*/
+opcode swinger, i, iiii
+ iquarterindex, iquartertime, itime, iswing xin
+ if (iquarterindex == 1 || iquarterindex == 3) then
+ itime = itime + (iswing * iquartertime)
+ endif
+ xout itime
+endop
+
+
+/*
+ Metronome with random variation
+
+ ktrigger drunkenmetro kfreq
+
+ ktrigger the metronome trigger
+ kfreq metronome frequency in hz
+*/
+opcode drunkenmetro, k, k
+ kfreq xin
+ kbtime = 1.0 / kfreq
+ kmetro metro kfreq
+ kdeltime random 0, (1.0 / kfreq) * 0.9
+ kout vdel_k kmetro, kdeltime, 1
+ xout kout
+endop
+
+
+
+
+#end \ No newline at end of file
diff --git a/site/udo/transient_detect.udo b/site/udo/transient_detect.udo
new file mode 100755
index 0000000..4acf1d3
--- /dev/null
+++ b/site/udo/transient_detect.udo
@@ -0,0 +1,216 @@
+#ifndef UDO_TRANSIENTDETECT
+#define UDO_TRANSIENTDETECT ##
+/*
+ Transient detection
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+/*
+ Internal transient detection base
+*/
+opcode _transientdetect, kk,kikkki
+ kin, iresponse, ktthresh, klowThresh, kdecThresh, idoubleLimit xin
+ kinDel delayk kin, iresponse / 1000
+ ktrig = ((kin > kinDel + ktthresh) ? 1 : 0)
+ klowGate = (kin < klowThresh ? 0 : 1)
+ ktrig = ktrig * klowGate
+ ktransLev init 0
+ ktransLev samphold kin, 1-ktrig
+ kreGate init 1
+ ktrig = ktrig * kreGate
+ kmaxAmp init -99999
+ kmaxAmp max kmaxAmp, kin
+ kdiff = kmaxAmp-kin
+ kreGate limit kreGate-ktrig, 0, 1
+ kreGate = (kdiff > kdecThresh ? 1 : kreGate)
+ kmaxAmp = (kreGate == 1 ? -99999 : kmaxAmp)
+ xout ktrig, kdiff
+endop
+
+
+
+opcode transientdetect, k, akkiikkk
+ ain, kattack, krelease, iresponse, idoublelimit, ktthresh, klowthresh, kdecthresh xin
+ afollow follow2 ain, kattack, krelease
+ kfollow downsamp afollow
+ kfollowdb = dbfsamp(kfollow)
+ ktrig, kdiff _transientdetect kfollowdb, iresponse, ktthresh, klowthresh, kdecthresh, idoublelimit
+ xout ktrig
+endop
+
+/*
+ Default transient detection
+ ktrigger transientdetect ainput
+
+ ktrigger fires when a transient has been detected
+ ainput the audio signal to track
+*/
+opcode transientdetect, k, a
+ ain xin
+ kattack init 0.05
+ krelease init 0.06
+ iresponse = 10
+ ktthresh = 9
+ klowthresh = -50
+ idoublelimit = 0.1
+ kdecthresh = 6
+ ktrig transientdetect ain, kattack, krelease, iresponse, idoublelimit, ktthresh, klowthresh, kdecthresh
+ xout ktrig
+endop
+
+/*
+
+opcode transientdetect, k, a
+ ain xin
+ kattack init 0.05
+ krelease init 0.06
+ afollow follow2 ain, kattack, krelease
+ kfollow downsamp afollow
+ kfollowdb = dbfsamp(kfollow)
+ iresponse = 10
+ ktthresh = 9
+ klowthresh = -50
+ idoublelimit = 0.1
+ kdecthresh = 6
+ ktrig, kdiff _transientdetect kfollowdb, iresponse, ktthresh, klowthresh, kdecthresh, idoublelimit
+ xout ktrig
+endop
+
+*/
+
+/*
+ Detect transients in audio ftable; count or output to ftable
+
+ p4 ftable to read sound from
+ p5 instrument name to invoke when complete, ftable contaqining transients is passed as p4
+ p6 0 = stage 1, count and recall; 1 = stage 2, write to ftable
+ p7 number of transients for ftable initialisation as used with stage 2
+*/
+instr _transientdetect_tofn_inner
+ ifnsound = p4
+ SonComplete = p5
+ istate = p6
+ itransientnum = p7
+
+ if (istate == 0) then
+ ifntransients = -1
+ else
+ ifntransients ftgen 0, 0, itransientnum, 7, 0
+ endif
+
+
+ kdone init 0
+ ktransientnum init 0
+ ktimek timeinstk
+ if (ktimek == 1) then
+ inputduration = ftlen(ifnsound) / ftsr(ifnsound)
+ kcycles = inputduration * kr
+ kcount init 0
+ while (kcount < kcycles) do
+ if (ftchnls(ifnsound) == 1) then
+ asound loscil 1, 1, ifnsound, 1
+ else
+ aL, aR loscil 1, 1, ifnsound, 1
+ asound = (aL + aR) / 2
+ endif
+ ;ktransient, kdiff _transientdetectinner asound
+ ktransient transientdetect asound
+ if (ktransient == 1) then
+ if (ifntransients != -1) then
+ tablew (inputduration / kcycles) * kcount, ktransientnum, ifntransients
+ endif
+ ktransientnum += 1
+ endif
+ kcount += 1
+ od
+ else
+ if (istate == 0) then
+ if (ktransientnum == 0) then
+ schedulek(SonComplete, 0, 3600, -1)
+ else
+ schedulek(p1, 0, 1, ifnsound, SonComplete, 1, ktransientnum)
+ endif
+ else
+ schedulek(SonComplete, 0, 3600, ifntransients)
+ endif
+ turnoff
+ endif
+endin
+
+
+opcode transientdetect_tofn, 0, iS
+ ifn, SonComplete xin
+ schedule("_transientdetect_tofn_inner", 0, 600, ifn, SonComplete, 0, 0)
+endop
+
+
+opcode randomtransient, ii, i
+ ifntransients xin
+ iftlen = ftlen(ifntransients)
+ if (iftlen < 2) then
+ istart = tab_i(0, ifntransients)
+ ilen = 0.1
+ else
+ istartindex = round(random(0, iftlen - 2))
+ istart = tab_i(istartindex, ifntransients)
+ iend = tab_i(istartindex + 1, ifntransients)
+ ilen = iend - istart
+ endif
+
+ if (ilen > 1) then
+ ilen = 1
+ endif
+ xout istart, ilen
+endop
+
+/*
+ By Brandtsegg, I think; tweaked by RK
+*/
+opcode onsetdetect, k, aiiiiii
+ ain, iMinFreq, iMaxFreq, iAboveMed, iOffset, iMinSec, iMedLen xin
+ ifftsize = 1024
+ iIndexStart limit int(iMinFreq*(ifftsize/sr))*2,0,sr/2
+ iIndexEnd limit int(iMaxFreq*(ifftsize/sr))*2,0,sr/2
+ fsrc pvsanal ain,ifftsize,ifftsize/4,ifftsize,1
+ kArr[] init ifftsize+2
+ kflag pvs2array kArr, fsrc
+ ksumold init 0
+ kMedIndex init 0
+ kMedSum init 0
+ kMedian[] init iMedLen
+ kMinDist init 0
+ iMinDist = iMinSec*(sr/ksmps)
+ kMinDist limit kMinDist-1,0,100000
+
+ if changed(kflag) == 1 && kMinDist == 0 then
+ ksum = 0
+ kIndex = iIndexStart
+
+ until kIndex = iIndexEnd do
+ ksum = ksum+kArr[kIndex]
+ kIndex += 2
+ od
+
+ kFLUX = ksum-ksumold
+ ksumold = ksum
+ kOnset = 0
+
+ if kFLUX > (kMedSum*iAboveMed)+iOffset then
+ kOnset = 1
+ kMinDist = iMinDist
+ endif
+
+ kMedian[kMedIndex] = (kFLUX>=0?kFLUX:0)
+ kMedSum = sumarray(kMedian)/iMedLen
+ kMedIndex = (kMedIndex+1)%iMedLen
+
+ endif
+
+ xout changed(kOnset)==1 && kOnset==1 ? 1 : 0
+
+endop
+
+#end
diff --git a/site/udo/twigs/checkpointing.udo b/site/udo/twigs/checkpointing.udo
new file mode 100755
index 0000000..bd2594e
--- /dev/null
+++ b/site/udo/twigs/checkpointing.udo
@@ -0,0 +1,120 @@
+#ifndef UDO_TWIGS_CHECKPOINTING
+#define UDO_TWIGS_CHECKPOINTING ##
+
+gitwgs_maxundolevels = 32
+gitwgs_checkpoints[] init gitwgs_maxundolevels
+gitwgs_checkpointstate = 0
+gitwgs_checkpointencodemult = 10000
+
+
+opcode twgs_checkpoint_encode, i, ii
+ ifnL, ifnR xin
+ iencoded = (ifnL * gitwgs_checkpointencodemult) + (ifnR / gitwgs_checkpointencodemult)
+ xout iencoded
+endop
+
+opcode twgs_checkpoint_decode, ii, i
+ iencoded xin
+ ifnL = int(iencoded) / gitwgs_checkpointencodemult
+ ifnR = frac(iencoded) * gitwgs_checkpointencodemult
+ xout ifnL, ifnR
+endop
+
+opcode twgs_checkpoint_clear, 0, 0
+ icheckpointnumber = gitwgs_checkpointstate
+ while (icheckpointnumber >= 0) do
+ ifnL, ifnR twgs_checkpoint_decode gitwgs_checkpoints[icheckpointnumber]
+ if (ifnL > 0 && ftexists(ifnL) == 1) then
+ tpvf_destroy ifnL
+ endif
+ if (ifnR > 0 && ftexists(ifnR) == 1) then
+ tpvf_destroy ifnR
+ endif
+ icheckpointnumber -= 1
+ od
+ gitwgs_checkpointstate = 0
+endop
+
+opcode twgs_checkpoint, 0, 0
+ imaxundo chnget "twgs_maxundo"
+ imaxundo = (imaxundo == -1) ? gitwgs_maxundolevels : imaxundo
+ if (imaxundo == 0 || imaxundo > gitwgs_maxundolevels) then
+ goto complete
+ endif
+
+ icheckpointnumber = gitwgs_checkpointstate
+ if (icheckpointnumber >= imaxundo) then
+ ifnL, ifnR twgs_checkpoint_decode gitwgs_checkpoints[0]
+ if (ifnL > 0 && ftexists(ifnL) == 1) then
+ tpvf_destroy ifnL
+ endif
+ if (ifnR > 0 && ftexists(ifnR) == 1) then
+ tpvf_destroy ifnR
+ endif
+ index = 1
+ itemp[] = gitwgs_checkpoints
+ while (index <= imaxundo) do
+ gitwgs_checkpoints[index - 1] = itemp[index]
+ index += 1
+ od
+ gitwgs_checkpointstate = icheckpointnumber ;- 1
+ else
+ gitwgs_checkpointstate = icheckpointnumber + 1
+ endif
+
+ ifnL = gitwgs_tpvfHandleL
+ ifnR = gitwgs_tpvfHandleR
+
+ ifnCheckpointL tpvf_clone ifnL
+print ifnCheckpointL
+ if (ifnR > 0) then
+ ifnCheckpointR tpvf_clone ifnR
+ else
+ ifnCheckpointR = 0
+ endif
+
+ iencoded twgs_checkpoint_encode ifnCheckpointL, ifnCheckpointR
+ gitwgs_checkpoints[icheckpointnumber] = iencoded
+
+complete:
+endop
+
+opcode twgs_undo, i, p
+ iapplyundo xin
+
+ icheckpointnumber = gitwgs_checkpointstate
+
+ icheckpointnumber -= 1
+ if (icheckpointnumber < 0) then
+ istatus = -1
+ goto complete
+ endif
+
+ gitwgs_checkpointstate = icheckpointnumber
+ ifnL, ifnR twgs_checkpoint_decode gitwgs_checkpoints[icheckpointnumber]
+
+ if (iapplyundo == 1) then ; apply or just step back and forget
+ if (ifnL > 0 && ftexists(ifnL) = 1) then
+ tpvf_destroy gitwgs_tpvfHandleL
+ gitwgs_tpvfHandleL = ifnL
+ endif
+ if (ifnR > 0 && ftexists(ifnR) == 1) then
+ tpvf_destroy gitwgs_tpvfHandleR
+ gitwgs_tpvfHandleR = ifnR
+ endif
+ else
+ if (ifnL > 0 && ftexists(ifnL) = 1) then
+ tpvf_destroy ifnL
+ endif
+ if (ifnL > 0 && ftexists(ifnR) = 1) then
+ tpvf_destroy ifnR
+ endif
+ endif
+
+ istatus = 1
+
+complete:
+ xout istatus
+endop
+
+#end
diff --git a/site/udo/twigs/transforms.udo b/site/udo/twigs/transforms.udo
new file mode 100755
index 0000000..c5a7239
--- /dev/null
+++ b/site/udo/twigs/transforms.udo
@@ -0,0 +1,285 @@
+#ifndef UDO_TWIGS_TRANSFORMS
+#define UDO_TWIGS_TRANSFORMS ##
+/*
+ Twigs transforms
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024, 2025
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+instr twgs_freqshift
+ icbid = p4
+ ishift = p5
+ inocheckpoint = p6
+ p3 = 3600
+
+ if (inocheckpoint != 1) then
+ twgs_checkpoint()
+ endif
+
+ indexbin = 0
+ ifnfreqL tab_i 3, gitwgs_tpvfHandleL
+ if (gitwgs_channels == 2) then
+ ifnfreqR tab_i 3, gitwgs_tpvfHandleR
+ endif
+ ibins = ftlen(gitwgs_fnbinselection)
+ while (indexbin < ftlen(gitwgs_fnbinselection)) do
+ if (tab_i(indexbin, gitwgs_fnbinselection) == 1) then
+ index, iendindex twgs_getselectionindexes indexbin
+ while (index < iendindex) do
+ ival = tab_i(index, ifnfreqL)
+ ival = max(min(ival + ishift, sr / 2), 0)
+ tabw_i(ival, index, ifnfreqL)
+ if (gitwgs_channels == 2) then
+ ival = tab_i(index, ifnfreqR)
+ ival = max(min(ival + ishift, sr / 2), 0)
+ tabw_i(ival, index, ifnfreqR)
+ endif
+ index += ibins
+ od
+ endif
+ indexbin += 1
+ od
+ io_sendstring("callback", twgs_refreshviewresponse(icbid))
+ turnoff
+endin
+
+instr twgs_amplify
+ icbid = p4
+ ifactor = p5
+ p3 = 3600
+
+ twgs_checkpoint()
+
+ indexbin = 0
+ ifnampL tab_i 2, gitwgs_tpvfHandleL
+ if (gitwgs_channels == 2) then
+ ifnampR tab_i 2, gitwgs_tpvfHandleR
+ endif
+ ibins = ftlen(gitwgs_fnbinselection)
+ while (indexbin < ftlen(gitwgs_fnbinselection)) do
+ if (tab_i(indexbin, gitwgs_fnbinselection) == 1) then
+ index, iendindex twgs_getselectionindexes indexbin
+ while (index < iendindex) do
+ ival = tab_i(index, ifnampL)
+ ival *= ifactor
+ tabw_i(ival, index, ifnampL)
+ if (gitwgs_channels == 2) then
+ ival = tab_i(index, ifnampR)
+ ival *= ifactor
+ tabw_i(ival, index, ifnampR)
+ endif
+ index += ibins
+ od
+ endif
+ indexbin += 1
+ od
+ io_sendstring("callback", twgs_refreshviewresponse(icbid))
+ turnoff
+endin
+
+
+instr twgs_movement
+ icbid = p4
+ iframeshift = p5
+
+ twgs_checkpoint()
+
+ itype chnget "twgs_movementtype"
+ interpolatevoid chnget "twgs_interpolatevoid"
+ interpratio chnget "twgs_interpolateratio"
+ icopy = 0
+ imoveinterp = 0
+ if (itype == 0) then
+ icopy = 1
+ else
+ if (itype == 1) then
+ imovemode = 0
+ else
+ imoveinterp = interpolatevoid
+ imovemode = itype - 1
+ endif
+ endif
+
+ p3 = 3600
+ ifnAmpL tab_i 2, gitwgs_tpvfHandleL
+ ifnFreqL tab_i 3, gitwgs_tpvfHandleL
+ if (gitwgs_channels == 2) then
+ ifnAmpR tab_i 2, gitwgs_tpvfHandleR
+ ifnFreqR tab_i 3, gitwgs_tpvfHandleR
+ endif
+ itablen = ftlen(ifnAmpL)
+ ifftsize tab_i 0, gitwgs_tpvfHandleL
+ ibins = ifftsize / 2
+ indexbin = 0
+ while (indexbin < ftlen(gitwgs_fnbinselection)) do
+ if (tab_i(indexbin, gitwgs_fnbinselection) == 1) then
+ istartindex, iendindex twgs_getselectionindexes indexbin
+;prints sprintf("bin %d, start %f, end %f\n", indexbin, istartindex / ibins, iendindex / ibins)
+ if (interpratio > 0) then
+ interpframes = floor(interpratio * ((iendindex - istartindex) / ibins))
+ interpaddIndex = interpframes * ibins
+
+ interpendaddAmpLend = (tab_i(iendindex, ifnAmpL) - tab_i(iendindex - interpaddIndex, ifnAmpL)) / interpframes
+ interpendaddFreqLend = (tab_i(iendindex, ifnFreqL) - tab_i(iendindex - interpaddIndex, ifnFreqL)) / interpframes
+ interpendaddAmpLstart = (tab_i(istartindex + interpaddIndex, ifnAmpL) - tab_i(istartindex, ifnAmpL)) / interpframes
+ interpendaddFreqLstart = (tab_i(istartindex + interpaddIndex, ifnFreqL) - tab_i(istartindex, ifnFreqL)) / interpframes
+ if (gitwgs_channels == 2) then
+ interpendaddAmpRend = (tab_i(istartindex + interpaddIndex, ifnAmpR) - tab_i(istartindex, ifnAmpR)) / interpframes
+ interpendaddFreqRend = (tab_i(istartindex + interpaddIndex, ifnFreqR) - tab_i(istartindex, ifnFreqR)) / interpframes
+ interpendaddAmpRstart = (tab_i(istartindex + interpaddIndex, ifnAmpR) - tab_i(istartindex, ifnAmpR)) / interpframes
+ interpendaddFreqRstart = (tab_i(istartindex + interpaddIndex, ifnFreqR) - tab_i(istartindex, ifnFreqR)) / interpframes
+ endif
+ endif
+ itemplen = ceil((iendindex - istartindex) / ibins)
+ ifntempAmpL ftgen 0, 0, -itemplen, 2, 0
+ ifntempFreqL ftgen 0, 0, -itemplen, 2, 0
+ if (gitwgs_channels == 2) then
+ ifntempAmpR ftgen 0, 0, -itemplen, 2, 0
+ ifntempFreqR ftgen 0, 0, -itemplen, 2, 0
+ endif
+
+ index = istartindex
+ iwriteindex = 0
+ while (index < iendindex) do
+ tabw_i(tab_i(index, ifnAmpL), iwriteindex, ifntempAmpL)
+ tabw_i(tab_i(index, ifnFreqL), iwriteindex, ifntempFreqL)
+ if (gitwgs_channels == 2) then
+ tabw_i(tab_i(index, ifnAmpR), iwriteindex, ifntempAmpR)
+ tabw_i(tab_i(index, ifnFreqR), iwriteindex, ifntempFreqR)
+ endif
+ index += ibins
+ iwriteindex += 1
+ od
+
+ isetvalAmpL = -1
+ isetvalFreqL = -1
+ isetvalAmpR = -1
+ isetvalFreqR = -1
+ interpvalAmpL = -1
+ interpvalFreqL = -1
+ interpvalAmpR = -1
+ interpvalFreqR = -1
+
+ if (imovemode == 0) then
+ isetvalAmpL = 0
+ isetvalFreqL = 0
+ isetvalAmpR = 0
+ isetvalFreqR = 0
+ else ; hold last: 1 = amp and freq, 2 = amp, 3 = freq
+ ilastindex = istartindex - ibins
+ if (ilastindex < 0) then
+ ilastindex = istartindex
+ endif
+ if (imovemode == 2 || imovemode == 1) then
+ isetvalAmpL = tab_i(ilastindex, ifnAmpL)
+ if (imoveinterp == 1) then
+ interpvalAmpL = (tab_i(iendindex, ifnAmpL) - isetvalAmpL) / abs:i(iframeshift)
+ endif
+ if (gitwgs_channels == 2) then
+ isetvalAmpR = tab_i(ilastindex, ifnAmpR)
+ if (imoveinterp == 1) then
+ interpvalAmpR = (tab_i(iendindex, ifnAmpR) - isetvalAmpR) / abs:i(iframeshift)
+ endif
+ endif
+ endif
+ if (imovemode == 3 || imovemode == 1) then
+ isetvalFreqL = tab_i(ilastindex, ifnFreqL)
+ if (imoveinterp == 1) then
+ interpvalFreqL = (tab_i(iendindex, ifnFreqL) - isetvalFreqL) / abs:i(iframeshift)
+ endif
+ if (gitwgs_channels == 2) then
+ isetvalFreqR = tab_i(ilastindex, ifnFreqR)
+ if (imoveinterp == 1) then
+ interpvalFreqR = (tab_i(iendindex, ifnFreqR) - isetvalFreqR) / abs:i(iframeshift)
+ endif
+ endif
+ endif
+ endif
+
+ index = istartindex
+ iabsindex = 0
+ ireadindex = 0
+ while (index < iendindex) do
+ inewindex = index + (iframeshift * ibins)
+
+ if (inewindex >= itablen - 1) then
+ inewindex = itablen - 1
+ elseif (inewindex < 0) then
+ inewindex = 0
+ endif
+ if (icopy == 0) then
+ if (imoveinterp == 1) then
+ isetvalAmpL = (isetvalAmpL != -1) ? isetvalAmpL + interpvalAmpL : -1
+ isetvalFreqL = (isetvalFreqL != -1) ? isetvalFreqL + interpvalFreqL : -1
+ if (gitwgs_channels == 2) then
+ isetvalAmpR = (isetvalAmpR != -1) ? isetvalAmpR + interpvalAmpR : -1
+ isetvalFreqR = (isetvalFreqR != -1) ? isetvalFreqR + interpvalFreqR : -1
+ endif
+ endif
+
+ if (isetvalAmpL > 0) then
+ tabw_i(isetvalAmpL, index, ifnAmpL)
+ endif
+ if (isetvalFreqL > 0) then
+ tabw_i(isetvalFreqL, index, ifnFreqL)
+ endif
+ if (gitwgs_channels > 0) then
+ if (isetvalAmpR != -1) then
+ tabw_i(isetvalAmpR, index, ifnAmpR)
+ endif
+ if (isetvalFreqR > 0) then
+ tabw_i(isetvalFreqR, index, ifnFreqR)
+ endif
+ endif
+ endif
+ ivalAmpL = tab_i(ireadindex, ifntempAmpL)
+ ivalFreqL = tab_i(ireadindex, ifntempFreqL)
+ if (interpratio > 0) then
+ if (iabsindex < istartindex + interpaddIndex) then
+ ivalAmpL += interpendaddAmpLstart
+ ivalFreqL += interpendaddFreqLstart
+ elseif (iabsindex > iendindex - interpaddIndex) then
+ ivalAmpL += interpendaddAmpLend
+ ivalFreqL += interpendaddFreqLend
+ endif
+ endif
+ tabw_i(ivalAmpL, inewindex, ifnAmpL)
+ tabw_i(ivalFreqL, inewindex, ifnFreqL)
+
+ if (gitwgs_channels == 2) then
+ ivalAmpR = tab_i(ireadindex, ifntempAmpR)
+ ivalFreqR = tab_i(ireadindex, ifntempFreqR)
+ if (interpratio > 0) then
+ if (iabsindex < istartindex + interpaddIndex) then
+ ivalAmpR += interpendaddAmpRstart
+ ivalFreqR += interpendaddFreqRstart
+ elseif (iabsindex > iendindex - interpaddIndex) then
+ ivalAmpR += interpendaddAmpRend
+ ivalFreqR += interpendaddFreqRend
+ endif
+ endif
+ tabw_i(ivalAmpR, inewindex, ifnAmpR)
+ tabw_i(ivalFreqR, inewindex, ifnFreqR)
+ endif
+ index += ibins
+ iabsindex += ibins
+ ireadindex += 1
+ od
+
+ ftfree ifntempAmpL, 0
+ ftfree ifntempFreqL, 0
+ if (gitwgs_channels == 2) then
+ ftfree ifntempAmpR, 0
+ ftfree ifntempFreqR, 0
+ endif
+ endif
+ indexbin += 1
+ od
+
+ io_sendstring("callback", twgs_refreshviewresponse(icbid))
+ turnoff
+endin
+
+#end
diff --git a/site/udo/twigs/twigs.udo b/site/udo/twigs/twigs.udo
new file mode 100755
index 0000000..a4bda3f
--- /dev/null
+++ b/site/udo/twigs/twigs.udo
@@ -0,0 +1,444 @@
+#ifndef UDO_TWIGS
+#define UDO_TWIGS ##
+/*
+ Twigs
+ Spectral transformer
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024, 2025
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "/pvs_fulltabproc.udo"
+#include "/host_platform.udo"
+#include "/lagdetect.udo"
+#include "/interop.udo"
+
+gitwgs_bufferL = -1
+gitwgs_bufferR = -1
+gitwgs_tpvfHandleL = -1
+gitwgs_tpvfHandleR = -1
+gitwgs_channels = -1
+gitwgs_userstopped = 0
+gitwgs_fnbinselection = -1
+gitwgs_fnbintimeselection = -1
+
+#include "/twigs/checkpointing.udo"
+
+opcode twgs_getselectionindexes, ii, i
+ indexbin xin
+ ibins = ftlen(gitwgs_fnbinselection)
+ itablen = ftlen(tab_i(2, gitwgs_tpvfHandleL))
+ iframes = itablen / ibins
+ istartindex = (round(tab_i(indexbin, gitwgs_fnbintimeselection) * iframes) * ibins) + indexbin
+ iendindex = (round(tab_i(indexbin + ibins, gitwgs_fnbintimeselection) * iframes) * ibins) + indexbin
+ if (iendindex > itablen) then
+ iendindex = itablen
+ endif
+
+;print tab_i(indexbin, gitwgs_fnbintimeselection)
+;print istartindex
+;print tab_i(indexbin + ibins, gitwgs_fnbintimeselection)
+;print iendindex
+ xout istartindex, iendindex
+endop
+
+opcode twgs_failresponse, S, ij
+ icbid, istatus xin
+ xout sprintf("{\"cbid\":%d,\"status\":%d}", icbid, istatus)
+endop
+
+
+opcode twgs_refreshviewresponse, S, i
+ icbid xin
+ ifnampL tab_i 2, gitwgs_tpvfHandleL
+ ifnfreqL tab_i 3, gitwgs_tpvfHandleL
+ iduration tab_i 4, gitwgs_tpvfHandleL
+ if (gitwgs_channels == 2) then
+ ifnampR tab_i 2, gitwgs_tpvfHandleR
+ ifnfreqR tab_i 3, gitwgs_tpvfHandleR
+ else
+ ifnampR = -1
+ ifnfreqR = -1
+ endif
+ xout sprintf("{\"cbid\":%d,\"status\":1,\"tables\":[%d,%d,%d,%d],\"duration\":%f,\"undolevel\":%d}", icbid, ifnampL, ifnfreqL, ifnampR, ifnfreqR, iduration, gitwgs_checkpointstate)
+endop
+
+
+opcode twgs_clearbuffers, 0, pp
+ ihandles, ibuffers xin
+ if (ibuffers == 1) then
+ if (gitwgs_bufferL > 0 && ftexists(gitwgs_bufferL) == 1) then
+ ftfree gitwgs_bufferL, 0
+ gitwgs_bufferL = -1
+ endif
+ if (gitwgs_bufferR > 0 && ftexists(gitwgs_bufferR) == 1) then
+ ftfree gitwgs_bufferR, 0
+ gitwgs_bufferR = -1
+ endif
+ endif
+
+ if (ihandles == 1) then
+ if (gitwgs_tpvfHandleL > 0 && ftexists(gitwgs_tpvfHandleL) == 1) then
+ ftfree gitwgs_tpvfHandleL, 0
+ gitwgs_tpvfHandleL = -1
+ endif
+ if (gitwgs_tpvfHandleR > 0 && ftexists(gitwgs_tpvfHandleR) == 1) then
+ ftfree gitwgs_tpvfHandleR, 0
+ gitwgs_tpvfHandleR = -1
+ endif
+ gitwgs_channels = -1
+ endif
+endop
+
+opcode twgs_loadfile, ik, Sjj
+ Spath, ifftsize, ifftdecimation xin
+ kdone init 0
+ if (filevalid(Spath) != 1) then
+ iresponse = -1
+ goto complete
+ endif
+
+ ifilesr = filesr(Spath)
+ ifilechannels = filenchnls(Spath)
+ ilens = filelen(Spath)
+ ilen = round(ilens * ifilesr)
+
+ if (ilen >= gihost_max32bitftlen || ilens * sr >= gihost_max32bitftlen) then ; limitation with WASM Csound build at the moment
+ iresponse = -2
+ goto complete
+ endif
+
+ gitwgs_channels = ifilechannels
+ twgs_clearbuffers()
+ twgs_checkpoint_clear()
+
+ gitwgs_bufferL = ftgen(0, 0, -ilen, 1, Spath, 0, 0, 1)
+ if (gitwgs_channels == 2) then
+ gitwgs_bufferR = ftgen(0, 0, -ilen, 1, Spath, 0, 0, 2)
+ imono = 0
+ else
+ imono = 1
+ endif
+
+ if (sr != ifilesr) then ; different sr causes issues in table reading opcodes, convert..
+ inewlen = ilens * sr
+ ifnnewL ftgen 0, 0, -inewlen, -2, 0
+ if (imono == 0) then
+ ifnnewR ftgen 0, 0, -inewlen, -2, 0
+ endif
+ ktimek timeinstk
+ ikcycles = ilens * kr
+ if (ktimek == 1) then
+ kcount = 0
+ while (kcount < ikcycles) do
+ aposw linseg 0, ilens, inewlen - 1
+ aposr linseg 0, ilens, ilen - 1
+ asig table3 aposr, gitwgs_bufferL
+ tablew asig, aposw, ifnnewL
+ if (imono == 0) then
+ asig table3 aposr, gitwgs_bufferR
+ tablew asig, aposw, ifnnewR
+ endif
+ kcount += 1
+ od
+ else
+ kdone = 1
+ endif
+
+ ftfree gitwgs_bufferL, 1
+ gitwgs_bufferL = ifnnewL
+ if (imono == 0) then
+ ftfree gitwgs_bufferR, 1
+ gitwgs_bufferR = ifnnewR
+ endif
+ else
+ kdone = 1
+ endif
+
+ iresponse = 1
+complete:
+ xout iresponse, kdone
+endop
+
+
+#include "/twigs/transforms.udo"
+
+
+instr twgs_undo
+ icbid = p4
+
+ istatus twgs_undo
+ if (istatus < 0) then
+ Sresponse = twgs_failresponse(icbid)
+ else
+ Sresponse = twgs_refreshviewresponse(icbid)
+ endif
+
+ io_sendstring("callback", Sresponse)
+ turnoff
+endin
+
+
+instr twgs_stop
+ gitwgs_userstopped = 1
+ turnoff2 "twgs_play", 0, 1
+ turnoff
+endin
+
+instr twgs_playcomplete_response
+ icbid = p4
+ istatus = 0
+ if (gitwgs_userstopped == 1) then
+ istatus = 3
+ endif
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":%d}", icbid, istatus))
+ turnoff
+endin
+
+instr twgs_playlag_response
+ icbid = p4
+ turnoff2 "twgs_play", 0, 0
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":-1}", icbid))
+ turnoff
+endin
+
+instr twgs_play
+ icbid = p4
+ iplaytype = p5 ; 0 = from analysis, 1 = from buffer
+ iselectedonly = p6
+ istart = p7
+ iend = p8
+ iresynthtype = p9 ; 0 = pvsynth, 1 = pvsadsyn
+ gitwgs_userstopped = 0
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1}", icbid))
+
+ ilen = tab_i(4, gitwgs_tpvfHandleL)
+ p3 = ilen * (iend - istart)
+
+ if (iplaytype == 0) then
+ if (iselectedonly == 1) then
+ ibinselection = gitwgs_fnbinselection
+ else
+ ibinselection = -1
+ endif
+
+ istartframe = tpvf_framecount(gitwgs_tpvfHandleL) * istart
+ aL, k_ tpvf_resynth gitwgs_tpvfHandleL, ibinselection, iresynthtype, 0, istartframe
+ aL dcblock aL
+ if (gitwgs_channels == 2) then
+ aR, k_ tpvf_resynth gitwgs_tpvfHandleR, ibinselection, iresynthtype, istartframe
+ aR dcblock aR
+ else
+ aR = aL
+ endif
+ else
+ apos lphasor 1
+ apos += (istart * ftlen(gitwgs_bufferL))
+ aL table3 apos, gitwgs_bufferL
+ if (gitwgs_channels == 2) then
+ aR table3 apos, gitwgs_bufferR
+ else
+ aR = aL
+ endif
+ endif
+
+ kpos linseg istart, p3, iend
+ chnset kpos, "twgs_playposratio"
+
+ klagging lagdetect 1.1
+ if (klagging == 1) then
+ schedulek("twgs_playlag_response", 0, 1, icbid)
+ endif
+
+ kreleasing init 0
+ ktimek timeinstk
+ iduration = (p3 * kr) ;+ (iextracycles / sr)
+ krelease release
+ if (kreleasing == 0 && (krelease == 1 || ktimek >= iduration)) then
+ kreleasing = 1
+ schedulek("twgs_playcomplete_response", 0, 1, icbid)
+ turnoff
+ endif
+ outs aL, aR
+endin
+
+
+instr twgs_resynth_response
+ icbid = p4
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1}", icbid))
+ turnoff
+endin
+
+instr twgs_resynth
+ icbid = p4
+ Snext = strget(p5)
+ iresynthtype = p6 ; 0 = pvsynth, 1 = pvsadsyn
+ p3 = 3600
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":5}", icbid))
+ twgs_clearbuffers(0, 1)
+ kdone init 0
+ gitwgs_bufferL, kdoneL tpvf_resynth_offline gitwgs_tpvfHandleL, iresynthtype
+ if (gitwgs_channels == 2) then
+ gitwgs_bufferR, kdoneR tpvf_resynth_offline gitwgs_tpvfHandleR, iresynthtype
+ else
+ kdoneR init 1
+ endif
+ if (kdoneL == 1 && kdoneR == 1) then
+ schedulek(Snext, 0, 1, icbid, 1)
+ turnoff
+ endif
+endin
+
+instr twgs_getbuffers
+ icbid = p4
+ if (gitwgs_channels == 2) then
+ Stables = sprintf("[%d,%d]", gitwgs_bufferL, gitwgs_bufferR)
+ else
+ Stables = sprintf("[%d]", gitwgs_bufferL)
+ endif
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1,\"tables\":%s}", icbid, Stables))
+ turnoff
+endin
+
+
+instr twgs_savefile_response
+ icbid = p4
+ Spath = p5
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1,\"path\":\"%s\"}", icbid, Spath))
+ turnoff
+endin
+
+
+instr twgs_savefile
+ icbid = p4
+ Spath = strget(p5)
+
+ ktimek timeinstk
+ idurations = ftlen(gitwgs_bufferL) / sr
+ ikcycles = idurations * kr
+ if (ktimek == 1) then
+ kcount init 0
+ while (kcount < ikcycles) do
+ apos lphasor 1
+ aL table apos, gitwgs_bufferR
+ if (gitwgs_channels == 1) then
+ fout Spath, 14, aL
+ else
+ aR table3 apos, gitwgs_bufferL
+ fout Spath, 14, aL, aR
+ endif
+ kcount += 1
+ od
+ else
+ schedulek("twgs_savefile_response", 0, 1, icbid, Spath)
+ turnoff
+ endif
+endin
+
+
+
+instr twgs_loadfilecomplete_response
+ icbid = p4
+ ifftsize tab_i 0, gitwgs_tpvfHandleL
+ ifftdecimation tab_i 1, gitwgs_tpvfHandleL
+ ifnampL tab_i 2, gitwgs_tpvfHandleL
+ ifnfreqL tab_i 3, gitwgs_tpvfHandleL
+ iduration tab_i 4, gitwgs_tpvfHandleL
+ if (gitwgs_channels == 2) then
+ ifnampR tab_i 2, gitwgs_tpvfHandleR
+ ifnfreqR tab_i 3, gitwgs_tpvfHandleR
+ else
+ ifnampR = -1
+ ifnfreqR = -1
+ endif
+
+ Sresponse = sprintf("{\"cbid\":%d,\"status\":1,\"channels\":%d,\"tables\":[%d,%d,%d,%d],\"bins\":%d,\"binseltab\":%d,\"bintimeseltab\":%d,\"duration\":%f,\"undolevel\":%d,\"fftdecim\":%d,\"sr\":%d}", icbid, gitwgs_channels, ifnampL, ifnfreqL, ifnampR, ifnfreqR, ifftsize / 2, gitwgs_fnbinselection, gitwgs_fnbintimeselection, iduration, gitwgs_checkpointstate, ifftdecimation, sr)
+ io_sendstring("callback", Sresponse)
+ turnoff
+endin
+
+instr twgs_loadfile_analysis
+ icbid = p4
+ ifftsize = p5
+ ifftdecimation = p6
+
+ itablen = tpvf_tablen(ftlen(gitwgs_bufferL), ifftsize, ifftdecimation)
+ if (itablen >= gihost_max32bitftlen) then
+ io_sendstring("callback", twgs_failresponse(icbid, -2))
+ turnoff
+ endif
+
+ kdoneL, gitwgs_tpvfHandleL tpvf_analyse gitwgs_bufferL, ifftsize, ifftdecimation
+ ftfree gitwgs_bufferL, 1
+ if (gitwgs_channels == 2) then
+ kdoneR, gitwgs_tpvfHandleR tpvf_analyse gitwgs_bufferR, ifftsize, ifftdecimation
+ ftfree gitwgs_bufferR, 1
+ else
+ istatusR = 1
+ kdoneR init 1
+ endif
+
+ if (gitwgs_fnbinselection > 0) then
+ ftfree gitwgs_fnbinselection, 0
+ endif
+ gitwgs_fnbinselection ftgen 0, 0, -(ifftsize / 2), -2, 0
+
+ if (gitwgs_fnbintimeselection > 0) then
+ ftfree gitwgs_fnbintimeselection, 0
+ endif
+ gitwgs_fnbintimeselection ftgen 0, 0, -ifftsize, -2, 0
+
+ if (kdoneL == 1 && kdoneR == 1) then
+ schedule("twgs_loadfilecomplete_response", 0, 1, icbid)
+ turnoff
+ endif
+endin
+
+
+instr twgs_loadftable
+ icbid = p4
+ ifftsize = p5
+ ifftdecimation = p6
+ ifnL = p7
+ ifnR = p8
+ iclearaudiobuffers = p9
+
+ if (ifnL <= 0 || ftexists(ifnL) == 0 || (ifnR > 0 && ftexists(ifnR) == 0)) then
+ io_sendstring("callback", twgs_failresponse(icbid, -1))
+ turnoff
+ endif
+
+ twgs_clearbuffers(1, iclearaudiobuffers)
+ twgs_checkpoint_clear()
+
+ gitwgs_bufferL = ifnL
+ if (ifnR > 0) then
+ gitwgs_bufferR = ifnR
+ gitwgs_channels = 2
+ else
+ gitwgs_channels = 1
+ endif
+
+ schedule("twgs_loadfile_analysis", 0, 1, icbid, ifftsize, ifftdecimation)
+ turnoff
+endin
+
+instr twgs_loadfile
+ icbid = p4
+ Spath = strget(p5)
+ ifftsize = p6
+ ifftdecimation = p7
+ istatus, kdone twgs_loadfile Spath
+ if (istatus < 0) then
+ io_sendstring("callback", twgs_failresponse(icbid, istatus))
+ turnoff
+ else
+ if (kdone == 1) then
+ schedulek("twgs_loadfile_analysis", 0, 1, icbid, ifftsize, ifftdecimation)
+ turnoff
+ endif
+ endif
+endin
+
+#end
diff --git a/site/udo/twist/automation.udo b/site/udo/twist/automation.udo
new file mode 100755
index 0000000..39585fb
--- /dev/null
+++ b/site/udo/twist/automation.udo
@@ -0,0 +1,241 @@
+#ifndef TWST_AUTOMATION
+#define TWST_AUTOMATION ##
+/*
+ Twist waveform editor and transformer
+ Automation and modulation
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+opcode twst_xa_rms, 0, S
+ Schannel xin
+ kscale chnget strcat(Schannel, "xrmsscale")
+ kporttime chnget strcat(Schannel, "porttime")
+
+ aL, aR, ileft, iright _twst_getcrossinput "xrms"
+ if (ileft == 1 && iright == 1) then
+ kvalue rms (aL + aR) / 2
+ elseif (ileft == 1) then
+ kvalue rms aL
+ else
+ kvalue rms aR
+ endif
+
+ kvalue *= kscale
+ if (kporttime > 0) then
+ kresultvalue portk kvalue, kporttime
+ else
+ kresultvalue = kvalue
+ endif
+ chnset kresultvalue, Schannel
+endop
+
+opcode twst_xa_pitchamdf, 0, S
+ Schannel xin
+ kscale chnget strcat(Schannel, "xpitchscale")
+ ipitchmin chnget strcat(Schannel, "xpitchmin")
+ ipitchmax chnget strcat(Schannel, "xpitchmax")
+ kporttime chnget strcat(Schannel, "porttime")
+ aL, aR, ileft, iright _twst_getcrossinput "xpitch"
+
+ if (ileft == 1 && iright == 1) then
+ kcps, krms pitchamdf (aL + aR) / 2, ipitchmin, ipitchmax
+ elseif (ileft == 1) then
+ kcps, krms pitchamdf aL, ipitchmin, ipitchmax
+ else
+ kcps, krms pitchamdf aR, ipitchmin, ipitchmax
+ endif
+
+ kvalue = ((ipitchmin + max:k(0, kcps)) / (ipitchmax - ipitchmin)) * kscale
+ if (kporttime > 0) then
+ kresultvalue portk kvalue, kporttime
+ else
+ kresultvalue = kvalue
+ endif
+ chnset kresultvalue, Schannel
+endop
+
+opcode twst_xa_pitch1, 0, S
+ Schannel xin
+ kscale chnget strcat(Schannel, "xpitchscale")
+ ihopsize chnget strcat(Schannel, "xpitchhopsize")
+ kporttime chnget strcat(Schannel, "porttime")
+
+ ipitchmin = 20
+ ipitchmax = sr / 2
+ ihopsize = pow(2, ihopsize)
+
+ aL, aR, ileft, iright _twst_getcrossinput "xpitch"
+ if (ileft == 1 && iright == 1) then
+ kcps, kamp ptrack (aL + aR) / 2, ihopsize
+ elseif (ileft == 1) then
+ kcps, kamp ptrack aL, ihopsize
+ else
+ kcps, kamp ptrack aR, ihopsize
+ endif
+
+ kvalue = ((ipitchmin + max:k(0, kcps)) / (ipitchmax - ipitchmin)) * kscale
+ if (kporttime > 0) then
+ kresultvalue portk kvalue, kporttime
+ else
+ kresultvalue = kvalue
+ endif
+ chnset kresultvalue, Schannel
+endop
+
+opcode twst_xa_pitch2, 0, S
+ Schannel xin
+ kscale chnget strcat(Schannel, "xpitchscale")
+ iperiod chnget strcat(Schannel, "xpitchperiod")
+ ipitchmin = octcps(chnget:i(strcat(Schannel, "xpitchmin")))
+ ipitchmax = octcps(chnget:i(strcat(Schannel, "xpitchmax")))
+ iampthresh chnget strcat(Schannel, "xpitchampthresh")
+ kporttime chnget strcat(Schannel, "porttime")
+
+ aL, aR, ileft, iright _twst_getcrossinput "xpitch"
+ if (ileft == 1 && iright == 1) then
+ koct, kamp pitch (aL + aR) / 2, iperiod, ipitchmin, ipitchmax, iampthresh
+ elseif (ileft == 1) then
+ koct, kamp pitch aL, iperiod, ipitchmin, ipitchmax, iampthresh
+ else
+ koct, kamp pitch aR, iperiod, ipitchmin, ipitchmax, iampthresh
+ endif
+
+ kcps cpsoct koct
+ kvalue = ((ipitchmin + max:k(0, kcps)) / (ipitchmax - ipitchmin)) * kscale
+ if (kporttime > 0) then
+ kresultvalue portk kvalue, kporttime
+ else
+ kresultvalue = kvalue
+ endif
+ chnset kresultvalue, Schannel
+endop
+
+opcode twst_xa_centroid, 0, S
+ Schannel xin
+ kscale chnget strcat(Schannel, "xcentroidscale")
+ ifftsize chnget strcat(Schannel, "xcentroidfftsize")
+ kperiod chnget strcat(Schannel, "xcentroidperiod")
+ kporttime chnget strcat(Schannel, "porttime")
+ ipitchmin = 20
+ ipitchmax = sr / 2
+ ktrig metro 1 / kperiod
+
+ aL, aR, ileft, iright _twst_getcrossinput "xcentroid"
+ if (ileft == 1 && iright == 1) then
+ kcent centroid (aL + aR) / 2, ktrig, ifftsize
+ elseif (ileft == 1) then
+ kcent centroid aL, ktrig, ifftsize
+ else
+ kcent centroid aL, ktrig, ifftsize
+ endif
+
+ kvalue = ((ipitchmin + max:k(0, kcent)) / (ipitchmax - ipitchmin)) * kscale
+ if (kporttime > 0) then
+ kresultvalue portk kvalue, kporttime
+ else
+ kresultvalue = kvalue
+ endif
+ chnset kresultvalue, Schannel
+endop
+
+opcode twst_mod_jitter, 0, S
+ Schannel xin
+ kbase chnget strcat(Schannel, "base")
+ kamp chnget strcat(Schannel, "amp")
+ kfreqmin chnget strcat(Schannel, "freqmin")
+ kfreqmax chnget strcat(Schannel, "freqmax")
+ imin chnget strcat(Schannel, "min")
+ imax chnget strcat(Schannel, "max")
+ kval jitter kamp, kfreqmin, kfreqmax
+ kval = kbase + (kval * (imax - imin))
+ kval = min:k(max:k(kval, imin), imax)
+ chnset kval, Schannel
+endop
+
+opcode twst_mod_jitter2, 0, S
+ Schannel xin
+ kbase chnget strcat(Schannel, "base")
+ ktotalamp chnget strcat(Schannel, "totalamp")
+ kamp1 chnget strcat(Schannel, "amp1")
+ kfreq1 chnget strcat(Schannel, "freq1")
+ kamp2 chnget strcat(Schannel, "amp2")
+ kfreq2 chnget strcat(Schannel, "freq2")
+ kamp3 chnget strcat(Schannel, "amp3")
+ kfreq3 chnget strcat(Schannel, "freq3")
+ imin chnget strcat(Schannel, "min")
+ imax chnget strcat(Schannel, "max")
+ kval jitter2 ktotalamp, kamp1, kfreq1, kamp2, kfreq2, kamp3, kfreq3, 1
+ kval = kbase + (kval * (imax - imin))
+ kval = min:k(max:k(kval, imin), imax)
+ chnset kval, Schannel
+endop
+
+opcode twst_mod_lfo, 0, S
+ Schannel xin
+ krate chnget strcat(Schannel, "rate")
+ kbase chnget strcat(Schannel, "base")
+ kgain chnget strcat(Schannel, "gain")
+ imin chnget strcat(Schannel, "min")
+ imax chnget strcat(Schannel, "max")
+ kfn twst_tf_getwaveformk chnget:k(strcat(Schannel, "wave"))
+ kval = kbase + (oscilikt:k(kgain, krate, kfn) * (imax - imin))
+ kval = min:k(max:k(kval, imin), imax)
+ chnset kval, Schannel
+endop
+
+opcode twst_mod_line, 0, S
+ Schannel xin
+ ilatency twst_getlatencyseconds
+ ifirst chnget strcat(Schannel, "first")
+ ilast chnget strcat(Schannel, "last")
+ if (ilatency > 0) then
+ ksig linseg ifirst, ilatency, ifirst, p3, ilast
+ else
+ ksig linseg ifirst, p3, ilast
+ endif
+
+ chnset ksig, Schannel
+endop
+
+opcode twst_mod_random, 0, S
+ Schannel xin
+ krate chnget strcat(Schannel, "rate")
+ kmin chnget strcat(Schannel, "min")
+ kmax chnget strcat(Schannel, "max")
+ kporttime chnget strcat(Schannel, "porttime")
+
+ imin chnget strcat(Schannel, "min")
+ imax chnget strcat(Schannel, "max")
+ initval random imin, imax
+
+ kval init initval
+ ktrig metro krate
+ if (ktrig == 1) then
+ kval = random:k(kmin, kmax)
+ endif
+ kportval portk kval, kporttime
+ chnset kportval, Schannel
+endop
+
+
+instr twst_automationprepare
+ icbid = p4
+ itemnum = pcount()
+ index = 5
+ Sinstr = "instr twst_automaterun\n"
+ while (index < itemnum + 1) do
+
+ Sinstr = strcat(strcat(Sinstr, strget(p(index))), "\n")
+ index += 1
+ od
+ Sinstr = strcat(Sinstr, "a_ init 0\nout a_\nendin\n")
+ ires compilestr Sinstr
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":%d}", icbid, (ires == 0) ? 1 : 0))
+endin
+
+
+#end \ No newline at end of file
diff --git a/site/udo/twist/checkpointing.udo b/site/udo/twist/checkpointing.udo
new file mode 100755
index 0000000..39c45e8
--- /dev/null
+++ b/site/udo/twist/checkpointing.udo
@@ -0,0 +1,127 @@
+#ifndef UDO_TWIST_CHECKPOINTING
+#define UDO_TWIST_CHECKPOINTING ##
+
+gitwst_maxundolevels = 32
+gitwst_checkpoints[][] init imaxinstances, gitwst_maxundolevels
+gitwst_checkpointstate[] init imaxinstances
+gitwst_checkpointencodemult = 10000
+
+
+opcode twst_checkpoint_encode, i, ii
+ ifnL, ifnR xin
+ iencoded = (ifnL * gitwst_checkpointencodemult) + (ifnR / gitwst_checkpointencodemult)
+ xout iencoded
+endop
+
+opcode twst_checkpoint_decode, ii, i
+ iencoded xin
+ ifnL = int(iencoded) / gitwst_checkpointencodemult
+ ifnR = frac(iencoded) * gitwst_checkpointencodemult
+ xout ifnL, ifnR
+endop
+
+opcode twst_checkpoint_clear, 0, j
+ instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ icheckpointnumber = gitwst_checkpointstate[instanceindex]
+ while (icheckpointnumber >= 0) do
+ ifnL, ifnR twst_checkpoint_decode gitwst_checkpoints[instanceindex][icheckpointnumber]
+ if (ifnL > 0 && ftexists(ifnL) == 1) then
+ ftfree ifnL, 0
+ endif
+ if (ifnR > 0 && ftexists(ifnR) == 1) then
+ ftfree ifnR, 0
+ endif
+ icheckpointnumber -= 1
+ od
+ gitwst_checkpointstate[instanceindex] = 0
+endop
+
+opcode twst_checkpoint, 0, j
+ instanceindex xin
+ imaxundo chnget "twst_maxundo"
+ imaxundo = (imaxundo == -1) ? gitwst_maxundolevels : imaxundo
+ if (imaxundo == 0 || imaxundo > gitwst_maxundolevels) then
+ goto complete
+ endif
+
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ icheckpointnumber = gitwst_checkpointstate[instanceindex]
+ if (icheckpointnumber >= imaxundo) then
+ ifnL, ifnR twst_checkpoint_decode gitwst_checkpoints[instanceindex][0]
+ if (ifnL > 0 && ftexists(ifnL) == 1) then
+ ftfree ifnL, 0
+ endif
+ if (ifnR > 0 && ftexists(ifnR) == 1) then
+ ftfree ifnR, 0
+ endif
+ index = 1
+ itemp[] getrow gitwst_checkpoints, instanceindex
+ while (index <= imaxundo) do
+ gitwst_checkpoints[instanceindex][index - 1] = itemp[index]
+ index += 1
+ od
+ gitwst_checkpointstate[instanceindex] = icheckpointnumber ;- 1
+ else
+ gitwst_checkpointstate[instanceindex] = icheckpointnumber + 1
+ endif
+
+ ifnL = gitwst_bufferL[instanceindex]
+ ifnR = gitwst_bufferR[instanceindex]
+ ilen = ftlen(ifnL)
+
+ ifnCheckpointL ftgen 0, 0, ilen, 2, 0
+ tableicopy ifnCheckpointL, ifnL
+ if (ifnR > 0) then
+ ifnCheckpointR ftgen 0, 0, ilen, 2, 0
+ tableicopy ifnCheckpointR, ifnR
+ else
+ ifnCheckpointR = 0
+ endif
+
+ iencoded twst_checkpoint_encode ifnCheckpointL, ifnCheckpointR
+ gitwst_checkpoints[instanceindex][icheckpointnumber] = iencoded
+
+complete:
+endop
+
+opcode twst_undo, i, jp
+ instanceindex, iapplyundo xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+
+ icheckpointnumber = gitwst_checkpointstate[instanceindex]
+
+ icheckpointnumber -= 1
+ if (icheckpointnumber < 0) then
+ istatus = -1
+ goto complete
+ endif
+
+ gitwst_checkpointstate[instanceindex] = icheckpointnumber
+ ifnL, ifnR twst_checkpoint_decode gitwst_checkpoints[instanceindex][icheckpointnumber]
+
+ if (iapplyundo == 1) then ; apply or just step back and forget
+ if (ifnL > 0 && ftexists(ifnL) = 1) then
+ ftfree gitwst_bufferL[instanceindex], 0
+ gitwst_bufferL[instanceindex] = ifnL
+ endif
+ if (ifnR > 0 && ftexists(ifnR) == 1) then
+ ftfree gitwst_bufferR[instanceindex], 0
+ gitwst_bufferR[instanceindex] = ifnR
+ endif
+ else
+ if (ifnL > 0 && ftexists(ifnL) = 1) then
+ ftfree ifnL, 0
+ endif
+ if (ifnL > 0 && ftexists(ifnR) = 1) then
+ ftfree ifnR, 0
+ endif
+ endif
+
+ istatus = 1
+
+complete:
+ xout istatus
+endop
+
+#end
diff --git a/site/udo/twist/checkpointing_hold.udo b/site/udo/twist/checkpointing_hold.udo
new file mode 100755
index 0000000..523d423
--- /dev/null
+++ b/site/udo/twist/checkpointing_hold.udo
@@ -0,0 +1,196 @@
+#ifndef UDO_TWIST_CHECKPOINTING
+#define UDO_TWIST_CHECKPOINTING ##
+
+; file checkpointing broken due to WASM FS issue, but keep in memory maybe better anyway
+
+#ifdef TWIST_FTCHECKPOINTING
+
+gitwst_checkpoints[][] init imaxinstances, gitwst_maxundolevels
+gitwst_checkpointstate[] init imaxinstances
+gitwst_checkpointencodemult = 10000
+
+opcode twst_checkpoint, 0, j
+ instanceindex xin
+ imaxundo chnget "maxundo"
+ imaxundo = (imaxundo == -1) ? gitwst_maxundolevels : imaxundo
+ if (imaxundo == 0 || imaxundo > gitwst_maxundolevels) then
+ goto complete
+ endif
+
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ icheckpointnumber = gitwst_checkpointstate[instanceindex]
+
+ if (icheckpointnumber >= imaxundo) then
+ ftfree gitwst_checkpoints[instanceindex][0], 0
+ index = 1
+ while (index < gitwst_maxundolevels) do
+ gitwst_checkpoints[instanceindex][index - 1] = gitwst_checkpoints[instanceindex][index]
+ index += 1
+ od
+ else
+ gitwst_checkpointstate[instanceindex] = icheckpointnumber + 1
+ endif
+
+ ifnL = gitwst_bufferL[instanceindex]
+ ifnR = gitwst_bufferR[instanceindex]
+ ilen = ftlen(ifnL)
+
+ ifnCheckpointL ftgen 0, 0, ilen, 2, 0
+ tableicopy ifnCheckpointL, ifnL
+ ifnEncodedL = ifnCheckpointL * gitwst_checkpointencodemult
+ if (ifnR > 0) then
+ ifnCheckpointR ftgen 0, 0, ilen, 2, 0
+ tableicopy ifnCheckpointR, ifnR
+ ifnEncodedR = ifnCheckpointR / gitwst_checkpointencodemult
+ else
+ ifnEncodedR = 0
+ endif
+
+ iencoded = ifnEncodedL + ifnEncodedR
+ gitwst_checkpoints[instanceindex][icheckpointnumber] = iencoded
+
+complete:
+endop
+
+opcode twst_undo, i, j
+ instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ Sloaded = ""
+
+ icheckpointnumber = gitwst_checkpointstate[instanceindex]
+
+ icheckpointnumber -= 1
+ if (icheckpointnumber < 0) then
+ istatus = -1
+ goto complete
+ endif
+
+ icheckpointEncoded = gitwst_checkpoints[instanceindex][icheckpointnumber]
+ ifnCheckpointL = icheckpointEncoded / gitwst_checkpointencodemult
+ ifnCheckpointR = icheckpointEncoded * gitwst_checkpointencodemult
+
+ ftfree gitwst_bufferL[instanceindex], 0
+ gitwst_bufferL[instanceindex] = ifnCheckpointL
+
+ if (ifnCheckpointR > 0) then
+ ftfree gitwst_bufferR[instanceindex], 0
+ gitwst_bufferR[instanceindex] = ifnCheckpointR
+ endif
+
+ istatus = 1
+
+complete:
+ xout istatus
+endop
+
+#else
+
+gitwst_checkpoint[][] init imaxinstances, 2
+gitwst_checkpointlengths[][] init imaxinstances, gitwst_maxundolevels
+opcode twst_checkpointfilename, S, jj
+ instanceindex, icheckpointnumber xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ icheckpointnumber = (icheckpointnumber == -1) ? gitwst_checkpoint[instanceindex][0] : icheckpointnumber
+ Sfile = sprintf("checkpoint-%d-%d", instanceindex, icheckpointnumber)
+ Sfile = strcat(host_tempdir(), strcat("/", Sfile))
+ xout Sfile
+endop
+
+opcode twst_checkpoint, 0, j
+ instanceindex xin
+ imaxundo chnget "maxundo"
+ if (imaxundo == 0 || imaxundo > gitwst_maxundolevels) then
+ goto complete
+ endif
+
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ icheckpointnumber = gitwst_checkpoint[instanceindex][0]
+ icheckpointoffset = gitwst_checkpoint[instanceindex][1]
+
+ if (icheckpointoffset + icheckpointnumber >= imaxundo) then
+ Sdelete = twst_checkpointfilename(icheckpointoffset)
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"delete\": [\"%s\", \"%s.len\"]}", giwst_globalcbid, Sdelete, Sdelete))
+ icheckpointoffset += 1
+ gitwst_checkpoint[instanceindex][1] = icheckpointoffset
+ endif
+
+ Sfile = twst_checkpointfilename(instanceindex)
+
+ gitwst_checkpointlengths[instanceindex][icheckpointnumber - icheckpointoffset] = ftlen(gitwst_bufferL[instanceindex])
+ /* can't use this as WASM not writing/closing files properly
+ ilen = ftlen(gitwst_bufferL[instanceindex])
+ ihf fiopen strcat(Sfile, ".len"), 0
+ fouti ihf, 0, 0, ilen
+ ficlose ihf*
+ ; nor this ;fprints strcat(Sfile, ".len"), "%d\n", ftlen(gitwst_bufferL[instanceindex])
+ */
+
+ if (gitwst_channels[instanceindex] == 1) then
+ ftsave Sfile, 0, gitwst_bufferL[instanceindex]
+ else
+ ftsave Sfile, 0, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ endif
+ gitwst_checkpoint[instanceindex][0] = icheckpointnumber + 1
+complete:
+endop
+
+opcode twst_undo, iS, j
+ instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ Sloaded = ""
+
+ icheckpointnumber = gitwst_checkpoint[instanceindex][0]
+ icheckpointoffset = gitwst_checkpoint[instanceindex][1]
+
+ if (icheckpointnumber - icheckpointoffset <= 0) then
+ istatus = -1
+ goto complete
+ endif
+
+ icheckpointnumber -= 1
+ gitwst_checkpoint[instanceindex][0] = icheckpointnumber
+ Sfile = twst_checkpointfilename(instanceindex)
+
+ ilen = gitwst_checkpointlengths[instanceindex][icheckpointnumber - icheckpointoffset]
+ /* can't use this as WASM not writing/closing files properly thus checkpoint step doesn't write
+ SlenFile = strcat(Sfile, ".len")
+ if (filevalid(Sfile) != 1 || filevalid(SlenFile) != 1) then
+ istatus = -2
+ goto complete
+ endif
+
+ ilen init 0
+ ihf fiopen SlenFile, 1
+ fini ihf, 0, 1, ilen
+ ficlose ihf
+ */
+
+ if (ilen == 0) then
+ istatus = -3
+ goto complete
+ endif
+
+ if (ilen != ftlen(gitwst_bufferL[instanceindex])) then
+ twst_clearbuffers gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ gitwst_bufferL[instanceindex] ftgen 0, 0, -ilen, -2, 0
+ if (gitwst_channels[instanceindex] == 2) then
+ gitwst_bufferR[instanceindex] ftgen 0, 0, -ilen, -2, 0
+ else
+ gitwst_bufferR[instanceindex] = 0
+ endif
+ endif
+
+ if (gitwst_channels[instanceindex] == 1) then
+ ftload Sfile, 0, gitwst_bufferL[instanceindex]
+ else
+ ftload Sfile, 0, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ endif
+ Sloaded = Sfile
+ istatus = 1
+
+complete:
+ xout istatus, Sloaded
+endop
+#end
+
+#end \ No newline at end of file
diff --git a/site/udo/twist/transform_api.udo b/site/udo/twist/transform_api.udo
new file mode 100755
index 0000000..9b916e3
--- /dev/null
+++ b/site/udo/twist/transform_api.udo
@@ -0,0 +1,421 @@
+#ifndef UDO_TWIST_TRANSFORMAPI
+#define UDO_TWIST_TRANSFORMAPI ##
+
+#define TWST_TRANSFORM #i_ = p4#
+
+opcode twst_param, k, S
+ Sname xin
+ Schannel = sprintf("%s_%s%d", nstrstr(p1), Sname, p4)
+ kvalue chnget Schannel
+ xout kvalue
+endop
+
+opcode twst_parami, i, S
+ Sname xin
+ Schannel = sprintf("%s_%s%d", nstrstr(p1), Sname, p4)
+ ivalue chnget Schannel
+ xout ivalue
+endop
+
+opcode twst_getinput, aaii, 0
+ aL chnget "twstfeedL"
+ aR chnget "twstfeedR"
+ xout aL, aR, gitwst_tf_state[0], gitwst_tf_state[1]
+endop
+
+opcode twst_setlatencysamples, 0, i
+ isamples xin
+ gitwst_tf_state[5] = isamples
+endop
+
+opcode twst_setlatencyseconds, 0, i
+ iseconds xin
+ twst_setlatencysamples iseconds * sr
+endop
+
+opcode twst_getlatencyseconds, i, 0
+ xout gitwst_tf_state[5] / sr
+endop
+
+opcode twst_tf_pitchscale_custom, k, S
+ SchanPrepend xin
+ if (twst_param:k(strcat(SchanPrepend, "pitchscalemode")) == 1) then
+ ksemitones = twst_param:k(strcat(SchanPrepend, "pitchsemitones"))
+ kscale = pow:k(2, ksemitones / 12)
+ else
+ kscale = twst_param:k(strcat(SchanPrepend, "pitchscale"))
+ endif
+ xout kscale
+endop
+
+opcode twst_tf_pitchscale, k, 0
+ xout twst_tf_pitchscale_custom("")
+endop
+
+opcode twst_tf_freq_custom, k, S
+ SchanPrepend xin
+ if (twst_param:k(strcat(SchanPrepend, "freqmode")) == 1) then
+ kfreq = cpsmidinn:k(twst_param:k(strcat(SchanPrepend, "note")))
+ else
+ kfreq = twst_param:k(strcat(SchanPrepend, "freq"))
+ endif
+ xout kfreq
+endop
+
+opcode twst_tf_freq, k, 0
+ xout twst_tf_freq_custom("")
+endop
+
+opcode twst_tf_freqi_custom, i, S
+ SchanPrepend xin
+ if (twst_parami(strcat(SchanPrepend, "freqmode")) == 1) then
+ ifreq = cpsmidinn:i(twst_parami(strcat(SchanPrepend,"note")))
+ else
+ ifreq = twst_parami(strcat(SchanPrepend,"freq"))
+ endif
+ xout ifreq
+endop
+
+opcode twst_tf_freqi, i, 0
+ xout twst_tf_freqi_custom("")
+endop
+
+opcode twst_tf_setplayposition, 0, k
+ kplayposratio xin
+ chnset kplayposratio, "twst_tfplayposratio"
+endop
+
+opcode twst_tf_getwintype, i, j
+ iwintype xin
+ if (iwintype == -1) then
+ iwintype = twst_parami("wintype")
+ endif
+ ifnWindow = gifnHanning
+ if (iwintype == 1) then
+ ifnWindow = gifnHamming
+ elseif (iwintype == 2) then
+ ifnWindow = gifnHalfSine
+ endif
+ xout ifnWindow
+endop
+
+opcode twst_tf_getwintypek, k, 0
+ kwintype = twst_param:k("wintype")
+ kfnWindow = gifnHanning
+ if (kwintype == 1) then
+ kfnWindow = gifnHamming
+ elseif (kwintype == 2) then
+ kfnWindow = gifnHalfSine
+ endif
+ xout kfnWindow
+endop
+
+opcode twst_tf_getwaveform, i, j
+ iwave xin
+ if (iwave == -1) then
+ iwave = twst_parami("wave")
+ endif
+ ifn = gifnSine
+ if (iwave == 1) then
+ ifn = gifnSquare
+ elseif (iwave == 2) then
+ ifn = gifnSaw
+ elseif (iwave == 3) then
+ ifn = gifnPulse
+ elseif (iwave == 4) then
+ ifn = gifnTriangle
+ endif
+ xout ifn
+endop
+
+opcode twst_tf_getwaveformk, k, J
+ kwave xin
+ kwave = (kwave == -1) ? twst_param:k("wave") : kwave
+ kfn = gifnSine
+ if (kwave == 1) then
+ kfn = gifnSquare
+ elseif (kwave == 2) then
+ kfn = gifnSaw
+ elseif (kwave == 3) then
+ kfn = gifnPulse
+ elseif (kwave == 4) then
+ kfn = gifnTriangle
+ endif
+ xout kfn
+endop
+
+opcode twst_getfinput, ffaaii, jJ
+ ifftsize, kpvslock xin
+ ifftsize = (ifftsize == -1) ? twst_parami("fftsize") : ifftsize
+ kpvslock = (kpvslock == -1) ? twst_param:k("pvslock") : kpvslock
+ iwintype = twst_parami("pvswintype")
+ idecimation = twst_parami("pvsdecimation")
+ iwinsizem = twst_parami("pvswinsizem")
+
+ iwinsize = ifftsize * iwinsizem
+ twst_setlatencysamples(iwinsize) ;idecimation)
+ aL, aR, ileft, iright twst_getinput
+
+ if (ileft == 0) then
+ fL pvsinit ifftsize
+ else
+ fLbase pvsanal aL, ifftsize, ifftsize / idecimation, iwinsize, 1
+ fL pvslock fLbase, kpvslock
+ endif
+
+ if (iright == 0) then
+ fR pvsinit ifftsize
+ else
+ fRbase pvsanal aR, ifftsize, ifftsize / idecimation, iwinsize, 1
+ fR pvslock fRbase, kpvslock
+ endif
+ xout fL, fR, aL, aR, ileft, iright
+endop
+
+/* TODO: LPC unstable with WASM
+opcode twst_getfinput, ffaaii, jJ
+ ifftsize, kpvslock xin
+ ifftsize = (ifftsize == -1) ? twst_parami("fftsize") : ifftsize
+ kpvslock = (kpvslock == -1) ? twst_param:k("pvslock") : kpvslock
+ ipvstype = twst_parami("pvstype")
+ iwintype = twst_parami("pvswintype")
+ iwinlpcfn = twst_tf_getwintype(twst_parami("pvswintypelpc"))
+ idecimation = twst_parami("pvsdecimation")
+ iwinsizem = twst_parami("pvswinsizem")
+ iorderm = twst_parami("pvsorderm")
+
+ if (ipvstype == 1) then
+ ifftsize = min(ifftsize, 1024) ; crashes in WASM with high FFT size and LPC
+ endif
+
+ iorder = min(max(1, round(ifftsize * iorderm)), ifftsize)
+ iwinsize = ifftsize * iwinsizem
+ twst_setlatencysamples(iwinsize) ;idecimation)
+ aL, aR, ileft, iright twst_getinput
+
+ if (ileft == 0) then
+ fL pvsinit ifftsize
+ else
+ if (ipvstype == 0) then
+ fLbase pvsanal aL, ifftsize, ifftsize / idecimation, iwinsize, 1
+ else
+ fLbase pvslpc aL, ifftsize, ifftsize / idecimation, iorder, iwinlpcfn
+ endif
+ fL pvslock fLbase, kpvslock
+ endif
+
+ if (iright == 0) then
+ fR pvsinit ifftsize
+ else
+ if (ipvstype == 0) then
+ fRbase pvsanal aR, ifftsize, ifftsize / idecimation, iwinsize, 1
+ else
+ fRbase pvslpc aR, ifftsize, ifftsize / idecimation, iorder, iwinlpcfn
+ endif
+ fR pvslock fRbase, kpvslock
+ endif
+ xout fL, fR, aL, aR, ileft, iright
+endop
+*/
+
+
+; ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+opcode twst_tf_getstate, iiiiii, 0
+ ilengths = ((gitwst_tf_state[3] - gitwst_tf_state[2]) / sr)
+ xout gitwst_tf_state[0], gitwst_tf_state[1], gitwst_tf_state[2], gitwst_tf_state[3], gitwst_tf_state[4], ilengths
+endop
+
+opcode twst_tf_isoffline, i, 0
+ xout gitwst_tf_state[6]
+endop
+
+opcode twst_tfi_getfn, ii, jjjjjj
+ instanceindex, ileft, iright, istartsamp, iendsamp, idocut xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ ileft = (ileft == -1) ? gitwst_tf_state[0] : ileft
+ iright = (iright == -1) ? gitwst_tf_state[1] : iright
+ istartsamp = (istartsamp == -1) ? gitwst_tf_state[2] : istartsamp
+ iendsamp = (iendsamp == -1) ? gitwst_tf_state[3] : iendsamp
+ idocut = (idocut == -1) ? gitwst_tf_state[4] : idocut
+ if (idocut == 1) then
+ itargetlen = iendsamp - istartsamp ;int(p3 * sr)
+ if (ileft == 1) then
+ ifnL ftgentmp 0, 0, -itargetlen, -2, 0
+ ftslicei gitwst_bufferL[instanceindex], ifnL, istartsamp, iendsamp, 1
+ else
+ ifnL = 0
+ endif
+ if (iright == 1) then
+ ifnR ftgentmp 0, 0, -itargetlen, -2, 0
+ ftslicei gitwst_bufferR[instanceindex], ifnR, istartsamp, iendsamp, 1
+ else
+ ifnR = 0
+ endif
+ else
+ ifnL = gitwst_bufferL[instanceindex]
+ ifnR = gitwst_bufferR[instanceindex]
+ endif
+ xout ifnL, ifnR
+endop
+
+opcode _twst_getcrossdata, iiiiiii, S
+ SchannelPrefix xin
+ if (strcmp(SchannelPrefix, "") == 0) then
+ ichannel = twst_parami("instchan")
+ instanceindex = twst_parami("instance")
+ istart = twst_parami("inststart")
+ iend = twst_parami("instend")
+ else
+ ichannel chnget strcat(SchannelPrefix, sprintf("instchan%d", p4))
+ instanceindex chnget strcat(SchannelPrefix, sprintf("instance%d", p4))
+ istart chnget strcat(SchannelPrefix, sprintf("inststart%d", p4))
+ iend chnget strcat(SchannelPrefix, sprintf("instend%d", p4))
+ endif
+ idoleft = 1
+ idoright = 1
+ if (ichannel == 0 || gitwst_channels[instanceindex] == 1) then
+ idoright = 0
+ elseif (ichannel == 1 && gitwst_channels[instanceindex] == 2) then
+ idoleft = 0
+ endif
+ if (istart == iend) then
+ istart = 0
+ iend = 1
+ endif
+ istart, ilen twst_getstartend istart, iend, instanceindex
+ ifnL = gitwst_bufferL[instanceindex]
+ ifnR = gitwst_bufferR[instanceindex]
+
+ xout idoleft, idoright, ifnL, ifnR, instanceindex, istart, ilen
+endop
+
+opcode _twst_getcrossdata, iiiiii, S
+ SchannelPrefix xin
+ ileft, iright, ifnL, ifnR, instanceindex, istart, iend _twst_getcrossdata SchannelPrefix
+ xout ifnL, ifnR, istart, iend - istart, ileft, iright
+endop
+
+opcode twst_getcrossdata, iiiiii, 0
+ ifnL, ifnR, istart, ilen, idoleft, idoright _twst_getcrossdata ""
+ xout ifnL, ifnR, istart, ilen, idoleft, idoright
+endop
+
+opcode twst_tfi_getcrossfn, ii, 0
+ ileft, iright, ifnL, ifnR, instanceindex, istart, ilen _twst_getcrossdata ""
+ idocut = (ftlen(ifnL) == ilen) ? 0 : 1
+ ifnLtemp, ifnRtemp twst_tfi_getfn instanceindex, ileft, iright, istart, istart + ilen, idocut
+ if (gitwst_channels[instanceindex] == 1) then
+ ifnRtemp = ifnLtemp
+ endif
+ xout ifnLtemp, ifnRtemp
+endop
+
+opcode _twst_getcrossinput, aaii, S
+ SchannelPrefix xin
+ ifnL, ifnR, istart, ilen, idoleft, idoright _twst_getcrossdata SchannelPrefix
+ if (strcmp(SchannelPrefix, "") == 0) then
+ ilooptype = twst_parami("otlooptype")
+ else
+ ichannel chnget strcat(SchannelPrefix, sprintf("looptype%d", p4))
+ endif
+ apos lphasor 1, 0, ilen, ilooptype
+ apos += istart
+
+ if (idoleft == 1) then
+ aL table3 apos, ifnL
+ endif
+ if (idoright == 1) then
+ aR table3 apos, ifnR
+ endif
+
+ if (idoright == 0) then
+ aR = aL
+ endif
+ if (idoleft == 0) then
+ aL = aR
+ endif
+ xout aL, aR, idoleft, idoright
+endop
+
+opcode twst_getcrossinput, aaii, 0
+ aL, aR, idoleft, idoright _twst_getcrossinput ""
+ xout aL, aR, idoleft, idoright
+endop
+
+opcode twst_getfcrossinput, ffii, 0
+ aL, aR, ileft, iright twst_getcrossinput
+ ifftsize = twst_parami("fftsize")
+ kpvslock = twst_param:k("pvslock")
+ idecimation = twst_parami("pvsdecimation")
+ iwinsizem = twst_parami("pvswinsizem")
+ iwinsize = ifftsize * iwinsizem
+
+ if (ileft == 0) then
+ fL pvsinit ifftsize
+ else
+ fLbase pvsanal aL, ifftsize, ifftsize / idecimation, iwinsize, 1
+ fL pvslock fLbase, kpvslock
+ endif
+
+ if (iright == 0) then
+ fR pvsinit ifftsize
+ else
+ fRbase pvsanal aR, ifftsize, ifftsize / idecimation, iwinsize, 1
+ fR pvslock fRbase, kpvslock
+ endif
+ xout fL, fR, ileft, iright
+endop
+
+opcode twst_tf_fresynth, a, f
+ fsig xin
+ imode = twst_parami("pvresmode")
+ if (imode == 0) then
+ aout pvsynth fsig
+ else
+ ifftsize = twst_parami("fftsize")
+ ioscnum = max(round((ifftsize * 0.5) * twst_parami("pvaoscnum")) - 1, 1)
+ kfmod = twst_param:k("pvafreqmod")
+ ibinoffset = round(ifftsize * 0.5 * twst_parami("pvabinoffset"))
+ ibinincr = twst_parami("pvabinincr")
+ ioscnum = ioscnum - ibinoffset
+ ioscnum = min(((ifftsize * 0.5) / ibinincr), ioscnum)
+ aout pvsadsyn fsig, ioscnum, kfmod, ibinoffset, ibinincr
+ endif
+ xout aout
+endop
+
+opcode twst_getrandombuffers, kk, k
+ kstereounique xin
+ kfnL = -1
+ kfnR = -1
+
+ if (kstereounique == 0) then
+ while (kfnL == -1) do
+ kindex = round:k(random:k(0, lenarray(gitwst_bufferL) - 1))
+ if (ftexists:k(gitwst_bufferL[kindex]) == 1) then
+ kfnL = gitwst_bufferL[kindex]
+ if (ftexists:k(gitwst_bufferR[kindex]) == 1) then
+ kfnR = gitwst_bufferR[kindex]
+ else
+ kfnR = kfnL
+ endif
+ endif
+ od
+ else
+ while (kfnL == -1) do
+ kfn = gitwst_bufferL[round:k(random:k(0, lenarray(gitwst_bufferL) - 1))]
+ if (ftexists:k(kfn) == 1) then
+ kfnL = kfn
+ endif
+ od
+ while (kfnR == -1) do
+ kfn = gitwst_bufferR[round:k(random:k(0, lenarray(gitwst_bufferR) - 1))]
+ if (ftexists:k(kfn) == 1) then
+ kfnR = kfn
+ endif
+ od
+ endif
+ xout kfnL, kfnR
+endop
+
+#end
diff --git a/site/udo/twist/transforms.udo b/site/udo/twist/transforms.udo
new file mode 100755
index 0000000..2587d18
--- /dev/null
+++ b/site/udo/twist/transforms.udo
@@ -0,0 +1,25 @@
+#ifndef TWST_TRANSFORMS
+#define TWST_TRANSFORMS ##
+/*
+ Twist waveform editor and transformer
+ Transforms
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "/twist/transforms/amplitude.udo"
+#include "/twist/transforms/cross_processing.udo"
+#include "/twist/transforms/delay.udo"
+#include "/twist/transforms/filter.udo"
+#include "/twist/transforms/frequency.udo"
+#include "/twist/transforms/general.udo"
+#include "/twist/transforms/generate.udo"
+#include "/twist/transforms/granular.udo"
+#include "/twist/transforms/harmonic.udo"
+#include "/twist/transforms/reverb.udo"
+#include "/twist/transforms/spectral.udo"
+#include "/twist/transforms/warping.udo"
+
+#end \ No newline at end of file
diff --git a/site/udo/twist/transforms/amplitude.udo b/site/udo/twist/transforms/amplitude.udo
new file mode 100755
index 0000000..d477b4f
--- /dev/null
+++ b/site/udo/twist/transforms/amplitude.udo
@@ -0,0 +1,179 @@
+#include "/twist/transform_api.udo"
+#include "/frequency_tools.udo"
+
+opcode _twst_tf_normalise_analyse, i, iii
+ ifn, istartsamp, iendsamp xin
+ iscale = 0
+ imaxpos = 0
+ imaxneg = 0
+ while (istartsamp < iendsamp) do
+ ival table istartsamp, ifn
+ if (ival > 0 && ival > imaxpos) then
+ imaxpos = ival
+ elseif (ival < 0 && ival < imaxneg) then
+ imaxneg = ival
+ endif
+ istartsamp += 1
+ od
+ iscale = ((1 / max(abs(imaxneg), abs(imaxpos))))
+ xout iscale
+endop
+
+instr twst_tf_normalise
+ $TWST_TRANSFORM
+ i_, i_, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ aL, aR, ileft, iright twst_getinput
+ istereoequal = twst_parami("equal")
+ kuserscale = twst_param:k("scale")
+
+ if (ileft == 1) then
+ iscalingL _twst_tf_normalise_analyse gitwst_bufferL[gitwst_instanceindex], istartsamp, iendsamp
+ endif
+ if (iright == 1) then
+ iscalingR _twst_tf_normalise_analyse gitwst_bufferR[gitwst_instanceindex], istartsamp, iendsamp
+ endif
+
+ if (istereoequal == 1 && ileft == 1 && iright == 1) then
+ iscaling = min(iscalingL, iscalingR)
+ aL *= iscaling * kuserscale
+ aR *= iscaling * kuserscale
+ elseif (ileft == 1) then
+ aL *= iscalingL * kuserscale
+ elseif (iright == 1) then
+ aR *= iscalingR * kuserscale
+ endif
+
+ outs aL, aR
+endin
+
+instr twst_tf_amplitude
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kgain = twst_param:k("gain")
+ kbalance = twst_param:k("balance")
+ if (ileft == 1) then
+ kb = max:k(1, (1 - kbalance) * 2)
+ aL *= kgain * kb
+ endif
+ if (iright == 1) then
+ kb = max:k(1, kbalance * 2)
+ aR *= kgain * kb
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_strobe
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ krate = twst_param:k("rate")
+ kholdtime = twst_param:k("holdtime")
+ kwindowed = twst_param:k("windowed")
+
+ ktrig metro krate
+ ktrig trighold ktrig, kholdtime
+ kamp = 1 - ktrig
+
+ if (kwindowed == 1) then
+ kenv portk kamp, kholdtime * 0.5
+ else
+ kenv = kamp
+ endif
+
+ if (ileft == 1) then
+ aL *= kenv
+ endif
+ if (iright == 1) then
+ aR *= kenv
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_bitcrush
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kcrush = twst_param:k("crush")
+ if (ileft == 1) then
+ aL bitcrush aL, kcrush
+ elseif (iright == 1) then
+ aR bitcrush aR, kcrush
+ endif
+ outs aL, aR
+endin
+
+
+instr twst_tf_suppress
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kmode = twst_param:k("mode")
+ kthreshold = twst_param:k("threshold")
+
+ if (ileft == 1) then
+ if (kmode == 0) then
+ aL limit aL, -kthreshold, kthreshold
+ elseif (kmode == 1) then
+ aL wrap aL, -kthreshold, kthreshold
+ elseif (kmode == 2) then
+ aL mirror aL, -kthreshold, kthreshold
+ endif
+ endif
+ if (iright == 1) then
+ if (kmode == 0) then
+ aR limit aR, -kthreshold, kthreshold
+ elseif (kmode == 1) then
+ aR wrap aR, -kthreshold, kthreshold
+ elseif (kmode == 2) then
+ aR mirror aR, -kthreshold, kthreshold
+ endif
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_pdclip
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kwidth = twst_param:k("width")
+ kcentre = twst_param:k("centre")
+ ibipolar = twst_parami("bipolar")
+ ifullscale = twst_parami("fullscale")
+
+ if (ileft == 1) then
+ aL pdclip aL, kwidth, kcentre, ibipolar, ifullscale
+ endif
+ if (iright == 1) then
+ aR pdclip aR, kwidth, kcentre, ibipolar, ifullscale
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_distort
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamount = twst_param:k("amount")
+ ihp = twst_parami("halfpower")
+ ifn twst_tf_getwaveform
+
+ if (ileft == 1) then
+ aL distort aL, kamount, ifn, ihp
+ endif
+ if (iright == 1) then
+ aL distort aR, kamount, ifn, ihp
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_distort1
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kpregain = twst_param:k("pregain")
+ kpostgain = twst_param:k("postgain")
+ kshape1 = twst_param:k("shape1")
+ kshape2 = twst_param:k("shape2")
+
+ if (ileft == 1) then
+ aL distort1 aL, kpregain, kpostgain, kshape1, kshape2, 1
+ endif
+ if (iright == 1) then
+ aL distort1 aR, kpregain, kpostgain, kshape1, kshape2, 1
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/cross_processing.udo b/site/udo/twist/transforms/cross_processing.udo
new file mode 100755
index 0000000..203c393
--- /dev/null
+++ b/site/udo/twist/transforms/cross_processing.udo
@@ -0,0 +1,176 @@
+#include "/twist/transform_api.udo"
+#include "/mfcc_match.udo"
+#include "/fftconvolve.udo"
+#include "/chop.udo"
+
+
+instr twst_tf_crossrearrange
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kminsamples = twst_param:k("minsamples")
+ kmaxsamples = twst_param:k("maxsamples")
+ kstereounique = twst_param:k("stereounique")
+ krate = twst_param:k("rate")
+
+ ktrig metro krate
+ async init 0
+
+ if (ktrig == 1) then
+ kfnLo, kfnRo twst_getrandombuffers kstereounique
+ ktablen = tableng:k(kfnLo)
+ klen = min:k(random:k(kminsamples, kmaxsamples), ktablen)
+ kstart = random:k(0, ktablen - klen)
+ async = 1
+ else
+ async = 0
+ endif
+
+ apos, a_ syncphasor 1 / (klen / sr), async
+ areadpos = (apos * klen) + kstart
+
+ if (ileft == 1) then
+ aL tablekt areadpos, kfnLo
+ endif
+ if (iright == 1) then
+ aR tablekt areadpos, kfnRo
+ endif
+
+ outs aL, aR
+endin
+
+instr twst_tf_directconvolve
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifnLo, ifnRo twst_tfi_getcrossfn
+ kamp = twst_parami("amp")
+ isizeratio = twst_parami("sizeratio")
+ if (ileft == 1) then
+ aL dconv aL * kamp, isizeratio * ftlen(ifnLo), ifnLo
+ endif
+ if (iright == 1) then
+ aR dconv aR * kamp, isizeratio * ftlen(ifnRo), ifnRo
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_blockconvolve
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ aLo, aRo, ilefto, irighto twst_getcrossinput
+ ifftsize = twst_parami("fftsize")
+ ioverlap = twst_parami("overlap")
+ ihopsize = ifftsize / ioverlap
+
+ if (ileft == 1 && ilefto == 1) then
+ aL blockconvolve aL, aLo, ifftsize, ihopsize
+ endif
+ if (iright == 1 && irighto == 1) then
+ aR blockconvolve aR, aRo, ifftsize, ihopsize
+ endif
+ outs aL, aR
+endin
+
+/* not in WASM at current
+instr twst_tf_tvconv
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ aLo, aRo, ilefto, irighto twst_getcrossinput
+ kapply1 = twst_param:k("apply1")
+ kapply2 = twst_param:k("apply2")
+ imode = twst_parami("mode")
+ iparts = twst_parami("parts")
+ idftfiltersize = twst_parami("dftfiltersize")
+ ifirfiltersize = twst_parami("firfiltersize")
+
+ if (imode == 1) then
+ iparts = 1
+ ifiltersize = ifirfiltersize
+ else
+ ifiltersize = idftfiltersize
+ endif
+
+ if (ileft == 1 && ilefto == 1) then
+ aL tvconv aL, aLo, kapply1, kapply2, iparts, ifiltersize
+ endif
+
+ if (iright == 1 && irighto == 1) then
+ aR tvconv aR, aRo, kapply1, kapply2, iparts, ifiltersize
+ endif
+ outs aL, aR
+endin
+*/
+
+instr twst_tf_crosssynth
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ fLo, fRo, ilefto, irighto twst_getfcrossinput
+ kamp1 = twst_param:k("amp1")
+ kamp2 = twst_param:k("amp2")
+
+ if (ileft == 1 && ilefto == 1) then
+ foutL pvscross fL, fLo, kamp1, kamp2
+ aL twst_tf_fresynth foutL
+ endif
+
+ if (iright == 1 && irighto == 1) then
+ foutR pvscross fR, fRo, kamp1, kamp2
+ aR twst_tf_fresynth foutR
+ endif
+ outs aL, aR
+endin
+
+
+instr twst_tf_morph
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ fLo, fRo, ilefto, irighto twst_getfcrossinput
+ kamp = twst_param:k("amp")
+ kfreq = twst_param:k("freq")
+
+ if (ileft == 1 && ilefto == 1) then
+ foutL pvsmorph fL, fLo, kamp, kfreq
+ aL twst_tf_fresynth foutL
+ endif
+
+ if (iright == 1 && irighto == 1) then
+ foutR pvsmorph fR, fRo, kamp, kfreq
+ aR twst_tf_fresynth foutR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_mfccmatch
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifnoL, ifnoR twst_tfi_getcrossfn
+ ifftsize = twst_parami("fftsize")
+ ifreqmin = twst_parami("freqmin")
+ ifreqmax = twst_parami("freqmax")
+ ibands = twst_parami("bands")
+ kstretch = twst_param:k("stretch")
+ kauditionreadyL init 0
+ kauditionreadyR init 0
+ ktimek timeinstk
+
+ if (ileft == 1) then
+ kdone, ifnAnalysisL mfm_analysecorpus ktimek, ifnoL, ifreqmin, ifreqmax, ifftsize, ibands, -1, 1
+ if (kdone == 1) then
+ kauditionreadyL = 1
+ aoutL mfm_matchplay aL, ifnoL, ifnAnalysisL, kstretch, ifreqmin, ifreqmax, ifftsize, ibands
+ endif
+ else
+ kauditionreadyL = 1
+ endif
+ if (iright == 1) then
+ kdone, ifnAnalysisR mfm_analysecorpus ktimek, ifnoR, ifreqmin, ifreqmax, ifftsize, ibands, -1, 1
+ if (kdone == 1) then
+ kauditionreadyR = 1
+ aoutR mfm_matchplay aR, ifnoR, ifnAnalysisR, kstretch, ifreqmin, ifreqmax, ifftsize, ibands
+ endif
+ else
+ kauditionreadyR = 1
+ endif
+
+ chnset (kauditionreadyL & kauditionreadyR), "auditionready"
+ outs aoutL, aoutR
+endin
diff --git a/site/udo/twist/transforms/delay.udo b/site/udo/twist/transforms/delay.udo
new file mode 100755
index 0000000..5d59612
--- /dev/null
+++ b/site/udo/twist/transforms/delay.udo
@@ -0,0 +1,72 @@
+#include "/twist/transform_api.udo"
+
+instr twst_tf_vdelay
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kdelay = twst_param:k("delay") * 1000
+ kfeedback = twst_param:k("feedback")
+ adelay = a(kdelay)
+
+ if (ileft == 1) then
+ afbkL init 0
+ aLd vdelay3 aL + afbkL, adelay, 1000
+ afbkL = aLd * kfeedback
+ aL = aLd
+ endif
+ if (iright == 1) then
+ afbkR init 0
+ aRd vdelay3 aR + afbkR, adelay, 1000
+ afbkR = aRd * kfeedback
+ aR = aRd
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_flanger
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kdelay = twst_param:k("delay")
+ kfeedback = twst_param:k("feedback")
+
+ adelay = a(kdelay)
+ if (ileft == 1) then
+ aL flanger aL, adelay, kfeedback
+ endif
+ if (iright == 1) then
+ aR flanger aR, adelay, kfeedback
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_phaser1
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("freq")
+ iord = twst_parami("order")
+ kfeedback = twst_param:k("feedback")
+ if (ileft == 1) then
+ aL phaser1 aL, kfreq, iord, kfeedback
+ endif
+ if (iright == 1) then
+ aR phaser1 aR, kfreq, iord, kfeedback
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_phaser2
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("freq")
+ kq = twst_param:k("q")
+ iord = twst_parami("order")
+ imode = twst_parami("mode")
+ ksep = twst_param:k("sep")
+ kfeedback = twst_param:k("feedback")
+ if (ileft == 1) then
+ aL phaser2 aL, kfreq, kq, iord, imode, ksep, kfeedback
+ endif
+ if (iright == 1) then
+ aR phaser2 aR, kfreq, kq, iord, imode, ksep, kfeedback
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/filter.udo b/site/udo/twist/transforms/filter.udo
new file mode 100755
index 0000000..38fad69
--- /dev/null
+++ b/site/udo/twist/transforms/filter.udo
@@ -0,0 +1,172 @@
+#include "/twist/transform_api.udo"
+
+instr twst_tf_lpf
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("frequency")
+ if (ileft == 1) then
+ aL butterlp aL, kfreq
+ endif
+ if (iright == 1) then
+ aR butterlp aR, kfreq
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_hpf
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("frequency")
+ if (ileft == 1) then
+ aL butterhp aL, kfreq
+ endif
+ if (iright == 1) then
+ aR butterhp aR, kfreq
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_bpf
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("frequency")
+ kbw = twst_param:k("bandwidth")
+ if (ileft == 1) then
+ aL butterbp aL, kfreq, kbw
+ endif
+ if (iright == 1) then
+ aR butterbp aR, kfreq, kbw
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_pareq
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("frequency")
+ kgain = twst_param:k("gain")
+ kq = twst_param:k("q")
+ if (ileft == 1) then
+ aL pareq aL, kfreq, kgain, kq
+ endif
+ if (iright == 1) then
+ aR pareq aR, kfreq, kgain, kq
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_dcblock
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ if (ileft == 1) then
+ aL dcblock2 aL
+ endif
+ if (iright = 1) then
+ aR dcblock2 aR
+ endif
+ outs aL, aR
+endin
+
+/* not in WASM
+{
+ name: "Non-linear filter",
+ instr: "twst_tf_nlfilter",
+ parameters: [
+ {name: "Parameter a", channel: "pa", min: 0, max: 1, dfault: 0.3},
+ {name: "Parameter b", channel: "pb", min: -1, max: 1, dfault: 0.1},
+ {name: "Parameter d", channel: "pd", min: 0, max: 1, dfault: 0.7},
+ {name: "Parameter C", channel: "pC", min: 0, max: 1, dfault: 0.12},
+ {name: "Parameter L", channel: "pL", min: 1, max: 220, dfault: 20},
+ {preset: "applymode"}
+ ]
+},
+
+instr twst_tf_nlfilter
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kpa = twst_param:k("pa")
+ kpb = twst_param:k("pb")
+ kpd = twst_param:k("pd")
+ kpC = twst_param:k("pC")
+ kpL = twst_param:k("pL")
+ nfilt
+ if (ileft == 1) then
+ aL nfilt2 aL, kpa, kpb, kpd, kpC, kpL
+ endif
+ if (iright == 1) then
+ aR nfilt2 aR, kpa, kpb, kpd, kpC, kpL
+ endif
+ outs aL, aR
+endin
+*/
+
+instr twst_tf_mooghpf
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("freq")
+ if (ileft == 1) then
+ aL mvchpf aL, kfreq
+ endif
+ if (iright == 1) then
+ aR mvchpf aR, kfreq
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_mooglpf
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("freq")
+ kres = twst_param:k("resonance")
+ kmode = twst_param:k("mode")
+ if (ileft == 1) then
+ if (kmode == 0) then
+ aL mvclpf1 aL, kfreq, kres
+ elseif (kmode == 1) then
+ aL mvclpf2 aL, kfreq, kres
+ elseif (kmode == 2) then
+ aL mvclpf3 aL, kfreq, kres
+ endif
+ endif
+ if (iright == 1) then
+ if (kmode == 0) then
+ aR mvclpf1 aR, kfreq, kres
+ elseif (kmode == 1) then
+ aR mvclpf2 aR, kfreq, kres
+ elseif (kmode == 2) then
+ aR mvclpf3 aR, kfreq, kres
+ endif
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_waveguide1
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("freq")
+ kcutoff = twst_param:k("cutoff")
+ kfeedback = twst_param:k("feedback")
+ if (ileft == 1) then
+ aL wguide1 aL, kfreq, kcutoff, kfeedback
+ endif
+ if (iright == 1) then
+ aR wguide1 aR, kfreq, kcutoff, kfeedback
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_tbvcf
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("freq")
+ kres = twst_param:k("resonance")
+ kdist = twst_param:k("dist")
+ kasym = twst_param:k("asym")
+ if (ileft == 1) then
+ aL tbvcf aL, kfreq, kres, kdist, kasym
+ endif
+ if (iright == 1) then
+ aR tbvcf aR, kfreq, kres, kdist, kasym
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/frequency.udo b/site/udo/twist/transforms/frequency.udo
new file mode 100755
index 0000000..a56b808
--- /dev/null
+++ b/site/udo/twist/transforms/frequency.udo
@@ -0,0 +1,65 @@
+#include "/twist/transform_api.udo"
+#include "/frequency_tools.udo"
+
+instr twst_tf_freqshift1
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kshift = twst_param:k("shift")
+ if (ileft == 1 && iright == 1) then
+ aL, aR freqshift1 aL, aR, kshift
+ elseif (ileft == 1) then
+ aL freqshift1 aL, kshift
+ elseif (iright == 1) then
+ aR freqshift1 aR, kshift
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_freqshift2
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kshift = twst_param:k("shift")
+ if (ileft == 1 && iright == 1) then
+ aL, aR freqshift2 aL, aR, kshift
+ elseif (ileft == 1) then
+ aL freqshift2 aL, kshift
+ elseif (iright == 1) then
+ aR freqshift2 aR, kshift
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_ringmod
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("frequency")
+ if (ileft == 1 && iright == 1) then
+ aL, aR ringmod1 aL, aR, kfreq
+ elseif (ileft == 1) then
+ aL ringmod1 aL, kfreq
+ elseif (iright == 1) then
+ aR ringmod1 aR, kfreq
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_exciter
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreqlow = twst_tf_freq_custom("low")
+ kfreqhigh = twst_tf_freq_custom("high")
+ kharmonics = twst_param:k("harmonics")
+ kblend = twst_param:k("blend")
+
+ if (kfreqhigh < kfreqlow) then
+ kfreqhigh = kfreqlow
+ endif
+
+ if (ileft == 1) then
+ aL exciter aL, kfreqlow, kfreqhigh, kharmonics, kblend
+ endif
+ if (iright == 1) then
+ aR exciter aR, kfreqlow, kfreqhigh, kharmonics, kblend
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/general.udo b/site/udo/twist/transforms/general.udo
new file mode 100755
index 0000000..b46f570
--- /dev/null
+++ b/site/udo/twist/transforms/general.udo
@@ -0,0 +1,28 @@
+#include "/twist/transform_api.udo"
+
+instr twst_tfi_reverse
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ ifnL, ifnR twst_tfi_getfn
+ ioffline twst_tf_isoffline
+ apos linseg (iendsamp - istartsamp) - 1, ilength, 0
+ if (ileft == 1) then
+ if (ioffline == 1) then
+ ifntempL ftgentmp 0, 0, -ftlen(ifnL), -2, 0
+ tableicopy ifntempL, ifnL
+ aL table3 apos, ifntempL
+ else
+ aL table3 apos, ifnL
+ endif
+ endif
+ if (iright == 1) then
+ if (ioffline == 1) then
+ ifntempR ftgentmp 0, 0, -ftlen(ifnR), -2, 0
+ tableicopy ifntempR, ifnR
+ aR table3 apos, ifntempR
+ else
+ aR table3 apos, ifnR
+ endif
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/generate.udo b/site/udo/twist/transforms/generate.udo
new file mode 100755
index 0000000..e7355a8
--- /dev/null
+++ b/site/udo/twist/transforms/generate.udo
@@ -0,0 +1,363 @@
+#include "/addsub.udo"
+
+instr twst_tf_gensilence
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ a0 init 0
+ if (ileft == 1) then
+ aL = a0
+ endif
+ if (iright == 1) then
+ aR = a0
+ endif
+ outs aL, aR
+endin
+
+
+instr twst_tf_genadditive
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifreq = twst_parami("minfreq")
+ ifreqmax = twst_parami("maxfreq")
+ ifreqstepmult = twst_parami("step")
+ ifreqstepmultrand = twst_parami("steprand")
+ iamp = twst_parami("amp")
+ iampmult = twst_parami("ampmult")
+
+ if (ileft == 1) then
+ aL as_additive ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult
+ endif
+ if (iright == 1) then
+ aR as_additive ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_gentone
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ kfreq twst_tf_freq
+ kfn twst_tf_getwaveformk
+
+ aosc oscilikt kamp, kfreq, kfn
+ if (ileft == 1) then
+ aL = aosc
+ endif
+ if (iright == 1) then
+ aR = aosc
+ endif
+ outs aL, aR
+endin
+
+
+opcode twst_tf_gensimpleadditive, a, kkkkkio
+ kamp, kmultiplier, kfreq, kstepmult, kampprofile, iharmonics, index xin
+ if (kampprofile == 0) then
+ kgain = 1
+ else
+ kgain = (1 - (index / iharmonics))
+ endif
+ aosc oscili (1 / iharmonics) * kgain, (kfreq * kmultiplier)
+ if (index < iharmonics) then
+ arec twst_tf_gensimpleadditive, min:k(kfreq * kstepmult, sr / 2), kstepmult, kampprofile, iharmonics, index + 1
+ aosc += arec
+ endif
+ xout aosc * kamp
+endop
+
+instr twst_tf_gensimpleadditive
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ kfreq twst_tf_freq
+ kmultiplier = twst_param:k("multiplier")
+ kstepmult = twst_param:k("stepmultiplier")
+ kampprofile = twst_param:k("ampprofile")
+ iharmonics = twst_parami("harmonics")
+
+ if (ileft == 1) then
+ aL twst_tf_gensimpleadditive kamp, kmultiplier, kfreq, kstepmult, kampprofile, iharmonics
+ endif
+ if (iright == 1) then
+ aR twst_tf_gensimpleadditive kamp, kmultiplier, kfreq, kstepmult, kampprofile, iharmonics
+ endif
+ outs aL, aR
+endin
+
+opcode twst_tf_genfeedback, a, kkkk
+ kfeedback, kfreq, kpostgain, kbw xin
+ asig init 0
+ asig += noise(0.00001, 0)
+ adel delay asig, 0.0001
+ asig += (adel * kfeedback)
+ asig butterbp asig, kfreq, kbw
+ asig butterbp asig, kfreq, kbw
+ asig tanh ain
+ asig *= kpostgain
+ xout asig
+endop
+
+instr twst_tf_genfeedback
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ kfeedback = twst_param:k("feedback")
+ kfreq twst_tf_freq
+ kpostgain = twst_param:k("postgain")
+ kbw = twst_param:k("bandwidth")
+ if (ileft == 1) then
+ aL twst_tf_genfeedback kfeedback, kfreq, kpostgain
+ aL *= kamp
+ endif
+ if (iright == 1) then
+ aR twst_tf_genfeedback kfeedback, kfreq, kpostgain
+ aR *= kamp
+ endif
+ outs aL, aR
+endin
+
+
+instr twst_tf_genfm
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq twst_tf_freq
+ kamp = twst_param:k("amp")
+ kcarrier = twst_param:k("carrier")
+ kmod = twst_param:k("modulator")
+ kindex = twst_param:k("index")
+ kstereo = twst_param:k("stereovar")
+ ifn twst_tf_getwaveform
+
+ if (ileft == 0 || iright == 0) then
+ kstereo = 1
+ endif
+
+ if (ileft == 1) then
+ aL foscili kamp, kfreq, kcarrier * kstereo, kmod * kstereo, kindex * kstereo, ifn
+ endif
+ if (iright == 1) then
+ kstereo = 1 - (kstereo - 1)
+ aR foscili kamp, kfreq, kcarrier * kstereo, kmod * kstereo, kindex * kstereo, ifn
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_genfmmodel
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq twst_tf_freq
+ ifmtype = twst_parami("fmtype")
+ kamp = twst_param:k("amp")
+ kc1 = twst_param:k("control1")
+ kc2 = twst_param:k("control2")
+ kvibdepth = twst_param:k("vibdepth")
+ kvibrate = twst_param:k("vibrate")
+ ifn1 twst_tf_getwaveform twst_parami("wave1")
+ ifn2 twst_tf_getwaveform twst_parami("wave2")
+ ifn3 twst_tf_getwaveform twst_parami("wave3")
+ ifn4 twst_tf_getwaveform twst_parami("wave4")
+ ifnv twst_tf_getwaveform twst_parami("vibwave")
+ kstereo = twst_param:k("stereovar")
+
+ if (ileft == 0 || iright == 0) then
+ kstereo = 1
+ endif
+
+ if (ileft == 1) then
+ ifmtypei = (ifmtype == 5) ? round(random(0, 4)) : ifmtype
+ if (ifmtypei == 0) then
+ aL fmb3 kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 1) then
+ aL fmbell kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 2) then
+ aL fmpercfl kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 3) then
+ aL fmrhode kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 4) then
+ aL fmwurlie kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ endif
+ endif
+ if (iright == 1) then
+ kstereo = 1 - (kstereo - 1)
+ ifmtypei = (ifmtype == 5) ? round(random(0, 4)) : ifmtype
+
+ if (ifmtypei == 0) then
+ aR fmb3 kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 1) then
+ aR fmbell kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 2) then
+ aR fmpercfl kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 3) then
+ aR fmrhode kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ elseif (ifmtypei == 4) then
+ aR fmwurlie kamp, kfreq, kc1 * kstereo, kc2 * kstereo, kvibdepth * kstereo, kvibrate * kstereo, ifn1, ifn2, ifn3, ifn4, ifnv
+ endif
+ endif
+ outs aL, aR
+endin
+
+
+opcode _twst_tf_genrepluck, a, kkiiikkkkk
+ kamp, kfn, ipluckpoint, ifreq, ipickuppoint, krefl, kexcitemode, kexcitefn, kexcitefreq, kexciteamp xin
+
+ if (kexcitemode == 0) then
+ aexcite noise kexciteamp, 0.5
+ else
+ aexcite oscilikt kexciteamp, kexcitefreq, kexcitefn
+ endif
+ aout repluck ipluckpoint, kamp, ifreq, ipickuppoint, krefl, aexcite
+ aout dcblock2 aout
+ xout aout
+endop
+
+instr twst_tf_genrepluck
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ kfn twst_tf_getwaveformk
+ ipluckpoint = twst_parami("pluckpoint")
+ ifreq twst_tf_freqi
+ ipickuppoint = twst_parami("pickpoint")
+ krefl = twst_param:k("reflection")
+ kexciteamp = twst_param:k("exciteamp")
+ kexcitemode = twst_param:k("excitemode")
+ kexcitefn twst_tf_getwaveformk twst_param:k("excitewave")
+ kexcitefreq = twst_tf_freq_custom("excite")
+
+ if (ileft == 1) then
+ aL _twst_tf_genrepluck kamp, kfn, ipluckpoint, ifreq, ipickuppoint, krefl, kexcitemode, kexcitefn, kexcitefreq, kexciteamp
+ endif
+ if (iright == 1) then
+ aR _twst_tf_genrepluck kamp, kfn, ipluckpoint, ifreq, ipickuppoint, krefl, kexcitemode, kexcitefn, kexcitefreq, kexciteamp
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_genwgbow
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ kfreq twst_tf_freq
+ kpres = twst_param:k("pressure")
+ kpos = twst_param:k("position")
+ kvibf = twst_param:k("vibfreq")
+ kvamp = twst_param:k("vibamp")
+ ifn twst_tf_getwaveform
+
+ if (ileft == 1) then
+ aL wgbow kamp, kfreq, kpres, kpos, kvibf, kvamp, ifn, 20
+ endif
+ if (iright == 1) then
+ aR wgbow kamp, kfreq, kpres, kpos, kvibf, kvamp, ifn, 20
+ endif
+ outs aL, aR
+endin
+
+/* not quite right, doesn't create sound as expected
+instr twst_tf_genwgbowedbar
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ kfreq twst_tf_freq
+ kpres = twst_param:k("pressure")
+ kpos = twst_param:k("position")
+ kgain = twst_param:k("filtergain")
+
+ if (ileft == 1) then
+ aL wgbowedbar kamp, kfreq, kpos, kpres, kgain
+ endif
+ if (iright == 1) then
+ aR wgbowedbar kamp, kfreq, kpos, kpres, kgain
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_genwgbrass
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ kfreq twst_tf_freq
+ ktension = twst_param:k("tension")
+ iattack = twst_parami("attack")
+ kvibf = twst_param:k("vibfreq")
+ kvamp = twst_param:k("vibamp")
+ ifn twst_tf_getwaveform
+
+ if (ileft == 1) then
+ aL wgbrass kamp, kfreq, ktension, iattack, kvibf, kvamp, ifn, 100
+ endif
+ if (iright == 1) then
+ aR wgbrass kamp, kfreq, ktension, iattack, kvibf, kvamp, ifn, 100
+ endif
+ outs aL, aR
+endin
+*/
+
+instr twst_tf_gennoise
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ktype = twst_param:k("type")
+ kamp = twst_param:k("amp")
+ kbeta = twst_param:k("beta")
+
+ if (ileft == 1) then
+ if (ktype == 0) then
+ aL unirand 2
+ aL = aL - 1
+ elseif (ktype == 1) then
+ aL pinker
+ endif
+ aL *= kamp
+ endif
+ if (iright == 1) then
+ if (ktype == 0) then
+ aR unirand 2
+ aR = aR - 1
+ elseif (ktype == 1) then
+ aR pinker
+ endif
+ aR *= kamp
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_genbamboo
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kamp = twst_param:k("amp")
+ inum = twst_parami("number")
+ ifreq1 = twst_parami("r1freq")
+ ifreq2 = twst_parami("r2freq")
+ ifreq3 = twst_parami("r3freq")
+
+ if (ileft == 1) then
+ aL bamboo kamp, 0, inum, 0, 0, ifreq1, ifreq2, ifreq3
+ endif
+ if (iright == 1) then
+ aR bamboo kamp, 0, inum, 0, 0, ifreq1, ifreq2, ifreq3
+ endif
+ outs aL, aR
+endin
+
+/* opcode unavailable in WASM
+{name: "Fractal noise", instr: "twst_tf_genfractalnoise", parameters: [
+ {name: "Type", options: ["White", "Pink", "Brown"], automatable: true, description: "Type of noise"},
+ {preset: "amp"},
+ {preset: "applymode"}
+]}
+
+instr twst_tf_genfractalnoise
+ aL, aR, ileft, iright twst_getinput
+ ktype = twst_param:k("type")
+ kamp = twst_param:k("amp")
+
+ if (ileft == 1) then
+ aL fractalnoise kamp, ktype
+ endif
+ if (iright == 1) then
+ aR fractalnoise kamp, ktype
+ endif
+ outs aL, aR
+endin
+*/
diff --git a/site/udo/twist/transforms/granular.udo b/site/udo/twist/transforms/granular.udo
new file mode 100755
index 0000000..c25a517
--- /dev/null
+++ b/site/udo/twist/transforms/granular.udo
@@ -0,0 +1,138 @@
+#include "/twist/transform_api.udo"
+#include "/fx_autoglitch.udo"
+#include "/sample_level.udo"
+
+instr twst_tfi_rearrange
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ istereounique = twst_parami("stereounique")
+ ichops = twst_parami("chopnumber")
+ ichopmin = twst_parami("chopmin")
+ ichopmax = twst_parami("chopmax")
+ ifnL, ifnR twst_tfi_getfn
+ if (ileft == 1 && iright == 1) then
+ if (istereounique == 1) then
+ aL smp_rearrange ichops, ichopmin, ichopmax, ifnL
+ aR smp_rearrange ichops, ichopmin, ichopmax, ifnR
+ else
+ aL, aR smp_rearrange ichops, ichopmin, ichopmax, ifnL, ifnR
+ endif
+ elseif (ileft == 1) then
+ aL smp_rearrange ichops, ichopmin, ichopmax, ifnL
+ elseif (iright == 1) then
+ aR smp_rearrange ichops, ichopmin, ichopmax, ifnR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tfi_grain
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ kamp = twst_param:k("amp")
+ kpitch = twst_tf_pitchscale()
+ kdensity = twst_param:k("density")
+ kgrainsize = twst_param:k("grainsize")
+ kampvar = twst_param:k("ampvar")
+ kpitchvar = twst_param:k("pitchvar")
+ irandom = twst_parami("randomread")
+ ifnWindow = twst_tf_getwintype()
+
+ ifnL, ifnR twst_tfi_getfn
+
+ kpitch *= (sr / ftlen(ifnL))
+
+ if (ileft == 1) then
+ aL grain kamp, kpitch, kdensity, kampvar, kpitchvar, kgrainsize, ifnL, ifnWindow, 0.5, irandom
+ endif
+ if (iright == 1) then
+ aR grain kamp, kpitch, kdensity, kampvar, kpitchvar, kgrainsize, ifnR, ifnWindow, 0.5, irandom
+ endif
+ outs aL, aR
+endin
+
+instr twst_tfi_syncgrain
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ kamp = twst_param:k("amp")
+ kfreq = twst_param:k("frequency")
+ kpitch = twst_tf_pitchscale()
+ kgrsize = twst_param:k("grainsize")
+ ioverlaps = twst_parami("overlaps")
+ itimescale = twst_parami("timescale")
+ ifnWindow = twst_tf_getwintype()
+
+ iprate = (1 / ioverlaps) * itimescale
+ p3 = ilength * itimescale
+
+ ifnL, ifnR twst_tfi_getfn
+
+ if (ileft == 1) then
+ aL syncgrain kamp, kfreq, kpitch, kgrsize, iprate, ifnL, ifnWindow, ioverlaps
+ endif
+ if (iright == 1) then
+ aR syncgrain kamp, kfreq, kpitch, kgrsize, iprate, ifnR, ifnWindow, ioverlaps
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_autoglitch
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kminratio = twst_param:k("minratio")
+ kchangerate = twst_param:k("changerate")
+ kchangechance = twst_param:k("changechance")
+ kporttime = twst_param:k("porttime")
+ kdo_distortion = twst_param:k("distortion")
+ kdo_ampchange = twst_param:k("ampchange")
+ ibuflens = twst_parami("buflens")
+ kreadmode = twst_param:k("readmode")
+ istereounique = twst_parami("stereounique")
+
+ twst_setlatencyseconds ibuflens
+
+ if (ileft == 1 && iright == 1) then
+ aL, aR fx_autoglitch aL, aR, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, istereounique, kreadmode
+ elseif (ileft == 1) then
+ aL fx_autoglitch aL, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, kreadmode
+ elseif (iright == 1) then
+ aR fx_autoglitch aR, kminratio, kchangerate, kchangechance, kporttime, kdo_distortion, kdo_ampchange, ibuflens, kreadmode
+ endif
+ outs aL, aR
+endin
+
+instr twst_tfi_retriglitch
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ ktriglen = twst_param:k("triglen")
+ kpitchscale = twst_tf_pitchscale()
+ kapplywindowing = twst_param:k("applywindowing")
+ ireadmode = twst_parami("readmode")
+ kwintype twst_tf_getwintypek
+
+ p3 = ilength
+ if (ireadmode == 0) then
+ atime linseg 0, p3, ilength
+ elseif (ireadmode == 1) then
+ ktime = twst_param:k("readtime")
+ twst_tf_setplayposition ktime
+ atime = a(ktime * ilength)
+ elseif (ireadmode == 2) then
+ itimescale = twst_parami("timescale")
+ p3 = ilength * itimescale
+ atime linseg 0, p3, ilength
+ elseif (ireadmode == 3) then
+ atime linseg ilength, p3, 0
+ elseif (ireadmode == 4) then
+ atime init -1
+ endif
+
+ ifnL, ifnR twst_tfi_getfn
+
+ if (ileft == 1) then
+ aL fx_retrigglitch ifnL, ktriglen, atime, kpitchscale, kapplywindowing, kwintype
+ endif
+ if (iright == 1) then
+ aR fx_retrigglitch ifnR, ktriglen, atime, kpitchscale, kapplywindowing, kwintype
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/harmonic.udo b/site/udo/twist/transforms/harmonic.udo
new file mode 100755
index 0000000..717de44
--- /dev/null
+++ b/site/udo/twist/transforms/harmonic.udo
@@ -0,0 +1,142 @@
+#include "/twist/transform_api.udo"
+
+instr twst_tf_resony
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq twst_tf_freq
+ kbw = twst_param:k("bandwidth")
+ inum = twst_parami("num")
+ ksep = twst_param:k("separation")
+ isepmode = twst_parami("sepmode")
+ ibalance = twst_parami("balance")
+
+ if (ileft == 1) then
+ aLr resony aL, kfreq, kbw, inum, ksep, isepmode
+ if (ibalance == 1) then
+ aL balance aLr, aL
+ else
+ aL = aLr
+ endif
+ endif
+ if (iright == 1) then
+ aRr resony aR, kfreq, kbw, inum, ksep, isepmode
+ if (ibalance == 1) then
+ aR balance aRr, aR
+ else
+ aR = aRr
+ endif
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_resonx
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq twst_tf_freq
+ kbw = twst_param:k("bandwidth")
+ inum = twst_parami("num")
+ ibalance = twst_parami("balance")
+
+ if (ileft == 1) then
+ aLr resonx aL, kfreq, kbw, inum
+ if (ibalance == 1) then
+ aL balance aLr, aL
+ else
+ aL = aLr
+ endif
+ endif
+ if (iright == 1) then
+ aRr resonx aR, kfreq, kbw, inum
+ if (ibalance == 1) then
+ aR balance aRr, aR
+ else
+ aR = aRr
+ endif
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_streson
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq twst_tf_freq
+ kfeedback = twst_param:k("feedback")
+ if (ileft == 1) then
+ aL streson aL, kfreq, kfeedback
+ endif
+ if (iright == 1) then
+ aR streson aR, kfreq, kfeedback
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_mvmfilter
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfreq = twst_param:k("freq")
+ kdecay = twst_param:k("decay")
+ kbalance = twst_param:k("balance")
+ if (ileft == 1) then
+ aLf mvmfilter aL, kfreq, kdecay
+ if (kbalance == 1) then
+ aL balance aLf, aL
+ else
+ aL = aLf
+ endif
+ endif
+ if (iright == 1) then
+ aRf mvmfilter aR, kfreq, kdecay
+ if (kbalance == 1) then
+ aL balance aRf, aR
+ else
+ aR = aRf
+ endif
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_harmon
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kestfreq = twst_param:k("estfreq")
+ kmaxvar = twst_param:k("maxvar")
+ kgenfreq1 = twst_param:k("genfreq1")
+ kgenfreq2 = twst_param:k("genfreq2")
+ iminfreq = twst_parami("minfreq")
+ ianalysistime = twst_parami("analysistime")
+ if (ileft == 1) then
+ aL harmon aL, kestfreq, kmaxvar, kgenfreq1, kgenfreq2, 0, iminfreq, ianalysistime
+ endif
+ if (iright == 1) then
+ aR harmon aR, kestfreq, kmaxvar, kgenfreq1, kgenfreq2, 0, iminfreq, ianalysistime
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_formantharmon
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kgenfreq1 = twst_param:k("genfreq1")
+ kgenfreq2 = twst_param:k("genfreq2")
+ kgenfreq3 = twst_param:k("genfreq3")
+ kgenfreq4 = twst_param:k("genfreq4")
+ iminfreq = octcps:i(twst_parami("minfreq"))
+ ipolarity = twst_parami("polarity")
+
+ ipupdate = twst_parami("pupdate")
+ iplow = octcps:i(twst_parami("plowfreq"))
+ iphigh = octcps:i(twst_parami("phighfreq"))
+ ipthresh = dbamp:i(twst_parami("pthresh"))
+ ipfrqs = twst_parami("pfrqs")
+ ipconfirm = twst_parami("pconfirms")
+
+ if (ileft == 1) then
+ koct, kamp pitch aL, ipupdate, iplow, iphigh, ipthresh, ipfrqs, ipconfirm
+ aL harmon4 aL, koct, kgenfreq1, kgenfreq2, kgenfreq3, kgenfreq4, 0, iminfreq, ipolarity
+ endif
+ if (iright == 1) then
+ koct, kamp pitch aR, ipupdate, iplow, iphigh, ipthresh, ipfrqs, ipconfirm
+ aR harmon4 aR, koct, kgenfreq1, kgenfreq2, kgenfreq3, kgenfreq4, 0, iminfreq, ipolarity
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/reverb.udo b/site/udo/twist/transforms/reverb.udo
new file mode 100755
index 0000000..e6f66ec
--- /dev/null
+++ b/site/udo/twist/transforms/reverb.udo
@@ -0,0 +1,80 @@
+#include "/twist/transform_api.udo"
+
+instr twst_tf_reverb1
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ktime = twst_param:k("time")
+ if (ileft == 1) then
+ aL reverb aL, ktime
+ endif
+ if (iright == 1) then
+ aR reverb aR, ktime
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_reverb2
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ktime = twst_param:k("time")
+ khfdamp = twst_param:k("hfdamp")
+ if (ileft == 1) then
+ aL nreverb aL, ktime, khfdamp
+ endif
+ if (iright == 1) then
+ aR nreverb aR, ktime, khfdamp
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_reverb3
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kroomsize = twst_param:k("roomsize")
+ khfdamp = twst_param:k("hfdamp")
+ if (ileft != 1) then
+ aL noise 0.001, 0.5
+ endif
+ if (iright != 1) then
+ aR noise 0.001, 0.5
+ endif
+ aL, aR freeverb aL, aR, kroomsize, khfdamp
+ outs aL, aR
+endin
+
+instr twst_tf_reverb4
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kfeedback = twst_param:k("feedback")
+ khfdamp = twst_param:k("hfdamp") * (sr / 2)
+ ipitchmod = twst_parami("pitchmod")
+ if (ileft != 1) then
+ aL noise 0.001, 0.5
+ endif
+ if (iright != 1) then
+ aR noise 0.001, 0.5
+ endif
+ aL, aR reverbsc aL, aR, kfeedback, khfdamp, sr, ipitchmod
+ outs aL, aR
+endin
+
+
+instr twst_tf_reverb5
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ iwidth = twst_parami("width")
+ idepth = twst_parami("depth")
+ iheight = twst_parami("height")
+ kposx = twst_param:k("posx") * iwidth
+ kposy = twst_param:k("posy") * idepth
+ kposz = twst_param:k("posz") * iheight
+ if (ileft == 1 && iright == 1) then
+ ainput = (aL + aR) * 0.5
+ elseif (ileft != 1) then
+ ainput = aR
+ else
+ ainput = aL
+ endif
+ aL, aR babo ainput, kposx, kposy, kposz, iwidth, idepth, iheight
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/spectral.udo b/site/udo/twist/transforms/spectral.udo
new file mode 100755
index 0000000..b5f2fab
--- /dev/null
+++ b/site/udo/twist/transforms/spectral.udo
@@ -0,0 +1,642 @@
+#include "/twist/transform_api.udo"
+#include "/pvs_tabproc.udo"
+#include "/spectral_transforms.udo"
+#include "/fx_autoglitch.udo"
+#include "/host_platform.udo"
+#include "/addsub.udo"
+
+instr twst_tf_tpvinvert
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kinvertamp = twst_param:k("invertamp")
+ kinvertfreq = twst_param:k("invertfreq")
+
+ if (ileft == 1) then
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_invert kreadyL, itpvdataL, kinvertamp, kinvertfreq
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_invert kreadyR, itpvdataR, kinvertamp, kinvertfreq
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+
+instr twst_tf_tpvbubble
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kchance = twst_param:k("chance")
+ kstereounique = twst_param:k("stereounique")
+
+ if (ileft == 1) then
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_bubble kreadyL, itpvdataL, kchance, kstereounique
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_bubble kreadyR, itpvdataR, kchance, kstereounique
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_tpvsmear
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ imaxframes = twst_parami("maxframes")
+ kframes = twst_param:k("frames")
+ kavgfreqs = twst_param:k("avgfreqs")
+ kincludeoriginal = twst_param:k("includeoriginal")
+
+ if (ileft == 1) then
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_smear kreadyL, itpvdataL, imaxframes, kframes, kavgfreqs, kincludeoriginal
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_smear kreadyR, itpvdataR, imaxframes, kframes, kavgfreqs, kincludeoriginal
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+
+instr twst_tf_tpvscramble
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kstep = twst_param:k("step")
+ kdoamp = twst_param:k("scrambleamp")
+ kdofreq = twst_param:k("scramblefreq")
+
+ if (ileft == 1) then
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_scramble kreadyL, itpvdataL, kstep, kdoamp, kdofreq
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_scramble kreadyR, itpvdataR, kstep, kdoamp, kdofreq
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_tpvthreshold
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kthreshold = twst_param:k("threshold")
+ kdirection = twst_param:k("direction")
+
+ if (ileft == 1) then
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_threshold kreadyL, itpvdataL, kthreshold, kdirection
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_threshold kreadyR, itpvdataR, kthreshold, kdirection
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_tpvfreeze
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kfreeze = twst_param:k("freeze")
+ kfreezeamp = twst_param:k("freezeamp")
+ kfreezefreq = twst_param:k("freezefreq")
+ kcrossfade = twst_param:k("crossfade")
+
+ if (ileft == 1) then
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_freeze1 kreadyL, itpvdataL, kfreeze, kfreezeamp, kfreezefreq, kcrossfade
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_freeze1 kreadyR, itpvdataR, kfreeze, kfreezeamp, kfreezefreq, kcrossfade
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_tpvwrap
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kwrapampbin = twst_param:k("wrapampbin")
+ kwrapfreqbin = twst_param:k("wrapfreqbin")
+
+ if (ileft == 1) then
+ i_, inumbins, i_, i_ pvsinfo fL
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_wrap kreadyL, itpvdataL, kwrapampbin * inumbins, kwrapfreqbin * inumbins
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ i_, inumbins, i_, i_ pvsinfo fR
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_wrap kreadyR, itpvdataR, kwrapampbin * inumbins, kwrapfreqbin * inumbins
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_tpvswap
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kampStart = twst_param:k("ampstart")
+ kampLength = twst_param:k("amplength")
+ kampTarget = twst_param:k("amptarget")
+ kfreqStart = twst_param:k("freqstart")
+ kfreqLength = twst_param:k("freqlength")
+ kfreqTarget = twst_param:k("freqtarget")
+ kwrapmode = twst_param:k("wrapmode")
+
+ if (ileft == 1) then
+ i_, inumbins, i_, i_ pvsinfo fL
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_swap kreadyL, itpvdataL, kampStart * inumbins, kampLength * inumbins, kampTarget * inumbins, kfreqStart * inumbins, kfreqLength * inumbins, kfreqTarget * inumbins, kwrapmode
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ i_, inumbins, i_, i_ pvsinfo fR
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_swap kreadyR, itpvdataR, kampStart * inumbins, kampLength * inumbins, kampTarget * inumbins, kfreqStart * inumbins, kfreqLength * inumbins, kfreqTarget * inumbins, kwrapmode
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_tpvaverage
+ $TWST_TRANSFORM
+ setksmps(64)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kmax = twst_param:k("maxframes")
+ kavgamp = twst_param:k("avgamp")
+ kavgfreq = twst_param:k("avgfreq")
+ krate = twst_param:k("rate")
+
+ ktrig metro krate
+
+ if (ileft == 1) then
+ kreadyL, itpvdataL tpv_anal fL
+ tpv_average kreadyL, itpvdataL, kmax, kavgamp, kavgfreq, ktrig
+ fL tpv_resynth itpvdataL, fL
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ kreadyR, itpvdataR tpv_anal fR
+ tpv_average kreadyR, itpvdataR, kmax, kavgamp, kavgfreq, ktrig
+ fR tpv_resynth itpvdataR, fR
+ aR twst_tf_fresynth fR
+ endif
+ outs aL, aR
+endin
+
+
+/*
+instr twst_tf_stencil
+ $TWST_TRANSFORM
+ Spvx = strcat(host_tempdir(), "twist_stencil.pvx")
+ ifftsize = twst_parami("fftsize")
+ kran init 0
+ ktimek timeinstk
+ if (ktimek == 1) then
+ ifnL, ifnR, istart, ilen, ileft, iright twst_getcrossdata
+ ikcycles = round(ilen / kr)
+ kcount = 1
+ while (kcount < ikcycles) do
+ aLo, aRo, ileft, iright twst_getcrossinput
+ if (ileft == 1 && iright == 1) then
+ ain = (aLo + aRo) * 0.5
+ elseif (ileft == 1) then
+ ain = aLo
+ elseif (iright == 1) then
+ ain = aRo
+ endif
+ f1 pvsanal ain, ifftsize, ifftsize/4, ifftsize, 1
+ pvsfwrite f1, Spvx
+ kcount += 1
+ od
+
+ elseif (ktimek == 1) then
+ schedulek("_twst_tf_stencilplayback", 0, p3, p4, Spvx) ; TODO : won't work offline
+ endif
+
+ aL, aR bus_read "stencilplayback"
+ kreleasing init 0
+ if (kreleasing == 0 && release:k() == 1) then
+ turnoff2 "_twst_tf_stencilplayback", 0, 1
+ kreleasing = 1
+ endif
+
+ outs aL, aR
+endin
+
+instr _twst_tf_stencilplayback
+ $TWST_TRANSFORM
+ Spvx = strget(p5)
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ ifftsize = twst_parami("fftsize")
+ kgain = twst_param:k("gain")
+ klevel = twst_param:k("level")
+
+ ifn ftgentmp 0, 0, (ifftsize * 2) + 1, 43, Spvx, 1
+
+ if (ileft == 1) then
+ fpsL pvstencil fL, kgain, klevel, ifn
+ aL twst_tf_fresynth fpsL
+ endif
+ if (iright == 1) then
+ fpsR pvstencil fR, kgain, klevel, ifn
+ aR twst_tf_fresynth fpsR
+ endif
+ bus_mix("stencilplayback", aL, aR)
+endin
+*/
+
+instr twst_tf_spectralshift
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kfreqincr = twst_param:k("freqincr")
+ kporttime = twst_param:k("porttime")
+ kfreqscale = twst_tf_pitchscale()
+ istart = twst_parami("start")
+ iend = twst_parami("end")
+ ifn = twst_tf_getwaveform()
+
+ if (ileft == 1) then
+ aL spc_shift fL, kfreqincr, istart, iend, kfreqscale, kporttime, ifn
+ endif
+ if (iright == 1) then
+ aR spc_shift fR, kfreqincr, istart, iend, kfreqscale, kporttime, ifn
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_spectraldelay
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kdeltime = twst_param:k("time")
+ kdeladd = twst_param:k("add")
+ kporttime = twst_param:k("porttime")
+ kfreqscale = twst_tf_pitchscale()
+ istart = twst_parami("start")
+ iend = twst_parami("end")
+ ifn = twst_tf_getwaveform()
+
+ if (ileft == 1) then
+ aL spc_delay fL, kdeltime, kdeladd, istart, iend, kfreqscale, kporttime, ifn
+ endif
+ if (iright == 1) then
+ aR spc_delay fR, kdeltime, kdeladd, istart, iend, kfreqscale, kporttime, ifn
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_spectralgate
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kthresh = twst_param:k("threshold")
+ khold = twst_param:k("hold")
+ kporttime = twst_param:k("porttime")
+ kfreqscale = twst_tf_pitchscale()
+ istart = twst_parami("start")
+ iend = twst_parami("end")
+ ifn = twst_tf_getwaveform()
+
+ if (ileft == 1) then
+ aL spc_gate fL, kthresh, khold, istart, iend, kfreqscale, kporttime, ifn
+ endif
+ if (iright == 1) then
+ aR spc_gate fR, kthresh, khold, istart, iend, kfreqscale, kporttime, ifn
+ endif
+ outs aL, aR
+endin
+
+instr twst_tfi_spectralgrain1
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ ifnL, ifnR twst_tfi_getfn
+
+ ireadmode = twst_parami("readmode")
+ if (ireadmode == 0) then
+ ktime linseg 0, p3, ilength
+ elseif (ireadmode == 1) then
+ ktime = twst_param:k("readtime")
+ twst_tf_setplayposition ktime
+ ktime *= ilength
+ elseif (ireadmode == 2) then
+ itimescale = twst_parami("timescale")
+ p3 = ilength * itimescale
+ ktime linseg 0, p3, ilength
+ elseif (ireadmode == 3) then
+ ktime linseg ilength, p3, 0
+ endif
+
+ kgraindur = twst_param:k("graindur")
+ ifftsize = twst_parami("fftsize")
+ ilayers = twst_parami("layers")
+ kporttime = twst_param:k("porttime")
+ kfreqscale = twst_tf_pitchscale()
+ kfreqrand = twst_param:k("freqrand")
+ kdurrand = twst_param:k("durrand")
+ kpitchrand = twst_param:k("pitchrand")
+ istart = twst_parami("start")
+ iend = twst_parami("end")
+ ifn = twst_tf_getwaveform()
+ twst_setlatencysamples(ifftsize)
+
+ if (ileft == 1) then
+ aL spc_grain1 ifnL, ktime, kgraindur, ifftsize, ilayers, istart, iend, kfreqscale, kfreqrand, kdurrand, kpitchrand, kporttime, ifn
+ endif
+ if (iright == 1) then
+ aR spc_grain1 ifnR, ktime, kgraindur, ifftsize, ilayers, istart, iend, kfreqscale, kfreqrand, kdurrand, kpitchrand, kporttime, ifn
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_spectralread
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ ifftsize = twst_parami("fftsize")
+ ktime = twst_param:k("readtime") * ilength
+ twst_tf_setplayposition ktime
+ twst_setlatencysamples(ifftsize)
+
+ ktimek timeinstk
+ ilensamps = iendsamp - istartsamp
+ ikcycles = ilength * kr
+ kcount init 0
+ if (ktimek == 1) then
+ while (kcount < ikcycles) do
+ apos linseg 0, ilength, iendsamp
+ if (ileft == 1 && iright == 1) then
+ aL table3 apos, gitwst_bufferL[gitwst_instanceindex]
+ aR table3 apos, gitwst_bufferR[gitwst_instanceindex]
+ asig = (aL + aR) * 0.5
+ elseif (ileft == 1) then
+ asig table3 apos, gitwst_bufferL[gitwst_instanceindex]
+ elseif (iright == 1) then
+ asig table3 apos, gitwst_bufferR[gitwst_instanceindex]
+ endif
+ fsig pvsanal asig, ifftsize, ifftsize/4, ifftsize, 1
+ ipbuf, k_ pvsbuffer fsig, ilength
+ kcount += 1
+ od
+ else
+ if (ileft == 1) then
+ fL pvsbufread ktime, ipbuf
+ aL twst_tf_fresynth fL
+ endif
+ if (iright == 1) then
+ fR pvsbufread ktime, ipbuf
+ aR twst_tf_fresynth fR
+ endif
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_centroidoscillator
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kporttime = twst_param:k("porttime")
+ kfreqscale = twst_tf_pitchscale()
+ kfn twst_tf_getwaveformk
+
+ if (ileft == 1) then
+ kcent pvscent fL
+ kfreq portk kcent * kfreqscale, kporttime
+ aL oscilikt 1, kfreq, kfn
+ endif
+ if (iright == 1) then
+ kcent pvscent fR
+ kfreq portk kcent * kfreqscale, kporttime
+ aR oscilikt 1, kfreq, kfn
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_binoscillator
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kporttime = twst_param:k("porttime")
+ kbin = twst_param:k("bin")
+ kfreqscale = twst_tf_pitchscale()
+ kfn twst_tf_getwaveformk
+
+ if (ileft == 1) then
+ i_, inumbins, i_, i_ pvsinfo fL
+ kamp, kfreqbase pvsbin fL, round:k(kbin * inumbins)
+ kfreq portk kfreqbase * kfreqscale, kporttime
+ aL oscilikt 1, kfreq, kfn
+ endif
+ if (iright == 1) then
+ i_, inumbins, i_, i_ pvsinfo fR
+ kamp, kfreqbase pvsbin fL, round:k(kbin * inumbins)
+ kfreq portk kfreqbase * kfreqscale, kporttime
+ aR oscilikt 1, kfreq, kfn
+ endif
+ outs aL, aR
+endin
+
+opcode _twst_tf_partialreconstruction, a, aikkkikkki
+ ain, ifftsize, kthresh, kminpoints, kmaxgap, imaxtracks, kampscale, kfreqscale, kmaxtracks, ifn xin
+ ffreq, fphase pvsifd ain, ifftsize, ifftsize/4, 1
+ ftrks partials ffreq, fphase, kthresh, kminpoints, kmaxgap, imaxtracks
+ aout resyn ftrks, kampscale, kfreqscale, kmaxtracks, ifn
+ aout dcblock aout
+ xout aout
+endop
+
+instr twst_tf_partialreconstruction
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifftsize = twst_parami("fftsize")
+ kthresh = twst_param:k("threshold")
+ kminpoints = twst_param:k("minpoints")
+ kmaxgap = twst_param:k("maxgap")
+ imaxtracks = round(twst_parami("anlmaxtracks") * ifftsize * 0.5)
+ kampscale = twst_param:k("ampscale")
+ kfreqscale = twst_tf_pitchscale()
+ kmaxtracks = round:k(twst_param:k("resmaxtracks") * ifftsize * 0.5)
+ ifn = twst_tf_getwaveform()
+ twst_setlatencysamples(ifftsize)
+
+ if (ileft == 1) then
+ aL _twst_tf_partialreconstruction aL, ifftsize, kthresh, kminpoints, kmaxgap, imaxtracks, kampscale, kfreqscale, kmaxtracks, ifn
+ endif
+ if (iright == 1) then
+ aR _twst_tf_partialreconstruction aR, ifftsize, kthresh, kminpoints, kmaxgap, imaxtracks, kampscale, kfreqscale, kmaxtracks, ifn
+ endif
+ outs aL, aR
+endin
+
+opcode _twst_tf_residual, a, aii
+ ain, ifftsize, ihsize xin
+ ffr, fphs pvsifd ain, ifftsize, ihsize, 1
+ ftrk partials ffr, fphs, 0, 1, 3, 500
+ aout sinsyn ftrk, 2, 500, gifnSine
+ asd delayr ifftsize / sr
+ asig deltapn ifftsize - ihsize
+ delayw ain
+ xout asig - aout
+endop
+
+instr twst_tf_residual
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifftsize = twst_parami("fftsize")
+ twst_setlatencysamples(ifftsize)
+ ihsize = ifftsize / 4
+ if (ileft == 1) then
+ aL _twst_tf_residual aL, ifftsize, ihsize
+ endif
+ if (iright == 1) then
+ aR _twst_tf_residual aR, ifftsize, ihsize
+ endif
+ outs aL, aR
+endin
+
+/* not in WASM
+instr twst_tf_trace
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ inumbins = twst_parami("fftsize") / 2
+ kbins = twst_param("bins") * inumbins
+
+ if (ileft == 1) then
+ foutL pvstrace fL, kbins
+ aL twst_tf_fresynth foutL
+ endif
+ if (iright == 1) then
+ foutR pvstrace fR, kbins
+ aR twst_tf_fresynth foutR
+ endif
+ outs aL, aR
+endin
+*/
+
+instr twst_tf_isolator
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kbin = twst_param("bin")
+ kattenuation = twst_param("attenuation")
+ kgain = twst_param("accentuation")
+
+ if (ileft == 1) then
+ foutL pvsarp fL, kbin, kattenuation, kgain
+ aL twst_tf_fresynth foutL
+ endif
+ if (iright == 1) then
+ foutR pvsarp fR, kbin, kattenuation, kgain
+ aR twst_tf_fresynth foutR
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_blur
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ ktime = twst_param:k("time")
+ if (ileft == 1) then
+ fL1 pvsblur fL, ktime, 3
+ aL twst_tf_fresynth fL1
+ endif
+ if (iright == 1) then
+ fR1 pvsblur fR, ktime, 3
+ aR twst_tf_fresynth fR1
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_spectralautoglitch
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kchangerate = twst_param:k("changerate")
+ kchangechance = twst_param:k("changechance")
+ kporttime = twst_param:k("porttime")
+ kdo_pitchalter = twst_param:k("pitchalter")
+ ifftsize = twst_parami("fftsize")
+
+ twst_setlatencysamples ifftsize
+
+ if (ileft == 1) then
+ aL fx_spectralautoglitch aL, kchangerate, kchangechance, kdo_pitchalter, kporttime, ifftsize
+ endif
+ if (iright == 1) then
+ aR fx_spectralautoglitch aR, kchangerate, kchangechance, kdo_pitchalter, kporttime, ifftsize
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_subtractive
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifreq = twst_parami("minfreq")
+ ifreqmax = twst_parami("maxfreq")
+ ifreqstepmult = twst_parami("step")
+ ifreqstepmultrand = twst_parami("steprand")
+ iamp = twst_parami("amp")
+ iampmult = twst_parami("ampmult")
+
+ if (ileft == 1) then
+ aL as_subtractive aL, ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult
+ endif
+ if (iright == 1) then
+ aR as_subtractive aR, ifreq, ifreqmax, ifreqstepmult, ifreqstepmultrand, iamp, iampmult
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_phasemash
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifftsize = twst_parami("fftsize")
+ kphasemode = twst_param:k("phasereplace")
+ kphasevalue = twst_param:k("phasevalue")
+
+ if (kphasemode == 0) then
+ kphasevalue *= 7
+ else
+ kphasevalue = ((kphasevalue * 2) - 1) * 3.141
+ endif
+
+ if (ileft == 1) then
+ aL spc_phasemash aL, kphasemode, kphasevalue, ifftsize
+ endif
+ if (iright == 1) then
+ aR spc_phasemash aR, kphasemode, kphasevalue, ifftsize
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/transforms/warping.udo b/site/udo/twist/transforms/warping.udo
new file mode 100755
index 0000000..3eb4e6e
--- /dev/null
+++ b/site/udo/twist/transforms/warping.udo
@@ -0,0 +1,210 @@
+#include "/twist/transform_api.udo"
+#include "/sample_level.udo"
+
+instr twst_tf_smphold
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kratio = twst_param:k("ratio")
+ if (ileft == 1) then
+ aL smp_hold aL, kratio
+ endif
+ if (iright == 1) then
+ aR smp_hold aR, kratio
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_fftpitchscale
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kscale = twst_tf_pitchscale()
+ kformant = twst_param:k("formants")
+ kcoefs = twst_param:k("formantcoefs")
+
+ if (ileft == 1) then
+ fL1 pvscale fL, kscale, kformant, 1, kcoefs
+ aL twst_tf_fresynth fL1
+ endif
+ if (iright == 1) then
+ fR1 pvscale fR, kscale, kformant, 1, kcoefs
+ aR twst_tf_fresynth fR1
+ endif
+ outs aL, aR
+endin
+
+opcode _twst_tf_autotune, f, fkkk
+ fsig, kthreshold, kformant, kcoefs xin
+ kfreq, kamp pvspitch fsig, kthreshold
+ if (kfreq > 20) then
+ knote ftom kfreq
+ kscale = cpsmidinn:k(int:k(knote)) / kfreq
+ fsigo pvscale fsig, kscale, kformant, 1, kcoefs
+ else
+ fsigo = fsig
+ endif
+ xout fsigo
+endop
+
+
+instr twst_tf_autotune
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kthreshold = twst_param:k("threshold")
+ kformant = twst_param:k("formants")
+ kcoefs = twst_param:k("formantcoefs")
+
+ if (ileft == 1) then
+ fL1 _twst_tf_autotune fL, kthreshold, kformant, kcoefs
+ aL twst_tf_fresynth fL1
+ endif
+ if (iright == 1) then
+ fR1 _twst_tf_autotune fR, kthreshold, kformant, kcoefs
+ aR twst_tf_fresynth fR1
+ endif
+ outs aL, aR
+endin
+
+
+instr twst_tf_hilbertpitchscale
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ ifftsize = twst_parami("fftsize")
+ kscale = twst_tf_pitchscale()
+ twst_setlatencysamples(ifftsize)
+
+ if (ileft == 1) then
+ ahL1, ahL2 hilbert2 aL, ifftsize, ifftsize / 4
+ amL, afmL fmanal ahL1, ahL2
+ aL oscil amL, afmL * kscale
+ endif
+ if (iright == 1) then
+ ahR1, ahR2 hilbert2 aR, ifftsize, ifftsize / 4
+ amR, afmR fmanal ahR1, ahR2
+ aR oscil amR, afmR * kscale
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_waveset
+ $TWST_TRANSFORM
+ aL, aR, ileft, iright twst_getinput
+ kreps = twst_param:k("reps")
+ if (ileft == 1) then
+ aL waveset aL, kreps
+ endif
+ if (iright == 1) then
+ aR waveset aR, kreps
+ endif
+ outs aL, aR
+endin
+
+instr twst_tf_freeze
+ $TWST_TRANSFORM
+ fL, fR, aL, aR, ileft, iright twst_getfinput
+ kfreezeamp = twst_param:k("freezeamp")
+ kfreezefreq = twst_param:k("freezefreq")
+ if (ileft == 1) then
+ fL1 pvsfreeze fL, kfreezeamp, kfreezefreq
+ aL twst_tf_fresynth fL1
+ endif
+ if (iright == 1) then
+ fR1 pvsfreeze fR, kfreezeamp, kfreezefreq
+ aR twst_tf_fresynth fR1
+ endif
+ outs aL, aR
+endin
+
+instr twst_tfi_sndwarp
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ ireadmode = twst_parami("readmode")
+ kpitchscale = twst_tf_pitchscale()
+ iwinsize = twst_parami("winsize")
+ irandwin = twst_parami("randwin")
+ ioverlap = twst_parami("overlap")
+ ifnWindow = twst_tf_getwintype()
+
+ p3 = ilength
+ if (ireadmode == 0) then
+ atime linseg 0, p3, ilength
+ elseif (ireadmode == 1) then
+ ktime = twst_param:k("readtime")
+ twst_tf_setplayposition ktime
+ atime = a(ktime * ilength)
+ elseif (ireadmode == 2) then
+ itimescale = twst_parami("timescale")
+ p3 = ilength * itimescale
+ atime linseg 0, p3, ilength
+ elseif (ireadmode == 3) then
+ atime linseg ilength, p3, 0
+ endif
+
+ ifnL, ifnR twst_tfi_getfn
+
+ kpitchscale *= ftsr(ifnL) / sr
+ apitchscale = a(kpitchscale)
+ if (ileft == 1) then
+ aL sndwarp 1, atime, apitchscale, ifnL, 0, iwinsize, irandwin, ioverlap, ifnWindow, 1
+ endif
+ if (iright == 1) then
+ aR sndwarp 1, atime, apitchscale, ifnR, 0, iwinsize, irandwin, ioverlap, ifnWindow, 1
+ endif
+ outs aL, aR
+endin
+
+instr twst_tfi_mincer
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+ ifftsize = twst_parami("fftsize")
+ kpitchscale = twst_tf_pitchscale()
+ klock = twst_param:k("phaselock")
+ ireadmode = twst_parami("readmode")
+ idecimation = twst_parami("decimation")
+
+ p3 = ilength
+ if (ireadmode == 0) then
+ atime linseg 0, p3, ilength
+ elseif (ireadmode == 1) then
+ ktime = twst_param:k("readtime")
+ twst_tf_setplayposition ktime
+ atime = a(ktime * ilength)
+ elseif (ireadmode == 2) then
+ itimescale = twst_parami("timescale")
+ p3 = ilength * itimescale
+ atime linseg 0, p3, ilength
+ elseif (ireadmode == 3) then
+ atime linseg ilength, p3, 0
+ endif
+
+ twst_setlatencysamples(ifftsize)
+ ifnL, ifnR twst_tfi_getfn
+ if (ileft == 1) then
+ aL mincer atime, 1, kpitchscale, ifnL, klock, ifftsize, idecimation
+ endif
+ if (iright == 1) then
+ aR mincer atime, 1, kpitchscale, ifnR, klock, ifftsize, idecimation
+ endif
+ outs aL, aR
+endin
+
+instr twst_tfi_paulstretch
+ $TWST_TRANSFORM
+ ileft, iright, istartsamp, iendsamp, idocut, ilength twst_tf_getstate
+
+ istretch = twst_parami("stretch")
+ iwinsize = twst_parami("winsize")
+ iduration = ilength * istretch
+ p3 = iduration
+
+ twst_setlatencyseconds iwinsize
+
+ ifnL, ifnR twst_tfi_getfn
+
+ if (ileft == 1) then
+ aL paulstretch istretch, iwinsize, ifnL
+ endif
+ if (iright == 1) then
+ aR paulstretch istretch, iwinsize, ifnR
+ endif
+ outs aL, aR
+endin
diff --git a/site/udo/twist/twist.udo b/site/udo/twist/twist.udo
new file mode 100755
index 0000000..34fa7cb
--- /dev/null
+++ b/site/udo/twist/twist.udo
@@ -0,0 +1,1304 @@
+#ifndef UDO_TWIST
+#define UDO_TWIST ##
+/*
+ Twist
+ Waveform editor and transformer
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+#include "/table_tools.udo"
+#include "/host_platform.udo"
+#include "/wavetables.udo"
+#include "/interop.udo"
+#include "/bussing.udo"
+#include "/chop.udo"
+#include "/transient_detect.udo"
+
+#ifdef TWST_FAILONLAG
+#include "/lagdetect.udo"
+#end
+
+gitwst_userstopped = 0
+imaxinstances = 16
+gitwst_instanceindex = 0
+gitwst_channels[] init imaxinstances
+gitwst_duration[] init imaxinstances
+gitwst_bufferL[] init imaxinstances
+gitwst_bufferR[] init imaxinstances
+
+gitwst_copyBufferL = 0
+gitwst_copyBufferR = 0
+
+gitwst_tf_state[] init 7
+
+#include "/twist/checkpointing.udo"
+
+opcode twst_clearbuffers, 0, jj
+ ibufferL, ibufferR xin
+ ibufferL = (ibufferL == -1) ? gitwst_bufferL[gitwst_instanceindex] : ibufferL
+ ibufferR = (ibufferR == -1) ? gitwst_bufferR[gitwst_instanceindex] : ibufferR
+
+ if (ibufferL > 0 && ftexists(ibufferL) == 1) then
+ ftfree ibufferL, 1
+ endif
+ if (ibufferR > 0 && ftexists(ibufferR) == 1) then
+ ftfree ibufferR, 1
+ endif
+endop
+
+opcode twst_samplestoratio, ii, iij
+ istart, iend, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ itotallen = ftlen(gitwst_bufferL[instanceindex])
+ if (istart > 1 && iend > 1) then
+ istart /= itotallen
+ iend /= itotallen
+ endif
+ xout istart, iend
+endop
+
+opcode twst_getstartend, iii, iij
+ istart, iend, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ itotallen = ftlen(gitwst_bufferL[instanceindex])
+ if (istart <= 1 && iend <= 1) then
+ istart *= itotallen
+ iend *= itotallen
+ ilen = int(iend - istart)
+ istart = int(istart)
+ iend = int(iend)
+ else
+ istart = max(0, istart)
+ iend = min(itotallen, iend)
+ ilen = iend - istart
+ endif
+ xout istart, ilen, iend
+endop
+
+opcode twst_getstartend, ii, iij
+ istart, iend, instanceindex xin
+ istart, ilen, iend twst_getstartend istart, iend, instanceindex
+ xout istart, ilen
+endop
+
+#include "/twist/transform_api.udo"
+
+opcode twst_createoverviewsextra, S, iSjjjp
+ icbid, Sextra, iselstart, iselend, instanceindex, istatus xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+
+ iduration = ftlen(gitwst_bufferL[instanceindex]) / ftsr(gitwst_bufferL[instanceindex])
+ if (qnan(iduration) == 1) then
+ iduration = 1
+ endif
+ gitwst_duration[instanceindex] = iduration
+ Sresponse = sprintf("{\"cbid\":%d,\"status\":%d,\"waveL\":%d,\"duration\":%f,\"undolevel\":%d", icbid, istatus, gitwst_bufferL[instanceindex], iduration, gitwst_checkpointstate[instanceindex])
+ if (gitwst_channels[instanceindex] == 2) then
+ Sresponse = strcat(Sresponse, sprintf(",\"waveR\":%d", gitwst_bufferR[instanceindex]))
+ endif
+
+ if (iselstart != -1 && iselend != -1) then
+ iselstart, iselend twst_samplestoratio iselstart, iselend, instanceindex
+ Sresponse = strcat(Sresponse, sprintf(",\"selstart\":%f,\"selend\":%f", iselstart, iselend))
+ endif
+
+ if (strcmp(Sextra, "") != 0) then
+ Sresponse = strcat(Sresponse, strcat(",", Sextra))
+ endif
+
+ xout strcat(Sresponse, "}")
+endop
+
+opcode twst_createoverviews, S, ijjp
+ icbid, iselstart, iselend, istatus xin
+ Sresponse twst_createoverviewsextra icbid, "", iselstart, iselend, -1, istatus
+ xout Sresponse
+endop
+
+
+opcode twst_failresponse, S, ij
+ icbid, istatus xin
+ xout sprintf("{\"cbid\":%d,\"status\":%d}", icbid, istatus)
+endop
+
+instr twst_successresponse
+ icbid = p4
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1}", icbid))
+ turnoff
+endin
+
+instr twst_checkalive
+ icbid = p4
+ io_sendstring("callback", sprintf("{\"cbid\": %d}", icbid))
+ turnoff
+endin
+
+opcode twst_setapplymode, aa, iaaaa
+ iuniqueid, aLorig, aRorig, aLnew, aRnew xin
+ kapplymode chnget sprintf("applymode%d", iuniqueid)
+ kdry chnget sprintf("applymodedry%d", iuniqueid)
+ kwet chnget sprintf("applymodewet%d", iuniqueid)
+ khpf chnget sprintf("applymodehpf%d", iuniqueid)
+ klpf chnget sprintf("applymodelpf%d", iuniqueid)
+
+ ; applymode 0 is replace
+ if (kapplymode == 1) then ; mix
+ aLnew = (aLorig * kdry) + (aLnew * kwet)
+ aRnew = (aRorig * kdry) + (aRnew * kwet)
+ elseif (kapplymode == 2) then ; modulate
+ aLnew = aLorig * aLnew
+ aRnew = aRorig * aRnew
+ elseif (kapplymode == 3) then ; demodulate
+ aLnew = tanh(aLorig / aLnew)
+ aRnew = tanh(aRorig / aRnew)
+ elseif (kapplymode == 4) then ; filter mix
+ kbw = klpf - khpf
+ kfreq = khpf + (kbw / 2)
+ aLbp butterbp aLnew, kfreq, kbw
+ aLbr butterbr aLorig, kfreq, kbw
+ aRbp butterbp aRnew, kfreq, kbw
+ aRbr butterbr aRorig, kfreq, kbw
+ aLnew = aLbp + aLbr
+ aRnew = aRbp + aRbr
+ endif
+ xout aLnew, aRnew
+endop
+
+
+opcode twst_playback, iaaiiiii, iiiSiioooOPo
+ istart, iend, ichannel, Stransform, ioffline, iautomating, icrossfadein, icrossfadeout, iuniqueid xin
+ itransforming = strcmp(Stransform, "")
+ if (istart == iend) then
+ istart = istart
+ iend = 1
+ endif
+
+ istatus = 1
+ itfi = (strcmp(strsub(Stransform, 5, 8), "tfi") == 0) ? 1 : 0
+ ibuflen = ftlen(gitwst_bufferL[gitwst_instanceindex])
+ istartsamp = ibuflen * istart
+ iendsamp = ibuflen * iend
+ iendsampw = iendsamp
+ ilensamp = iendsamp - istartsamp
+ iextracycles = 0
+ idurations = ilensamp / ftsr(gitwst_bufferL[gitwst_instanceindex])
+ p3 = idurations
+ icrossfadeins = idurations * icrossfadein
+ icrossfadeouts = idurations * icrossfadeout
+
+ ileft = 1
+ iright = 1
+ if (ichannel == 0 || gitwst_channels[gitwst_instanceindex] == 1) then
+ iright = 0
+ elseif (ichannel == 1 && gitwst_channels[gitwst_instanceindex] == 2) then
+ ileft = 0
+ endif
+
+ if (ioffline == 1) then
+ idelaysamples = gitwst_tf_state[5]
+ if (idelaysamples > 0) then
+ idelays = idelaysamples / sr
+ iextracycles = round(kr * idelays)
+ else
+ idelays = 0
+ endif
+ else
+ idelaysamples = 0
+ iextracycles = 0
+ idelays = 0
+ endif
+
+ gitwst_tf_state[0] = ileft
+ gitwst_tf_state[1] = iright
+ gitwst_tf_state[2] = istartsamp
+ gitwst_tf_state[3] = iendsamp
+ gitwst_tf_state[5] = 0 ; latency for fft processing etc
+ gitwst_tf_state[6] = ioffline
+
+ aL init 0
+ aR init 0
+ ifnL = gitwst_bufferL[gitwst_instanceindex]
+ ifnR = gitwst_bufferR[gitwst_instanceindex]
+ ifnTargetL = gitwst_bufferL[gitwst_instanceindex]
+ ifnTargetR = gitwst_bufferR[gitwst_instanceindex]
+
+ if (itfi == 1) then
+ idocut = 0
+ if (istart != 0 && iend != 1) then
+ idocut = 1
+ endif
+
+ ; only 4 pfields in subinstr allowed
+ gitwst_tf_state[4] = idocut
+ aoutL, aoutR subinstr Stransform, iuniqueid
+ ; aoutL, aoutR twst_setapplymode iuniqueid, aL, aR, aoutL, aoutR ; no applymode for tfi
+ if (iautomating == 1) then
+ a_ subinstr "twst_automaterun"
+ endif
+
+ if (ioffline == 1) then
+ itargetlen = int(p3 * sr)
+ iextracycles += (p3 * kr)
+ iendsampw = istartsamp + itargetlen
+
+ if (p3 != idurations) then
+ inewlen = (ftlen(gitwst_bufferL[gitwst_instanceindex]) - ilensamp) + itargetlen
+ if (inewlen >= gihost_max32bitftlen) then ; limitation with WASM Csound build at the moment
+ istatus = -2
+ goto complete
+ endif
+
+ ifnTargetL ftgen 0, 0, -inewlen, -2, 0
+ ifnTargetR ftgen 0, 0, -inewlen, -2, 0
+
+ if (istartsamp != 0) then ; copy part before commit
+ indexr = 0
+ indexw = 0
+ while (indexw < istartsamp - 1) do
+ if (ileft == 1) then
+ tabw_i tab_i(indexr, gitwst_bufferL[gitwst_instanceindex]), indexw, ifnTargetL
+ endif
+ if (iright == 1) then
+ tabw_i tab_i(indexr, gitwst_bufferR[gitwst_instanceindex]), indexw, ifnTargetR
+ endif
+ indexr += 1
+ indexw += 1
+ od
+
+/* aliasing from ftslice???
+ if (ileft == 1) then
+ ftslicei gitwst_bufferL[gitwst_instanceindex], ifnTargetL, 0, istartsamp, 1
+ endif
+ if (iright == 1) then
+ ftslicei gitwst_bufferR[gitwst_instanceindex], ifnTargetR, 0, istartsamp, 1
+ endif
+*/
+ endif
+
+ if (iendsampw < ftlen(ifnTargetL)) then ; copy part after commit
+ indexr = iendsamp + 1
+ indexw = iendsampw + 1
+ while (indexw < inewlen - 1) do
+ if (ileft == 1) then
+ tabw_i tab_i(indexr, gitwst_bufferL[gitwst_instanceindex]), indexw, ifnTargetL
+ endif
+ if (iright == 1) then
+ tabw_i tab_i(indexr, gitwst_bufferR[gitwst_instanceindex]), indexw, ifnTargetR
+ endif
+ indexr += 1
+ indexw += 1
+ od
+ endif
+ else
+ inewlen = ibuflen
+ endif
+ endif
+ idurations = p3
+
+ else ; is not tfi
+ inewlen = ibuflen ;ilensamp
+ aposr linseg istartsamp, idurations, iendsampw ;iendsamp
+
+ if (ileft == 1) then
+ aL table3 aposr, ifnL
+ endif
+ if (iright == 1) then
+ aR table3 aposr, ifnR
+ endif
+
+ if (itransforming == 1) then
+ if (iautomating == 1) then
+ a_ subinstr "twst_automaterun"
+ endif
+ chnset aL, "twstfeedL"
+ chnset aR, "twstfeedR"
+ aoutL, aoutR subinstr Stransform, iuniqueid
+ aoutL, aoutR twst_setapplymode iuniqueid, aL, aR, aoutL, aoutR
+ else
+ aoutL = aL
+ aoutR = aR
+ endif
+ endif ; end not tfi
+
+ if (ioffline == 0) then
+ chnset -1, "twst_tfplayposratio"
+ kplayposratio chnget "twst_tfplayposratio"
+ if (kplayposratio >= 0) then
+ chnset (kplayposratio * (iend - istart)) + istart, "twst_playposratio"
+ else
+ ktimeenv linseg istart, p3, iend
+ chnset ktimeenv, "twst_playposratio"
+ endif
+ endif
+
+
+ if ((icrossfadein != 0 || icrossfadeout != 0) && (icrossfadein + icrossfadeout) < 1) then
+ icrossfadeins = max(0.0000001, icrossfadeins) ; god damn times can't seem to be 0 in linseg
+ icrossfademids = idurations - (icrossfadeins + icrossfadeouts)
+ icrossfademids = max(0.0000001, icrossfademids)
+ icrossfadeouts = max(0.0000001, icrossfadeouts)
+
+ if (itfi == 1) then
+ imidsamp1 = istartsamp + (icrossfadeins * sr)
+ imidsamp2 = iendsamp - (icrossfadeouts * sr)
+ if (idelays != 0) then
+ aposr linseg istartsamp, idelays, istartsamp, icrossfadeins, imidsamp1, icrossfademids, imidsamp2, icrossfadeouts, iendsamp
+ else
+ aposr linseg istartsamp, icrossfadeins, imidsamp1, icrossfademids, imidsamp2, icrossfadeouts, iendsamp
+ endif
+ if (ileft == 1) then
+ aL table3 aposr, ifnL
+ endif
+ if (iright = 1) then
+ aR table3 aposr, ifnR
+ endif
+
+ endif
+
+ if (idelays != 0) then
+ acrossfade linseg 0, idelays, 0, icrossfadeins, 1, icrossfademids, 1, icrossfadeouts, 0
+ else
+ acrossfade linseg 0, icrossfadeins, 1, icrossfademids, 1, icrossfadeouts, 0
+ endif
+
+ if (ileft == 1) then
+ aoutL = (aL * (1 - acrossfade)) + (aoutL * acrossfade)
+ endif
+ if (iright == 1) then
+ aoutR = (aR * (1 - acrossfade)) + (aoutR * acrossfade)
+ endif
+ endif
+
+ if (chnget:i("twst_dcblockoutputs") == 1) then
+ aoutL dcblock2 aoutL
+ aoutR dcblock2 aoutR
+ endif
+
+ if (chnget:i("twst_tanhoutputs") == 1) then
+ aoutL tanh aoutL
+ aoutR tanh aoutR
+ endif
+
+ anull init 0
+ if (ioffline == 1) then
+ if (itfi == 1) then
+ iendsampw = (idurations * sr) + istartsamp
+ endif
+
+ if (idelays != 0) then
+ aposw linseg istartsamp, idelays, istartsamp, idurations, iendsampw
+ else
+ aposw linseg istartsamp, idurations, iendsampw
+ endif
+
+ if (ileft == 1) then
+ tablew aoutL, aposw, ifnTargetL
+ endif
+ if (iright == 1) then
+ tablew aoutR, aposw, ifnTargetR
+ endif
+ amonitorL = anull
+ amonitorR = anull
+ else
+ amonitorL = aoutL
+ if (gitwst_channels[gitwst_instanceindex] == 1) then
+ amonitorR = amonitorL
+ else
+ amonitorR = aoutR
+ endif
+ endif
+
+ inewlen = ftlen((ifnTargetL > 0) ? ifnTargetL : ifnTargetR)
+
+complete:
+ xout istatus, amonitorL, amonitorR, ifnTargetL, ifnTargetR, iextracycles, istartsamp / inewlen, iendsampw / inewlen
+endop
+
+
+
+instr twst_setinstance
+ icbid = p4
+ gitwst_instanceindex = p5
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1}", icbid))
+ turnoff
+endin
+
+opcode twst_copy, k, opjj
+ istart, iend, ichannel, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+
+ istart, ilen, iend twst_getstartend istart, iend
+
+ twst_clearbuffers(gitwst_copyBufferL, gitwst_copyBufferR)
+ gitwst_copyBufferL = -1
+ gitwst_copyBufferR = -1
+
+ if (gitwst_channels[instanceindex] == 2 && ichannel == -1) then
+ kdone, gitwst_copyBufferL, gitwst_copyBufferR chop_copyk istart, ilen, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ elseif (ichannel == 1) then
+ kdone, gitwst_copyBufferR, i_ chop_copyk istart, ilen, gitwst_bufferR[instanceindex]
+ else
+ kdone, gitwst_copyBufferL, i_ chop_copyk istart, ilen, gitwst_bufferL[instanceindex]
+ endif
+ xout kdone
+endop
+
+instr twst_copy
+ icbid = p4
+ istart = p5
+ iend = p6
+ ichannel = p7
+ inocheckpoint = p8
+
+ kdone twst_copy istart, iend, ichannel
+ if (kdone == 1) then
+ schedulek("twst_successresponse", 0, 1, icbid)
+ turnoff
+ endif
+endin
+
+
+opcode twst_trim, k, opjj
+ istart, iend, ichannel, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ istart, ilen, iend twst_getstartend istart, iend
+
+ kdone init 0
+ kdone, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex] chop_trimk istart, ilen, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ xout kdone
+endop
+
+instr twst_trim
+ icbid = p4
+ istart = p5
+ iend = p6
+ ichannel = p7
+ inocheckpoint = p8
+
+ if (inocheckpoint == 0) then
+ twst_checkpoint()
+ endif
+
+ kdone twst_trim istart, iend, ichannel
+ if (kdone == 1) then
+ schedulek("twst_overviews_response", 0, 1, icbid, 0, 1)
+ turnoff
+ endif
+endin
+
+
+opcode twst_cut, ki, opjj
+ istart, iend, ichannel, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ istart, ilen, iend twst_getstartend istart, iend
+
+ twst_clearbuffers(gitwst_copyBufferL, gitwst_copyBufferR)
+ gitwst_copyBufferL = -1
+ gitwst_copyBufferR = -1
+ kdone init 0
+
+ if (gitwst_channels[instanceindex] == 2) then
+ if (ichannel == -1) then
+ kdone, gitwst_copyBufferL, gitwst_copyBufferR, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex] chop_cutk istart, ilen, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ elseif (ichannel == 0) then
+ kdone1, gitwst_copyBufferL, i_ chop_copyk istart, ilen, gitwst_bufferL[instanceindex]
+ kdone chop_setsilencek istart, ilen, gitwst_bufferL[instanceindex], -1, kdone1
+ elseif (ichannel == 1) then
+ kdone1, gitwst_copyBufferR, i_ chop_copyk istart, ilen, gitwst_bufferR[instanceindex]
+ kdone chop_setsilencek istart, ilen, gitwst_bufferR[instanceindex], -1, kdone1
+ endif
+ else
+ kdone, gitwst_copyBufferL, i_, gitwst_bufferL[instanceindex], i_ chop_cutk istart, ilen, gitwst_bufferL[instanceindex]
+ endif
+
+ inewlen = ftlen(gitwst_bufferL[instanceindex])
+ xout kdone, istart / inewlen
+endop
+
+
+
+instr twst_overviews_response
+ icbid = p4
+ iselstart = p5
+ iselend = p6
+ io_sendstring("callback", twst_createoverviews(icbid, iselstart, iselend))
+ turnoff
+endin
+
+instr twst_cut
+ icbid = p4
+ istart = p5
+ iend = p6
+ ichannel = p7
+ inocheckpoint = p8
+
+ if (inocheckpoint == 0) then
+ twst_checkpoint()
+ endif
+
+ kdone, istart twst_cut istart, iend, ichannel
+ if (kdone == 1) then
+ schedulek("twst_overviews_response", 0, 1, icbid, istart, istart)
+ turnoff
+ endif
+endin
+
+
+opcode twst_delete, ki, opjj
+ istart, iend, ichannel, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ istart, ilen, iend twst_getstartend istart, iend
+ kdone init 0
+
+ if (gitwst_channels[instanceindex] == 2) then
+ if (ichannel == -1) then
+ kdone, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex] chop_deletek istart, ilen, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ elseif (ichannel == 0) then
+ kdone chop_setsilencek istart, ilen, gitwst_bufferL[instanceindex]
+ elseif (ichannel == 1) then
+ kdone chop_setsilencek istart, ilen, gitwst_bufferR[instanceindex]
+ endif
+ else
+ kdone, gitwst_bufferL[instanceindex], i_ chop_deletek istart, ilen, gitwst_bufferL[instanceindex]
+ endif
+
+ inewlen = ftlen(gitwst_bufferL[instanceindex])
+ xout kdone, istart / inewlen
+endop
+
+
+instr twst_delete
+ icbid = p4
+ istart = p5
+ iend = p6
+ ichannel = p7
+ inocheckpoint = p8
+
+ if (inocheckpoint == 0) then
+ twst_checkpoint()
+ endif
+
+ kdone, istart twst_delete istart, iend, ichannel
+ if (kdone == 1) then
+ schedulek("twst_overviews_response", 0, 1, icbid, istart, istart)
+ turnoff
+ endif
+endin
+
+opcode twst_trypaste, ikii, ojjpo
+ istart, ichannel, instanceindex, inumber, imix xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ istart, ilen, iend twst_getstartend istart, 1
+ inewlen = ftlen(gitwst_bufferL[instanceindex]) + (ftlen(gitwst_copyBufferL) * inumber)
+
+ if (inewlen >= gihost_max32bitftlen) then ; limitation with WASM Csound build at the moment
+ iresponse = -2
+ goto complete
+ elseif (gitwst_copyBufferL < 1 && gitwst_copyBufferR < 1) then
+ iresponse = -1
+ goto complete
+ endif
+
+ kdone init 0
+ if (gitwst_channels[instanceindex] == 1) then
+ kdone, gitwst_bufferL[instanceindex], i_, ipastelen chop_pastek gitwst_copyBufferL, -1, gitwst_bufferL[instanceindex], -1, istart, inumber, imix
+ elseif (gitwst_copyBufferR > 0 && ichannel == -1) then
+ kdone, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex], ipastelen chop_pastek gitwst_copyBufferL, gitwst_copyBufferR, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex], istart, inumber, imix
+ elseif (ichannel == 0) then
+ isrc = (gitwst_copyBufferL > 0) ? gitwst_copyBufferL : gitwst_copyBufferR
+ kdone1, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex], ipastelen chop_pastek isrc, isrc, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex], istart, inumber, imix
+ kdone chop_setsilencek istart, ilen, gitwst_bufferR[instanceindex], -1, kdone1
+ elseif (ichannel == 1) then
+ isrc = (gitwst_copyBufferR > 0) ? gitwst_copyBufferR : gitwst_copyBufferL
+ kdone1, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex], ipastelen chop_pastek isrc, isrc, gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex], istart, inumber, imix
+ kdone chop_setsilencek istart, ilen, gitwst_bufferL[instanceindex], -1, kdone1
+ endif
+ iresponse = 1
+ inewlen = ftlen(gitwst_bufferL[instanceindex])
+
+complete:
+ xout iresponse, kdone, istart, (istart + ipastelen)
+endop
+
+opcode twst_paste, k, ojjp
+ istart, ichannel, instanceindex, inumber xin
+ istatus, kdone, istart, iend twst_trypaste istart, ichannel, instanceindex, inumber
+ xout kdone
+endop
+
+
+instr twst_paste
+ icbid = p4
+ istart = p5
+ iend = p6
+ ichannel = p7
+ inocheckpoint = p8
+
+ if (inocheckpoint == 0) then
+ twst_checkpoint()
+ endif
+
+ istatus, kdone, istart, iend twst_trypaste istart, ichannel
+ if (istatus < 0) then
+ io_sendstring("callback", twst_failresponse(icbid, istatus))
+ turnoff
+ else
+ if (kdone == 1) then
+ schedulek("twst_overviews_response", 0, 1, icbid, istart, iend)
+ turnoff
+ endif
+ endif
+endin
+
+
+
+instr twst_pastespecial
+ icbid = p4
+ istart = p5
+ iend = p6
+ ichannel = p7
+ inocheckpoint = p8
+
+ if (inocheckpoint == 0) then
+ twst_checkpoint()
+ endif
+
+ inumber = chnget:i(sprintf("%s_repetitions0", nstrstr(p1)))
+ inumber = (inumber < 1) ? 1 : inumber
+ imix = chnget:i(sprintf("%s_mixpaste0", nstrstr(p1)))
+ istatus, kdone, istart, iend twst_trypaste istart, ichannel, -1, inumber, imix
+ if (istatus < 0) then
+ io_sendstring("callback", twst_failresponse(icbid, istatus))
+ turnoff
+ else
+ if (kdone == 1) then
+ schedulek("twst_overviews_response", 0, 1, icbid, istart, iend)
+ turnoff
+ endif
+ endif
+endin
+
+
+instr twst_nexttransientresponse
+ icbid = p4
+ iselstart = p5
+ iselend = p6
+ if (iselstart == -1 && iselend == -1) then
+ io_sendstring("callback", sprintf("{\"cbid\":%d}", icbid))
+ else
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"selstart\":%f,\"selend\":%f}", icbid, iselstart, iselend))
+ endif
+ turnoff
+endin
+
+instr twst_nexttransient
+ icbid = p4
+ istart = p5
+ iend = p6
+ ichannel = p7
+ p3 = 60
+
+ instanceindex = gitwst_instanceindex
+ isamps = ftlen(gitwst_bufferL[instanceindex])
+
+ istartsamp = iend * isamps
+ idurationsamp = isamps - istartsamp
+ idurations = idurationsamp / sr
+
+ ileft = 1
+ iright = 1
+ if (ichannel == 0 || gitwst_channels[gitwst_instanceindex] == 1) then
+ iright = 0
+ elseif (ichannel == 1 && gitwst_channels[gitwst_instanceindex] == 2) then
+ ileft = 0
+ endif
+
+ ktimek timeinstk
+ ikcycles = idurationsamp / ksmps
+ if (ktimek == 1) then
+ kcount init 0
+ while (kcount < ikcycles) do
+ apos linseg istartsamp, idurations, isamps
+ if (ileft == 1 && iright == 1) then
+ asig = (table3:a(apos, gitwst_bufferL[instanceindex]) + table3:a(apos, gitwst_bufferR[instanceindex])) * 0.5
+ elseif (iright == 1) then
+ asig = table3:a(apos, gitwst_bufferR[instanceindex])
+ else
+ asig = table3:a(apos, gitwst_bufferL[instanceindex])
+ endif
+ ktrig transientdetect asig
+ if (ktrig == 1 && kcount != 0) then
+ kselend = ((kcount * ksmps) + istartsamp) / isamps
+ kselstart = (istart == iend) ? kselend : istart
+ schedulek("twst_nexttransientresponse", 0, 1, icbid, kselstart, kselend)
+ turnoff
+ endif
+ kcount += 1
+ od
+ else
+ schedulek("twst_nexttransientresponse", 0, 1, icbid, -1, -1)
+ turnoff
+ endif
+endin
+
+
+instr twst_undo
+ icbid = p4
+ istatus twst_undo
+ if (istatus < 0) then
+ Sresponse = twst_failresponse(icbid)
+ else
+ Sresponse = twst_createoverviews(icbid)
+ endif
+
+ io_sendstring("callback", Sresponse)
+ turnoff
+endin
+
+
+instr twst_destroytables
+ ifnL = p4
+ ifnR = p5
+ twst_clearbuffers(ifnL, ifnR)
+ turnoff
+endin
+
+
+opcode twst_loadfile, ik, Sj
+ Spath, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ kdone init 0
+ if (filevalid(Spath) != 1) then
+ iresponse = -1
+ goto complete
+ endif
+
+ ifilesr = filesr(Spath)
+ ifilechannels = filenchnls(Spath)
+ ilens = filelen(Spath)
+ ilen = round(ilens * ifilesr)
+
+ if (ilen >= gihost_max32bitftlen || ilens * sr >= gihost_max32bitftlen) then ; limitation with WASM Csound build at the moment
+ iresponse = -2
+ goto complete
+ endif
+
+ twst_checkpoint_clear(instanceindex)
+
+ twst_clearbuffers(gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex])
+ gitwst_channels[instanceindex] = ifilechannels
+
+ gitwst_bufferL[instanceindex] = ftgen(0, 0, -ilen, 1, Spath, 0, 0, 1)
+ if (gitwst_channels[instanceindex] == 2) then
+ gitwst_bufferR[instanceindex] = ftgen(0, 0, -ilen, 1, Spath, 0, 0, 2)
+ gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex], kdone tab_samplerateconvert gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex]
+ imono = 0
+ else
+ gitwst_bufferL[instanceindex], kdone tab_samplerateconvert gitwst_bufferL[instanceindex]
+ imono = 1
+ endif
+
+
+ iresponse = 1
+complete:
+ xout iresponse, kdone
+endop
+
+
+/*
+opcode twst_loadfile, ik, Sj
+ Spath, instanceindex xin
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ kdone init 0
+ if (filevalid(Spath) != 1) then
+ iresponse = -1
+ goto complete
+ endif
+
+ ifilesr = filesr(Spath)
+ ifilechannels = filenchnls(Spath)
+ ilens = filelen(Spath)
+ ilen = round(ilens * ifilesr)
+
+ if (ilen >= gihost_max32bitftlen || ilens * sr >= gihost_max32bitftlen) then ; limitation with WASM Csound build at the moment
+ iresponse = -2
+ goto complete
+ endif
+
+ twst_checkpoint_clear(instanceindex)
+
+ gitwst_channels[instanceindex] = ifilechannels
+ twst_clearbuffers(gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex])
+
+ gitwst_bufferL[instanceindex] = ftgen(0, 0, -ilen, 1, Spath, 0, 0, 1)
+ if (gitwst_channels[instanceindex] == 2) then
+ gitwst_bufferR[instanceindex] = ftgen(0, 0, -ilen, 1, Spath, 0, 0, 2)
+ imono = 0
+ else
+ imono = 1
+ endif
+
+ if (sr != ifilesr) then ; different sr causes issues in table reading opcodes, convert..
+ inewlen = ilens * sr
+ ifnnewL ftgen 0, 0, -inewlen, -2, 0
+ if (imono == 0) then
+ ifnnewR ftgen 0, 0, -inewlen, -2, 0
+ endif
+ ktimek timeinstk
+ ikcycles = ilens * kr
+ if (ktimek == 1) then
+ kcount = 0
+ while (kcount < ikcycles) do
+ aposw linseg 0, ilens, inewlen - 1
+ aposr linseg 0, ilens, ilen - 1
+ asig table3 aposr, gitwst_bufferL[instanceindex]
+ tablew asig, aposw, ifnnewL
+ if (imono == 0) then
+ asig table3 aposr, gitwst_bufferR[instanceindex]
+ tablew asig, aposw, ifnnewR
+ endif
+ kcount += 1
+ od
+ else
+ kdone = 1
+ endif
+
+ ftfree gitwst_bufferL[instanceindex], 1
+ gitwst_bufferL[instanceindex] = ifnnewL
+ if (imono == 0) then
+ ftfree gitwst_bufferR[instanceindex], 1
+ gitwst_bufferR[instanceindex] = ifnnewR
+ endif
+ else
+ kdone = 1
+ endif
+
+ iresponse = 1
+complete:
+ xout iresponse, kdone
+endop
+*/
+
+instr twst_loadclipboard
+ icbid = p4
+ p3 = 60
+
+ instanceindex = gitwst_instanceindex
+ twst_clearbuffers(gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex])
+
+ ilen = ftlen(gitwst_copyBufferL)
+ ifnL ftgen 0, 0, -ilen, -2, 0
+ tableicopy ifnL, gitwst_copyBufferL
+ gitwst_bufferL[instanceindex] = ifnL
+ if (gitwst_copyBufferR > 0 && ftexists(gitwst_copyBufferR) == 1) then
+ ifnR ftgen 0, 0, -ilen, -2, 0
+ tableicopy ifnR, gitwst_copyBufferR
+ gitwst_bufferR[instanceindex] = ifnR
+ gitwst_channels[instanceindex] = 2
+ else
+ gitwst_channels[instanceindex] = 1
+ gitwst_bufferR[instanceindex] = 0
+ endif
+ schedule("twst_overviews_response", 0, 1, icbid, 0, 0)
+ turnoff
+endin
+
+instr twst_loadftable
+ icbid = p4
+ ifnL = p5
+ ifnR = p6
+ iclearbuffers = p7
+ p3 = 60
+ instanceindex = gitwst_instanceindex
+
+ if (ifnL <= 0 || ftexists(ifnL) == 0 || (ifnR > 0 && ftexists(ifnR) == 0)) then
+ io_sendstring("callback", twst_failresponse(icbid, -1))
+ turnoff
+ endif
+
+ twst_checkpoint_clear(instanceindex)
+
+ if (iclearbuffers == 1) then
+ twst_clearbuffers(gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex])
+ endif
+
+ gitwst_bufferL[instanceindex] = ifnL
+ if (ifnR > 0) then
+ gitwst_bufferR[instanceindex] = ifnR
+ gitwst_channels[instanceindex] = 2
+ else
+ gitwst_channels[instanceindex] = 1
+ endif
+
+ schedule("twst_overviews_response", 0, 1, icbid, 0, 0)
+ turnoff
+endin
+
+instr twst_getbuffers
+ icbid = p4
+ instanceindex = gitwst_instanceindex
+ if (gitwst_channels[instanceindex] == 2) then
+ Stables = sprintf("[%d,%d]", gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex])
+ else
+ Stables = sprintf("[%d]", gitwst_bufferL[instanceindex])
+ endif
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1,\"tables\":%s}", icbid, Stables))
+ turnoff
+endin
+
+
+instr twst_loadfile
+ icbid = p4
+ p3 = 120
+ Spath = strget(p5)
+ istatus, kdone twst_loadfile Spath, -1, 1
+ if (istatus < 0) then
+ Sresponse = twst_failresponse(icbid, istatus)
+ io_sendstring("callback", Sresponse)
+ turnoff
+ else
+ if (kdone == 1) then
+ schedulek("twst_overviews_response", 0, 1, icbid, 0, 0)
+ turnoff
+ endif
+ endif
+endin
+
+
+
+/*
+instr twst_loadfile
+ icbid = p4
+ Spath = strget(p5)
+ if (filevalid(Spath) != 1) then
+ Sresponse = twst_failresponse(icbid)
+ else
+ twst_clearbuffers()
+ gitwst_channels[gitwst_instanceindex] = filenchnls(Spath)
+ gitwst_bufferL[gitwst_instanceindex] = ftgen(0, 0, 0, 1, Spath, 0, 0, 1)
+ if (gitwst_channels[gitwst_instanceindex] == 2) then
+ gitwst_bufferR[gitwst_instanceindex] = ftgen(0, 0, 0, 1, Spath, 0, 0, 2)
+ endif
+ Sresponse = twst_createoverviews(icbid)
+ endif
+ io_sendstring("callback", Sresponse)
+ turnoff
+endin
+*/
+
+
+
+opcode twst_createempty, i, ijj
+ iduration, ichannels, instanceindex xin
+ ichannels = (ichannels == -1) ? 2 : ichannels
+ instanceindex = (instanceindex == -1) ? gitwst_instanceindex : instanceindex
+ twst_clearbuffers(gitwst_bufferL[instanceindex], gitwst_bufferR[instanceindex])
+ idurationsamps = iduration * sr
+
+ if (idurationsamps >= gihost_max32bitftlen) then ; limitation with WASM Csound build at the moment
+ iresponse = -2
+ goto complete
+ endif
+
+ twst_checkpoint_clear(instanceindex)
+
+ gitwst_bufferL[instanceindex] ftgen 0, 0, -idurationsamps, -2, 0
+ if (ichannels == 2) then
+ gitwst_channels[instanceindex] = 2
+ gitwst_bufferR[instanceindex] ftgen 0, 0, -idurationsamps, -2, 0
+ else
+ gitwst_channels[instanceindex] = 1
+ endif
+ iresponse = 1
+complete:
+ xout iresponse
+endop
+
+
+instr twst_createempty
+ icbid = p4
+ iduration = p5
+ ichannels = p6
+ p3 = 60
+ istatus twst_createempty iduration, ichannels
+ if (istatus < 0) then
+ io_sendstring("callback", twst_failresponse(icbid, istatus))
+ else
+ schedule("twst_overviews_response", 0, 1, icbid, 0, 0)
+ endif
+ turnoff
+endin
+
+
+instr twst_savefile_response
+ icbid = p4
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1}", icbid))
+ turnoff
+endin
+
+
+instr twst_savefile
+ icbid = p4
+ p3 = 60
+ Spath = strget(p5)
+ ktimek timeinstk
+ idurations = ftlen(gitwst_bufferL[gitwst_instanceindex]) / ftsr(gitwst_bufferL[gitwst_instanceindex])
+ ikcycles = idurations * kr
+ if (ktimek == 1) then
+ kcount init 0
+ while (kcount < ikcycles) do
+ apos lphasor 1
+ aL table apos, gitwst_bufferL[gitwst_instanceindex]
+ if (gitwst_channels[gitwst_instanceindex] == 1) then
+ fout Spath, 14, aL
+ else
+ aR table3 apos, gitwst_bufferR[gitwst_instanceindex]
+ fout Spath, 14, aL, aR
+ endif
+ kcount += 1
+ od
+ else
+ schedulek("twst_savefile_response", 0, 1, icbid)
+ turnoff
+ endif
+endin
+
+
+
+instr twst_auditioncomplete_response
+ icbid = p4
+ istatus = 0
+ if (gitwst_userstopped == 1) then
+ istatus = 3
+ endif
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":%d}", icbid, istatus))
+ turnoff
+endin
+
+instr twst_stop
+ gitwst_userstopped = 1
+ turnoff2 "twst_audition", 0, 1
+ turnoff2 "twst_record", 0, 1
+ turnoff
+endin
+
+#ifdef TWST_FAILONLAG
+instr twst_auditionlag_response
+ icbid = p4
+ turnoff2 "twst_audition", 0, 0
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":-1}", icbid))
+ turnoff
+endin
+#end
+
+instr twst_play
+ schedule("twst_audition", 0, p3, p4, p5, p6, p7, "", 0)
+ turnoff
+endin
+
+instr twst_audition
+ icbid = p4
+ gitwst_userstopped = 0
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1}", icbid))
+ istart = p5
+ iend = p6
+ if (istart == iend) then
+ iend = 1
+ endif
+ ichannel = p7
+ Stransform = strget(p8)
+ iautomating = p9
+ icrossfadein = p10
+ icrossfadeout = p11
+ iuniqueid = p12
+ iapplymode chnget sprintf("applymode%d", iuniqueid)
+ kapplymodedry chnget sprintf("applymodedry%d", iuniqueid)
+ kapplymodewet chnget sprintf("applymodewet%d", iuniqueid)
+
+ i_, aL, aR, i_, i_, iextracycles, i_, i_ twst_playback istart, iend, ichannel, Stransform, 0, iautomating, icrossfadein, icrossfadeout, iuniqueid
+
+ gitwst_currentplayduration = p3
+
+#ifdef TWST_FAILONLAG
+ if (strcmp(Stransform, "") != 0) then
+ klagging lagdetect 0.8
+ if (klagging == 1) then
+ schedulek("twst_auditionlag_response", 0, 1, icbid)
+ endif
+ endif
+#end
+
+ kreleasing init 0
+ ktimek timeinstk
+ iduration = (p3 * kr) + (iextracycles / sr)
+ krelease release
+ if (kreleasing == 0 && (krelease == 1 || ktimek >= iduration)) then
+ kreleasing = 1
+ schedulek("twst_auditioncomplete_response", 0, 1, icbid)
+ turnoff
+ endif
+
+ outs aL, aR
+endin
+
+
+instr twst_recordcomplete_response
+ icbid = p4
+ iselstart = p5
+ iselend = p6
+ io_sendstring("callback", twst_createoverviews(icbid, iselstart, iselend, 2))
+endin
+
+instr twst_record
+ icbid = p4
+ gitwst_userstopped = 0
+ io_sendstring("callback", sprintf("{\"cbid\":%d,\"status\":1}", icbid))
+ istart = p5
+ iend = p6
+ if (istart == iend) then
+ iend = 1
+ endif
+ ichannel = p7
+ ibuflen = ftlen(gitwst_bufferL[gitwst_instanceindex])
+ istartsamp, ilensamp, iendsamp twst_getstartend istart, iend, gitwst_instanceindex
+ ilens = ilensamp / sr
+ p3 = ilens
+
+ twst_checkpoint()
+
+ apos linseg istartsamp, ilens, iendsamp
+ aL init 0
+ aR init 0
+
+ if (gitwst_channels[gitwst_instanceindex] == 1) then
+ if (ichannel == 1) then
+ aL inch 2
+ else
+ aL inch 1
+ endif
+ tablew aL, apos, gitwst_bufferL[gitwst_instanceindex]
+ else
+ if (ichannel == -1 || ichannel == 0) then
+ aL inch 1
+ tablew aL, apos, gitwst_bufferL[gitwst_instanceindex]
+ endif
+ if (ichannel == -1 || ichannel == 1) then
+ aR inch 2
+ tablew aR, apos, gitwst_bufferR[gitwst_instanceindex]
+ endif
+ endif
+
+ chnset k(aL), "recordmonitorL"
+ chnset k(aR), "recordmonitorR"
+
+ ktimeenv linseg istart, p3, iend
+ chnset ktimeenv, "twst_playposratio"
+ kreleasing init 0
+ ktimek timeinstk
+ iduration = p3 * kr
+ if (kreleasing == 0 && (release:k() == 1 || ktimek >= iduration)) then
+ kreleasing = 1
+ klastwritten = k(apos)
+ schedulek("twst_recordcomplete_response", 0, 1, icbid, istart, klastwritten / ibuflen)
+ turnoff
+ endif
+endin
+
+
+
+instr twst_commit_response
+ icbid = p4
+ ifnL = p5
+ ifnR = p6
+ iselstart = p7
+ iselend = p8
+ if (ifnL > 0 && ifnL != gitwst_bufferL[gitwst_instanceindex]) then
+ if (gitwst_bufferL[gitwst_instanceindex] > 0 && ftexists(gitwst_bufferL[gitwst_instanceindex]) == 1) then
+ ftfree gitwst_bufferL[gitwst_instanceindex], 0
+ endif
+ gitwst_bufferL[gitwst_instanceindex] = ifnL
+ endif
+ if (ifnR > 0 && ifnR != gitwst_bufferR[gitwst_instanceindex]) then
+ if (gitwst_bufferR[gitwst_instanceindex] > 0 && gitwst_bufferR[gitwst_instanceindex] == 1) then
+ ftfree gitwst_bufferR[gitwst_instanceindex], 0
+ endif
+ gitwst_bufferR[gitwst_instanceindex] = ifnR
+ endif
+
+ io_sendstring("callback", twst_createoverviews(icbid, iselstart, iselend))
+ turnoff
+endin
+
+instr twst_commit
+ icbid = p4
+ istart = p5
+ iend = p6
+ if (istart == iend) then
+ istart = 0
+ iend = 1
+ endif
+ ichannel = p7
+ Stransform = strget(p8)
+ iautomating = p9
+ icrossfadein = p10
+ icrossfadeout = p11
+ inoCheckpoint = p12
+ iuniqueid = p13
+
+ ibuflen = ftlen(gitwst_bufferL[gitwst_instanceindex])
+ istartsamp = ibuflen * istart
+ iendsamp = ibuflen * iend
+ idurations = (iendsamp - istartsamp) / ftsr(gitwst_bufferL[gitwst_instanceindex])
+
+ if (inoCheckpoint == 0) then
+ twst_checkpoint()
+ endif
+
+ iblocks = 100
+ ikcycles = round(idurations * kr)
+ if (ikcycles < iblocks) then
+ ikcyclesperblock = ikcycles
+ else
+ ikcyclesperblock = round(ikcycles / iblocks)
+ endif
+ ktotalcount init 0
+ klastpercent init 100
+
+ kreleasing init 0
+ ifnL = 0
+ ifnR = 0
+
+ if (ktotalcount < ikcycles) then
+ kcount = 0
+ while (kcount < ikcyclesperblock) do
+ istatus, a_, a_, ifnL, ifnR, iextracycles, iselstart, iselend twst_playback istart, iend, ichannel, Stransform, 1, iautomating, icrossfadein, icrossfadeout, iuniqueid
+ if (istatus <= 0) then
+ io_sendstring("callback", twst_failresponse(icbid, istatus))
+ turnoff
+ endif
+ ikcycles += iextracycles ; weird in loop but it's a k loop, so i is set here...
+
+ kcount += 1
+ ktotalcount += 1
+ od
+ kpercent = round((100 / ikcycles) * ktotalcount)
+ if (kpercent != klastpercent) then
+ io_send "percent", kpercent
+ klastpercent = kpercent
+ endif
+ else
+ schedulek("twst_commit_response", 0, 1, icbid, ifnL, ifnR, iselstart, iselend)
+ turnoff
+ endif
+endin
+
+
+#include "/twist/automation.udo"
+#include "/twist/transforms.udo"
+
+
+#end
diff --git a/site/udo/txt_tools.udo b/site/udo/txt_tools.udo
new file mode 100755
index 0000000..e260635
--- /dev/null
+++ b/site/udo/txt_tools.udo
@@ -0,0 +1,98 @@
+#ifndef UDO_TXTTOOLS
+#define UDO_TXTTOOLS ##
+/*
+ Text tools
+
+ This file is part of the SONICS UDO collection by Richard Knight 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+/*
+ Print a notification prepended with a line of asterisks
+
+ tt_notify Stext
+
+ Stext text to print
+*/
+opcode tt_notify, 0, S
+ Stext xin
+ Snew = "\n"
+ iwidth = 60
+ index = 0
+ while (index < iwidth) do
+ Snew = strcat(Snew, "*");
+ index += 1
+ od
+ prints strcat(strcat(Snew, "\n"), strcat(Stext, "\n\n"))
+endop
+
+
+
+/*
+ Print a notification prepended with a line of asterisks and exit
+
+ tt_notify Stext
+
+ Stext text to print
+*/
+opcode tt_notify_fatal, 0, S
+ Stext xin
+ tt_notify(Stext)
+ exitnow
+endop
+
+
+/*
+ Return a number of seconds as HH:MM:SS format
+
+ Stime tt_parsetime iseconds
+
+ iseconds seconds to parse
+
+ Stime formatted time
+
+*/
+opcode tt_parsetime, S, i
+ input xin
+ ihours = floor(input / 3600)
+ iminutes = floor((input - (ihours * 3600)) / 60)
+ iseconds = input - (ihours * 3600) - (iminutes * 60)
+ xout sprintf("%02d:%02d:%05.2f", ihours, iminutes, iseconds)
+endop
+
+
+/*
+ Print a timestamped notification prepended with a line of asterisks and exit
+
+ tt_notifytime Stext
+
+ Stext text to print
+*/
+opcode tt_notifytime, 0, S
+ Stext xin
+ Stime tt_parsetime times()
+ tt_notify sprintf("%s | %s", Stime, Stext)
+endop
+
+
+/*
+ Strip newline from end of line: built-in opcode has some problems
+
+ Soutput tt_stripnewline Sinput
+
+ Soutput processed without newline at end if existent
+
+ Sinput line to process
+*/
+opcode tt_stripnewline, S, S
+ Sline xin
+ index = strindex(Sline, "\n")
+ if (index != -1) then
+ Sline = strsub(Sline, 0, index)
+ endif
+ xout Sline
+endop
+
+#end
diff --git a/site/udo/uniqueid.udo b/site/udo/uniqueid.udo
new file mode 100755
index 0000000..68688f8
--- /dev/null
+++ b/site/udo/uniqueid.udo
@@ -0,0 +1,81 @@
+#ifndef UDO_UNIQUEID
+#define UDO_UNIQUEID ##
+/*
+ Unique ID assignments
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022, 2024
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+; globals for internal use
+giUniqueID = 0
+giUniqueFrac = 0
+
+
+/*
+ Get a unique integer ID
+
+ id uniqueid
+
+ id the ID
+*/
+opcode uniqueid, i, 0
+ id = giUniqueID
+ if (giUniqueID + 1 >= 99999999) then ; large guard for 32bit
+ giUniqueID = 0
+ else
+ giUniqueID += 1
+ endif
+ xout id
+endop
+
+
+/*
+ Get a unique decimal/fractional ID
+
+ id uniquefrac
+
+ id the ID
+*/
+opcode uniquefrac, i, 0
+ id = giUniqueFrac
+ giUniqueFrac += 0.0000001 ; smallest for 32bit
+ if (giUniqueFrac >= 1) then
+ giUniqueFrac = 0
+ endif
+ xout id
+endop
+
+
+/*
+ Get an array of unique fractional instrument numbers given a base instrument number
+
+ instrs[] uniqueinstrnums instrnum, inum
+ instrs[] uniqueinstrnums Sinstr, inum
+
+ instrs[] array of unique fractional numbers for the instrument number instrnum
+ Sinstr the base instrument name
+ instrnum the base instrument number
+ inum how many references to generate
+*/
+opcode uniqueinstrnums, i[], ii
+ instrnum, inum xin
+ instrs[] init inum
+ index = 0
+ while (index < inum) do
+ instrs[index] = instrnum + uniquefrac()
+ index += 1
+ od
+ xout instrs
+endop
+
+; overload for named instrument
+opcode uniqueinstrnums, i[], Si
+ Sinstr, inum xin
+ instrs[] uniqueinstrnums nstrnum(Sinstr), inum
+ xout instrs
+endop
+
+#endif
diff --git a/site/udo/wavetables.udo b/site/udo/wavetables.udo
new file mode 100755
index 0000000..564836e
--- /dev/null
+++ b/site/udo/wavetables.udo
@@ -0,0 +1,31 @@
+#ifndef UDO_WAVETABLES
+#define UDO_WAVETABLES ##
+/*
+ Standard regular wave function tables
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+ipoints = 8192
+gifnSine ftgen 0, 0, ipoints, 10, 1
+gifnSquare ftgen 0, 0, ipoints, 10, 1, 0 , .33, 0, .2 , 0, .14, 0 , .11, 0, .09
+gifnSaw ftgen 0, 0, ipoints, 10, 0, .2, 0, .4, 0, .6, 0, .8, 0, 1, 0, .8, 0, .6, 0, .4, 0, .2
+gifnPulse ftgen 0, 0, ipoints, 10, 1, 1, 1, 1, 0.7, 0.5, 0.3, 0.1
+gifnTriangle ftgen 0, 0, ipoints, 9, 1, 1, 0, 3, 0.333, 180, 5, 0.2, 0, 7, 0.143, 180, 9, 0.111, 0
+gifnCosine ftgen 0, 0, ipoints, 9, 1, 1, 90
+gifnHalfSine ftgen 0, 0, 2048, 9, 0.5, 1, 0
+gifnSigmoid ftgen 0, 0, 257, 9, .5, 1, 270
+gifnHanning ftgen 0, 0, 2048, 20, 2
+gifnHamming ftgen 0, 0, 2048, 20, 1, 1
+
+giwavetables[] fillarray gifnSine, gifnSquare, gifnSaw, gifnPulse, gifnCosine, gifnTriangle ;, gifnHalfSine, gifnSigmoid
+gSwavetables[] fillarray "Sine", "Square", "Saw", "Pulse", "Cosine", "Triangle" ;, "Half sine", "Sigmoid"
+
+opcode wavetable_random, i, 0
+ xout giwavetables[int(random(0, lenarray(giwavetables)-1))]
+endop
+
+#end
+
diff --git a/site/udo/wiimote.udo b/site/udo/wiimote.udo
new file mode 100755
index 0000000..474d2ba
--- /dev/null
+++ b/site/udo/wiimote.udo
@@ -0,0 +1,227 @@
+#ifndef UDO_WIIMOTE
+#define UDO_WIIMOTE ##
+/*
+ Wiimote interface
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "interop.udo"
+
+; button bit masks
+#define WII_1 #2#
+#define WII_2 #1#
+#define WII_A #8#
+#define WII_B #4#
+#define WII_HOME #128#
+#define WII_MINUS #16#
+#define WII_PLUS #4096#
+#define WII_LEFT #256#
+#define WII_RIGHT #512#
+#define WII_UP #2048#
+#define WII_DOWN #1024#
+
+
+; everything goes to globals when connected
+gkwii_pitch init 0
+gkwii_roll init 0
+gkwii_accelX init 0
+gkwii_accelY init 0
+gkwii_accelZ init 0
+gkwii_buttons init 0
+
+
+#ifdef USE_WIIMOTE_NUNCHUCK
+#define WII_Z #-33#
+#define WII_C #-34#
+
+gkwii_ncang init 0
+gkwii_ncmag init 0
+gkwii_ncpitch init 0
+gkwii_ncroll init 0
+#end
+
+
+/*
+ Get the state of a wiimote button
+
+ kstate wii_button ibutton
+
+ kstate 1 if pressed/held or 0 if not
+ ibutton the bit mask of the button; use the macros defined
+*/
+opcode wii_button, k, i
+ ibutton xin
+ if (ibutton < 0) then ; nunchuck buttons not captured by wiidata(0)
+ ibutton abs ibutton
+ kvalue = wiidata(ibutton)
+ else
+ kvalue = (gkwii_buttons & ibutton == ibutton) ? 1 : 0
+ endif
+ xout kvalue
+endop
+
+
+
+/*
+ Keep an instrument on for as long as a wiimote button is held
+
+ wii_buttonhold ibutton, Sinstrument
+
+ ibutton the bit mask of the button; use the macros defined
+ Sinstrument instrument name to start/stop accordingly
+*/
+opcode wii_buttonhold, 0, iS
+ ibutton, Sinstrument xin
+ kbut = wii_button(ibutton)
+ if (changed:k(kbut) == 1) then
+ if (kbut == 1) then
+ schedulek(Sinstrument, 0, 999999) ; longnum here as -1 not good for p3
+ else
+ turnoff2(Sinstrument, 0, 1)
+ endif
+ endif
+endop
+
+
+opcode wii_buttonhold, 0, iSi
+ ibutton, Sinstrument, ip4 xin
+ kbut = wii_button(ibutton)
+ if (changed:k(kbut) == 1) then
+ if (kbut == 1) then
+ schedulek(Sinstrument, 0, -1, ip4)
+ else
+ turnoff2(Sinstrument, 0, 1)
+ endif
+ endif
+endop
+
+/*
+ Use the wiimote minus and plus buttons to scroll through item indexes
+
+ kitem wii_pager imaxitems [, iteminitial=0, ksetindex=-1]
+
+ kitem the selected item/page
+ imaxindex maximum number of items
+ initialindex initial value
+ ksetindex set the item index directly with this
+*/
+opcode wii_pager, k, ioJ
+ imaxindex, initialindex, ksetindex xin
+ kcurrentitem init initialindex
+
+ kplus = wii_button($WII_PLUS)
+ kminus = wii_button($WII_MINUS)
+ if (kminus == 1 && changed:k(kminus) == 1) then
+ if (kcurrentitem == 0) then
+ kcurrentitem = imaxindex
+ else
+ kcurrentitem -= 1
+ endif
+ elseif (kplus == 1 && changed:k(kplus) == 1) then
+ if (kcurrentitem == imaxindex) then
+ kcurrentitem = 0
+ else
+ kcurrentitem += 1
+ endif
+ elseif (changed:k(ksetindex) == 1) then
+ if (ksetindex >= 0 && ksetindex <= imaxindex) then
+ kcurrentitem = ksetindex
+ endif
+ endif
+ xout kcurrentitem
+endop
+
+
+instr wii_reset
+ gkwii_pitch = 0
+ gkwii_roll = 0
+ gkwii_accelX = 0
+ gkwii_accelY = 0
+ gkwii_accelZ = 0
+ gkwii_buttons = 0
+#ifdef USE_WIIMOTE_NUNCHUCK
+ gkwii_ncang init 0
+ gkwii_ncmag init 0
+ gkwii_ncpitch init 0
+ gkwii_ncroll init 0
+#end
+ turnoff
+endin
+
+instr _wii_handler_watchdog
+ icbid = p4
+ SonFail = p5
+ kmetro metro 0.5
+ if (kmetro == 1) then
+ if (active:k("wii_handler") == 0) then
+ schedulek(SonFail, 0, -1, icbid)
+ turnoff
+ endif
+ endif
+endin
+
+
+instr _wii_handler_default_fail
+ icbid = p4
+ schedule("wii_reset", 0, 1)
+ io_sendstring("callback", sprintf("{\"cbid\": %d, \"success\": false}", icbid))
+ turnoff
+endin
+
+/*
+ Connect wiimote and set global variables from it
+*/
+instr wii_handler
+ icbid = p4
+ SonComplete = strget(p5)
+ SonFailWatchdog = strget(p6)
+ iconnecttimeout = (p7 == 0) ? 1 : p7
+
+ if (strcmp(SonFailWatchdog, "") == 0) then
+ SonFailWatchdog = "_wii_handler_default_fail"
+ endif
+ schedule("_wii_handler_watchdog", 0, p3, icbid, SonFailWatchdog)
+
+ iwiisuccess wiiconnect iconnecttimeout, 1
+
+ if (icbid > 0) then
+ io_sendstring("callback", sprintf("{\"cbid\": %d, \"success\": %s}", icbid, (iwiisuccess == 1) ? "true" : "false"))
+ endif
+
+ if (iwiisuccess == 0) then
+ turnoff
+ endif
+
+ if (strcmp(SonComplete, "") != 0) then
+ schedule(SonComplete, 0, -1)
+ endif
+
+ ;gkwii_battery = wiidata(27)
+ ;printk2 gkwii_battery
+
+ ; set range of pitch and roll as 0 to 1
+ wiirange 20, 0, 1
+ wiirange 21, 0, 1
+
+ gkwii_accelX = wiidata(23) ;/ 5 ;abs:k(wiidata(23)) / 5
+ gkwii_accelY = wiidata(24) / 5 ;abs:k(wiidata(24)) / 5
+ gkwii_accelZ = wiidata(25) / 5 ;abs:k(wiidata(25)) / 5
+ gkwii_pitch = wiidata(20)
+ gkwii_roll = wiidata(21)
+ gkwii_buttons = wiidata(0) ; bit pattern
+
+#ifdef USE_WIIMOTE_NUNCHUCK
+ wiirange 30, 0, 1
+ wiirange 31, 0, 1
+ gkwii_ncang = wiidata(28) / 360
+ gkwii_ncmag = wiidata(29) / 1.2
+ gkwii_ncpitch = wiidata(30)
+ gkwii_ncroll = wiidata(31)
+#end
+
+endin
+
+#end
diff --git a/site/udo/wiimote_fltk.udo b/site/udo/wiimote_fltk.udo
new file mode 100755
index 0000000..38bb91d
--- /dev/null
+++ b/site/udo/wiimote_fltk.udo
@@ -0,0 +1,200 @@
+#ifndef UDO_WIIMOTE
+#define UDO_WIIMOTE ##
+/*
+ Wiimote FLTK debugging/substitution interface
+
+ This file is part of the SONICS UDO collection by Richard Knight 2023
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "interop.udo"
+
+
+FLpanel "WiiBug", 600, 500, 100, 100, 0, 1, 1
+ gkwii_pitch, gkwii_roll, gij1x, gij1y FLjoy "Main pitch and roll", 0, 1, 0, 1, 0, 0, -1, -1, 200, 200, 0, 0
+#ifdef USE_WIIMOTE_NUNCHUCK
+ gkwii_ncpitch, gkwii_ncroll, gij2x, gij2y FLjoy "Nunchuck pitch and roll", 0, 1, 0, 1, 0, 0, -1, -1, 200, 200, 200, 0
+ gkj3x, gkj3y, gij3x, gij3y FLjoy "Nunchuck angle and mag", 0, 1, 0, 1, 0, 0, -1, -1, 200, 200, 400, 0
+#end
+ gkb1, gib1 FLbutton "1", 1, 0, 2, 50, 50, 0, 250, -1
+ gkb2, gib2 FLbutton "2", 1, 0, 2, 50, 50, 50, 250, -1
+ gkbA, gibA FLbutton "A", 1, 0, 2, 50, 50, 100, 250, -1
+ gkbB, gibB FLbutton "B", 1, 0, 2, 50, 50, 150, 250, -1
+ gkbHome, gibHome FLbutton "Home", 1, 0, 2, 50, 50, 200, 250, -1
+ gkbMinus, gibMinus FLbutton "Minus", 1, 0, 2, 50, 50, 250, 250, -1
+ gkbPlus, gibPlus FLbutton "Plus", 1, 0, 2, 50, 50, 300, 250, -1
+
+ gkbLeft, gibLeft FLbutton "Left", 1, 0, 2, 50, 50, 0, 300, -1
+ gkbRight, gibRight FLbutton "Right", 1, 0, 2, 50, 50, 50, 300, -1
+ gkbUp, gibUp FLbutton "Up", 1, 0, 2, 50, 50, 100, 300, -1
+ gkbDown, gibDown FLbutton "Down", 1, 0, 2, 50, 50, 150, 300, -1
+
+#ifdef USE_WIIMOTE_NUNCHUCK
+ gkbZ, gibC FLbutton "Z", 1, 0, 2, 50, 50, 200, 300, -1
+ gkbC, gibZ FLbutton "C", 1, 0, 2, 50, 50, 250, 300, -1
+#end
+
+ gkwii_accelX, gis1 FLslider "Accel X", 0, 1, 0, 1, -1, 400, 35, 0, 350
+ gkwii_accelY, gis2 FLslider "Accel Y", 0, 1, 0, 1, -1, 400, 35, 0, 400
+ gkwii_accelZ, gis3 FLslider "Accel Z", 0, 1, 0, 1, -1, 400, 35, 0, 450
+FLpanelEnd
+
+FLrun
+
+; button bit masks
+#define WII_1 #2#
+#define WII_2 #1#
+#define WII_A #8#
+#define WII_B #4#
+#define WII_HOME #128#
+#define WII_MINUS #16#
+#define WII_PLUS #4096#
+#define WII_LEFT #256#
+#define WII_RIGHT #512#
+#define WII_UP #2048#
+#define WII_DOWN #1024#
+
+
+
+#ifdef USE_WIIMOTE_NUNCHUCK
+#define WII_Z #-33#
+#define WII_C #-34#
+gkwii_ncang init 0
+gkwii_ncmag init 0
+#end
+
+/*
+ Get the state of a wiimote button
+
+ kstate wii_button ibutton
+
+ kstate 1 if pressed/held or 0 if not
+ ibutton the bit mask of the button; use the macros defined
+*/
+opcode wii_button, k, i
+ ibutton xin
+ if (ibutton == $WII_1) then
+ kvalue = gkb1
+ elseif (ibutton == $WII_2) then
+ kvalue = gkb2
+ elseif (ibutton == $WII_A) then
+ kvalue = gkbA
+ elseif (ibutton == $WII_B) then
+ kvalue = gkbB
+ elseif (ibutton == $WII_HOME) then
+ kvalue = gkbHome
+ elseif (ibutton == $WII_MINUS) then
+ kvalue = gkbMinus
+ elseif (ibutton == $WII_PLUS) then
+ kvalue = gkbPlus
+ elseif (ibutton == $WII_LEFT) then
+ kvalue = gkbLeft
+ elseif (ibutton == $WII_RIGHT) then
+ kvalue = gkbRight
+ elseif (ibutton == $WII_UP) then
+ kvalue = gkbUp
+ elseif (ibutton == $WII_DOWN) then
+ kvalue = gkbDown
+#ifdef USE_WIIMOTE_NUNCHUCK
+ elseif (ibutton == $WII_Z) then
+ kvalue = gkbZ
+ elseif (ibutton == $WII_C) then
+ kvalue = gkbC
+#end
+ else
+ kvalue init 0
+ endif
+ xout kvalue
+endop
+
+
+/*
+ Keep an instrument on for as long as a wiimote button is held
+
+ wii_buttonhold ibutton, Sinstrument
+
+ ibutton the bit mask of the button; use the macros defined
+ Sinstrument instrument name to start/stop accordingly
+*/
+opcode wii_buttonhold, 0, iS
+ ibutton, Sinstrument xin
+ kbut = wii_button(ibutton)
+ if (changed:k(kbut) == 1) then
+ if (kbut == 1) then
+ schedulek(Sinstrument, 0, -1)
+ else
+ turnoff2(Sinstrument, 0, 1)
+ endif
+ endif
+endop
+
+opcode wii_buttonhold, 0, iSi
+ ibutton, Sinstrument, ip4 xin
+ kbut = wii_button(ibutton)
+ if (changed:k(kbut) == 1) then
+ if (kbut == 1) then
+ schedulek(Sinstrument, 0, -1, ip4)
+ else
+ turnoff2(Sinstrument, 0, 1)
+ endif
+ endif
+endop
+
+/*
+ Use the wiimote minus and plus buttons to scroll through item indexes
+
+ kitem wii_pager imaxitems [, iteminitial=0, ksetindex=-1]
+
+ kitem the selected item/page
+ imaxindex maximum number of items
+ initialindex initial value
+ ksetindex set the item index directly with this
+*/
+opcode wii_pager, k, ioJ
+ imaxindex, initialindex, ksetindex xin
+ kcurrentitem init initialindex
+
+ kplus = wii_button($WII_PLUS)
+ kminus = wii_button($WII_MINUS)
+ if (kminus == 1 && changed:k(kminus) == 1) then
+ if (kcurrentitem == 0) then
+ kcurrentitem = imaxindex
+ else
+ kcurrentitem -= 1
+ endif
+ elseif (kplus == 1 && changed:k(kplus) == 1) then
+ if (kcurrentitem == imaxindex) then
+ kcurrentitem = 0
+ else
+ kcurrentitem += 1
+ endif
+ elseif (changed:k(ksetindex) == 1) then
+ if (ksetindex >= 0 && ksetindex <= imaxindex) then
+ kcurrentitem = ksetindex
+ endif
+ endif
+ xout kcurrentitem
+endop
+
+
+
+
+
+instr wii_handler
+ icbid = p4
+ SonComplete = strget(p5)
+ SonFailWatchdog = strget(p6)
+ iconnecttimeout = (p7 == 0) ? 1 : p7
+
+ if (icbid > 0) then
+ io_sendstring("callback", sprintf("{\"cbid\": %d, \"success\": true}", icbid))
+ endif
+
+ if (strcmp(SonComplete, "") != 0) then
+ schedule(SonComplete, 0, -1)
+ endif
+
+endin
+
+#end