From 496d60dc1cd83cbb7901996632879f058cfdae8a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 3 May 2024 14:56:16 -0400 Subject: [PATCH] testing --- packages/core/controls.mjs | 11 +++++ packages/core/cyclist.mjs | 2 +- packages/core/neocyclist.mjs | 11 ++++- packages/core/repl.mjs | 6 +-- packages/superdough/superdough.mjs | 5 ++- packages/superdough/worklets.mjs | 65 ++++++++++++++++++++++++++++++ packages/webaudio/webaudio.mjs | 7 ++-- 7 files changed, 98 insertions(+), 9 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 166f0ac23..2364cd994 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -430,6 +430,17 @@ export const { crush } = registerControl('crush'); */ export const { coarse } = registerControl('coarse'); +/** + * modulate the output gain of a sound with a continuous wave + * + * @name gainmod + * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. + * @example + * s("triangle").gainmod("2:1:0") + * + */ +export const { gainmod } = registerControl('gainmod'); + /** * Allows you to set the output channels on the interface * diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs index 08b893752..d61267475 100644 --- a/packages/core/cyclist.mjs +++ b/packages/core/cyclist.mjs @@ -56,7 +56,7 @@ export class Cyclist { // the following line is dumb and only here for backwards compatibility // see https://github.com/tidalcycles/strudel/pull/1004 const deadline = targetTime - phase; - onTrigger?.(hap, deadline, duration, this.cps, targetTime); + onTrigger?.(hap, deadline, duration, this.cps, targetTime, end); } }); } catch (e) { diff --git a/packages/core/neocyclist.mjs b/packages/core/neocyclist.mjs index 4600d0ef9..6bc0a8404 100644 --- a/packages/core/neocyclist.mjs +++ b/packages/core/neocyclist.mjs @@ -4,6 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ +import Fraction from 'fraction.js'; import { logger } from './logger.mjs'; export class NeoCyclist { @@ -86,7 +87,15 @@ export class NeoCyclist { this.latency + this.worker_time_dif; const duration = hap.duration / this.cps; - onTrigger?.(hap, 0, duration, this.cps, targetTime); + onTrigger?.( + hap, + 0, + duration, + this.cps, + targetTime, + this.cycle, + // + Fraction(this.latency).div(this.cps) + ); } }); }; diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 5b7bcd71b..af5935787 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -178,15 +178,15 @@ export function repl({ export const getTrigger = ({ getTime, defaultOutput }) => - async (hap, deadline, duration, cps, t) => { + async (hap, deadline, duration, cps, t, cycle = 0) => { // TODO: get rid of deadline after https://github.com/tidalcycles/strudel/pull/1004 try { if (!hap.context.onTrigger || !hap.context.dominantTrigger) { - await defaultOutput(hap, deadline, duration, cps, t); + await defaultOutput(hap, deadline, duration, cps, t, cycle); } if (hap.context.onTrigger) { // call signature of output / onTrigger is different... - await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps, t); + await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps, t, cycle); } } catch (err) { logger(`[cyclist] error: ${err.message}`, 'error'); diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index c0ba96e06..138a3a38d 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -260,7 +260,8 @@ export function resetGlobalEffects() { analysersData = {}; } -export const superdough = async (value, t, hapDuration) => { +export const superdough = async (value, t, hapDuration, cps, cycle) => { + console.log({ cps, cycle }); const ac = getAudioContext(); if (typeof value !== 'object') { throw new Error( @@ -322,6 +323,7 @@ export const superdough = async (value, t, hapDuration) => { phasercenter, // coarse, + gainmod, crush, shape, shapevol = 1, @@ -470,6 +472,7 @@ export const superdough = async (value, t, hapDuration) => { 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 })); + gainmod !== undefined && chain.push(getWorklet(ac, 'gainmod-processor', { speed: gainmod, cps, cycle })); compressorThreshold !== undefined && chain.push( diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index bab28987e..f29d1028f 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1,6 +1,36 @@ // coarse, crush, and shape processors adapted from dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js // LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE +const linearEnvelope = (startVal, EndVal, startTime, endTime, currentTime, hold = 0) => { + let min = 0.001; + currentTime = currentTime - hold; + if (startTime > currentTime) { + return 1; + } + if (currentTime > endTime) { + return min; + } + // change relative start time to 0 to prevent numeric overflow + currentTime = currentTime - startTime; + endTime = endTime - startTime; + startTime = 0; + + let x1 = startTime; + let y1 = startVal; + let x2 = endTime; + let y2 = EndVal; + + // Calculate the growth or decay rate (b) + let b = y1 / y2 / (x1 - x2); + + // Calculate the initial value (a) + let a = y1 / (b * x1); + + let x = currentTime; + // calculate y for any x + return a * (b * x); +}; + class CoarseProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [{ name: 'coarse', defaultValue: 1 }]; @@ -62,6 +92,41 @@ class CrushProcessor extends AudioWorkletProcessor { } registerProcessor('crush-processor', CrushProcessor); +class GainModProcessor extends AudioWorkletProcessor { + static get parameterDescriptors() { + return [ + { name: 'cps', defaultValue: 0.5 }, + { name: 'speed', defaultValue: 0.5 }, + { name: 'cycle', defaultValue: 0 }, + ]; + } + + constructor() { + super(); + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const blockSize = 128; + + let crush = parameters.crush[0] ?? 8; + crush = Math.max(1, crush); + + if (input[0] == null || output[0] == null) { + return false; + } + for (let n = 0; n < blockSize; n++) { + for (let i = 0; i < input.length; i++) { + const x = Math.pow(2, crush - 1); + output[i][n] = Math.round(input[i][n] * x) / x; + } + } + return true; + } +} +registerProcessor('gainmod-processor', GainModProcessor); + class ShapeProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index f82a436a9..36d11b096 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -17,9 +17,9 @@ const hap2value = (hap) => { export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps); // uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004 -export const webaudioOutput = (hap, deadline, hapDuration, cps, t) => - superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration); - +export const webaudioOutput = (hap, deadline, hapDuration, cps, t, cycle) => { + return superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration, cps, cycle); +}; Pattern.prototype.webaudio = function () { return this.onTrigger(webaudioOutputTrigger); }; @@ -30,6 +30,7 @@ export function webaudioScheduler(options = {}) { defaultOutput: webaudioOutput, ...options, }; + const { defaultOutput, getTime } = options; return new strudel.Cyclist({ ...options,