diff --git a/dev/public/audio/cycfi-q-pitch-test/1a-Low-E.wav b/dev/public/audio/cycfi-q-pitch-test/1a-Low-E.wav new file mode 100644 index 0000000..c644815 Binary files /dev/null and b/dev/public/audio/cycfi-q-pitch-test/1a-Low-E.wav differ diff --git a/dev/public/audio/cycfi-q-pitch-test/2c-A-24th.wav b/dev/public/audio/cycfi-q-pitch-test/2c-A-24th.wav new file mode 100644 index 0000000..912c33c Binary files /dev/null and b/dev/public/audio/cycfi-q-pitch-test/2c-A-24th.wav differ diff --git a/dev/public/audio/cycfi-q-pitch-test/GLines3.wav b/dev/public/audio/cycfi-q-pitch-test/GLines3.wav new file mode 100644 index 0000000..5c49907 Binary files /dev/null and b/dev/public/audio/cycfi-q-pitch-test/GLines3.wav differ diff --git a/dev/public/audio/cycfi-q-pitch-test/GStaccato.wav b/dev/public/audio/cycfi-q-pitch-test/GStaccato.wav new file mode 100644 index 0000000..05213cf Binary files /dev/null and b/dev/public/audio/cycfi-q-pitch-test/GStaccato.wav differ diff --git a/dev/public/audio/freesound/584282__yellowtree__clean-guitar-loop.wav b/dev/public/audio/freesound/584282__yellowtree__clean-guitar-loop.wav new file mode 100644 index 0000000..99ca2a6 Binary files /dev/null and b/dev/public/audio/freesound/584282__yellowtree__clean-guitar-loop.wav differ diff --git a/dev/src/dev.tsx b/dev/src/dev.tsx index 6978a67..ceb4715 100644 --- a/dev/src/dev.tsx +++ b/dev/src/dev.tsx @@ -30,16 +30,14 @@ touchStart(liveAudioContext); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ReactDOM.createRoot(document.getElementById("root")!).render( - - - - }> - } /> - } /> - - - - + + + }> + } /> + } /> + + + ); function Main() { diff --git a/dev/src/dsp-definitions/27-pitchtracker-2.ts b/dev/src/dsp-definitions/27-pitchtracker-2.ts new file mode 100644 index 0000000..f120e7e --- /dev/null +++ b/dev/src/dsp-definitions/27-pitchtracker-2.ts @@ -0,0 +1,44 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +pitchTracker(N, t, x) = loop ~ _ + with { + xHighpassed = fi.highpass(1, 20.0, x); + loop(y) = an.zcr(t, fi.lowpass(N, max(20.0, y), xHighpassed)) * ma.SR * .5; + }; + +average_since(max_count, trig, x) = return + with { + count = ba.countup(max_count, trig); + loop(fb, x) = (fb * (trig == 0)) + x; + return = x : loop ~ _ : /(count + 1); + }; + +value_before_increase(hz) = ba.sAndH(hz > hz', hz'); + +pitchTrackerCleanup(hz) = average_since(9999, hz > hz', hz) : value_before_increase; + +// input = (_ * 0.0) + os.osc(440.0); +input = _; +pitch = input : pitchTracker(2, 0.01); +pitch_clean = pitch : pitchTrackerCleanup; +osc = pitch_clean : os.osc * 0.4; +`; + +const dspDefinition: DspDefinition = { + id: "pitch-tracker-2", + name: "Pitchtracker 2", + description: "Finds pitch 2", + dsp, + type: "offline", + output: ["input", "pitch", "pitch_clean", "osc"], + inputFile: "/audio/cycfi-q-pitch-test/Hammer-Pull High E.wav", + inputOffset: 26000, + outputLength: 26000, + sampleRate: 48000, + channels: 1, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/28-average-since.ts b/dev/src/dsp-definitions/28-average-since.ts new file mode 100644 index 0000000..e111479 --- /dev/null +++ b/dev/src/dsp-definitions/28-average-since.ts @@ -0,0 +1,34 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +average_since(trig, x) = return +with { + count = ba.countup(9999, trig); + loop(fb, x) = (fb * (trig == 0)) + x; + return = x : loop ~ _ : /(count + 1); +}; + +process = average_since; +`; + +const dspDefinition: DspDefinition = { + id: "average-since", + name: "Average since", + description: "Average since", + dsp, + type: "offline", + input: [ + [1, 0, 0, 0, 1, 0, 0, 0], + [4, 3, 2, 1, 2, 3, 4, 5], + ], + output: ["process"], + expect: { + process: [[4, 3.5, 3, 2.5, 2, 2.5, 3, 3.5]], + }, + channels: 2, + sampleRate: 44100, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/29-autocorrelation-2.ts b/dev/src/dsp-definitions/29-autocorrelation-2.ts new file mode 100644 index 0000000..ab47d72 --- /dev/null +++ b/dev/src/dsp-definitions/29-autocorrelation-2.ts @@ -0,0 +1,106 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +// settings + +longest_detectable_period = ma.SR / 80.0; +shortest_detectable_period = ma.SR / 1318.0; + +sample_points = 64 * 2; +sample_stride = 4; + +// utils + +changed(x) = x != x'; +only_changed(x) = ba.if(changed(x), x, -1); +count_stride(total, stride, trig) = ba.countup((total - 1) * stride, trig) / stride : int; + +min_since(trig, x) = return +with { + loop(fb, x) = ba.if(trig, x, min(x, fb)); + return = x : loop ~ _; +}; + +mean_since(trig, x) = return +with { + count = ba.countup(9999, trig); + loop(fb, x) = (fb * (trig == 0)) + x; + return = x : loop ~ _ : /(count + 1); +}; + +// process + +input = _; +envelope = an.amp_follower_ar(0.0,0.1); +start_capture = envelope : >(0.1) : ba.impulsify : @(longest_detectable_period); + +samples_since_start_capture = ba.countup(99999, start_capture); +samples_since_capture_end = samples_since_start_capture - (sample_points - 1) * sample_stride; +start_detect_pitch = samples_since_capture_end >= shortest_detectable_period : ba.impulsify; +trigger_sample_capture = start_capture : count_stride(sample_points, sample_stride) : only_changed; + +each(i,curr_v) = result +with { + samples_ago = (sample_points - i - 1) * sample_stride; + offset_v = curr_v : @(samples_ago); + captured_v = ba.sAndH(trigger_sample_capture(offset_v) == i); + diff_v = curr_v - captured_v; + result = diff_v * diff_v; +}; + +autocorrelated_difference = _ <: par(i, sample_points, each(i)) :> _; + +culumative_mean_difference(v) = result +with { + acd = v : autocorrelated_difference; + mean_since_start_capture = acd : mean_since(v : start_detect_pitch); + result = acd : /(mean_since_start_capture) : min(20.0); +}; + +cmd = culumative_mean_difference; + +best_match_foo(v) = result +with { + min_since_start_capture = v : culumative_mean_difference : min_since(v : start_detect_pitch); + min_changed = min_since_start_capture : changed; + period = v : samples_since_capture_end : ba.sAndH(min_changed); + result = (ma.SR / (period + 0.001)) : max(20.0) : min(20000.0); +}; + +best_match = best_match_foo; +osc = best_match : os.osc * 0.4; +`; + +const dspDefinition: DspDefinition = { + id: "autocorrelation-2", + name: "Auto-correlation 2", + description: "Auto-correlates pitch 2", + dsp, + type: "offline", + output: [ + "input", + "trigger_sample_capture", + "samples_since_capture_end", + "autocorrelated_difference", + "cmd", + "best_match", + "osc", + ], + inputFile: "/audio/cycfi-q-pitch-test/GStaccato.wav", + inputOffset: 25400, + outputLength: 192000, // 27921, // 192000, + + // inputFile: "/audio/cycfi-q-pitch-test/2c-A-24th.wav", + // inputOffset: 7624, + // outputLength: 20000, // 27921, // 192000, + + // inputFile: "/audio/cycfi-q-pitch-test/1a-Low-E.wav", + // inputOffset: 47500, + // outputLength: 55000, // 27921, // 192000, + sampleRate: 48000, + channels: 1, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/30-echoloop.ts b/dev/src/dsp-definitions/30-echoloop.ts index 1222e3d..5c88559 100644 --- a/dev/src/dsp-definitions/30-echoloop.ts +++ b/dev/src/dsp-definitions/30-echoloop.ts @@ -18,7 +18,7 @@ wet(x) = x * wet_volume; dsp(l, r) = (l + r * 0.5), (r : right_channel_delay); -loop(dsp, fb, l) = (l, fb : dsp), fb; +loop(dsp, fb, x) = (x, fb : dsp), fb; lr(l, r) = r, l; alchemist(dsp, x) = x : loop(dsp) ~ fb_loop : !,_,_ : lr; diff --git a/dev/src/dsp-definitions/31-echoloop-live.ts b/dev/src/dsp-definitions/31-echoloop-live.ts index 5be7b15..e1be6bb 100644 --- a/dev/src/dsp-definitions/31-echoloop-live.ts +++ b/dev/src/dsp-definitions/31-echoloop-live.ts @@ -1,12 +1,5 @@ import type { DspDefinition } from "../types"; -// L -> send -> sum ---> L -// ^ -// delay(main) -// R ------------+---- delay(offset) -> R - -// foo[OWL:A], bar[OWL:B] - const dsp = ` import("stdfaust.lib"); @@ -47,25 +40,28 @@ bitcrusher(nbits,x) = return with { // params // -dry_volume_param = hslider("dry", 1.0, 0.0, 1.0, 0.01); -wet_volume_param = hslider("wet", 0.5, 0.0, 1.0, 0.01); +dry_volume_param = hslider("dry", 0.0, 0.0, 1.0, 0.01); +wet_volume_param = hslider("wet", 1.0, 0.0, 1.0, 0.01); button_alt = checkbox("alt[OWL:B1]"); button_snapshot = button("snapshot[OWL:B2]"); -time_param = hslider("time[OWL:A]", 500.0, 0.0, 1000.0, 0.1); -feedback_param = hslider("feedback[OWL:B]", 0.5, 0.0, 1.0, 0.01); +time_param = hslider("time[OWL:A]", 500.0, 0.0, 1000.0, 0.1) : si.smoo; +ping_pong_spacing_param = hslider("pingpong[TEMP]", 0.5, 0.0, 1.0, 0.01) : si.smoo; +feedback_param = hslider("feedback[OWL:B]", 0.5, 0.0, 1.0, 0.01) : si.smoo; // // param layers // -delay_left_time = time_param : layerValue(500.0, button_alt, 0, 10.0) * 0.001 * ma.SR; -delay_right_time = time_param : layerValue(250.0, button_alt, 1, 10.0) * 0.001 * ma.SR; +// superseded +// delay_left_time = time_param : layerValue(500.0, button_alt, 0, 10.0); +// delay_right_time = time_param : layerValue(250.0, button_alt, 1, 10.0); -delay_left = de.delay(ma.SR, delay_left_time); -delay_right = de.delay(ma.SR, delay_right_time); +delay_a_time = time_param * (1.0 - ping_pong_spacing_param); +delay_b_time = time_param * ping_pong_spacing_param; +send_amount = 1.0; // TODO make a param feedback_amount = feedback_param : layerValue(0.5, button_alt, 0, 0.05); bitcrush_amount = feedback_param : layerValue(1.0, button_alt, 1, 0.05); @@ -73,8 +69,19 @@ bitcrush_amount = feedback_param : layerValue(1.0, button_alt, 1, 0.05); // dsp // -feedback_path(x) = (x * feedback_amount) : delay_left : bitcrusher(bitcrush_amount * 16.0); -dsp(l, r) = (l + (r : feedback_path)), (r : delay_right); +delay(t) = de.delay(ma.SR, t * 0.001 * ma.SR); +delay_a = delay(delay_a_time); +delay_b = delay(delay_b_time); + +dsp(l, r) = out +with { + in_l = l; + in_r = r; + feedback = in_r : *(feedback_amount) : delay_b : bitcrusher(bitcrush_amount * 16.0); + out_l = in_l : *(send_amount) : +(feedback) : delay_a; + out_r = in_r : delay_b; + out = out_l,out_r; +}; // // simulated feedback loop @@ -86,7 +93,7 @@ feedback_loop = _; // routing // -loop(dsp, fb, l) = (l, fb : dsp), fb; +loop(dsp, fb, x) = (x, fb : dsp), fb; lr(l, r) = r, l; alchemist(dsp, x) = x : loop(dsp) ~ feedback_loop : !,_,_ : lr; diff --git a/dev/src/dsp-definitions/32-min-since.ts b/dev/src/dsp-definitions/32-min-since.ts new file mode 100644 index 0000000..1380ff7 --- /dev/null +++ b/dev/src/dsp-definitions/32-min-since.ts @@ -0,0 +1,36 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +min_since(trig, x) = return +with { + loop(fb, x) = ba.if(trig, x, min(x, fb)); + return = x : loop ~ _; +}; + +process = min_since; +`; + +const dspDefinition: DspDefinition = { + id: "min-since", + name: "Min since", + description: "Min since", + dsp, + type: "offline", + input: [ + [1, 0, 0, 0, 1, 0, 0, 0], + [4, 3, 2, 3, 9, 3, 4, 5], + ], + output: ["process"], + expect: { + process: [ + [4, 3, 2, 2, 9, 3, 3, 3], + [4, 3, 2, 2, 9, 3, 3, 3], + ], + }, + channels: 2, + sampleRate: 44100, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/33-pitchtracker-3.ts b/dev/src/dsp-definitions/33-pitchtracker-3.ts new file mode 100644 index 0000000..77465b7 --- /dev/null +++ b/dev/src/dsp-definitions/33-pitchtracker-3.ts @@ -0,0 +1,70 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +// settings + +// utils + +reduce_while(reducer, trig, x) = x : loop ~ _ +with { + loop(prev, next) = ba.if(trig, next, reducer(prev, next)); +}; + +max_while = reduce_while(max); + +// process + +input = _; +envelope = an.amp_follower_ar(0.0,0.1); +start_note = envelope : >(0.1) : ba.impulsify; + +amplitude = an.amp_follower_ar(0.0,0.01) : max(0.05); +compress(x) = x / amplitude(x) * 10; + +prep = fi.dcblockerat(40.0) : max(0.0); + +zippy = prep : compress; +tick = prep : compress : >=(9) : ba.impulsify; +tick_rise = tick : ba.countup(9999); + + + +// tick = prep : zippy : >(0.5) : ==(0) : ba.countup(9999); // can get too swamped with similar sized spikes +// osc = best_match : os.osc * 0.4; +`; + +const dspDefinition: DspDefinition = { + id: "pitchtracker-3", + name: "Pitch tracker 3", + description: "Tracks pitch 3", + dsp, + type: "offline", + output: ["input", "tick", "tick_rise", "start_note", "amplitude", "zippy"], + + // inputFile: "/audio/cycfi-q-pitch-test/GStaccato.wav", + // inputOffset: 25400, + // outputLength: 192000, + + // inputFile: "/audio/cycfi-q-pitch-test/2c-A-24th.wav", + // inputOffset: 7624, + // outputLength: 20000, // 27921, // 192000, + + inputFile: "/audio/cycfi-q-pitch-test/1a-Low-E.wav", + inputOffset: 47500, + outputLength: 55000, // 27921, // 192000, + + // inputFile: "/audio/cycfi-q-pitch-test/Tapping D.wav", + // inputOffset: 30400, + // outputLength: 88200, + + // inputFile: "/audio/cycfi-q-pitch-test/Hammer-Pull High E.wav", + // inputOffset: 26000, + // outputLength: 40000, + + sampleRate: 48000, + channels: 1, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/34-additive-synth.ts b/dev/src/dsp-definitions/34-additive-synth.ts new file mode 100644 index 0000000..db496ad --- /dev/null +++ b/dev/src/dsp-definitions/34-additive-synth.ts @@ -0,0 +1,36 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +// osc +a = button("a"); +b = button("b"); +c = button("c"); +gate = a | b | c; +pitch = ba.if(a, 60, ba.if(b, 62, 63)); + +// env +volA = hslider("attack",0.01,0.01,4,0.01); +volD = hslider("decay",2.6,0.01,8,0.01); +volS = hslider("sustain",1,0,1,0.01); +volR = hslider("release",2.0,0.01,8,0.01); + +envelop = en.adsre(volA,volD,volS,volR,gate); + +osc(f) = os.osc(f) + (os.osc(f * 2.0) * 0.2) + (os.osc(f * 6.0) * 0.1); + +fx = osc(pitch : ba.midikey2hz) * envelop * 0.3; + +process = fx <: _,_; +`; + +const dspDefinition: DspDefinition = { + id: "additive-synth", + name: "Additive synth", + description: "Additive synth", + dsp, + type: "live", +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/35-recorded-guitar.ts b/dev/src/dsp-definitions/35-recorded-guitar.ts new file mode 100644 index 0000000..139d5e3 --- /dev/null +++ b/dev/src/dsp-definitions/35-recorded-guitar.ts @@ -0,0 +1,19 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); +process = _ <: *(1.0),*(1.0); +`; + +const dspDefinition: DspDefinition = { + id: "recorded-guitar", + name: "Recorded guitar", + description: + "Plays a freesound recording of a guitar sound from yellowtree with no alterations", + dsp, + type: "live", + inputFile: "/audio/freesound/584282__yellowtree__clean-guitar-loop.wav", + loopLength: 20, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/36-wizard-delay.ts b/dev/src/dsp-definitions/36-wizard-delay.ts new file mode 100644 index 0000000..8859dda --- /dev/null +++ b/dev/src/dsp-definitions/36-wizard-delay.ts @@ -0,0 +1,52 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +delay_max = ma.SR * 5; + +wet_param = 0.4; +dry_param = 0.4; +fb_param = 0.3; +time_param = 1.0; + +mod_speed_param = 3.0; +mod_depth_param = 0.0003; +mod = 1.0 - ((os.osc(mod_speed_param) * 0.5 + 0.5) * mod_depth_param); + +modx = mod * (os.lf_sawpos(time_param * (1 / 3)) + 0.001); + +delay(time, fb, x) = x + fb : de.fdelay(delay_max, time * mod) : fi.highpass(2, 200.0) : fi.lowpass(2, 20000.0); +fb = *(fb_param); +echo(time) = delay(time) ~ fb; + +wet_l = echo(time_param * ma.SR); // * 0.8 +wet_r = echo(time_param * ma.SR); +wet = _ <: wet_l,wet_r : *(wet_param),*(wet_param); +dry = *(dry_param); + +process = _ <: (dry <: _,_),wet :> _,_; +`; + +const dspDefinition: DspDefinition = { + id: "wizard", + name: "Wizard delay", + description: "Rebeltech wizard-based delay pedal DSP", + dsp, + type: "live", + inputFile: "/audio/freesound/584282__yellowtree__clean-guitar-loop.wav", + inputOffset: 0, + loopLength: 16 + 8, +}; + +export default dspDefinition; + +// ideas +// time, offset, l vs r time, feedback, width, eq, overlay, sample rate +// layers? + +// A: time +// B: feedback +// C: EQ +// D: overlay +// E: diff --git a/dev/src/dsp-definitions/37-filter-bank.ts b/dev/src/dsp-definitions/37-filter-bank.ts new file mode 100644 index 0000000..d8dee6b --- /dev/null +++ b/dev/src/dsp-definitions/37-filter-bank.ts @@ -0,0 +1,29 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +wet_param = 1.6; +dry_param = 0.4; + +noise = no.pink_noise : fi.highpass(2, 800.0) : fi.lowpass(2, 3000.0); + +wet = _ : co.compressor_mono(8.0,-24.0,0.001,0.1) : ve.vocoder(16,0.001,0.2,2.0,_,noise) : *(wet_param) <: _,_; + +// wet = _ : an.amp_follower_ar(0.0,0.1) * noise <: _,_; + +dry = *(dry_param); +process = _ <: (dry <: _,_),wet :> _,_; +`; + +const dspDefinition: DspDefinition = { + id: "filter-bank", + name: "Filter bank", + description: "Playing with filter banks, vocoders, panning, EQ", + dsp, + type: "live", + inputFile: "/audio/freesound/584282__yellowtree__clean-guitar-loop.wav", + loopLength: 20, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/38-cloudify.ts b/dev/src/dsp-definitions/38-cloudify.ts new file mode 100644 index 0000000..450cd32 --- /dev/null +++ b/dev/src/dsp-definitions/38-cloudify.ts @@ -0,0 +1,65 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +// +// synth +// + +tune1 = waveform{65,-1,-1,68,-1,-1,-1,-1,65,-1,-1,66,-1,-1,-1,-1}; +tune2 = waveform{-1,-70,-1,-1,-1,-1,-1,-1,-1,-70,-1,-1,-1,-1,-1,-1}; +tune3 = waveform{-1,-1,-72,-1,-1,-1,-1,-1,-1,-1,-72,-1,-1,-1,-1,-1}; +tune4 = waveform{53,-1,-1,-1,56,-1,-1,-1,53,-1,-1,-1,51,-1,-1,-1}; + +tunePlayer(tune) = tuneAmp,tuneFreq +with { + tuneNow = tune,int(os.phasor(16,0.15)) : rdtable; + tuneGate = tuneNow > 0 : ba.sAndH(tuneNow != 0); + tuneAmp = en.adsre(0.0001,1.0,0.0001,3.0,tuneGate); + tuneFreq = tuneNow : ba.sAndH(tuneNow > 0); +}; + +vibrato = +(os.osc(4.0) * 0.1); + +synthVoice(gate) = vibrato : ba.midikey2hz : os.osc : *(gate) : *(0.1); + + +voice1 = tunePlayer(tune1) : synthVoice; +voice2 = tunePlayer(tune2) : synthVoice; +voice3 = tunePlayer(tune3) : synthVoice; +voice4 = tunePlayer(tune4) : synthVoice; + +synth = voice1 + voice2 + voice3 + voice4 <: _,_; + +// +// noise +// + +noise = no.pink_noise : fi.highpass(2, 3200.0) : fi.lowpass(2, 12800.0); + +// +// program +// + +total = 2; +each(i) = _ : de.delay(ma.SR * 1.0, ma.SR * 1.0 * ((i + 1) / total)) : sp.panner(i * (total - 1)) : _,_; +delays = _ <: par(i, total, each(i)) :> _,_; +noiseMaker = ve.vocoder(16,0.001,0.2,2.0,_,noise) : *(1.0) <: _,_; +noiseMakerWithDry = _ <: _,noiseMaker :> _; + +wet = _ : noiseMakerWithDry : delays : _,_; +dry = *(1.0); +process = synth <: (dry <: _,_),wet :> _,_; +`; + +const dspDefinition: DspDefinition = { + id: "cloudify", + name: "Cloudify", + description: + "Adds a set of panned delay lines fading in and out and mixes in sound effects from a secondary input", + dsp, + type: "live", +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/39-autosampler.ts b/dev/src/dsp-definitions/39-autosampler.ts new file mode 100644 index 0000000..b3c536a --- /dev/null +++ b/dev/src/dsp-definitions/39-autosampler.ts @@ -0,0 +1,52 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +threshold_on = 0.2; +threshold_off = 0.02; +release = 0.004; +table_size = 100000; +sampling_time = 10300; +sampling_time_2 = sampling_time * 2; + +on_up(x) = x > x'; +invert_boolean(x) = x == 0; +env_threshold(on, off, x) = x > on, x < off : ba.on_and_off : >(0); +counter(trig) = on_up(trig) : + ~ _; + +input = _; +env = input : an.amp_follower_ar(0.0, release); +env_active = env : env_threshold(threshold_on, threshold_off); +env_on = env_active : ba.impulsify; +gated = input * env_active; + +env_active_timer = env_active : invert_boolean : ba.countup(table_size); +env_active_sweep = env_active_timer : ba.sweep(table_size); + +record_head = env_active_sweep * (env_active_sweep <= sampling_time * 2); +playback_head = ((env_active_sweep - 1) % sampling_time) + 1; +playback_head2 = max(0, (((env_active_sweep - (sampling_time / 2)) - 1) % sampling_time) + 1); + +vol(v) = cos(v * ma.PI * 2.0) * -0.5 + 0.5; + +table(pb, x) = rwtable(table_size, 0.0, record_head(x), x, pb(x)) * vol(pb(x) / sampling_time); +fx(x) = table(playback_head, x) + table(playback_head2, x) + (x * max(0.0, 1.0 - (env_active_sweep / sampling_time) * 2.0)); +process = fx; +`; + +const dspDefinition: DspDefinition = { + id: "autosampler", + name: "Autosampler", + description: "Grabbing a chunk of audio based on an env", + dsp, + type: "offline", + inputFile: "/audio/cycfi-q-pitch-test/GLines3.wav", + channels: 1, + sampleRate: 48000, + output: ["env_active", "process"], + outputLength: 48000 * 20, + inputOffset: 18000 + 13000 + 300000, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/40-pulse-delay.ts b/dev/src/dsp-definitions/40-pulse-delay.ts new file mode 100644 index 0000000..d123467 --- /dev/null +++ b/dev/src/dsp-definitions/40-pulse-delay.ts @@ -0,0 +1,46 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +delay_max = ma.SR * 5; + +wet_param = 1.0; +dry_param = 1.0; +fb_param = 0.8; +time_param = 1.0 * ma.SR; + +squash(x) = 1.0 - (1.0 / (x + 1.0)); +stretch(mn, mx, x) = (x - mn) / (mx - mn) : min(1.0) : max(0.0); +env = *(10.0) : an.amp_follower_ar(0.1, 0.1) : squash : stretch(0.2, 0.7); +swell(x) = x * env(x); + +mod_speed_param = 3.0; +mod_depth_param = 0.0002; +mod = 1.0 - ((os.osc(mod_speed_param) * 0.5 + 0.5) * mod_depth_param); + +delay(time, fb, x) = x + fb : de.fdelay(delay_max, time * mod) : fi.highpass(2, 200.0) : fi.lowpass(2, 2000.0); +fb = *(fb_param); +echo(time) = delay(time) ~ fb; + +wet = swell <: echo(time_param) :> _; +dry(x) = x * dry_param * (1.0 - env(x) * 0.9); + +process = _ <: (dry,wet) :> _; + +`; + +const dspDefinition: DspDefinition = { + id: "pulse-delay", + name: "Pulse delay", + description: "Smooth off transients before high feedback clean delay", + dsp, + type: "offline", + inputFile: "/audio/cycfi-q-pitch-test/GLines3.wav", + channels: 1, + sampleRate: 48000, + outputLength: 48000 * 20, + inputOffset: 18000 + 13000, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/41-autosampler-2.ts b/dev/src/dsp-definitions/41-autosampler-2.ts new file mode 100644 index 0000000..1bea930 --- /dev/null +++ b/dev/src/dsp-definitions/41-autosampler-2.ts @@ -0,0 +1,49 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +table_size = 999999; +start = 4000.0; +len = 581.0; +end = start + len; + +raw = _; + +// use trigger to start! + +no_play = (ba.time > start) & (ba.time <= end); +rec_active = ba.time > start; +slide = (ba.time - start) / len * 0.1; +slide_floor = float(int(slide)); +slide_remain = slide % 1.0; + +rec_head = rec_active : ba.sweep(9999999.0); +play_head = ba.if(no_play, rec_head, no_play' == 0 : ba.sweep(end - start) + 1); +play_head_progress = play_head / len; + +cross(a, b, f, x) = a(x) * (1.0 - f) + b(x) * f; + +table(p) = rwtable(table_size, 0.0, int(rec_head), _, int(p)); + +microloop(r) = cross(table(play_head + len * (r + 1.0)), table(play_head + len * r), play_head_progress); + +wet = cross(microloop(slide_floor),microloop(slide_floor + 1.0), slide_remain); +out = cross(_,wet,min(rec_head / 1000.0, 1.0)); +`; + +const dspDefinition: DspDefinition = { + id: "autosampler-2", + name: "Autosampler 2", + description: "Grabbing a single cycle of audio and osc it", + dsp, + type: "offline", + channels: 1, + sampleRate: 48000, + inputFile: "/audio/cycfi-q-pitch-test/1a-Low-E.wav", + inputOffset: 47500 + 5000 + 30000, + outputLength: 200000, + output: ["raw", "out"], +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/42-multiband-filter.ts b/dev/src/dsp-definitions/42-multiband-filter.ts new file mode 100644 index 0000000..bd8fe4b --- /dev/null +++ b/dev/src/dsp-definitions/42-multiband-filter.ts @@ -0,0 +1,76 @@ +import type { DspDefinition } from "../types"; + +const dsp = ` +import("stdfaust.lib"); + +filter_top_freq = 625.0; +highest_freq = 1250.0; +smallest_cycle = ma.SR / highest_freq; +filter_bands = 8; + +safe_divide(num, denom) = num / max(denom, 1) - num * (denom == 0); + +pitch(x) = found_pitch,0 +with { + crossed_up(x) = (x' < 0) & (x >= 0); + + crossed = crossed_up(x); + counted = ba.countup(ma.SR, crossed'); + + cycle_pulse = counted : ba.if(crossed, _, 0.0); + cycle_held = counted : ba.sAndH(crossed); + matched = ba.if(crossed, abs(cycle_held' - cycle_held) < 5.0, -1.0); + + count_matches(x) = ba.pulse_countup(x != 0, max(0.0, x)); + match_counted = matched : count_matches; + + found_pitch = (match_counted >= 4.0) * cycle_held; +}; + +env_threshold(on, off, x) = x > on, x < off : ba.on_and_off : >(0); +gate = an.amp_follower_ar(0.0, 0.004) : env_threshold(0.1, 0.02); + +filter = fi.mth_octave_filterbank(3, 1, filter_top_freq, filter_bands); +per_band_pitch = par(i, filter_bands, pitch : _,!); + +ggg(x) = gate(x) * x; +input = ggg; + +process(x) = cycle_caught <: _,_ +with { + note_on = gate(x); + + catch(x) = x; + + filter_highs(x) = x * (x >= smallest_cycle); + + cycle = x : *(note_on) : filter : per_band_pitch :> *(note_on); + cycle_caught = cycle : filter_highs : catch; + + hz = safe_divide(ma.SR, cycle_caught); + hz_rounded = hz : ba.hz2midikey : +(0.5) : floor : -(0.5) : ba.midikey2hz; + osc = os.osc(hz_rounded) * 0.1 * (cycle_caught > 0.0); +}; + +// process_625 = input : filter : per_band_pitch : ba.selector(0, filter_bands) <: _,_; +// process_312 = input : filter : per_band_pitch : ba.selector(1, filter_bands) <: _,_; +// process_156 = input : filter : per_band_pitch : ba.selector(2, filter_bands) <: _,_; +// process_78 = input : filter : per_band_pitch : ba.selector(3, filter_bands) <: _,_; +`; + +const dspDefinition: DspDefinition = { + id: "multiband-filter", + name: "Multiband filter", + description: "Multiband filter", + dsp, + type: "offline", + inputFile: "/audio/cycfi-q-pitch-test/GLines3.wav", + channels: 1, + sampleRate: 48000, + // output: ["input", "process_625", "process_312", "process_156", "process_78"], + output: ["input", "process"], + outputLength: 48000 * 5, + inputOffset: 18000 + 13000 + 402920, +}; + +export default dspDefinition; diff --git a/dev/src/dsp-definitions/all.ts b/dev/src/dsp-definitions/all.ts index d07310f..b245644 100644 --- a/dev/src/dsp-definitions/all.ts +++ b/dev/src/dsp-definitions/all.ts @@ -26,8 +26,22 @@ import zeroCrossingDetection from "./23-zero-crossing-detection"; import pitchtracker from "./24-pitchtracker"; import autocorrelation from "./25-autocorrelation"; import envelopeDetector from "./26-envelope-detector"; +import pitchtracker2 from "./27-pitchtracker-2"; +import averageSince from "./28-average-since"; +import autocorrelation2 from "./29-autocorrelation-2"; import echoloop from "./30-echoloop"; import echoloopLive from "./31-echoloop-live"; +import minSince from "./32-min-since"; +import pitchtracker3 from "./33-pitchtracker-3"; +import additiveSynth from "./34-additive-synth"; +import recordedGuitar from "./35-recorded-guitar"; +import wizardDelay from "./36-wizard-delay"; +import filterBank from "./37-filter-bank"; +import cloudify from "./38-cloudify"; +import autosampler from "./39-autosampler"; +import pulseDelay from "./40-pulse-delay"; +import autosampler2 from "./41-autosampler-2"; +import multibandFilter from "./42-multiband-filter"; export const all: DspDefinition[] = [ sineWave, @@ -56,6 +70,20 @@ export const all: DspDefinition[] = [ pitchtracker, autocorrelation, envelopeDetector, + pitchtracker2, + averageSince, + autocorrelation2, echoloop, echoloopLive, + minSince, + pitchtracker3, + additiveSynth, + recordedGuitar, + wizardDelay, + filterBank, + cloudify, + autosampler, + pulseDelay, + autosampler2, + multibandFilter, ]; diff --git a/dev/src/faust-live-player.ts b/dev/src/faust-live-player.ts index c15001a..96d65d0 100644 --- a/dev/src/faust-live-player.ts +++ b/dev/src/faust-live-player.ts @@ -4,12 +4,16 @@ import { DspDefinition, isDspLive } from "./types"; import { compile, FaustNode } from "mosfez-faust/faust"; import type { UIItem } from "mosfez-faust/faust"; import { audioSource } from "mosfez-faust/audio-source"; +import { fetchFile } from "./fetch"; +import { toAudioBuffer } from "mosfez-faust/convert"; + +let total = 0; export type UseFaustLivePlayerResult = { ui: UIItem[]; params: string[]; node: FaustNode; - source?: MediaStreamAudioSourceNode; + source?: MediaStreamAudioSourceNode | AudioBufferSourceNode; audioContext?: AudioContext; }; @@ -17,22 +21,44 @@ export function useFaustLivePlayer( audioContext: AudioContext, dspDefinition: DspDefinition ): UseFaustLivePlayerResult | undefined { - const effectCountRef = useRef(0); const audioNode = useRef(); const [result, setResult] = useState(); useEffect(() => { - if (!isDspLive(dspDefinition)) return; - const count = ++effectCountRef.current; + if (!isDspLive(dspDefinition) || total >= 1) return; + total++; - compile(audioContext, dspDefinition.dsp).then(async (node) => { - if (effectCountRef.current !== count) return; + let source: MediaStreamAudioSourceNode | AudioBufferSourceNode | undefined; - let source: MediaStreamAudioSourceNode | undefined; + compile(audioContext, dspDefinition.dsp).then(async (node) => { + const { inputFile, inputOffset = 0, loopLength = 0 } = dspDefinition; if (node.numberOfInputs > 0) { - source = await audioSource(audioContext); - source.connect(node); + if (inputFile) { + const response = await fetchFile(inputFile); + if (!response.ok) { + throw new Error(`Could not load sound file "${inputFile}"`); + } + const arrayBuffer = await response.arrayBuffer(); + + source = audioContext.createBufferSource(); + + source.buffer = await toAudioBuffer(arrayBuffer, audioContext); + source.connect(node); + node.connect(audioContext.destination); + if (inputOffset) { + source.loopStart = inputOffset; + source.loop = true; + } + if (loopLength) { + source.loopEnd = loopLength; + source.loop = true; + } + source.start(0, inputOffset); + } else { + source = await audioSource(audioContext); + source.connect(node); + } } node.connect(audioContext.destination); @@ -52,10 +78,15 @@ export function useFaustLivePlayer( }); return () => { + total--; if (audioNode.current) { audioNode.current.disconnect(); audioNode.current.destroy(); } + + if (source && source instanceof AudioBufferSourceNode) { + source.stop(); + } }; }, [dspDefinition]); diff --git a/dev/src/faust-offline-renderer.ts b/dev/src/faust-offline-renderer.ts index 83013a1..69422b3 100644 --- a/dev/src/faust-offline-renderer.ts +++ b/dev/src/faust-offline-renderer.ts @@ -114,7 +114,7 @@ async function faustOfflineRender( if (inputFile) { const response = await fetchFile(inputFile); if (!response.ok) { - throw new Error(`Could not load sound file "${input}"`); + throw new Error(`Could not load sound file "${inputFile}"`); } const arrayBuffer = await response.arrayBuffer(); const audioBuffer = await toAudioBuffer(arrayBuffer, offlineContext); diff --git a/dev/src/offline-visualisations.tsx b/dev/src/offline-visualisations.tsx index 92089f2..9b33041 100644 --- a/dev/src/offline-visualisations.tsx +++ b/dev/src/offline-visualisations.tsx @@ -177,7 +177,9 @@ export function Plot(props: PlotProps) { startDragPan.current = pan; }; - const handleWheel = (e: React.WheelEvent) => { + const handleWheel = (e) => { + e.stopPropagation(); + e.preventDefault(); const { pixelY } = normalizeWheel(e); const dir = pixelY < 0 ? 2 : 0.5; setPlotState(([p, z, h]) => { @@ -208,6 +210,8 @@ export function Plot(props: PlotProps) { const canvas = canvasRef.current; const drawContext = canvas?.getContext("2d"); + canvas?.addEventListener("wheel", handleWheel); + if (drawContext) { drawContext.clearRect(0, 0, width, height); const max = width / zoomWidth; @@ -227,7 +231,19 @@ export function Plot(props: PlotProps) { drawContext.fillRect(px, Math.round(y), pw, 1); } } - }, [output, width, height, highlight, pan, zoomWidth, zoomHeight]); + return () => { + canvas?.removeEventListener("wheel", handleWheel); + }; + }, [ + output, + width, + height, + highlight, + pan, + zoomWidth, + zoomHeight, + handleWheel, + ]); return ( ); } diff --git a/dev/src/types.ts b/dev/src/types.ts index 05f2549..032eb61 100644 --- a/dev/src/types.ts +++ b/dev/src/types.ts @@ -9,6 +9,9 @@ export type DspDefinitionCommon = { export type DspDefinitionLive = DspDefinitionCommon & { type: "live"; dsp: string; + inputOffset?: number; + inputFile?: string; + loopLength?: number; }; export type DspDefinitionOffline = DspDefinitionCommon & {