From 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 13 Apr 2025 18:48:02 +0100 Subject: initial --- site/udo/707sd.udo | 271 ++++ site/udo/DEPRECATE_SORT_opcodes.udo | 211 ++++ site/udo/_TESTS/feedback_test.csd | 41 + site/udo/_TESTS/mixer_emulator.csd | 132 ++ site/udo/_TESTS/mixer_emulator_1604a.csd | 144 +++ site/udo/__config__.dist.udo | 44 + site/udo/addsub.udo | 170 +++ site/udo/aodb.udo | 80 ++ site/udo/array_3d.udo | 132 ++ site/udo/array_tools.udo | 235 ++++ site/udo/autorecord.udo | 108 ++ site/udo/bpmdetect.udo | 57 + site/udo/bsamp.udo | 206 ++++ site/udo/bussing.udo | 268 ++++ site/udo/bussing_quad.udo | 172 +++ site/udo/chop.udo | 715 +++++++++++ site/udo/chord_detect.udo | 154 +++ site/udo/chords.udo | 455 +++++++ site/udo/convolutiondb.udo | 110 ++ site/udo/cs81z.udo | 208 ++++ site/udo/csv.udo | 75 ++ site/udo/delays.udo | 42 + site/udo/experimental.udo | 41 + site/udo/feedback.udo | 164 +++ site/udo/fftconvolve.udo | 116 ++ site/udo/fnml/clay_workings.csd | 58 + site/udo/fnml/instrument_automel.udo | 89 ++ site/udo/fnml/instrument_gchord1.udo | 260 ++++ site/udo/fnml/instrument_portchord.udo | 132 ++ site/udo/fnml/instrument_sineblips.udo | 81 ++ site/udo/fnml/instrument_tikbank.udo | 78 ++ site/udo/fnml/instrument_tikclay.udo | 118 ++ site/udo/fnml/instrument_vocal.udo | 73 ++ site/udo/fnml/transition_click.udo | 143 +++ site/udo/fnml/transition_mburn.udo | 186 +++ site/udo/fnml/transition_snare.udo | 140 +++ site/udo/fnml/transition_snare_preSOUNDDB.udo | 133 ++ site/udo/fnml/transition_test.csd | 39 + site/udo/fnml/transitional/base.udo | 141 +++ site/udo/frequency_tools.udo | 375 ++++++ site/udo/fx_autoglitch.udo | 470 +++++++ site/udo/fx_autoglitchbeat.udo | 168 +++ site/udo/host_platform.udo | 45 + site/udo/host_tools.udo | 69 ++ site/udo/interop.udo | 178 +++ site/udo/interop.web.udo | 172 +++ site/udo/json.udo | 562 +++++++++ site/udo/lagdetect.udo | 59 + site/udo/legacy/sequencing_melodic.udo | 831 +++++++++++++ site/udo/legacy/sequencing_melodic_persistence.udo | 246 ++++ site/udo/mfcc_match.udo | 178 +++ site/udo/midi.udo | 65 + site/udo/midimap.udo | 247 ++++ site/udo/oprepare.udo | 93 ++ site/udo/pgdb.udo | 313 +++++ site/udo/pvs_fsegproc.udo | 135 ++ site/udo/pvs_fulltabproc.udo | 197 +++ site/udo/pvs_tabproc.udo | 640 ++++++++++ site/udo/pvs_tools.udo | 31 + site/udo/quad.udo | 79 ++ site/udo/sample_level.udo | 90 ++ site/udo/sampling.udo | 77 ++ site/udo/scss/base.udo | 1046 ++++++++++++++++ site/udo/scss/elasticlip.udo | 823 ++++++++++++ site/udo/scss/elasticlip_arranger.udo | 213 ++++ site/udo/scss/elasticlip_sequencer.udo | 133 ++ site/udo/scss/mixer/_effects.udo | 247 ++++ site/udo/scss/mixer/base.udo | 253 ++++ site/udo/scss/mixer/test.csd | 41 + site/udo/scss/persistence.udo | 366 ++++++ site/udo/scss/scss_persistence_test.csd | 100 ++ site/udo/scss/scss_test.csd | 125 ++ site/udo/scss/seqtable.udo | 196 +++ site/udo/sequencing.udo | 329 +++++ site/udo/sequencing_melodic.udo | 807 ++++++++++++ site/udo/sequencing_melodic_persistence.udo | 275 +++++ site/udo/sequencing_melodic_persistence.web.udo | 213 ++++ site/udo/sequencing_melodic_portamento.udo | 310 +++++ site/udo/sequencing_scheduled.udo | 286 +++++ site/udo/sequencing_table.udo | 370 ++++++ site/udo/sound_db.udo | 152 +++ site/udo/sound_melsys.udo | 215 ++++ site/udo/sound_sdb.udo | 271 ++++ site/udo/sounddb.udo | 270 ++++ site/udo/soundfont.udo | 68 + site/udo/soundfonts/Rhodes/Crysrhod.sf2 | Bin 0 -> 212560 bytes .../soundfonts/Rhodes/Galaxy_Electric_Pianos.sf2 | Bin 0 -> 30299302 bytes site/udo/soundfonts/Rhodes/JR_elepiano.sf2 | Bin 0 -> 470228 bytes site/udo/soundfonts/Rhodes/KR-O5R-Operator.sf2 | Bin 0 -> 23758082 bytes site/udo/soundfonts/Rhodes/jRhodes3c-stereo.sf2 | Bin 0 -> 32793738 bytes site/udo/soundfonts/Rhodes/vibra.sf2 | Bin 0 -> 201844 bytes site/udo/soundxdb.udo | 201 +++ site/udo/soundxdb_extract.udo | 67 + site/udo/spectral_sampler.udo | 126 ++ site/udo/spectral_transforms.udo | 358 ++++++ site/udo/string_tools.udo | 375 ++++++ site/udo/synth_drums.udo | 229 ++++ site/udo/synth_instruments.udo | 78 ++ site/udo/tab2wav.udo | 151 +++ site/udo/table_tools.udo | 244 ++++ site/udo/tabproc.udo | 217 ++++ site/udo/tempo_tools.udo | 72 ++ site/udo/transient_detect.udo | 216 ++++ site/udo/twigs/checkpointing.udo | 120 ++ site/udo/twigs/transforms.udo | 285 +++++ site/udo/twigs/twigs.udo | 444 +++++++ site/udo/twist/automation.udo | 241 ++++ site/udo/twist/checkpointing.udo | 127 ++ site/udo/twist/checkpointing_hold.udo | 196 +++ site/udo/twist/transform_api.udo | 421 +++++++ site/udo/twist/transforms.udo | 25 + site/udo/twist/transforms/amplitude.udo | 179 +++ site/udo/twist/transforms/cross_processing.udo | 176 +++ site/udo/twist/transforms/delay.udo | 72 ++ site/udo/twist/transforms/filter.udo | 172 +++ site/udo/twist/transforms/frequency.udo | 65 + site/udo/twist/transforms/general.udo | 28 + site/udo/twist/transforms/generate.udo | 363 ++++++ site/udo/twist/transforms/granular.udo | 138 +++ site/udo/twist/transforms/harmonic.udo | 142 +++ site/udo/twist/transforms/reverb.udo | 80 ++ site/udo/twist/transforms/spectral.udo | 642 ++++++++++ site/udo/twist/transforms/warping.udo | 210 ++++ site/udo/twist/twist.udo | 1304 ++++++++++++++++++++ site/udo/txt_tools.udo | 98 ++ site/udo/uniqueid.udo | 81 ++ site/udo/wavetables.udo | 31 + site/udo/wiimote.udo | 227 ++++ site/udo/wiimote_fltk.udo | 200 +++ 129 files changed, 26852 insertions(+) create mode 100755 site/udo/707sd.udo create mode 100755 site/udo/DEPRECATE_SORT_opcodes.udo create mode 100755 site/udo/_TESTS/feedback_test.csd create mode 100755 site/udo/_TESTS/mixer_emulator.csd create mode 100755 site/udo/_TESTS/mixer_emulator_1604a.csd create mode 100755 site/udo/__config__.dist.udo create mode 100755 site/udo/addsub.udo create mode 100755 site/udo/aodb.udo create mode 100755 site/udo/array_3d.udo create mode 100755 site/udo/array_tools.udo create mode 100755 site/udo/autorecord.udo create mode 100755 site/udo/bpmdetect.udo create mode 100755 site/udo/bsamp.udo create mode 100755 site/udo/bussing.udo create mode 100755 site/udo/bussing_quad.udo create mode 100755 site/udo/chop.udo create mode 100755 site/udo/chord_detect.udo create mode 100755 site/udo/chords.udo create mode 100755 site/udo/convolutiondb.udo create mode 100755 site/udo/cs81z.udo create mode 100755 site/udo/csv.udo create mode 100755 site/udo/delays.udo create mode 100755 site/udo/experimental.udo create mode 100755 site/udo/feedback.udo create mode 100755 site/udo/fftconvolve.udo create mode 100755 site/udo/fnml/clay_workings.csd create mode 100755 site/udo/fnml/instrument_automel.udo create mode 100755 site/udo/fnml/instrument_gchord1.udo create mode 100755 site/udo/fnml/instrument_portchord.udo create mode 100755 site/udo/fnml/instrument_sineblips.udo create mode 100755 site/udo/fnml/instrument_tikbank.udo create mode 100755 site/udo/fnml/instrument_tikclay.udo create mode 100755 site/udo/fnml/instrument_vocal.udo create mode 100755 site/udo/fnml/transition_click.udo create mode 100755 site/udo/fnml/transition_mburn.udo create mode 100755 site/udo/fnml/transition_snare.udo create mode 100755 site/udo/fnml/transition_snare_preSOUNDDB.udo create mode 100755 site/udo/fnml/transition_test.csd create mode 100755 site/udo/fnml/transitional/base.udo create mode 100755 site/udo/frequency_tools.udo create mode 100755 site/udo/fx_autoglitch.udo create mode 100755 site/udo/fx_autoglitchbeat.udo create mode 100755 site/udo/host_platform.udo create mode 100755 site/udo/host_tools.udo create mode 100755 site/udo/interop.udo create mode 100755 site/udo/interop.web.udo create mode 100755 site/udo/json.udo create mode 100755 site/udo/lagdetect.udo create mode 100755 site/udo/legacy/sequencing_melodic.udo create mode 100755 site/udo/legacy/sequencing_melodic_persistence.udo create mode 100755 site/udo/mfcc_match.udo create mode 100755 site/udo/midi.udo create mode 100755 site/udo/midimap.udo create mode 100755 site/udo/oprepare.udo create mode 100755 site/udo/pgdb.udo create mode 100755 site/udo/pvs_fsegproc.udo create mode 100755 site/udo/pvs_fulltabproc.udo create mode 100755 site/udo/pvs_tabproc.udo create mode 100755 site/udo/pvs_tools.udo create mode 100755 site/udo/quad.udo create mode 100755 site/udo/sample_level.udo create mode 100755 site/udo/sampling.udo create mode 100755 site/udo/scss/base.udo create mode 100755 site/udo/scss/elasticlip.udo create mode 100755 site/udo/scss/elasticlip_arranger.udo create mode 100755 site/udo/scss/elasticlip_sequencer.udo create mode 100755 site/udo/scss/mixer/_effects.udo create mode 100755 site/udo/scss/mixer/base.udo create mode 100755 site/udo/scss/mixer/test.csd create mode 100755 site/udo/scss/persistence.udo create mode 100755 site/udo/scss/scss_persistence_test.csd create mode 100755 site/udo/scss/scss_test.csd create mode 100755 site/udo/scss/seqtable.udo create mode 100755 site/udo/sequencing.udo create mode 100755 site/udo/sequencing_melodic.udo create mode 100755 site/udo/sequencing_melodic_persistence.udo create mode 100755 site/udo/sequencing_melodic_persistence.web.udo create mode 100755 site/udo/sequencing_melodic_portamento.udo create mode 100755 site/udo/sequencing_scheduled.udo create mode 100755 site/udo/sequencing_table.udo create mode 100755 site/udo/sound_db.udo create mode 100755 site/udo/sound_melsys.udo create mode 100755 site/udo/sound_sdb.udo create mode 100755 site/udo/sounddb.udo create mode 100755 site/udo/soundfont.udo create mode 100755 site/udo/soundfonts/Rhodes/Crysrhod.sf2 create mode 100755 site/udo/soundfonts/Rhodes/Galaxy_Electric_Pianos.sf2 create mode 100755 site/udo/soundfonts/Rhodes/JR_elepiano.sf2 create mode 100755 site/udo/soundfonts/Rhodes/KR-O5R-Operator.sf2 create mode 100755 site/udo/soundfonts/Rhodes/jRhodes3c-stereo.sf2 create mode 100755 site/udo/soundfonts/Rhodes/vibra.sf2 create mode 100755 site/udo/soundxdb.udo create mode 100755 site/udo/soundxdb_extract.udo create mode 100755 site/udo/spectral_sampler.udo create mode 100755 site/udo/spectral_transforms.udo create mode 100755 site/udo/string_tools.udo create mode 100755 site/udo/synth_drums.udo create mode 100755 site/udo/synth_instruments.udo create mode 100755 site/udo/tab2wav.udo create mode 100755 site/udo/table_tools.udo create mode 100755 site/udo/tabproc.udo create mode 100755 site/udo/tempo_tools.udo create mode 100755 site/udo/transient_detect.udo create mode 100755 site/udo/twigs/checkpointing.udo create mode 100755 site/udo/twigs/transforms.udo create mode 100755 site/udo/twigs/twigs.udo create mode 100755 site/udo/twist/automation.udo create mode 100755 site/udo/twist/checkpointing.udo create mode 100755 site/udo/twist/checkpointing_hold.udo create mode 100755 site/udo/twist/transform_api.udo create mode 100755 site/udo/twist/transforms.udo create mode 100755 site/udo/twist/transforms/amplitude.udo create mode 100755 site/udo/twist/transforms/cross_processing.udo create mode 100755 site/udo/twist/transforms/delay.udo create mode 100755 site/udo/twist/transforms/filter.udo create mode 100755 site/udo/twist/transforms/frequency.udo create mode 100755 site/udo/twist/transforms/general.udo create mode 100755 site/udo/twist/transforms/generate.udo create mode 100755 site/udo/twist/transforms/granular.udo create mode 100755 site/udo/twist/transforms/harmonic.udo create mode 100755 site/udo/twist/transforms/reverb.udo create mode 100755 site/udo/twist/transforms/spectral.udo create mode 100755 site/udo/twist/transforms/warping.udo create mode 100755 site/udo/twist/twist.udo create mode 100755 site/udo/txt_tools.udo create mode 100755 site/udo/uniqueid.udo create mode 100755 site/udo/wavetables.udo create mode 100755 site/udo/wiimote.udo create mode 100755 site/udo/wiimote_fltk.udo (limited to 'site/udo') 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 @@ + + +-odac +;-+rtmidi=alsa -Q hw:1 -M hw:1 -odac + + +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 + + + + + +i2 0 3600 + + + + \ 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 @@ + + +-odac8 +;--env:INCDIR="D:/Documents/Csound/_UDO" +--omacro:HOST=AUDIO +;-m0 + + +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 + + + +i"controller" 0 3600 + + \ 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 @@ + + +-odac +;--env:INCDIR="D:/Documents/Csound/_UDO" +--omacro:HOST=AUDIO +;-m0 + + +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 + + + +i"controller" 0 3600 + + \ 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 @@ + + +-odac + + +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 + + + + +i"play_clay_roll" 0 6 + + \ 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 @@ + + +-odac + + +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 + + + + +i"master" 0 510 +i"debug" 0 501 + + \ 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 @@ + + +-odac + + +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 + + + +i"test_boot" 0 60 + + \ 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 @@ + + +-odac + + +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 + + + +i1 0.1 1 + + + \ 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 @@ + + +-odac +-m0 +-M0 + + +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 + + + + + +f0 z + + \ 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 Binary files /dev/null and b/site/udo/soundfonts/Rhodes/Crysrhod.sf2 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 Binary files /dev/null and b/site/udo/soundfonts/Rhodes/Galaxy_Electric_Pianos.sf2 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 Binary files /dev/null and b/site/udo/soundfonts/Rhodes/JR_elepiano.sf2 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 Binary files /dev/null and b/site/udo/soundfonts/Rhodes/KR-O5R-Operator.sf2 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 Binary files /dev/null and b/site/udo/soundfonts/Rhodes/jRhodes3c-stereo.sf2 differ diff --git a/site/udo/soundfonts/Rhodes/vibra.sf2 b/site/udo/soundfonts/Rhodes/vibra.sf2 new file mode 100755 index 0000000..7c1c415 Binary files /dev/null and b/site/udo/soundfonts/Rhodes/vibra.sf2 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 -- cgit v1.2.3