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:
2025-11-17 20:03:40 +01:00
parent f414573655
commit ee48f9475f
26 changed files with 6027 additions and 273 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -207,15 +207,11 @@ export function Waveform({
const actualX = x + scrollOffset;
const clickedTime = (actualX / visibleWidth) * duration;
// Shift key for selection
if (e.shiftKey && onSelectionChange) {
setIsSelecting(true);
setSelectionStart(clickedTime);
// Start selection on drag
setIsSelecting(true);
setSelectionStart(clickedTime);
if (onSelectionChange) {
onSelectionChange({ start: clickedTime, end: clickedTime });
} else if (onSeek) {
// Regular dragging for scrubbing (without auto-play)
setIsDragging(true);
onSeek(clickedTime, false);
}
};
@@ -234,31 +230,33 @@ export function Waveform({
// Handle selection dragging
if (isSelecting && onSelectionChange && selectionStart !== null) {
setIsDragging(true); // Mark that we're dragging
const start = Math.min(selectionStart, clampedTime);
const end = Math.max(selectionStart, clampedTime);
onSelectionChange({ start, end });
}
// Handle scrubbing (without auto-play during drag)
else if (isDragging && onSeek) {
onSeek(clampedTime, false);
}
};
const handleMouseUp = (e: React.MouseEvent<HTMLCanvasElement>) => {
// If we were dragging (scrubbing), trigger auto-play on mouse up
if (isDragging && onSeek && !isSelecting) {
// If we didn't drag (just clicked), seek to that position and clear selection
if (!isDragging && onSeek) {
const canvas = canvasRef.current;
if (canvas) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const visibleWidth = width * zoom;
const actualX = x + scrollOffset;
const releaseTime = (actualX / visibleWidth) * duration;
const clampedTime = Math.max(0, Math.min(duration, releaseTime));
// Auto-play on mouse up after dragging
const clickTime = (actualX / visibleWidth) * duration;
const clampedTime = Math.max(0, Math.min(duration, clickTime));
// Seek and auto-play
onSeek(clampedTime, true);
// Clear selection on click
if (onSelectionChange) {
onSelectionChange(null);
}
}
}
// If we dragged, the selection is already set via handleMouseMove
setIsDragging(false);
setIsSelecting(false);