You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
3.6 KiB

class AdditiveProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.MAX_PARTIALS = 500;
// Sine lookup table
this.SINE_TABLE_SIZE = 4096;
this.sineTable = new Float32Array(this.SINE_TABLE_SIZE);
for (let i = 0; i < this.SINE_TABLE_SIZE; i++) {
this.sineTable[i] = Math.sin((i / this.SINE_TABLE_SIZE) * 2 * Math.PI);
}
// Partial state arrays
this.freq = new Float32Array(this.MAX_PARTIALS);
this.amp = new Float32Array(this.MAX_PARTIALS);
this.phase = new Float32Array(this.MAX_PARTIALS);
this.targetFreq = new Float32Array(this.MAX_PARTIALS);
this.targetAmp = new Float32Array(this.MAX_PARTIALS);
this.activeCount = 0;
this.targetCount = 0;
// LPF state
this.lastSample = 0;
this.port.onmessage = e => {
const { freqs, amps } = e.data;
let count = freqs.length;
if (count > this.MAX_PARTIALS) {
// Supercell aggregation
const factor = count / this.MAX_PARTIALS;
for (let i = 0; i < this.MAX_PARTIALS; i++) {
let sumFreq = 0, sumAmp = 0;
const start = Math.floor(i * factor);
const end = Math.floor((i + 1) * factor);
for (let j = start; j < end; j++) {
sumFreq += freqs[j];
sumAmp += amps[j];
}
this.targetFreq[i] = sumFreq / (end - start);
this.targetAmp[i] = sumAmp / (end - start);
if (this.freq[i] === 0) this.phase[i] = Math.random();
}
this.targetCount = this.MAX_PARTIALS;
} else {
// Direct assignment
this.targetFreq.set(freqs);
this.targetAmp.set(amps);
for (let i = 0; i < count; i++) {
if (this.freq[i] === 0) this.phase[i] = Math.random();
}
this.targetCount = count;
}
};
}
process(_, outputs) {
const out = outputs[0][0];
const COUNT_SMOOTH = 0.02;
this.activeCount += (this.targetCount - this.activeCount) * COUNT_SMOOTH;
const count = Math.floor(this.activeCount);
if (count === 0) {
out.fill(0);
return true;
}
// Dynamic smoothing
const AMP_SMOOTH = 0.01 * Math.min(count / 50, 1);
const FREQ_SMOOTH = 0.01 * Math.min(count / 50, 1);
const srInv = 1 / sampleRate;
const tableSize = this.SINE_TABLE_SIZE;
for (let i = 0; i < out.length; i++) {
let sample = 0;
for (let c = 0; c < count; c++) {
this.freq[c] += (this.targetFreq[c] - this.freq[c]) * FREQ_SMOOTH;
this.amp[c] += (this.targetAmp[c] - this.amp[c]) * AMP_SMOOTH;
// Continuous phase
this.phase[c] += this.freq[c] * srInv;
if (this.phase[c] >= 1) this.phase[c] -= 1;
const idx = Math.floor(this.phase[c] * tableSize) % tableSize;
sample += this.sineTable[idx] * this.amp[c];
}
// Only apply soft LPF if we have more than a few partials
const useLPF = count >= 4;
if (useLPF) {
out[i] = this.lastSample * 0.95 + sample * 0.05;
this.lastSample = out[i];
} else {
out[i] = sample;
}
}
return true;
}
}
registerProcessor('additive-processor', AdditiveProcessor);

Powered by TurnKey Linux.