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 & {