Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 58 additions & 26 deletions components/audio/DAW/Multitrack/AudioProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,28 @@ class AudioProcessor {
processing.reject(new Error(data.error));
this.processingQueue.delete(clipId);
break;

case 'decode-failed':
// Worker fetched audio but couldn't decode (Firefox/Safari lack
// OfflineAudioContext in workers). Decode on main thread using
// the already-fetched data to avoid a second network request.
debugLog('AudioProcessor', `🔄 Main-thread decode for ${clipId} (worker transferred data)`);
processing.onProgress?.('decoding', 70);
{
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
audioCtx.decodeAudioData(data.arrayBuffer)
.then(audioBuffer => {
const peaks = this.generateSimplePeaks(audioBuffer);
processing.resolve({ duration: audioBuffer.duration, peaks, method: 'worker-fetch-main-decode' });
})
.catch(err => {
processing.reject(new Error('Main-thread decode failed: ' + err.message));
})
.finally(() => {
this.processingQueue.delete(clipId);
});
}
break;
}
}

Expand Down Expand Up @@ -451,32 +473,42 @@ class AudioProcessor {
self.postMessage({ type: 'progress', clipId, stage: 'decoding', progress: 60 });
console.log('🔧 Worker: Starting audio decode for', clipId);

// Use OfflineAudioContext in worker
const decodeStart = performance.now();
const sampleRate = WORKER_CONSTANTS.DEFAULT_SAMPLE_RATE;
const offlineCtx = new OfflineAudioContext(2, sampleRate, sampleRate);
const audioBuffer = await offlineCtx.decodeAudioData(arrayBuffer);
const decodeTime = Math.round(performance.now() - decodeStart);
console.log('🔧 Worker: Decode completed in', decodeTime + 'ms for', clipId, '(duration:', audioBuffer.duration.toFixed(2) + 's)');

self.postMessage({ type: 'progress', clipId, stage: 'generating-peaks', progress: 85 });
console.log('🌊 Worker: Generating peaks for', clipId);

// Generate peaks
const peaksStart = performance.now();
const peaks = generatePeaks(audioBuffer, WORKER_CONSTANTS.DEFAULT_SAMPLES_PER_PIXEL);
const peaksTime = Math.round(performance.now() - peaksStart);
console.log('🌊 Worker: Peaks generated in', peaksTime + 'ms for', clipId, '(' + peaks.length + ' samples)');

const totalTime = Math.round(performance.now() - startTime);
console.log('✅ Worker: Completed', clipId, 'in', totalTime + 'ms total');

self.postMessage({
type: 'success',
clipId,
duration: audioBuffer.duration,
peaks
});
// OfflineAudioContext is not available in Web Workers on Firefox/Safari.
// If decode fails, transfer the fetched data back so the main thread
// can decode without re-fetching.
try {
const decodeStart = performance.now();
const sampleRate = WORKER_CONSTANTS.DEFAULT_SAMPLE_RATE;
const offlineCtx = new OfflineAudioContext(2, sampleRate, sampleRate);
const audioBuffer = await offlineCtx.decodeAudioData(arrayBuffer);
const decodeTime = Math.round(performance.now() - decodeStart);
console.log('🔧 Worker: Decode completed in', decodeTime + 'ms for', clipId, '(duration:', audioBuffer.duration.toFixed(2) + 's)');

self.postMessage({ type: 'progress', clipId, stage: 'generating-peaks', progress: 85 });
console.log('🌊 Worker: Generating peaks for', clipId);

const peaksStart = performance.now();
const peaks = generatePeaks(audioBuffer, WORKER_CONSTANTS.DEFAULT_SAMPLES_PER_PIXEL);
const peaksTime = Math.round(performance.now() - peaksStart);
console.log('🌊 Worker: Peaks generated in', peaksTime + 'ms for', clipId, '(' + peaks.length + ' samples)');

const totalTime = Math.round(performance.now() - startTime);
console.log('✅ Worker: Completed', clipId, 'in', totalTime + 'ms total');

self.postMessage({
type: 'success',
clipId,
duration: audioBuffer.duration,
peaks
});
} catch (decodeError) {
console.warn('⚠️ Worker: Decode failed for', clipId, '- transferring data to main thread:', decodeError.message);
self.postMessage({
type: 'decode-failed',
clipId,
arrayBuffer: arrayBuffer
}, [arrayBuffer]);
}

} catch (error) {
const totalTime = Math.round(performance.now() - startTime);
Expand Down
4 changes: 3 additions & 1 deletion components/audio/DAW/Multitrack/ClipEffectParametersModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export default function ClipEffectParametersModal({
clipId,
effectId,
effectType,
currentParameters
currentParameters,
logOperation = null
}) {
const { tracks, updateTrack } = useMultitrack();

Expand Down Expand Up @@ -121,6 +122,7 @@ export default function ClipEffectParametersModal({
<EffectComponent
parameters={currentParameters}
onParametersChange={handleParameterChange}
logOperation={logOperation}
/>
</Modal.Body>

Expand Down
1 change: 1 addition & 0 deletions components/audio/DAW/Multitrack/ClipEffectsRack.js
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ export default function ClipEffectsRack({ show, onHide, selectedClipId, logOpera
effectId={selectedEffect.id}
effectType={selectedEffect.type}
currentParameters={selectedEffect.parameters}
logOperation={logOperation}
/>
)}
</Modal>
Expand Down
5 changes: 4 additions & 1 deletion components/audio/DAW/Multitrack/effects/ClipEQ.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ function calculateFrequencyResponse(bands, sampleRate, numPoints = 512) {
/**
* Clip EQ Component
*/
export default function ClipEQ({ parameters, onParametersChange }) {
export default function ClipEQ({ parameters, onParametersChange, logOperation = null }) {
const canvasRef = useRef(null);
const [eqBands, setEqBands] = useState(parameters.bands || EQPresets.flat.bands);
const [outputGain, setOutputGain] = useState(parameters.outputGain || 0);
Expand Down Expand Up @@ -396,6 +396,9 @@ export default function ClipEQ({ parameters, onParametersChange }) {
setEqBands(preset.bands.map(band => ({ ...band })));
setOutputGain(preset.outputGain);
onParametersChange({ bands: preset.bands, outputGain: preset.outputGain });
if (logOperation && presetKey !== 'flat') {
logOperation('eq_preset_applied', { presetName: preset.name });
}
}
}}
className="bg-secondary text-white border-0"
Expand Down
Loading