ersatzben

MEMORY PALACE

Sep 7th, 2025
999
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.99 KB | None | 0 0
  1. /*
  2. ▖ ▖ ▄▖ ▜
  3. ▛▖▞▌█▌▛▛▌▛▌▛▘▌▌ ▙▌▀▌▐ ▀▌▛▘█▌
  4. ▌▝ ▌▙▖▌▌▌▙▌▌ ▙▌ ▌ █▌▐▖█▌▙▖▙▖
  5. ▄▌
  6. multi-tap delay with chorus/reverb capabilities
  7. by @ersatzben
  8.  
  9. - channels parameter = number of taps
  10. - first tap occurs at time_ms, last tap at time_ms * span
  11. - curve parameter controls (non)linearity of how taps are distributed across that period
  12. - curve: 1 = linear, >1 bunched at start (early reflections), <1 bunched at end (reverse effect)
  13. - all other controls as described below
  14.  
  15. SETUP
  16. - two inputs
  17. - two outputs
  18.  
  19. */
  20.  
  21.  
  22. History counter(0);
  23.  
  24. // ---------- TAP PARAMETERS --------------
  25. Param channels(42, min=1, max=50); // number of taps
  26. Param time_ms(50, min=0.01, max=250); // first tap time (ms)
  27. Param span(7, min=2, max=10); // last tap time (as multiple of first)
  28. Param curve(3.2, min=0.1, max=5); // distribution of taps
  29. Param diffusion(0.25, min=0, max=1); // blur (jitter + allpass)
  30. Param modRate(0.0001, min=0, max=10); // modulation rate
  31. Param modDepth(0.002, min=0, max=0.02); // modulation depth
  32.  
  33. // ---------- FEEDBACK PARAMETERS ---------
  34. Param fb(0.8, min=0, max=0.99999); // feedback
  35. Param damp(0.05, min=0, max=0.99); // LPF damping
  36. Param ap_coeff(0.6, min=-0.7, max=0.7); // allpass coefficient
  37. Param hp_hz(25, min=5, max=200); // HPF bass control
  38. Param width(1.0, min=0, max=1); // stereo spread of taps
  39. Param freeze(0.0, min=0, max=1); // freeze (0 = off, 1 = on)
  40.  
  41. // ---------- MIXING PARAMETERS -----------
  42. Param wet(1.0, min=0, max=1); // dry/wet mix
  43. Param inGain(4.0, min=0, max=8); // input gain
  44. Param drive(0.02, min=0, max=3); // output soft saturation / drive
  45. Param xmix(1.0, min=0, max=1); // L/R stereo crosstalk
  46. Param xangle(0.25, min=0, max=1); // crosstalk rotation angle
  47. Param xpasses(1, min=1, max=2); // crosstalk passes (once/twice)
  48.  
  49. // ---------- RE-TIME XFADE ----------
  50. Param xfade_ms(30, min=0, max=100);
  51.  
  52. // ---------- data ----------
  53. Data buf(samplerate*5, 50);
  54. Data dtime(50,1);
  55. Data lp_z(50,1);
  56. Data ap1_x_z(50,1); // AP1 x[n-1]
  57. Data ap1_y_z(50,1); // AP1 y[n-1]
  58. Data ap2_x_z(50,1); // AP2 x[n-1]
  59. Data ap2_y_z(50,1); // AP2 y[n-1]
  60. Data dry1_z(50,1); // 1-sample dry delay for orthonormal mix
  61. Data seed(50,1);
  62. Data tmp_fb(50,1);
  63. Data mix_fb(50,1);
  64. Data dc_lp(50,1);
  65. Data prev_dt(50,1);
  66. Data xf_p(50,1);
  67. Data lfo_p(50,1);
  68.  
  69. // ---------- init ----------
  70. if (counter == 0) {
  71. for (i=0; i<50; i+=1) {
  72. t = sin(i * 127.1) * 43758.5453;
  73. j = t - floor(t);
  74. poke(seed, j, i);
  75. poke(lp_z, 0, i);
  76. poke(ap1_x_z, 0, i);
  77. poke(ap1_y_z, 0, i);
  78. poke(ap2_x_z, 0, i);
  79. poke(ap2_y_z, 0, i);
  80. poke(dry1_z, 0, i);
  81. poke(tmp_fb, 0, i);
  82. poke(mix_fb, 0, i);
  83. poke(dc_lp, 0, i);
  84. poke(prev_dt, 1, i);
  85. poke(xf_p, 1, i);
  86. poke(lfo_p, fract(j + 0.123*i), i);
  87. }
  88. }
  89.  
  90. // ---------- param shaping ----------
  91. base = slide(mstosamps(time_ms), samplerate/20, samplerate/20);
  92. maxd = base * slide(span, samplerate/8, samplerate/8);
  93. ch = max(1, floor(channels));
  94. buflen = dim(buf, 0);
  95.  
  96. fb_smooth = slide(fb, samplerate/10, samplerate/10);
  97. damp_smooth = slide(damp, samplerate/10, samplerate/10);
  98. wet_smooth = slide(wet, samplerate/20, samplerate/20);
  99. width_smooth = slide(width, samplerate/20, samplerate/20);
  100. inGain_smooth = slide(inGain, samplerate/10, samplerate/10);
  101. drive_smooth = slide(drive, samplerate/15, samplerate/15);
  102. modRate_smooth = slide(modRate, samplerate/5, samplerate/5);
  103. modDepth_smooth = slide(modDepth, samplerate/10, samplerate/10);
  104. freeze_smooth = slide(freeze, samplerate/50, samplerate/50);
  105. xmix_smooth = slide(xmix, samplerate/15, samplerate/15);
  106. xangle_smooth = slide(xangle, samplerate/15, samplerate/15);
  107. curve_smooth = slide(curve, samplerate/8, samplerate/8);
  108. diffusion_smooth = slide(diffusion, samplerate/12, samplerate/12);
  109.  
  110. // AP coeff: tighter clamp + smoothing
  111. ap_coeff_smooth = slide(clamp(ap_coeff, -0.75, 0.75), samplerate/30, samplerate/30);
  112.  
  113. // freeze as continuous mix
  114. freeze_mix = clamp(freeze_smooth, 0, 1);
  115. in_atten = 1 - freeze_mix;
  116. freeze_fb = mix(fb_smooth, 0.9995, freeze_mix);
  117.  
  118. // ---------- delay table with diffusion-driven jitter (stronger) ----------
  119. for (i=0; i<ch; i+=1) {
  120. t01 = (ch==1) ? 0 : (i / (ch-1));
  121. shaped = mix(base, maxd, pow(t01, curve_smooth));
  122. j = peek(seed, i);
  123. jofs = (j - 0.5) * (0.2 * diffusion_smooth) * shaped; // was 0.1
  124. dt = clamp(shaped + jofs, 1, buflen-2);
  125. poke(dtime, dt, i);
  126. }
  127.  
  128. // pointer
  129. w = wrap(counter, 0, buflen);
  130.  
  131. // ---------- read + per-channel filtering ----------
  132. accL = 0; accR = 0;
  133.  
  134. hp_fc = max(0, hp_hz) * (1 - freeze_mix);
  135. hp_a = (hp_fc <= 0) ? 0 : (1 - exp(-twopi * hp_fc / samplerate));
  136.  
  137. xf_inc = (xfade_ms <= 0) ? 1 : (1000 / (xfade_ms * samplerate));
  138.  
  139. xinL = inGain_smooth * in1 * in_atten;
  140. xinR = inGain_smooth * in2 * in_atten;
  141.  
  142. for (i=0; i<ch; i+=1) {
  143. dt_now = peek(dtime, i);
  144. dt_prev = peek(prev_dt, i);
  145.  
  146. ph = peek(lfo_p, i) + modRate_smooth / samplerate;
  147. ph = ph - floor(ph);
  148. poke(lfo_p, ph, i);
  149. lfo = sin(twopi * (peek(seed,i) + ph));
  150. md = modDepth_smooth * (1 - freeze_mix);
  151.  
  152. xf = peek(xf_p, i);
  153. dt_thresh = 8;
  154. trig = (abs(dt_now - dt_prev) > dt_thresh) && (xf >= 0.999);
  155. xf = trig ? 0 : min(1, xf + xf_inc);
  156. poke(xf_p, xf, i);
  157.  
  158. dmod_old = dt_prev * md * lfo;
  159. dmod_new = dt_now * md * lfo;
  160.  
  161. rpos_old = wrap(w - (dt_prev + dmod_old), 0, buflen);
  162. rpos_new = wrap(w - (dt_now + dmod_new), 0, buflen);
  163.  
  164. y_old = peek(buf, rpos_old, i, 1);
  165. y_new = peek(buf, rpos_new, i, 1);
  166.  
  167. y = mix(y_old, y_new, xf);
  168. y = (abs(y) < 1e-20) ? 0 : y;
  169.  
  170. if (xf >= 0.999) { poke(prev_dt, dt_now, i); }
  171.  
  172. // damping with time tilt
  173. tsec = dt_now / samplerate;
  174. tf = clamp(1.0 / (1.0 + 2.0 * tsec), 0.1, 1.0);
  175. a_base = 1 - (1 - damp_smooth) * tf;
  176. a = a_base * (1 - freeze_mix);
  177.  
  178. z0 = peek(lp_z, i);
  179. y_lp = (1 - a) * y + a * z0;
  180. poke(lp_z, y_lp, i);
  181.  
  182. // -------- STRONGER ALLPASS DIFFUSION (two cascaded stages) --------
  183. ap_depth = diffusion_smooth * (1 - freeze_mix); // 0..1
  184.  
  185. // nonlinear depth mapping: hits harder near 1
  186. depth_sh = sqrt(ap_depth); // 0..1, faster rise
  187. g_base = ap_coeff_smooth * (0.3 + 0.7 * depth_sh); // scale with depth
  188. g1 = g_base; // stage 1
  189. g2 = -0.9 * g_base; // stage 2, opposite sign
  190.  
  191. // tiny contraction per stage
  192. ap_loss1 = 1 - (0.0015 * ap_depth);
  193. ap_loss2 = 1 - (0.0015 * ap_depth);
  194.  
  195. // Stage 1: y1 = -g1*x + x[n-1] + g1*y[n-1]
  196. x1_n = y_lp;
  197. x1_nm1 = peek(ap1_x_z, i); x1_nm1 = (abs(x1_nm1) < 1e-20) ? 0 : x1_nm1;
  198. y1_nm1 = peek(ap1_y_z, i); y1_nm1 = (abs(y1_nm1) < 1e-20) ? 0 : y1_nm1;
  199. y1_raw = ap_loss1 * (-g1 * x1_n + x1_nm1 + g1 * y1_nm1);
  200. poke(ap1_x_z, x1_n, i);
  201. poke(ap1_y_z, y1_raw, i);
  202.  
  203. // Stage 2: y2 = -g2*y1 + y1[n-1] + g2*y2[n-1]
  204. x2_n = y1_raw;
  205. x2_nm1 = peek(ap2_x_z, i); x2_nm1 = (abs(x2_nm1) < 1e-20) ? 0 : x2_nm1;
  206. y2_nm1 = peek(ap2_y_z, i); y2_nm1 = (abs(y2_nm1) < 1e-20) ? 0 : y2_nm1;
  207. y2_raw = ap_loss2 * (-g2 * x2_n + x2_nm1 + g2 * y2_nm1);
  208. poke(ap2_x_z, x2_n, i);
  209. poke(ap2_y_z, y2_raw, i);
  210.  
  211. // dry leg phase-align then orthonormal mix with cascaded AP
  212. y_lp_d = peek(dry1_z, i);
  213. poke(dry1_z, y_lp, i);
  214.  
  215. phi = ap_depth * (pi * 0.5); // 0..90°
  216. c_ap = cos(phi);
  217. s_ap = sin(phi);
  218. y_mix_ap = c_ap * y_lp_d + s_ap * y2_raw; // at 0 → dry; at 1 → AP cascade
  219.  
  220. // DC blocker on feedback candidate
  221. dc0 = peek(dc_lp, i);
  222. dc1 = dc0 + hp_a * (y_mix_ap - dc0);
  223. y_fb = (hp_fc <= 0) ? y_mix_ap : (y_mix_ap - dc1);
  224. poke(dc_lp, dc1, i);
  225.  
  226. // store for rotation mixing
  227. poke(tmp_fb, y_fb, i);
  228.  
  229. // stereo sum via unity-gain M/S width law
  230. side = (i % 2);
  231. Lc = (side==0) ? y : 0;
  232. Rc = (side==1) ? y : 0;
  233. M = 0.5 * (Lc + Rc);
  234. S = 0.5 * (Lc - Rc);
  235. S *= width_smooth;
  236. accL += M + S;
  237. accR += M - S;
  238. }
  239.  
  240. // ---------- pure rotation diffusion (gain-invariant) ----------
  241. theta = (xmix_smooth * xangle_smooth) * (pi * 0.5);
  242. c = cos(theta);
  243. s = sin(theta);
  244.  
  245. // pass 1
  246. for (i=0; i<ch; i+=2) {
  247. j = (i + 1) % ch;
  248. u = peek(tmp_fb, i);
  249. v = peek(tmp_fb, j);
  250. u2 = c*u - s*v;
  251. v2 = s*u + c*v;
  252. poke(mix_fb, u2, i);
  253. poke(mix_fb, v2, j);
  254. }
  255.  
  256. // pass 2
  257. if (xpasses > 1) {
  258. for (i=1; i<ch; i+=2) {
  259. j = (i + 1) % ch;
  260. u = peek(mix_fb, i);
  261. v = peek(mix_fb, j);
  262. u2 = c*u - s*v;
  263. v2 = s*u + c*v;
  264. poke(mix_fb, u2, i);
  265. poke(mix_fb, v2, j);
  266. }
  267. }
  268.  
  269. // optional: slight fb trim with diffusion for stability
  270. freeze_fb *= (1 - 0.0008 * diffusion_smooth);
  271.  
  272. // ---------- write ----------
  273. for (i=0; i<ch; i+=1) {
  274. y_mix = peek(mix_fb, i);
  275. x_in_ch = ((i % 2)==0) ? xinL : xinR;
  276. poke(buf, x_in_ch + freeze_fb * y_mix, w, i);
  277. }
  278.  
  279. // ---------- outputs ----------
  280. procL = accL / ch;
  281. procR = accR / ch;
  282.  
  283. k = max(1e-6, drive_smooth);
  284. procL_driven = tanh(k * procL) / tanh(k);
  285. procR_driven = tanh(k * procR) / tanh(k);
  286. lim = 0.99;
  287. procL_driven = clamp(procL_driven, -lim, lim);
  288. procR_driven = clamp(procR_driven, -lim, lim);
  289.  
  290. out1 = mix(in1, procL_driven, wet_smooth);
  291. out2 = mix(in2, procR_driven, wet_smooth);
  292.  
  293. // ---------- advance ----------
  294. counter += 1;
  295.  
  296. /*
  297. Copyright (c) 2025 Ben Johnson
  298.  
  299. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  300. associated documentation files (the "Software"), to deal in the Software without restriction,
  301. including without limitation the rights to use, copy, modify, merge, publish, distribute,
  302. sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
  303. furnished to do so, subject to the following conditions:
  304.  
  305. The above copyright notice and this permission notice shall be included in all copies or
  306. substantial portions of the Software.
  307.  
  308. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
  309. NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  310. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  311. DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
  312. OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  313. */
  314.  
Advertisement
Add Comment
Please, Sign In to add comment