// SonicForge Studio AudioWorklet DSP Processor // Real-time priority audio rendering thread for low-latency DSP class SonicDSPProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ { name: 'volumeDb', defaultValue: 0, minValue: -60, maxValue: 12 }, { name: 'pan', defaultValue: 0, minValue: -1, maxValue: 1 } ]; } constructor() { super(); this.sampleCount = 0; this.isPlaying = true; this.port.onmessage = (event) => { if (!event.data) return; if (event.data.type === 'SEEK') { this.sampleCount = Math.floor(event.data.sampleIndex || 0); } else if (event.data.type === 'PAUSE') { this.isPlaying = false; } else if (event.data.type === 'PLAY') { this.isPlaying = true; } }; } process(inputs, outputs, parameters) { const input = inputs[0]; const output = outputs[0]; if (!input || !output || input.length === 0) return true; const numChannels = Math.min(input.length, output.length); const blockSize = output[0].length; const volumeDbParam = parameters.volumeDb; const panParam = parameters.pan; const volDb = volumeDbParam.length === 1 ? volumeDbParam[0] : 0; const panVal = panParam.length === 1 ? panParam[0] : 0; // Constant-Power Panning Law (21_CLIENT_PRE.md ยง5) const gain = Math.pow(10, volDb / 20); const theta = ((panVal + 1) / 2) * (Math.PI / 2); const gainL = Math.cos(theta) * gain; const gainR = Math.sin(theta) * gain; const inputL = input[0] || new Float32Array(blockSize); const inputR = input[1] || inputL; const outputL = output[0]; const outputR = output[1] || outputL; for (let i = 0; i < blockSize; i++) { if (this.isPlaying) { outputL[i] = inputL[i] * gainL; if (output.length > 1) { outputR[i] = inputR[i] * gainR; } this.sampleCount++; } else { outputL[i] = 0; if (output.length > 1) outputR[i] = 0; } } // Lock-free playhead position update to Main Thread if (this.sampleCount % 512 === 0) { this.port.postMessage({ type: 'POSITION_UPDATE', sampleCount: this.sampleCount }); } return true; } } registerProcessor('sonic-dsp-processor', SonicDSPProcessor);