Skip to content

Add/remove/solo tracks - #41

Open
PlkMarudny wants to merge 11 commits into
ronak-create:mainfrom
PlkMarudny:add-tracks
Open

Add/remove/solo tracks#41
PlkMarudny wants to merge 11 commits into
ronak-create:mainfrom
PlkMarudny:add-tracks

Conversation

@PlkMarudny

@PlkMarudny PlkMarudny commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
image

What does this PR do?

Add/delete/solo track feature has been added. +V adds a video track, +A add an audio track. Empty track can be removed via a context menu.
Each track has an additional 'solo' button, that toggles the active state of it, disabling other tracks.

Type of change

  • Bug fix
  • New feature (GUI/API)
  • Docs
  • Refactor / internal

How was it verified?

  • node --check server.js && node --check app.js && node --check mcp-server.js passes
  • Opened the editor and confirmed the change in preview
  • Confirmed the change in an export (fast or realtime), if it affects rendering
  • Updated CLAUDE.md / README.md if the schema, props, or API changed

Checklist

  • No new runtime dependencies added
  • Preview and export render identically (single compositor)
  • Commits are focused and messages are descriptive

Summary by CodeRabbit

  • New Features
    • Data-driven timeline tracks with +V / +A creation, per-track solo controls, and removal of empty tracks via context menu (including updated track keyboard shortcuts).
    • Upgraded linked audio to N-way per-channel stems with linked editing and discrete-channel routing, plus dynamic audio bus rebuilding as tracks change.
  • Documentation
    • Updated project schema and README editing guidance for variable lane counts, solo/mute behavior, and channel labeling/removal rules.
  • Style
    • Refreshed timeline track header UI (new tools bar, improved solo visuals) and expanded context menu styling.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR replaces fixed timeline tracks with persisted dynamic video/audio lanes, adds lane creation, removal, enable, and solo controls, and extends linked audio from stereo pairs to discrete multichannel stems across playback, metering, display, waveform rendering, and export.

Changes

Dynamic tracks and multichannel audio

Layer / File(s) Summary
Dynamic track model and project persistence
app.js, CLAUDE.md
Track definitions are generated, loaded, persisted, validated against clips, and applied to shared track-height and audio-track state.
Track controls and timeline interface
app.js, index.html, style.css, README.md
Timeline headers support adding, removing, enabling, and soloing tracks, with updated context-menu, shortcut, documentation, and visual-state behavior.
Linked multichannel audio routing
app.js
Video-linked audio is created per source channel, labeled and routed through dynamic buses, with consistent discrete-channel handling for playback, waveform paths, and offline export.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Media
  participant Timeline
  participant AudioGraph
  participant Export
  Media->>Timeline: add video and linked channel clips
  Timeline->>AudioGraph: assign clips to dynamic audio buses
  AudioGraph->>AudioGraph: route discrete channels
  Export->>AudioGraph: mix routed channels offline
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding, removing, and soloing timeline tracks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PlkMarudny

Copy link
Copy Markdown
Contributor Author

Accidentally, I include also another commit, this one is big. As AAC audio can contain more than one channel, all of them are linked and displayed on a timeline, not just a stereo pair. If there is not enough audio tracks on the timeline, they are added to incorporate all channels.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app.js (1)

2768-2799: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Delete buses for removed tracks in syncAudioGraphTracks.

Buses are created for new ids but never removed for deleted ones, so audio.trackBus accumulates stale, disconnected nodes. Because a re-added track reuses the same id (nextTrackId returns max+1), the stale bus is reused and only reconnected to master inside the if (audio.meter) branch — so if the meter worklet failed to install, a re-added track's bus would stay silent.

♻️ Suggested cleanup
   const ids = audioTrackIds();
   for (const id of ids) {
     if (!audio.trackBus[id]) {
       const g = audio.ctx.createGain();
       audio.trackBus[id] = g;
       g.connect(audio.master);
     }
   }
+  for (const id of Object.keys(audio.trackBus)) {
+    if (!ids.includes(id)) {
+      try { audio.trackBus[id].disconnect(); } catch { }
+      delete audio.trackBus[id];
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 2768 - 2799, Update syncAudioGraphTracks to remove
entries from audio.trackBus whose IDs are absent from the current ids list,
disconnecting each removed bus before deletion. Perform this cleanup
independently of the audio.meter branch, and ensure buses for current IDs are
connected to audio.master so re-added tracks remain audible even when meter
installation failed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app.js`:
- Around line 169-181: Update ensureTracksCoverClips so each newly discovered
track name is marked as present immediately after adding it, before processing
subsequent clips. Use the existing TRACK_IDS set (or an equivalent per-call seen
set) to prevent multiple makeTrack calls for the same missing lane, while
preserving the existing sorting and height application behavior.
- Around line 1671-1677: Update buildTrackDOM() so the track ID interpolation in
the generated innerHTML uses escapeHtml(t.id), matching the existing escaping
used for clip labels. Leave the surrounding track controls and other
interpolation unchanged.

---

Nitpick comments:
In `@app.js`:
- Around line 2768-2799: Update syncAudioGraphTracks to remove entries from
audio.trackBus whose IDs are absent from the current ids list, disconnecting
each removed bus before deletion. Perform this cleanup independently of the
audio.meter branch, and ensure buses for current IDs are connected to
audio.master so re-added tracks remain audible even when meter installation
failed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2afad12f-9da9-477a-a4d6-5ba8a93f5b5a

📥 Commits

Reviewing files that changed from the base of the PR and between d584430 and 3a61499.

📒 Files selected for processing (5)
  • CLAUDE.md
  • README.md
  • app.js
  • index.html
  • style.css

Comment thread app.js
Comment thread app.js
@PlkMarudny

Copy link
Copy Markdown
Contributor Author

Apologies, not the whole commit was included.

@PlkMarudny

Copy link
Copy Markdown
Contributor Author

The feature has been updated. Now, dropping a video file with more audio channels than is available on the timeline, adds a necessary number of audio tracks.

@PlkMarudny

Copy link
Copy Markdown
Contributor Author

I am a little bit lost, not sure now if I mentioned anywhere the major upgrade how AAC audio tracks are handled. Let's say we have a multichannel audio - 3.0 layout. Before, an audio track was treated as a stereo pair, now all the channels are extracted into 3 mono audio tracks.

@PlkMarudny

Copy link
Copy Markdown
Contributor Author

The latest commit is rebased on main, and includes improved audio track handling. The main feature is still the possisbility to add/remove (empty) tracks and to solo a track with a single click.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
app.js (3)

1547-1573: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Two async paths build the same stem set.

finish() (via attachLinkedAudioChannels) and reconcileAudioChannels(c) at line 1572 both probe the channel count, both grow A-tracks, and both create/remove linked stems for the same linkGroup. They converge because reconcileAudioChannels re-reads project.clips after its await, but the outcome (and whether the user sees one toast or two) depends on promise resolution order. Consider making finish() the single owner and calling reconcileAudioChannels only for the later replace/reload paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 1547 - 1573, Make the video initialization path use a
single channel-reconciliation owner: retain finish() and its
attachLinkedAudioChannels flow for the initial async probe, and remove the
immediate reconcileAudioChannels(c) call from this kind === "video" block. Keep
reconcileAudioChannels available for later replace/reload paths so
initialization produces one consistent stem set and toast.

258-282: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Track removal isn't undoable.

removeTimelineTrack mutates TRACKS/project.tracks without pushUndo(), and addTimelineTrack likewise. Since removal is only allowed on empty lanes the blast radius is small, but Ctrl+Z after a mis-click silently does nothing (or restores clips onto a lane that no longer exists if undo state predates the add).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 258 - 282, Update removeTimelineTrack and
addTimelineTrack to record their track-list mutations through pushUndo(),
ensuring Ctrl+Z restores a removed track or reverses an added track along with
the corresponding project.tracks state. Capture the undo state at the
appropriate point before each mutation while preserving the existing empty-lane
removal behavior.

228-246: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Dead duplicate ensureAudioTrackCount — remove the stale copy.

Function declarations hoist, so the later implementation at lines 1465-1491 overrides this one. The live version also uses MAX_TRACKS_PER_KIND, handles disabled-track bookkeeping, rebuilds clips, and leaves MAX_AUDIO_TRACKS only in documentation/comment/text. Removing the lines 228-246 copy prevents the two versions drifting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.js` around lines 228 - 246, Remove the earlier duplicate
ensureAudioTrackCount function declaration near the top of app.js. Keep the
later implementation as the sole live definition, including its
MAX_TRACKS_PER_KIND limit, disabled-track bookkeeping, and clip rebuilding
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app.js`:
- Around line 1493-1514: Update attachLinkedAudioChannels to release each
existing audio stem before removing it from project.clips: iterate the
filtered-out clips and call releaseClipEl(...) using the same cleanup pattern as
reconcileAudioChannels. Preserve the existing filtering and channel recreation
behavior while ensuring associated media elements and gain nodes are released.
- Around line 288-317: Update showTrackCtxMenu so the newly created Remove track
button receives focus after the menu is added to the document, allowing keyboard
users to activate or dismiss the destructive action. Keep the existing
disabled-state and click-handler behavior unchanged.
- Around line 162-170: Update applyTracksFromProject to deduplicate valid track
definitions by id before clearing and rebuilding TRACKS. Preserve the existing
kind filtering and first-definition behavior, then pass only one definition per
id to makeTrack so duplicate lanes are not persisted or rendered.
- Around line 1475-1491: Update the track-addition flow containing
sortTracksInPlace, serializeTracks, and syncAudioGraphTracks to call
scheduleSave() after the project state and UI have been updated. Ensure
reconcileAudioChannels persists newly created audio lanes and disabledTracks
even when it exits early because the stem set is already correct.

---

Nitpick comments:
In `@app.js`:
- Around line 1547-1573: Make the video initialization path use a single
channel-reconciliation owner: retain finish() and its attachLinkedAudioChannels
flow for the initial async probe, and remove the immediate
reconcileAudioChannels(c) call from this kind === "video" block. Keep
reconcileAudioChannels available for later replace/reload paths so
initialization produces one consistent stem set and toast.
- Around line 258-282: Update removeTimelineTrack and addTimelineTrack to record
their track-list mutations through pushUndo(), ensuring Ctrl+Z restores a
removed track or reverses an added track along with the corresponding
project.tracks state. Capture the undo state at the appropriate point before
each mutation while preserving the existing empty-lane removal behavior.
- Around line 228-246: Remove the earlier duplicate ensureAudioTrackCount
function declaration near the top of app.js. Keep the later implementation as
the sole live definition, including its MAX_TRACKS_PER_KIND limit,
disabled-track bookkeeping, and clip rebuilding behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5819a0db-34e6-4825-954f-4436c42337a9

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc4700 and fc53e01.

📒 Files selected for processing (5)
  • CLAUDE.md
  • README.md
  • app.js
  • index.html
  • style.css
🚧 Files skipped from review as they are similar to previous changes (4)
  • README.md
  • index.html
  • CLAUDE.md
  • style.css

Comment thread app.js
Comment thread app.js
Comment thread app.js
Comment thread app.js
= added 4 commits July 28, 2026 12:30
Assuming a stereo downmix, each audio clip has a new property 'Pan', this allows to build a proper audio in the final output
@PlkMarudny

Copy link
Copy Markdown
Contributor Author

I think this PR should really be merged. What has been implemented:

  1. Audio panning per clip. As source audio tracks are decomposed onto A1,..., An, timeline and the default (and only for now) audio downmix is stereo, this allows to build a proper audio mix. For example: a video clip has a voice-over recorded on one track, this lands on A1 and is routed to the left stereo channel. Just set the pan to 0 on that audio clip and voice-over is placed in the center of the stereo scene.
  2. Master audio meter has been added.
  3. Tracks audio meters are collapsible. master L/R meter always visible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant