Remove mipmaps, fix clamping of parameters

This commit is contained in:
Aria
2025-09-29 14:15:50 -07:00
parent f360d394a7
commit 4de0c11f8c
2 changed files with 27 additions and 54 deletions
+7 -25
View File
@@ -5,7 +5,6 @@ import {
destroyAudioWorkletNode,
getADSRValues,
getFrequencyFromValue,
getLfo,
getParamADSR,
getPitchEnvelope,
getVibratoOscillator,
@@ -14,7 +13,6 @@ import {
} from './helpers.mjs';
import { logger } from './logger.mjs';
const WT_MAX_MIP_LEVELS = 6;
export const Warpmode = Object.freeze({
NONE: 0,
ASYM: 1,
@@ -50,25 +48,7 @@ async function loadWavetableFrames(url, label, frameLen = 2048) {
const start = i * frameLen;
frames[i] = ch0.subarray(start, start + frameLen);
}
// build mipmaps
const mipmaps = [frames];
let levelFrames = frames;
for (let level = 1; level < WT_MAX_MIP_LEVELS; level++) {
const prevLen = levelFrames[0].length;
if (prevLen <= 32) break;
const nextLen = prevLen >> 1;
const next = levelFrames.map((src) => {
const out = new Float32Array(nextLen);
for (let j = 0; j < nextLen; j++) {
out[j] = (src[2 * j] + src[2 * j + 1]) / 2;
}
return out;
});
mipmaps.push(next);
levelFrames = next;
}
return { frames, mipmaps, frameLen, numFrames };
return { frames, frameLen, numFrames };
}
const loadCache = {};
@@ -222,7 +202,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
};
export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
const { s, n = 0, duration } = value;
const { s, n = 0, duration, clip } = value;
const ac = getAudioContext();
const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
let { warpmode } = value;
@@ -232,7 +212,10 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
const frequency = getFrequencyFromValue(value);
const { url, label } = getCommonSampleInfo(value, tables);
const payload = await loadWavetableFrames(url, label, frameLen);
const holdEnd = t + duration;
let holdEnd = t + duration;
if (clip !== undefined) {
holdEnd = Math.min(t + clip * duration, holdEnd);
}
const endWithRelease = holdEnd + release;
const envEnd = endWithRelease + 0.01;
const source = getWorklet(
@@ -252,7 +235,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
},
{ outputChannelCount: [2] },
);
source.port.postMessage({ type: 'tables', payload });
source.port.postMessage({ type: 'table', payload });
if (ac.currentTime > t) {
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
return;
@@ -328,7 +311,6 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
const timeoutNode = webAudioTimeout(
ac,
() => {
source.disconnect();
destroyAudioWorkletNode(source);
vibratoOscillator?.stop();
node.disconnect();
+20 -29
View File
@@ -137,7 +137,7 @@ class LFOProcessor extends AudioWorkletProcessor {
}
}
process(inputs, outputs, parameters) {
process(_inputs, outputs, parameters) {
const begin = parameters['begin'][0];
if (currentTime >= parameters.end[0]) {
return false;
@@ -510,7 +510,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
},
];
}
process(input, outputs, params) {
process(_input, outputs, params) {
if (currentTime <= params.begin[0]) {
return true;
}
@@ -1061,7 +1061,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
constructor(options) {
super(options);
this.tables = null;
this.frames = null;
this.frameLen = 0;
this.numFrames = 0;
this.phase = [];
@@ -1069,23 +1069,14 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
this.port.onmessage = (e) => {
const { type, payload } = e.data || {};
if (type === 'tables') {
this.tables = payload.mipmaps;
if (type === 'table') {
this.frames = payload.frames;
this.frameLen = payload.frameLen;
this.numFrames = this.tables[0].length;
this.numFrames = this.frames.length;
}
};
}
_chooseMip(dphi) {
const approxHarm = Math.min(64, 1 / Math.max(1e-6, dphi));
let level = 0;
while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) {
level++;
}
return level;
}
_mirror(x) {
return 1 - Math.abs(2 * x - 1);
}
@@ -1222,11 +1213,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
}
_sampleFrame(frame, phase) {
const pos = phase * frame.length;
const len = frame.length;
const pos = phase * len;
const i = pos | 0;
const frac = pos - i;
const a = frame[i % frame.length];
const b = frame[(i + 1) % frame.length];
const a = frame[i];
const i1 = i + 1 < len ? i + 1 : 0; // fast wrap
const b = frame[i1];
return a + (b - a) * frac;
}
@@ -1240,23 +1233,23 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const outL = outputs[0][0];
const outR = outputs[0][1] || outputs[0][0];
if (!this.tables) {
if (!this.frames) {
outL.fill(0);
if (outR !== outL) outR.set(outL);
return true;
}
const invSR = 1 / sampleRate;
for (let i = 0; i < outL.length; i++) {
const detune = pv(parameters.detune, i);
const tablePos = pv(parameters.position, i);
const tablePos = clamp(pv(parameters.position, i), 0, 1);
const idx = tablePos * (this.numFrames - 1);
const fIdx = idx | 0;
const frac = idx - fIdx;
const warpAmount = pv(parameters.warp, i);
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
const warpMode = pv(parameters.warpMode, i);
const voices = pv(parameters.voices, i);
const phaseRand = pv(parameters.phaserand, i);
const spread = voices > 1 ? pv(parameters.spread, i) : 0;
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
const spread = voices > 1 ? clamp(pv(parameters.spread, i), 0, 1) : 0;
const gain1 = Math.sqrt(0.5 - 0.5 * spread);
const gain2 = Math.sqrt(0.5 + 0.5 * spread);
let f = pv(parameters.frequency, i);
@@ -1272,15 +1265,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
gainR = gain1;
}
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
const dPhase = fVoice / sampleRate;
const level = this._chooseMip(dPhase);
const table = this.tables[level];
const dPhase = fVoice * invSR;
// warp phase then sample
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
const s0 = this._sampleFrame(table[fIdx], ph);
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
const s0 = this._sampleFrame(this.frames[fIdx], ph);
const s1 = this._sampleFrame(this.frames[Math.min(this.numFrames - 1, fIdx + 1)], ph);
let s = s0 + (s1 - s0) * frac;
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
s = -s;