Handle detune in the presence of pitch envelope

This commit is contained in:
Aria 2025-10-04 01:37:57 -05:00
parent 99e14dae5c
commit 120f89d57f
5 changed files with 20 additions and 18 deletions

View file

@ -796,7 +796,7 @@ describe('Pattern', () => {
}); });
}); });
describe('apply', () => { describe('apply', () => {
it('Can apply a function', () => { (it('Can apply a function', () => {
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle()); expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
}), }),
it('Can apply a pattern of functions', () => { it('Can apply a pattern of functions', () => {
@ -804,7 +804,7 @@ describe('Pattern', () => {
expect(sequence('a', 'b').apply(fast(2), fast(3)).firstCycle()).toStrictEqual( expect(sequence('a', 'b').apply(fast(2), fast(3)).firstCycle()).toStrictEqual(
sequence('a', 'b').fast(2, 3).firstCycle(), sequence('a', 'b').fast(2, 3).firstCycle(),
); );
}); }));
}); });
describe('layer', () => { describe('layer', () => {
it('Can layer up multiple functions', () => { it('Can layer up multiple functions', () => {

View file

@ -230,12 +230,12 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
begin: t, begin: t,
end: envEnd, end: envEnd,
frequency, frequency,
detune: value.detune, freqspread: value.detune,
position: value.wt, position: value.wt,
warp: value.warp, warp: value.warp,
warpMode: warpmode, warpMode: warpmode,
voices: Math.max(value.unison ?? 1, 1), voices: Math.max(value.unison ?? 1, 1),
spread: value.spread, panspread: value.spread,
phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0, phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0,
}, },
{ outputChannelCount: [2] }, { outputChannelCount: [2] },

View file

@ -1049,14 +1049,15 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
return [ return [
{ name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
{ name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY }, { name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
{ name: 'frequency', defaultValue: 220, minValue: 0.01, maxValue: 20000 }, { name: 'frequency', defaultValue: 440, min: Number.EPSILON },
{ name: 'detune', defaultValue: 0.18 }, { name: 'detune', defaultValue: 0 },
{ name: 'position', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'freqspread', defaultValue: 0.18, min: 0 },
{ name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'position', defaultValue: 0, min: 0, max: 1 },
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
{ name: 'warpMode', defaultValue: 0 }, { name: 'warpMode', defaultValue: 0 },
{ name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 }, { name: 'voices', defaultValue: 1, min: 1 },
{ name: 'spread', defaultValue: 0.7, minValue: 0, maxValue: 1 }, { name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
{ name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 }, { name: 'phaserand', defaultValue: 0, min: 0, max: 1 },
]; ];
} }
@ -1269,6 +1270,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
} }
for (let i = 0; i < outL.length; i++) { for (let i = 0; i < outL.length; i++) {
const detune = pv(parameters.detune, i); const detune = pv(parameters.detune, i);
const freqspread = pv(parameters.freqspread, i);
const tablePos = clamp(pv(parameters.position, i), 0, 1); const tablePos = clamp(pv(parameters.position, i), 0, 1);
const idx = tablePos * (this.numFrames - 1); const idx = tablePos * (this.numFrames - 1);
const fIdx = idx | 0; const fIdx = idx | 0;
@ -1277,9 +1279,9 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const warpMode = pv(parameters.warpMode, i); const warpMode = pv(parameters.warpMode, i);
const voices = pv(parameters.voices, i); const voices = pv(parameters.voices, i);
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1); const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
const spread = voices > 1 ? clamp(pv(parameters.spread, i), 0, 1) : 0; const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0;
const gain1 = Math.sqrt(0.5 - 0.5 * spread); const gain1 = Math.sqrt(0.5 - 0.5 * panspread);
const gain2 = Math.sqrt(0.5 + 0.5 * spread); const gain2 = Math.sqrt(0.5 + 0.5 * panspread);
let f = pv(parameters.frequency, i); let f = pv(parameters.frequency, i);
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
const normalizer = 1 / Math.sqrt(voices); const normalizer = 1 / Math.sqrt(voices);
@ -1292,7 +1294,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
gainL = gain2; gainL = gain2;
gainR = gain1; gainR = gain1;
} }
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, freqspread, n)); // voice detune
const dPhase = fVoice * this.invSR; const dPhase = fVoice * this.invSR;
const level = this._chooseMip(dPhase); const level = this._chooseMip(dPhase);
const table = this.tables[level]; const table = this.tables[level];

View file

@ -18,8 +18,8 @@
--docsearch-modal-background: var(--background); --docsearch-modal-background: var(--background);
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #fff 30%); --docsearch-muted-color: color-mix(in srgb, var(--foreground), #fff 30%);
--docsearch-key-gradient: var(--foreground); --docsearch-key-gradient: var(--foreground);
--docsearch-key-shadow: inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground), --docsearch-key-shadow:
0 1px 2px 1px var(--gutterBackground); inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground), 0 1px 2px 1px var(--gutterBackground);
} }
.dark { .dark {
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #000 30%); --docsearch-muted-color: color-mix(in srgb, var(--foreground), #000 30%);

View file

@ -381,7 +381,7 @@ Sampler effects are functions that can be used to change the behaviour of sample
### scrub ### scrub
<JsDoc client:idle name="Pattern.scrub" h={0} />{' '} <JsDoc client:idle name="Pattern.scrub" h={0} />
### speed ### speed