From f3f5b65e1eac5ae28c8c43306c585a07a3514261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Tue, 18 Nov 2025 07:46:27 +0100 Subject: [PATCH] feat: implement Ableton Live-style DAW layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major UX refactor to match professional DAW workflows (Ableton/Bitwig): **Layout Changes:** - Removed sidebar completely - Track actions moved to header toolbar (Add/Import/Clear All) - Each track now shows its own devices/effects in the track strip - Master section moved to bottom footer area - Full-width waveform display **Track Strip (left panel):** - Track name (editable inline) - Color indicator - Collapse/Solo/Mute/Delete buttons - Volume slider with percentage - Pan slider with L/C/R indicator - Collapsible "Devices" section showing track effects - Shows effect count in header - Each effect card shows: name, enable/disable toggle, remove button - Effects are colored based on enabled/disabled state - Click to expand/collapse devices section **Master Section (bottom):** - Transport controls (Play/Pause/Stop) with timeline - Master volume control - Master effects placeholder (to be implemented) **Benefits:** - True DAW experience like Ableton Live - Each track is self-contained with its own effect chain - No context switching between tabs - Effects are always visible for each track - More screen space for waveforms - Professional mixer-style layout Note: Effects are visible but not yet applied to audio - that's next! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- components/editor/AudioEditor.tsx | 106 ++++++++++++++++-------------- components/tracks/Track.tsx | 60 +++++++++++++++++ components/tracks/TrackList.tsx | 16 +++++ 3 files changed, 134 insertions(+), 48 deletions(-) diff --git a/components/editor/AudioEditor.tsx b/components/editor/AudioEditor.tsx index bf5c273..0fd3048 100644 --- a/components/editor/AudioEditor.tsx +++ b/components/editor/AudioEditor.tsx @@ -1,11 +1,11 @@ 'use client'; import * as React from 'react'; -import { Music } from 'lucide-react'; +import { Music, Plus, Upload, Trash2 } from 'lucide-react'; import { PlaybackControls } from './PlaybackControls'; -import { SidePanel } from '@/components/layout/SidePanel'; import { ThemeToggle } from '@/components/layout/ThemeToggle'; import { CommandPalette } from '@/components/ui/CommandPalette'; +import { Button } from '@/components/ui/Button'; import type { CommandAction } from '@/components/ui/CommandPalette'; import { useMultiTrack } from '@/lib/hooks/useMultiTrack'; import { useMultiTrackPlayer } from '@/lib/hooks/useMultiTrackPlayer'; @@ -272,9 +272,29 @@ export function AudioEditor() { {/* Compact Header */}
{/* Left: Logo */} -
- -

Audio UI

+
+
+ +

Audio UI

+
+ + {/* Track Actions */} +
+ + + {tracks.length > 0 && ( + + )} +
{/* Right: Command Palette + Theme Toggle */} @@ -286,32 +306,6 @@ export function AudioEditor() { {/* Main content area */}
- {/* Side Panel */} - - {/* Main canvas area */}
{/* Multi-Track View */} @@ -331,26 +325,42 @@ export function AudioEditor() { />
- {/* Multi-Track Playback Controls */} -
- -
+ {/* Transport Controls + Master Channel */} +
+ {/* Transport Controls */} +
+ +
+ + {/* Master Track Strip */} +
+
+

Master

+ Final mix output +
+ {/* Master effects will go here */} +
+ Master effects: {masterEffectChain.effects.length} +
+
+
+ {/* Import Track Dialog */} void; onSeek?: (time: number) => void; onLoadAudio?: (buffer: AudioBuffer) => void; + onToggleEffect?: (effectId: string) => void; + onRemoveEffect?: (effectId: string) => void; } export function Track({ @@ -41,12 +43,15 @@ export function Track({ onNameChange, onSeek, onLoadAudio, + onToggleEffect, + onRemoveEffect, }: TrackProps) { const canvasRef = React.useRef(null); const containerRef = React.useRef(null); const fileInputRef = React.useRef(null); const [isEditingName, setIsEditingName] = React.useState(false); const [nameInput, setNameInput] = React.useState(String(track.name || 'Untitled Track')); + const [showDevices, setShowDevices] = React.useState(true); const inputRef = React.useRef(null); const handleNameClick = () => { @@ -378,6 +383,61 @@ export function Track({ : `R${Math.round(track.pan * 100)}`} + + {/* Devices/Effects Section */} +
+ + + {showDevices && ( +
+ {track.effectChain.effects.length === 0 ? ( +
+ No devices +
+ ) : ( + track.effectChain.effects.map((effect) => ( +
+ {effect.name} +
+ + +
+
+ )) + )} +
+ )} +
)} diff --git a/components/tracks/TrackList.tsx b/components/tracks/TrackList.tsx index d879629..d99930d 100644 --- a/components/tracks/TrackList.tsx +++ b/components/tracks/TrackList.tsx @@ -107,6 +107,22 @@ export function TrackList({ onLoadAudio={(buffer) => onUpdateTrack(track.id, { audioBuffer: buffer }) } + onToggleEffect={(effectId) => { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.map((e) => + e.id === effectId ? { ...e, enabled: !e.enabled } : e + ), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); + }} + onRemoveEffect={(effectId) => { + const updatedChain = { + ...track.effectChain, + effects: track.effectChain.effects.filter((e) => e.id !== effectId), + }; + onUpdateTrack(track.id, { effectChain: updatedChain }); + }} /> ))}