This commit is contained in:
Jade (Rose) Rowland
2024-05-03 14:56:16 -04:00
parent 47b65846e9
commit 496d60dc1c
7 changed files with 98 additions and 9 deletions
+11
View File
@@ -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
*
+1 -1
View File
@@ -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) {
+10 -1
View File
@@ -4,6 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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)
);
}
});
};
+3 -3
View File
@@ -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');
+4 -1
View File
@@ -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(
+65
View File
@@ -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 [
+4 -3
View File
@@ -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,