#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