Files
audio-ui/lib/audio/effects/fade.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

117 lines
3.0 KiB
TypeScript

/**
* Fade in/out effects
*/
import { getAudioContext } from '../context';
export type FadeType = 'linear' | 'exponential' | 'logarithmic';
/**
* Apply fade in to audio buffer
* @param buffer - Source audio buffer
* @param duration - Fade duration in seconds
* @param type - Fade curve type
* @returns New audio buffer with fade in applied
*/
export function applyFadeIn(
buffer: AudioBuffer,
duration: number,
type: FadeType = 'linear'
): AudioBuffer {
const audioContext = getAudioContext();
const fadeSamples = Math.min(
Math.floor(duration * buffer.sampleRate),
buffer.length
);
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++) {
if (i < fadeSamples) {
const progress = i / fadeSamples;
const gain = calculateFadeGain(progress, type);
outputData[i] = inputData[i] * gain;
} else {
outputData[i] = inputData[i];
}
}
}
return outputBuffer;
}
/**
* Apply fade out to audio buffer
* @param buffer - Source audio buffer
* @param duration - Fade duration in seconds
* @param type - Fade curve type
* @returns New audio buffer with fade out applied
*/
export function applyFadeOut(
buffer: AudioBuffer,
duration: number,
type: FadeType = 'linear'
): AudioBuffer {
const audioContext = getAudioContext();
const fadeSamples = Math.min(
Math.floor(duration * buffer.sampleRate),
buffer.length
);
const fadeStartSample = buffer.length - fadeSamples;
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++) {
if (i >= fadeStartSample) {
const progress = (i - fadeStartSample) / fadeSamples;
const gain = calculateFadeGain(1 - progress, type);
outputData[i] = inputData[i] * gain;
} else {
outputData[i] = inputData[i];
}
}
}
return outputBuffer;
}
/**
* Calculate fade gain based on progress and curve type
* @param progress - Progress from 0 to 1
* @param type - Fade curve type
* @returns Gain value from 0 to 1
*/
function calculateFadeGain(progress: number, type: FadeType): number {
switch (type) {
case 'linear':
return progress;
case 'exponential':
// Exponential curve: faster at the start, slower at the end
return progress * progress;
case 'logarithmic':
// Logarithmic curve: slower at the start, faster at the end
return Math.sqrt(progress);
default:
return progress;
}
}