From 3a37b4825a62bb4b336e7b7ed6e7b44680815664 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 27 Jul 2025 02:36:27 -0400 Subject: [PATCH] two algos --- packages/core/controls.mjs | 36 +++++---- packages/superdough/helpers.mjs | 15 ++++ packages/superdough/superdough.mjs | 13 +++- packages/superdough/synth.mjs | 14 +--- test/__snapshots__/examples.test.mjs.snap | 93 +++++++++++------------ 5 files changed, 96 insertions(+), 75 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 326343e10..000e84d40 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -551,6 +551,28 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); export const { drive } = registerControl('drive'); +/** + * waveshaping distortion with selectable algorithms + * + * @name shape + * @param {number | Pattern} amount + * @example + * $: s("909bd").beat("0,4,8,11,14",16).shape("<2 4 1>").shapetype("<0 1 2>*2") + * + */ +export const { shape } = registerControl(['shape', 'shapevol', 'shapetype']); + +/** + * filter overdrive for supported filter types + * + * @name shapetype + * @param {number | Pattern} type + * @example + * $: s("909bd").beat("0,4,8,11,14",16).shape("2").shapetype("<0 1 2>") + * + */ +export const { shapeType } = registerControl('shapetype'); + /** * Create byte beats with custom expressions * @@ -1510,19 +1532,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', // ['sclap'], // ['sclaves'], // ['scrash'], -/** - * (Deprecated) Wave shaping distortion. WARNING: can suddenly get unpredictably loud. - * Please use distort instead, which has a more predictable response curve - * second option in optional array syntax (ex: ".9:.5") applies a postgain to the output - * - * - * @name shape - * @param {number | Pattern} distortion between 0 and 1 - * @example - * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") - * - */ -export const { shape } = registerControl(['shape', 'shapevol']); + /** * Wave shaping distortion. CAUTION: it can get loud. * Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 6bde69373..04157bc2b 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -277,3 +277,18 @@ export function applyFM(param, value, begin) { } return { stop }; } + +export const saturationAlgos = [ + (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x)), //S Curve sourced from: https://www.nickwritesablog.com/sound-design-in-web-audio-neurofunk-bass-part-1/ + (x, k) => Math.tanh(x * k), +]; + +export function makeSaturationCurve(amount, n_samples, algo = 0) { + const k = typeof amount === 'number' ? amount : 50; + const curve = new Float32Array(n_samples); + for (let i = 0; i < n_samples; i++) { + const x = (i * 2) / n_samples - 1; + curve[i] = saturationAlgos[algo % saturationAlgos.length]?.(x, amount); + } + return curve; +} diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 7d90eb15d..ff3a937f0 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -9,7 +9,7 @@ import './reverb.mjs'; import './vowel.mjs'; import { clamp, nanFallback, _mod, cycleToSeconds, secondsToCycle } from './util.mjs'; import workletsUrl from './worklets.mjs?audioworklet'; -import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs'; +import { createFilter, gainNode, getCompressor, getWorklet, makeSaturationCurve } from './helpers.mjs'; import { map } from 'nanostores'; import { logger } from './logger.mjs'; import { loadBuffer } from './sampler.mjs'; @@ -147,6 +147,7 @@ let defaultDefaultValues = { phaserdepth: 0.75, shapevol: 1, distortvol: 1, + saturatevol: 1, delay: 0, byteBeatExpression: '0', delayfeedback: 0.5, @@ -573,6 +574,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) crush, shape, shapevol = getDefaultValue('shapevol'), + shapetype, distort, distortvol = getDefaultValue('distortvol'), pan, @@ -676,6 +678,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) // gain stage chain.push(gainNode(gain)); + if (shape != null) { + const shaper = new WaveShaperNode(ac); + shaper.curve = makeSaturationCurve(shape, ac.sampleRate, shapetype); + const shapeGain = new GainNode(ac, { gain: shapevol }); + chain.push(shaper); + chain.push(shapeGain); + } + //filter const ftype = getFilterType(value.ftype); if (cutoff !== undefined) { @@ -741,7 +751,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) // effects coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); - shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol })); distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol })); if (tremolosync != null) { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 8f4686a80..5ae4df326 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -9,6 +9,7 @@ import { getVibratoOscillator, webAudioTimeout, getWorklet, + makeSaturationCurve, } from './helpers.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; @@ -42,17 +43,6 @@ const waveformAliases = [ ]; const noises = ['pink', 'white', 'brown', 'crackle']; -function makeSaturationCurve(amount, n_samples) { - const k = typeof amount === 'number' ? amount : 50; - const curve = new Float32Array(n_samples); - - for (let i = 0; i < n_samples; i++) { - const x = (i * 2) / n_samples - 1; - curve[i] = Math.tanh(x * k); - } - return curve; -} - export function registerSynthSounds() { [...waveforms].forEach((s) => { registerSound( @@ -124,7 +114,7 @@ export function registerSynthSounds() { const sat = new WaveShaperNode(ctx); // tri to sine diode shaper emulation - sat.curve = makeSaturationCurve(2, ctx.sampleRate); + sat.curve = makeSaturationCurve(2, ctx.sampleRate, 1); const mix = gainNode(mixGain); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index e6bfa44aa..da64edbd0 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -8612,54 +8612,51 @@ exports[`runs examples > example "setcpm" example index 0 1`] = ` exports[`runs examples > example "shape" example index 0 1`] = ` [ - "[ 0/1 → 1/8 | s:hh shape:0 ]", - "[ 0/1 → 1/4 | s:bd shape:0 ]", - "[ 1/8 → 1/4 | s:hh shape:0 ]", - "[ 1/4 → 3/8 | s:hh shape:0 ]", - "[ 1/4 → 1/2 | s:sd shape:0 ]", - "[ 3/8 → 1/2 | s:hh shape:0 ]", - "[ 1/2 → 5/8 | s:hh shape:0 ]", - "[ 5/8 → 3/4 | s:bd shape:0 ]", - "[ 5/8 → 3/4 | s:hh shape:0 ]", - "[ 3/4 → 7/8 | s:hh shape:0 ]", - "[ 3/4 → 1/1 | s:sd shape:0 ]", - "[ 7/8 → 1/1 | s:hh shape:0 ]", - "[ 1/1 → 9/8 | s:hh shape:0.2 ]", - "[ 1/1 → 5/4 | s:bd shape:0.2 ]", - "[ 9/8 → 5/4 | s:hh shape:0.2 ]", - "[ 5/4 → 11/8 | s:hh shape:0.2 ]", - "[ 5/4 → 3/2 | s:sd shape:0.2 ]", - "[ 11/8 → 3/2 | s:hh shape:0.2 ]", - "[ 3/2 → 13/8 | s:hh shape:0.2 ]", - "[ 13/8 → 7/4 | s:bd shape:0.2 ]", - "[ 13/8 → 7/4 | s:hh shape:0.2 ]", - "[ 7/4 → 15/8 | s:hh shape:0.2 ]", - "[ 7/4 → 2/1 | s:sd shape:0.2 ]", - "[ 15/8 → 2/1 | s:hh shape:0.2 ]", - "[ 2/1 → 17/8 | s:hh shape:0.4 ]", - "[ 2/1 → 9/4 | s:bd shape:0.4 ]", - "[ 17/8 → 9/4 | s:hh shape:0.4 ]", - "[ 9/4 → 19/8 | s:hh shape:0.4 ]", - "[ 9/4 → 5/2 | s:sd shape:0.4 ]", - "[ 19/8 → 5/2 | s:hh shape:0.4 ]", - "[ 5/2 → 21/8 | s:hh shape:0.4 ]", - "[ 21/8 → 11/4 | s:bd shape:0.4 ]", - "[ 21/8 → 11/4 | s:hh shape:0.4 ]", - "[ 11/4 → 23/8 | s:hh shape:0.4 ]", - "[ 11/4 → 3/1 | s:sd shape:0.4 ]", - "[ 23/8 → 3/1 | s:hh shape:0.4 ]", - "[ 3/1 → 25/8 | s:hh shape:0.6 ]", - "[ 3/1 → 13/4 | s:bd shape:0.6 ]", - "[ 25/8 → 13/4 | s:hh shape:0.6 ]", - "[ 13/4 → 27/8 | s:hh shape:0.6 ]", - "[ 13/4 → 7/2 | s:sd shape:0.6 ]", - "[ 27/8 → 7/2 | s:hh shape:0.6 ]", - "[ 7/2 → 29/8 | s:hh shape:0.6 ]", - "[ 29/8 → 15/4 | s:bd shape:0.6 ]", - "[ 29/8 → 15/4 | s:hh shape:0.6 ]", - "[ 15/4 → 31/8 | s:hh shape:0.6 ]", - "[ 15/4 → 4/1 | s:sd shape:0.6 ]", - "[ 31/8 → 4/1 | s:hh shape:0.6 ]", + "[ 0/1 → 1/16 | s:909bd shape:2 shapetype:0 ]", + "[ 1/4 → 5/16 | s:909bd shape:2 shapetype:0 ]", + "[ 1/2 → 9/16 | s:909bd shape:2 shapetype:1 ]", + "[ 11/16 → 3/4 | s:909bd shape:2 shapetype:1 ]", + "[ 7/8 → 15/16 | s:909bd shape:2 shapetype:1 ]", + "[ 1/1 → 17/16 | s:909bd shape:4 shapetype:2 ]", + "[ 5/4 → 21/16 | s:909bd shape:4 shapetype:2 ]", + "[ 3/2 → 25/16 | s:909bd shape:4 shapetype:0 ]", + "[ 27/16 → 7/4 | s:909bd shape:4 shapetype:0 ]", + "[ 15/8 → 31/16 | s:909bd shape:4 shapetype:0 ]", + "[ 2/1 → 33/16 | s:909bd shape:1 shapetype:1 ]", + "[ 9/4 → 37/16 | s:909bd shape:1 shapetype:1 ]", + "[ 5/2 → 41/16 | s:909bd shape:1 shapetype:2 ]", + "[ 43/16 → 11/4 | s:909bd shape:1 shapetype:2 ]", + "[ 23/8 → 47/16 | s:909bd shape:1 shapetype:2 ]", + "[ 3/1 → 49/16 | s:909bd shape:2 shapetype:0 ]", + "[ 13/4 → 53/16 | s:909bd shape:2 shapetype:0 ]", + "[ 7/2 → 57/16 | s:909bd shape:2 shapetype:1 ]", + "[ 59/16 → 15/4 | s:909bd shape:2 shapetype:1 ]", + "[ 31/8 → 63/16 | s:909bd shape:2 shapetype:1 ]", +] +`; + +exports[`runs examples > example "shapetype" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | s:909bd shape:2 shapetype:0 ]", + "[ 1/4 → 5/16 | s:909bd shape:2 shapetype:0 ]", + "[ 1/2 → 9/16 | s:909bd shape:2 shapetype:0 ]", + "[ 11/16 → 3/4 | s:909bd shape:2 shapetype:0 ]", + "[ 7/8 → 15/16 | s:909bd shape:2 shapetype:0 ]", + "[ 1/1 → 17/16 | s:909bd shape:2 shapetype:1 ]", + "[ 5/4 → 21/16 | s:909bd shape:2 shapetype:1 ]", + "[ 3/2 → 25/16 | s:909bd shape:2 shapetype:1 ]", + "[ 27/16 → 7/4 | s:909bd shape:2 shapetype:1 ]", + "[ 15/8 → 31/16 | s:909bd shape:2 shapetype:1 ]", + "[ 2/1 → 33/16 | s:909bd shape:2 shapetype:2 ]", + "[ 9/4 → 37/16 | s:909bd shape:2 shapetype:2 ]", + "[ 5/2 → 41/16 | s:909bd shape:2 shapetype:2 ]", + "[ 43/16 → 11/4 | s:909bd shape:2 shapetype:2 ]", + "[ 23/8 → 47/16 | s:909bd shape:2 shapetype:2 ]", + "[ 3/1 → 49/16 | s:909bd shape:2 shapetype:0 ]", + "[ 13/4 → 53/16 | s:909bd shape:2 shapetype:0 ]", + "[ 7/2 → 57/16 | s:909bd shape:2 shapetype:0 ]", + "[ 59/16 → 15/4 | s:909bd shape:2 shapetype:0 ]", + "[ 31/8 → 63/16 | s:909bd shape:2 shapetype:0 ]", ] `;