Files
audio-ui/lib/audio/effects/normalize.ts
Sebastian Krüger ee48f9475f feat: add advanced audio effects and improve UI
Phase 6.5 Advanced Effects:
- Add Pitch Shifter with semitones and cents adjustment
- Add Time Stretch with pitch preservation using overlap-add
- Add Distortion with soft/hard/tube types and tone control
- Add Bitcrusher with bit depth and sample rate reduction
- Add AdvancedParameterDialog with real-time waveform visualization
- Add 4 professional presets per effect type

Improvements:
- Fix undefined parameter errors by adding nullish coalescing operators
- Add global custom scrollbar styling with color-mix transparency
- Add custom-scrollbar utility class for side panel
- Improve theme-aware scrollbar appearance in light/dark modes
- Fix parameter initialization when switching effect types

Integration:
- All advanced effects support undo/redo via EffectCommand
- Effects accessible via command palette and side panel
- Selection-based processing support
- Toast notifications for all effects

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 20:03:40 +01:00

133 lines
3.7 KiB
TypeScript

/**
* Normalization effects
*/
import { getAudioContext } from '../context';
/**
* Normalize audio to peak amplitude
* @param buffer - Source audio buffer
* @param targetPeak - Target peak amplitude (0.0 to 1.0, default 1.0)
* @returns New audio buffer with normalized audio
*/
export function normalizePeak(buffer: AudioBuffer, targetPeak: number = 1.0): AudioBuffer {
const audioContext = getAudioContext();
// Find the absolute peak across all channels
let maxPeak = 0;
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const channelData = buffer.getChannelData(channel);
for (let i = 0; i < buffer.length; i++) {
const abs = Math.abs(channelData[i]);
if (abs > maxPeak) {
maxPeak = abs;
}
}
}
// Calculate gain factor
const gainFactor = maxPeak > 0 ? targetPeak / maxPeak : 1.0;
// Create output buffer and apply gain
const outputBuffer = audioContext.createBuffer(
buffer.numberOfChannels,
buffer.length,
buffer.sampleRate
);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const inputData = buffer.getChannelData(channel);
const outputData = outputBuffer.getChannelData(channel);
for (let i = 0; i < buffer.length; i++) {
outputData[i] = inputData[i] * gainFactor;
}
}
return outputBuffer;
}
/**
* Normalize audio to RMS (loudness)
* @param buffer - Source audio buffer
* @param targetRMS - Target RMS level (0.0 to 1.0, default 0.5)
* @returns New audio buffer with normalized audio
*/
export function normalizeRMS(buffer: AudioBuffer, targetRMS: number = 0.5): AudioBuffer {
const audioContext = getAudioContext();
// Calculate RMS across all channels
let sumSquares = 0;
let totalSamples = 0;
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const channelData = buffer.getChannelData(channel);
for (let i = 0; i < buffer.length; i++) {
sumSquares += channelData[i] * channelData[i];
totalSamples++;
}
}
const currentRMS = Math.sqrt(sumSquares / totalSamples);
const gainFactor = currentRMS > 0 ? targetRMS / currentRMS : 1.0;
// Create output buffer and apply gain
const outputBuffer = audioContext.createBuffer(
buffer.numberOfChannels,
buffer.length,
buffer.sampleRate
);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const inputData = buffer.getChannelData(channel);
const outputData = outputBuffer.getChannelData(channel);
for (let i = 0; i < buffer.length; i++) {
outputData[i] = inputData[i] * gainFactor;
// Clamp to prevent distortion
outputData[i] = Math.max(-1, Math.min(1, outputData[i]));
}
}
return outputBuffer;
}
/**
* Get peak amplitude of audio buffer
* @param buffer - Audio buffer
* @returns Peak amplitude (0.0 to 1.0)
*/
export function getPeakAmplitude(buffer: AudioBuffer): number {
let maxPeak = 0;
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const channelData = buffer.getChannelData(channel);
for (let i = 0; i < buffer.length; i++) {
const abs = Math.abs(channelData[i]);
if (abs > maxPeak) {
maxPeak = abs;
}
}
}
return maxPeak;
}
/**
* Get RMS amplitude of audio buffer
* @param buffer - Audio buffer
* @returns RMS amplitude
*/
export function getRMSAmplitude(buffer: AudioBuffer): number {
let sumSquares = 0;
let totalSamples = 0;
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const channelData = buffer.getChannelData(channel);
for (let i = 0; i < buffer.length; i++) {
sumSquares += channelData[i] * channelData[i];
totalSamples++;
}
}
return Math.sqrt(sumSquares / totalSamples);
}