Add back mipmaps; add caching
This commit is contained in:
parent
bad498a1e5
commit
a2b8407fbb
2 changed files with 56 additions and 22 deletions
|
|
@ -38,21 +38,25 @@ export const Warpmode = Object.freeze({
|
||||||
FLIP: 21,
|
FLIP: 21,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadWavetableFrames(url, label, frameLen = 2048) {
|
const seenKeys = new Set();
|
||||||
const buf = await loadBuffer(url, label);
|
async function getPayload(url, label, frameLen = 2048) {
|
||||||
const ch0 = buf.getChannelData(0);
|
const key = `${url},${frameLen}`;
|
||||||
const total = ch0.length;
|
if (!seenKeys.has(key)) {
|
||||||
const numFrames = Math.max(1, Math.floor(total / frameLen));
|
const buf = await loadBuffer(url, label);
|
||||||
const frames = new Array(numFrames);
|
const ch0 = buf.getChannelData(0);
|
||||||
for (let i = 0; i < numFrames; i++) {
|
const total = ch0.length;
|
||||||
const start = i * frameLen;
|
const numFrames = Math.max(1, Math.floor(total / frameLen));
|
||||||
frames[i] = ch0.subarray(start, start + frameLen);
|
const frames = new Array(numFrames);
|
||||||
|
for (let i = 0; i < numFrames; i++) {
|
||||||
|
const start = i * frameLen;
|
||||||
|
frames[i] = ch0.subarray(start, start + frameLen);
|
||||||
|
}
|
||||||
|
seenKeys.add(key);
|
||||||
|
return { frames, frameLen, numFrames, key };
|
||||||
}
|
}
|
||||||
return { frames, frameLen, numFrames };
|
return { frameLen, key }; // worklet will use the cached version
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadCache = {};
|
|
||||||
|
|
||||||
function humanFileSize(bytes, si) {
|
function humanFileSize(bytes, si) {
|
||||||
var thresh = si ? 1000 : 1024;
|
var thresh = si ? 1000 : 1024;
|
||||||
if (bytes < thresh) return bytes + ' B';
|
if (bytes < thresh) return bytes + ' B';
|
||||||
|
|
@ -97,6 +101,7 @@ async function decodeAtNativeRate(arr) {
|
||||||
return await tempAC.decodeAudioData(arr);
|
return await tempAC.decodeAudioData(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadCache = {};
|
||||||
const loadBuffer = (url, label) => {
|
const loadBuffer = (url, label) => {
|
||||||
url = url.replace('#', '%23');
|
url = url.replace('#', '%23');
|
||||||
if (!loadCache[url]) {
|
if (!loadCache[url]) {
|
||||||
|
|
@ -211,7 +216,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||||
}
|
}
|
||||||
const frequency = getFrequencyFromValue(value);
|
const frequency = getFrequencyFromValue(value);
|
||||||
const { url, label } = getCommonSampleInfo(value, tables);
|
const { url, label } = getCommonSampleInfo(value, tables);
|
||||||
const payload = await loadWavetableFrames(url, label, frameLen);
|
const payload = await getPayload(url, label, frameLen);
|
||||||
let holdEnd = t + duration;
|
let holdEnd = t + duration;
|
||||||
if (clip !== undefined) {
|
if (clip !== undefined) {
|
||||||
holdEnd = Math.min(t + clip * duration, holdEnd);
|
holdEnd = Math.min(t + clip * duration, holdEnd);
|
||||||
|
|
@ -305,7 +310,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||||
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
||||||
const envGain = ac.createGain();
|
const envGain = ac.createGain();
|
||||||
const node = source.connect(envGain);
|
const node = source.connect(envGain);
|
||||||
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
|
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
||||||
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
||||||
const handle = { node, source };
|
const handle = { node, source };
|
||||||
const timeoutNode = webAudioTimeout(
|
const timeoutNode = webAudioTimeout(
|
||||||
|
|
|
||||||
|
|
@ -1043,6 +1043,7 @@ function brownian(x, oct = 4) {
|
||||||
return (sum / norm) * 2 - 1;
|
return (sum / norm) * 2 - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tablesCache = {};
|
||||||
class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
|
|
@ -1061,7 +1062,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this.frames = null;
|
|
||||||
this.frameLen = 0;
|
this.frameLen = 0;
|
||||||
this.numFrames = 0;
|
this.numFrames = 0;
|
||||||
this.phase = [];
|
this.phase = [];
|
||||||
|
|
@ -1069,9 +1069,28 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
this.port.onmessage = (e) => {
|
this.port.onmessage = (e) => {
|
||||||
const { type, payload } = e.data || {};
|
const { type, payload } = e.data || {};
|
||||||
if (type === 'table') {
|
if (type === 'table') {
|
||||||
this.frames = payload.frames;
|
const key = payload.key;
|
||||||
this.frameLen = payload.frameLen;
|
this.frameLen = payload.frameLen;
|
||||||
this.numFrames = this.frames.length;
|
if (!tablesCache[key]) {
|
||||||
|
const tables = [payload.frames];
|
||||||
|
let table = tables[0];
|
||||||
|
for (let level = 1; level < 1; level++) {
|
||||||
|
const nextLen = table.length >> 1;
|
||||||
|
const nextTable = table.map((frame) => {
|
||||||
|
const avg = new Float32Array(nextLen);
|
||||||
|
for (let i = 0; i < nextLen; i++) {
|
||||||
|
avg[i] = (frame[2 * i] + frame[2 * i + 1]) / 2;
|
||||||
|
}
|
||||||
|
return avg;
|
||||||
|
});
|
||||||
|
tables.push(nextTable);
|
||||||
|
table = nextTable;
|
||||||
|
if (nextLen <= 32) break;
|
||||||
|
}
|
||||||
|
tablesCache[key] = tables;
|
||||||
|
}
|
||||||
|
this.tables = tablesCache[key];
|
||||||
|
this.numFrames = this.tables[0].length;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1222,6 +1241,15 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
return a + (b - a) * frac;
|
return a + (b - a) * frac;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_chooseMip(dphi) {
|
||||||
|
const approxHarm = clamp(dphi, 1e-6, 64);
|
||||||
|
let level = 0;
|
||||||
|
while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
process(_inputs, outputs, parameters) {
|
process(_inputs, outputs, parameters) {
|
||||||
if (currentTime >= parameters.end[0]) {
|
if (currentTime >= parameters.end[0]) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1231,8 +1259,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
}
|
}
|
||||||
const outL = outputs[0][0];
|
const outL = outputs[0][0];
|
||||||
const outR = outputs[0][1] || outputs[0][0];
|
const outR = outputs[0][1] || outputs[0][0];
|
||||||
|
if (!this.tables) {
|
||||||
if (!this.frames) {
|
|
||||||
outL.fill(0);
|
outL.fill(0);
|
||||||
if (outR !== outL) outR.set(outL);
|
if (outR !== outL) outR.set(outL);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1253,7 +1280,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
const gain2 = Math.sqrt(0.5 + 0.5 * spread);
|
const gain2 = Math.sqrt(0.5 + 0.5 * spread);
|
||||||
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 = 0.3 / Math.sqrt(voices);
|
const normalizer = 1 / Math.sqrt(voices);
|
||||||
for (let n = 0; n < voices; n++) {
|
for (let n = 0; n < voices; n++) {
|
||||||
const isOdd = (n & 1) == 1;
|
const isOdd = (n & 1) == 1;
|
||||||
let gainL = gain1;
|
let gainL = gain1;
|
||||||
|
|
@ -1265,12 +1292,14 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
}
|
}
|
||||||
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
|
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
|
||||||
const dPhase = fVoice * invSR;
|
const dPhase = fVoice * invSR;
|
||||||
|
const level = this._chooseMip(dPhase);
|
||||||
|
const table = this.tables[level];
|
||||||
|
|
||||||
// warp phase then sample
|
// warp phase then sample
|
||||||
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
||||||
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||||
const s0 = this._sampleFrame(this.frames[fIdx], ph);
|
const s0 = this._sampleFrame(table[fIdx], ph);
|
||||||
const s1 = this._sampleFrame(this.frames[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
||||||
let s = s0 + (s1 - s0) * frac;
|
let s = s0 + (s1 - s0) * frac;
|
||||||
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||||
s = -s;
|
s = -s;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue