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>
243 lines
5.9 KiB
TypeScript
243 lines
5.9 KiB
TypeScript
/**
|
|
* Effect commands for undo/redo system
|
|
*/
|
|
|
|
import { BaseCommand } from '../command';
|
|
|
|
export class EffectCommand extends BaseCommand {
|
|
private originalBuffer: AudioBuffer;
|
|
private modifiedBuffer: AudioBuffer;
|
|
private applyCallback: (buffer: AudioBuffer) => void;
|
|
private description: string;
|
|
|
|
constructor(
|
|
originalBuffer: AudioBuffer,
|
|
modifiedBuffer: AudioBuffer,
|
|
applyCallback: (buffer: AudioBuffer) => void,
|
|
description: string
|
|
) {
|
|
super();
|
|
this.originalBuffer = originalBuffer;
|
|
this.modifiedBuffer = modifiedBuffer;
|
|
this.applyCallback = applyCallback;
|
|
this.description = description;
|
|
}
|
|
|
|
getDescription(): string {
|
|
return this.description;
|
|
}
|
|
|
|
execute(): void {
|
|
this.applyCallback(this.modifiedBuffer);
|
|
}
|
|
|
|
undo(): void {
|
|
this.applyCallback(this.originalBuffer);
|
|
}
|
|
|
|
redo(): void {
|
|
this.execute();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Factory function to create effect commands
|
|
*/
|
|
export function createEffectCommand(
|
|
originalBuffer: AudioBuffer,
|
|
effectFunction: (buffer: AudioBuffer) => AudioBuffer | Promise<AudioBuffer>,
|
|
applyCallback: (buffer: AudioBuffer) => void,
|
|
description: string
|
|
): EffectCommand {
|
|
const result = effectFunction(originalBuffer);
|
|
const modifiedBuffer = result instanceof Promise ? originalBuffer : result;
|
|
return new EffectCommand(originalBuffer, modifiedBuffer, applyCallback, description);
|
|
}
|
|
|
|
/**
|
|
* Factory function to create async effect commands
|
|
*/
|
|
export async function createAsyncEffectCommand(
|
|
originalBuffer: AudioBuffer,
|
|
effectFunction: (buffer: AudioBuffer) => Promise<AudioBuffer>,
|
|
applyCallback: (buffer: AudioBuffer) => void,
|
|
description: string
|
|
): Promise<EffectCommand> {
|
|
const modifiedBuffer = await effectFunction(originalBuffer);
|
|
return new EffectCommand(originalBuffer, modifiedBuffer, applyCallback, description);
|
|
}
|
|
|
|
/**
|
|
* Factory for gain effect command
|
|
*/
|
|
export function createGainCommand(
|
|
buffer: AudioBuffer,
|
|
gainValue: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): EffectCommand {
|
|
return createEffectCommand(
|
|
buffer,
|
|
(buf) => {
|
|
// Import will happen at runtime
|
|
const { applyGain } = require('@/lib/audio/effects/gain');
|
|
return applyGain(buf, gainValue);
|
|
},
|
|
applyCallback,
|
|
`Apply Gain (${gainValue.toFixed(2)}x)`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for normalize peak command
|
|
*/
|
|
export function createNormalizePeakCommand(
|
|
buffer: AudioBuffer,
|
|
targetPeak: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): EffectCommand {
|
|
return createEffectCommand(
|
|
buffer,
|
|
(buf) => {
|
|
const { normalizePeak } = require('@/lib/audio/effects/normalize');
|
|
return normalizePeak(buf, targetPeak);
|
|
},
|
|
applyCallback,
|
|
`Normalize to Peak (${targetPeak.toFixed(2)})`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for normalize RMS command
|
|
*/
|
|
export function createNormalizeRMSCommand(
|
|
buffer: AudioBuffer,
|
|
targetRMS: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): EffectCommand {
|
|
return createEffectCommand(
|
|
buffer,
|
|
(buf) => {
|
|
const { normalizeRMS } = require('@/lib/audio/effects/normalize');
|
|
return normalizeRMS(buf, targetRMS);
|
|
},
|
|
applyCallback,
|
|
`Normalize to RMS (${targetRMS.toFixed(2)})`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for fade in command
|
|
*/
|
|
export function createFadeInCommand(
|
|
buffer: AudioBuffer,
|
|
duration: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): EffectCommand {
|
|
return createEffectCommand(
|
|
buffer,
|
|
(buf) => {
|
|
const { applyFadeIn } = require('@/lib/audio/effects/fade');
|
|
return applyFadeIn(buf, duration);
|
|
},
|
|
applyCallback,
|
|
`Fade In (${duration.toFixed(2)}s)`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for fade out command
|
|
*/
|
|
export function createFadeOutCommand(
|
|
buffer: AudioBuffer,
|
|
duration: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): EffectCommand {
|
|
return createEffectCommand(
|
|
buffer,
|
|
(buf) => {
|
|
const { applyFadeOut } = require('@/lib/audio/effects/fade');
|
|
return applyFadeOut(buf, duration);
|
|
},
|
|
applyCallback,
|
|
`Fade Out (${duration.toFixed(2)}s)`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for reverse command
|
|
*/
|
|
export function createReverseCommand(
|
|
buffer: AudioBuffer,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): EffectCommand {
|
|
return createEffectCommand(
|
|
buffer,
|
|
(buf) => {
|
|
const { reverseAudio } = require('@/lib/audio/effects/reverse');
|
|
return reverseAudio(buf);
|
|
},
|
|
applyCallback,
|
|
'Reverse Audio'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for low-pass filter command
|
|
*/
|
|
export async function createLowPassFilterCommand(
|
|
buffer: AudioBuffer,
|
|
frequency: number,
|
|
Q: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): Promise<EffectCommand> {
|
|
return createAsyncEffectCommand(
|
|
buffer,
|
|
async (buf) => {
|
|
const { applyLowPassFilter } = require('@/lib/audio/effects/filters');
|
|
return await applyLowPassFilter(buf, frequency, Q);
|
|
},
|
|
applyCallback,
|
|
`Low-Pass Filter (${frequency}Hz)`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for high-pass filter command
|
|
*/
|
|
export async function createHighPassFilterCommand(
|
|
buffer: AudioBuffer,
|
|
frequency: number,
|
|
Q: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): Promise<EffectCommand> {
|
|
return createAsyncEffectCommand(
|
|
buffer,
|
|
async (buf) => {
|
|
const { applyHighPassFilter } = require('@/lib/audio/effects/filters');
|
|
return await applyHighPassFilter(buf, frequency, Q);
|
|
},
|
|
applyCallback,
|
|
`High-Pass Filter (${frequency}Hz)`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory for band-pass filter command
|
|
*/
|
|
export async function createBandPassFilterCommand(
|
|
buffer: AudioBuffer,
|
|
frequency: number,
|
|
Q: number,
|
|
applyCallback: (buffer: AudioBuffer) => void
|
|
): Promise<EffectCommand> {
|
|
return createAsyncEffectCommand(
|
|
buffer,
|
|
async (buf) => {
|
|
const { applyBandPassFilter } = require('@/lib/audio/effects/filters');
|
|
return await applyBandPassFilter(buf, frequency, Q);
|
|
},
|
|
applyCallback,
|
|
`Band-Pass Filter (${frequency}Hz)`
|
|
);
|
|
}
|