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.

103 lines
3.4 KiB

class GranularProcessor 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
this.freq = new Float32Array(this.MAX_PARTIALS);
this.phase = new Float32Array(this.MAX_PARTIALS);
this.amp = new Float32Array(this.MAX_PARTIALS);
this.envPhase = new Float32Array(this.MAX_PARTIALS);
this.envDuration = new Float32Array(this.MAX_PARTIALS);
this.targetCount = 0;
this.port.onmessage = e => {
const { freqs, amps, durations } = e.data;
const count = freqs.length;
let factor = 1;
if (count > this.MAX_PARTIALS) factor = count / this.MAX_PARTIALS;
for (let i = 0; i < this.MAX_PARTIALS && i * factor < count; i++) {
const start = Math.floor(i * factor);
const end = Math.floor((i + 1) * factor);
let sumFreq = 0;
let sumAmp = 0;
let sumDur = 0;
for (let j = start; j < end; j++) {
sumFreq += freqs[j];
sumAmp += amps[j];
sumDur += durations[j];
}
const n = end - start;
this.freq[i] = sumFreq / n;
this.amp[i] = (sumAmp / n) * 1.5; // louder
this.envDuration[i] = sumDur / n;
this.envPhase[i] = 0;
}
this.targetCount = Math.min(count, this.MAX_PARTIALS);
};
}
process(_, outputs) {
const out = outputs[0][0];
const srInv = 1 / sampleRate;
const attackTime = 0.005; // 5ms attack
const decayTimeFactor = 1.2; // longer decay
const expFactor = 5; // controls exponential decay speed
for (let i = 0; i < out.length; i++) {
let sample = 0;
for (let c = 0; c < this.targetCount; c++) {
let env = 0;
if (this.envPhase[c] < 1) {
const t = this.envPhase[c] * this.envDuration[c];
if (t < attackTime) {
// linear attack
env = t / attackTime;
} else {
// exponential decay
const decayTime = this.envDuration[c] * decayTimeFactor;
const decayProgress = (t - attackTime) / Math.max(decayTime, 0.001);
env = Math.exp(-expFactor * decayProgress);
}
this.envPhase[c] += srInv / Math.max(this.envDuration[c], 0.001);
}
this.phase[c] += this.freq[c] * srInv;
if (this.phase[c] >= 1) this.phase[c] -= 1;
const idx = Math.floor(this.phase[c] * this.SINE_TABLE_SIZE) % this.SINE_TABLE_SIZE;
sample += this.sineTable[idx] * this.amp[c] * env;
}
// soft limiting
if (sample > 1) sample = 1;
else if (sample < -1) sample = -1;
out[i] = sample;
}
return true;
}
}
registerProcessor('granular-processor', GranularProcessor);

Powered by TurnKey Linux.