Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Per-clip **stereo pan** (`props.pan`, −1…+1) on video/audio clips — inspector
slider + keyframes; preview, audio-hold, and Fast export all honor it. Linked
stems default to L `−1` / R `+1` / center for other channels; projects saved
before pan migrate once via `panSchema` (so omitting `pan: 0` later does not
re-hard-pan a centered stem). Compact MCP keeps `pan` on linked stems.
- Stereo **master meter** (L/R) on the monitor — post-pan program sum in the
same RMS / LUFS / Peak modes as the per-track bars; A-tracks + video spill
route through one summed path when the meter worklet is active. Per-track
bars collapse via **◂** / **▸** beside the master strip (master L/R stay visible).
- Preview playback speed — a monitor toolbar toggle plus **J**/**K**/**L** shortcuts cycle the preview player through 1×, 1.5×, 2×, and 4× (L faster, J slower, K play/pause and reset to 1×). It rides on top of each clip's own speed and is forced back to 1× during export, so renders always come out at real time.

### Fixed
- Audio graph teardown on project reload — clip chains
(`MediaElementSource → splitter → gain → panner → bus`) are now fully
disconnected via `releaseClipEl` instead of only clearing the maps (which
left nodes wired to live track buses). Panner attach degrades gracefully if
`StereoPannerNode` is unavailable; inspector volume/pan changes refresh
audio-hold voices.

## [1.6.0] - 2026-07-14

### Added
Expand Down
40 changes: 29 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ Examples in `library/svg/`: `sparkles.svg` (loop), `lower-third.svg`,
"width": 1280, "height": 720, "fps": 30, // canvas/export settings
"background": "#000000", // canvas color behind all clips (optional)
"revision": 7, // bump on every write!
"panSchema": 1, // 1 = pan-aware; omit only on pre-pan projects (UI migrates once)
"markers": [ { "t": 2.5 }, { "t": 5.0, "label": "drop" } ],
// ^ beat/cue markers: gold diamonds on the ruler, snap targets for clip edges.
"inPoint": 10.023, // in timeline marker; paired with outPoint sets the focus on the part of the timeline
Expand All @@ -166,7 +167,16 @@ Examples in `library/svg/`: `sparkles.svg` (loop), `lower-third.svg`,
{ "id": "f_broll", "name": "B-roll", "parentId": null, "open": true }
],
"disabledTracks": [ "A2" ],
// ^ optional — track ids (V4 V3 V2 V1 A1 A2 A3) omitted from preview/export when listed
// ^ optional — track ids (e.g. V4 V3 V2 V1 A1 A2 A3) omitted from preview/export when listed
"tracks": [ // optional — timeline lanes (default V3…V1 + A1…A4)
{ "id": "V3", "kind": "video" }, // video: higher number drawn on top (V1 under V2 under V3…)
{ "id": "V2", "kind": "video" },
{ "id": "V1", "kind": "video" },
{ "id": "A1", "kind": "audio" }, // audio: A1…An top→bottom; +V/+A in the UI appends Vn+1 / An+1
{ "id": "A2", "kind": "audio" },
{ "id": "A3", "kind": "audio" },
{ "id": "A4", "kind": "audio" }
],
"media": [
{ "id": "m_abc", "name": "intro.mp4", "kind": "video", // video|audio|image|svg
"src": "/media/intro.mp4", // path under ./media or ./library
Expand All @@ -178,12 +188,12 @@ Examples in `library/svg/`: `sparkles.svg` (loop), `lower-third.svg`,
"id": "c_xyz", // unique string
"mediaId": "m_abc", // null for kind:"text" and kind:"adjust"
"kind": "video", // video | audio | image | svg | text | adjust
"track": "V1", // V3 V2 V1 (top→bottom video) | A1 A2 A3 A4 (audio)
"track": "V1", // Vn…V1 (video) | A1…An (audio); extras via UI +V/+A or tracks[]
"start": 0, // timeline position, seconds
"in": 2.5, // offset into source media, seconds (0 for image/svg/text)
"duration": 5, // clip length on timeline, seconds
"name": "intro",
"linkGroup": "lg_abc", // OPTIONAL — AV link: video + its L/R audio companions share one id
"linkGroup": "lg_abc", // OPTIONAL — AV link: video + per-channel audio stems share one id
"props": { /* all optional — see the props reference below */ },
// OPTIONAL — keyframe animation. Times are seconds RELATIVE TO CLIP START.
// "ease" sits on the DESTINATION keyframe of each segment:
Expand Down Expand Up @@ -250,6 +260,7 @@ Examples in `library/svg/`: `sparkles.svg` (loop), `lower-third.svg`,
| prop | default | notes |
|---|---|---|
| `volume` | 1 | 0–2 |
| `pan` | 0 | −1 (full left) … 0 (center) … +1 (full right). Linked stems default L `−1` / R `+1` / other `0`. Projects saved before pan migrate once on load (`panSchema`); keep `panSchema: 1` (and explicit `pan` on stems) when rewriting the document so a centered stem is not re-hard-panned. |
| `speed` | 1 | 0.25–4× playback rate. **Keyframable → speed ramps**: with `keyframes.speed` the engine time-remaps (media time = `in` + ∫speed dt), in preview and in the export audio mix. Static case: source window consumed = `duration × speed`, so `in + duration×speed ≤ media.duration`. |

**Text clips only:**
Expand Down Expand Up @@ -315,7 +326,7 @@ footage. Example: 0.3 s impact shake over everything =
`{kind:"adjust", track:"V3", duration:0.3, props:{shake:18}}`.

**Animatable props** (usable in `keyframes`): x, y, scale, rotation, opacity,
volume, speed, brightness, contrast, saturation, hue, blur, grayscale, sepia,
volume, pan, speed, brightness, contrast, saturation, hue, blur, grayscale, sepia,
invert, temperature, tint, vignette, cornerRadius, shake, rgbSplit, grain,
fontSize, letterSpacing, glow.

Expand All @@ -325,14 +336,19 @@ glitch (RGB split + jitter) · pop (overshoot scale — stickers/captions).

### Semantics

- Rendering order: V1 is drawn first, then V2, V3 on top. SVG/image/text clips
go on any V track (V2/V3 are handy overlay lanes).
- **Audio tracks A1–A4** hold both standalone audio files *and* linked companions
- Rendering order: V1 is drawn first, then V2, V3, … on top. SVG/image/text clips
go on any V track (higher V lanes are handy overlay lanes). Add lanes with the
timeline **+V** / **+A** buttons (or by listing them in `tracks`); up to 16 per kind.
Right-click an empty track header → **Remove track** (disabled if the lane has
clips or is the last video/audio track).
- **Audio tracks A1–An** hold both standalone audio files *and* linked companions
for imported video. Dropping a video creates the picture on a V track
(`props.volume: 0` so it isn't doubled) plus stereo L/R `kind:"audio"` clips
on A1/A2 that share a `linkGroup` (and the same `mediaId` / timing). Standalone
music/SFX also live on A1–A4. Linked partners move/trim/split together — edit
timing on any member of the group; do not treat A-tracks as music-only.
(`props.volume: 0` so it isn't doubled) plus one `kind:"audio"` clip per source
channel on consecutive A-tracks that share a `linkGroup` (and the same
`mediaId` / timing). Each stem is mono-isolated then placed with `pan` (defaults
preserve stereo: L `−1`, R `+1`). Standalone music/SFX also live on A-tracks. Linked partners
move/trim/split together — edit timing on any member of the group; do not treat
A-tracks as music-only.
- Media is `fit`-ted to the canvas (default "contain"), then crop → scale/x/y/
rotation → flips apply.
- `props` keys are all optional — missing keys get the defaults above.
Expand Down Expand Up @@ -425,6 +441,8 @@ of previous durations.

**Music bed**: audio file on A1, `props.volume: 0.3`, trim with `in`/`duration`.

**Center a mono stem**: linked or standalone audio clip with `props.pan: 0` (inspector **Pan** slider, −1…+1). Stereo video stems default to L `−1` / R `+1`; set A1 to `0` to center that channel in the mix.

**Cinematic grade**: `props.filterPreset: "teal-orange"` (tweak with
`temperature`/`vignette` on top).

Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,22 @@ same time.

**Editing**

- 3 video tracks + 4 audio tracks, drag/trim/split/snap, undo/redo
- Video + audio tracks (default 3+4; add more with **+V** / **+A** in the track
header; right-click an empty header → **Remove track**), drag/trim/split/snap,
undo/redo
- **Settings** (cog in the top bar) — optional prefs stored in this browser via
`localStorage`. Enable **Link timeline and Project bin selection** so picking a
timeline clip highlights its media in Project, and clicking a Project item
selects every timeline clip that uses it (off by default).
- Video + audio tracks (default 3+4), drag/trim/split/snap, undo/redo
- **+V** / **+A** in the track header add a video or audio lane (up to 16 each)
- Right-click an empty track header → **Remove track** (disabled if the lane
has clips, or if it would leave you with zero video/audio tracks)
- Track header **S** solos that lane (mutes all others); click again to restore
the previous mute state. Using the mute toggle while soloed exits solo.
- Dropping a video with more audio channels than A-tracks **adds the missing
lanes automatically** (up to 16) and toasts how many were added; each channel
becomes a linked stem on its own A-track
- **Direct manipulation on the monitor** — click a clip or title on the preview to
move, resize (corner handles), or rotate (top handle, Shift-snap) it directly
- **Timeline multi-select** — rubber-band marquee (drag on empty track area),
Expand Down
Loading
Loading