mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-13 09:00:39 -04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48231ea12a | |||
| c8a442234e | |||
| efda32eedb | |||
| 95d04d1149 | |||
| 358b1e3f56 | |||
| fe7e8f7add | |||
| 45d329d3d1 | |||
| fab8d2193d | |||
| 150835aa4d | |||
| c4826b7dfb | |||
| bd61c1dc7e | |||
| 36587ae747 | |||
| 6c01c16a0c | |||
| be36898803 | |||
| df047fd48d | |||
| 68840de188 | |||
| 020a6bc75f | |||
| 7bc6fcbf2a |
@@ -2786,3 +2786,17 @@ export const scrub = register(
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transient shaper. Gives independent control over the emphasis on transients
|
||||||
|
* and sustains
|
||||||
|
*
|
||||||
|
* @name transient
|
||||||
|
* @param {number | Pattern} attack Emphasis on transients; between -1 (deaccentuate) and 1 (accentuate)
|
||||||
|
* @param {number | Pattern} sustain Emphasis on the sustains; between -1 (deaccentuate) and 1 (accentuate)
|
||||||
|
* @example
|
||||||
|
* s("bd").transient("<-1 -0.5 0 0.5 1>")
|
||||||
|
* @example
|
||||||
|
* s("hh*16").bank("tr909").transient("<-1:1 1:-1>")
|
||||||
|
*/
|
||||||
|
export const { transient } = registerControl(['transient', 'transsustain']);
|
||||||
|
|||||||
+26
-9
@@ -479,14 +479,24 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
|||||||
|
|
||||||
let listeners = {};
|
let listeners = {};
|
||||||
const refs = {};
|
const refs = {};
|
||||||
|
const refsByChan = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
|
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
|
||||||
|
*
|
||||||
|
* The output is a function that accepts a midi cc value to query as well as (optionally) a midi channel
|
||||||
* @param {string | number} input MIDI device name or index defaulting to 0
|
* @param {string | number} input MIDI device name or index defaulting to 0
|
||||||
* @returns {Function}
|
* @returns {function(number, number=): Pattern} A function from (cc, channel?) to a pattern.
|
||||||
|
* When queried, the pattern will produces the most recently received midi value (normalized to 0 to 1)
|
||||||
|
* that came through that cc number (and channel, if provided)
|
||||||
* @example
|
* @example
|
||||||
* let cc = await midin('IAC Driver Bus 1')
|
* const cc = await midin('IAC Driver Bus 1')
|
||||||
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
|
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
|
||||||
|
* @example
|
||||||
|
* const allCC = await midin('IAC Driver Bus 1')
|
||||||
|
* const cc = (ccNum) => allCC(ccNum, 2) // just channel 2
|
||||||
|
* note("c a f e").s("saw")
|
||||||
|
* .when(cc(0).gt(0), x => x.postgain(0))
|
||||||
*/
|
*/
|
||||||
export async function midin(input) {
|
export async function midin(input) {
|
||||||
if (isPattern(input)) {
|
if (isPattern(input)) {
|
||||||
@@ -511,17 +521,24 @@ export async function midin(input) {
|
|||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// ensure refs for this input are initialized
|
refs[input] ??= {};
|
||||||
if (!refs[input]) {
|
refsByChan[input] ??= {};
|
||||||
refs[input] = {};
|
const cc = (cc, chan) => {
|
||||||
}
|
if (chan !== undefined) {
|
||||||
const cc = (cc) => ref(() => refs[input][cc] || 0);
|
return ref(() => refsByChan[input][cc]?.[chan] || 0);
|
||||||
|
}
|
||||||
|
return ref(() => refs[input][cc] || 0);
|
||||||
|
};
|
||||||
|
|
||||||
listeners[input] && device.removeListener('midimessage', listeners[input]);
|
listeners[input] && device.removeListener('midimessage', listeners[input]);
|
||||||
listeners[input] = (e) => {
|
listeners[input] = (e) => {
|
||||||
const cc = e.dataBytes[0];
|
const ccNum = e.dataBytes[0];
|
||||||
const v = e.dataBytes[1];
|
const v = e.dataBytes[1];
|
||||||
refs[input] && (refs[input][cc] = v / 127);
|
const chan = e.message.channel;
|
||||||
|
const scaled = v / 127;
|
||||||
|
refsByChan[input][ccNum] ??= {};
|
||||||
|
refsByChan[input][ccNum][chan] = scaled;
|
||||||
|
refs[input][ccNum] = scaled;
|
||||||
};
|
};
|
||||||
device.addListener('midimessage', listeners[input]);
|
device.addListener('midimessage', listeners[input]);
|
||||||
return cc;
|
return cc;
|
||||||
|
|||||||
@@ -448,6 +448,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
compressorKnee,
|
compressorKnee,
|
||||||
compressorAttack,
|
compressorAttack,
|
||||||
compressorRelease,
|
compressorRelease,
|
||||||
|
transient,
|
||||||
|
transsustain,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
@@ -529,6 +531,23 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
chain.push(sourceNode);
|
chain.push(sourceNode);
|
||||||
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
||||||
|
|
||||||
|
transient !== undefined &&
|
||||||
|
chain.push(
|
||||||
|
getWorklet(
|
||||||
|
ac,
|
||||||
|
'transient-processor',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
processorOptions: {
|
||||||
|
attack: transient,
|
||||||
|
sustain: transsustain,
|
||||||
|
begin: t,
|
||||||
|
end: endWithRelease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// gain stage
|
// gain stage
|
||||||
chain.push(gainNode(gain));
|
chain.push(gainNode(gain));
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ const PI = Math.PI;
|
|||||||
const TWO_PI = 2 * PI;
|
const TWO_PI = 2 * PI;
|
||||||
const INVSR = 1 / sampleRate;
|
const INVSR = 1 / sampleRate;
|
||||||
|
|
||||||
|
const timeToCoeff = (t) => 1 - Math.exp(-INVSR / t);
|
||||||
|
const dbToLin = (db) => Math.pow(10, db / 20);
|
||||||
|
|
||||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
const mod = (n, m) => ((n % m) + m) % m;
|
const mod = (n, m) => ((n % m) + m) % m;
|
||||||
const lerp = (a, b, n) => n * (b - a) + a;
|
const lerp = (a, b, n) => n * (b - a) + a;
|
||||||
@@ -1384,3 +1387,82 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor);
|
registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor);
|
||||||
|
|
||||||
|
class TransientProcessor extends AudioWorkletProcessor {
|
||||||
|
static get parameterDescriptors() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
this.gainCoeff = timeToCoeff(0.2);
|
||||||
|
this.avgGain = 1;
|
||||||
|
let {
|
||||||
|
attackTime = 0.003,
|
||||||
|
sustainTime = 0.08,
|
||||||
|
attack = 0,
|
||||||
|
sustain = 0,
|
||||||
|
sensitivity = 0.1,
|
||||||
|
mix = 1,
|
||||||
|
begin = 0,
|
||||||
|
end = 0,
|
||||||
|
} = options.processorOptions;
|
||||||
|
attackTime = clamp(attackTime, 0.0005, 0.05);
|
||||||
|
sustainTime = clamp(sustainTime, 0.01, 0.5);
|
||||||
|
this.attackCoeff = timeToCoeff(attackTime);
|
||||||
|
this.sustainCoeff = timeToCoeff(sustainTime);
|
||||||
|
this.attackAmt = clamp(attack, -1, 1);
|
||||||
|
this.sustainAmt = clamp(sustain, -1, 1);
|
||||||
|
this.scaling = 0.5 + 5 * clamp(sensitivity, 0, 1);
|
||||||
|
this.mix = clamp(mix, 0, 1);
|
||||||
|
this.begin = begin;
|
||||||
|
this.end = end;
|
||||||
|
this.attackEnv = new Float32Array(2); // assume stereo
|
||||||
|
this.sustainEnv = new Float32Array(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, _params) {
|
||||||
|
const input = inputs[0];
|
||||||
|
const output = outputs[0];
|
||||||
|
if (currentTime >= this.end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= this.begin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const channels = input.length;
|
||||||
|
if (channels > this.attackEnv.length) {
|
||||||
|
this.attackEnv = new Float32Array(channels);
|
||||||
|
this.sustainEnv = new Float32Array(channels);
|
||||||
|
}
|
||||||
|
let avgGain = this.avgGain;
|
||||||
|
for (let ch = 0; ch < channels; ch++) {
|
||||||
|
let attEnv = this.attackEnv[ch];
|
||||||
|
let susEnv = this.sustainEnv[ch];
|
||||||
|
for (let n = 0; n < blockSize; n++) {
|
||||||
|
const sample = input[ch][n];
|
||||||
|
const x = Math.abs(sample);
|
||||||
|
attEnv = lerp(attEnv, x, this.attackCoeff);
|
||||||
|
susEnv = lerp(susEnv, x, this.sustainCoeff);
|
||||||
|
const peakiness = clamp((this.scaling * (attEnv - susEnv)) / (susEnv + 1e-6), -1.5, 1.5);
|
||||||
|
const attScale = peakiness > 0 ? peakiness : 0;
|
||||||
|
const susScale = peakiness < 0 ? -peakiness : 0;
|
||||||
|
const attackGain = dbToLin(this.attackAmt * attScale * 18);
|
||||||
|
const sustainGain = dbToLin(this.sustainAmt * susScale * 36);
|
||||||
|
const gain = clamp(attackGain * sustainGain, 0, 8);
|
||||||
|
avgGain = lerp(avgGain, gain, this.gainCoeff);
|
||||||
|
const makeup = avgGain > 1e-3 ? 1 / avgGain : 1;
|
||||||
|
const wet = sample * gain * makeup;
|
||||||
|
let y = lerp(sample, wet, this.mix);
|
||||||
|
y /= 1 + Math.abs(y); // soft clip
|
||||||
|
output[ch][n] = y;
|
||||||
|
}
|
||||||
|
this.attackEnv[ch] = attEnv;
|
||||||
|
this.sustainEnv[ch] = susEnv;
|
||||||
|
}
|
||||||
|
this.avgGain = avgGain;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('transient-processor', TransientProcessor);
|
||||||
|
|||||||
@@ -7048,6 +7048,27 @@ exports[`runs examples > example "midin" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "midin" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | note:c s:saw ]",
|
||||||
|
"[ 1/4 → 1/2 | note:a s:saw ]",
|
||||||
|
"[ 1/2 → 3/4 | note:f s:saw ]",
|
||||||
|
"[ 3/4 → 1/1 | note:e s:saw ]",
|
||||||
|
"[ 1/1 → 5/4 | note:c s:saw ]",
|
||||||
|
"[ 5/4 → 3/2 | note:a s:saw ]",
|
||||||
|
"[ 3/2 → 7/4 | note:f s:saw ]",
|
||||||
|
"[ 7/4 → 2/1 | note:e s:saw ]",
|
||||||
|
"[ 2/1 → 9/4 | note:c s:saw ]",
|
||||||
|
"[ 9/4 → 5/2 | note:a s:saw ]",
|
||||||
|
"[ 5/2 → 11/4 | note:f s:saw ]",
|
||||||
|
"[ 11/4 → 3/1 | note:e s:saw ]",
|
||||||
|
"[ 3/1 → 13/4 | note:c s:saw ]",
|
||||||
|
"[ 13/4 → 7/2 | note:a s:saw ]",
|
||||||
|
"[ 7/2 → 15/4 | note:f s:saw ]",
|
||||||
|
"[ 15/4 → 4/1 | note:e s:saw ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "midiport" example index 0 1`] = `
|
exports[`runs examples > example "midiport" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c midiport:0 ]",
|
"[ 0/1 → 1/4 | note:c midiport:0 ]",
|
||||||
@@ -12008,6 +12029,84 @@ exports[`runs examples > example "tour" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "transient" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | s:bd transient:-1 ]",
|
||||||
|
"[ 1/1 → 2/1 | s:bd transient:-0.5 ]",
|
||||||
|
"[ 2/1 → 3/1 | s:bd transient:0 ]",
|
||||||
|
"[ 3/1 → 4/1 | s:bd transient:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "transient" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/16 → 1/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/8 → 3/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/16 → 1/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/4 → 5/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/16 → 3/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/8 → 7/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 7/16 → 1/2 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/2 → 9/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 9/16 → 5/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/8 → 11/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 11/16 → 3/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/4 → 13/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 13/16 → 7/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 7/8 → 15/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 15/16 → 1/1 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/1 → 17/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 17/16 → 9/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 9/8 → 19/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 19/16 → 5/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 5/4 → 21/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 21/16 → 11/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 11/8 → 23/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 23/16 → 3/2 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 3/2 → 25/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 25/16 → 13/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 13/8 → 27/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 27/16 → 7/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 7/4 → 29/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 29/16 → 15/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 15/8 → 31/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 31/16 → 2/1 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 2/1 → 33/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 33/16 → 17/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 17/8 → 35/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 35/16 → 9/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 9/4 → 37/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 37/16 → 19/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 19/8 → 39/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 39/16 → 5/2 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/2 → 41/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 41/16 → 21/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 21/8 → 43/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 43/16 → 11/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 11/4 → 45/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 45/16 → 23/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 23/8 → 47/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 47/16 → 3/1 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/1 → 49/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 49/16 → 25/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 25/8 → 51/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 51/16 → 13/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 13/4 → 53/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 53/16 → 27/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 27/8 → 55/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 55/16 → 7/2 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 7/2 → 57/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 57/16 → 29/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 29/8 → 59/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 59/16 → 15/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 15/4 → 61/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 61/16 → 31/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 31/8 → 63/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 63/16 → 4/1 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "transpose" example index 0 1`] = `
|
exports[`runs examples > example "transpose" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:C2 ]",
|
"[ 0/1 → 1/4 | note:C2 ]",
|
||||||
|
|||||||
Reference in New Issue
Block a user