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>
This commit is contained in:
242
lib/history/commands/effect-command.ts
Normal file
242
lib/history/commands/effect-command.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 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)`
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user