Three bugs in a row came from the same root: a time value crossing an API boundary as a bare f64, with the caller and the callee disagreeing about whether it meant seconds or beats. Recording landed at the wrong time, MIDI clips grew too fast, and a 1-second split played back as half a second. Each was "obviously" one domain at the call site and read as the other on the far side. This makes the mismatch a compile error. Backend API — every time-carrying f64 is gone: - Commands: Seek/SetOffset/SetTrimStart/SetTrimEnd -> Seconds; MoveClip/ ExtendClip/CreateMidiClip/AddMidiNote/AddLoadedMidiClip/ UpdateMidiClipNotes/AddMidiClipSync and all four automation commands -> Beats; TrimClip -> TrimRange. - Events/queries: PlaybackPosition, WaveformChunksReady's time range, AudioFileReady::duration, PoolFileInfo, get_playhead_seconds -> Seconds. - Serialized: MidiClipData::duration and AutomationKeyframeData::time -> Beats. Both newtypes are #[serde(transparent)], so the .beam on-disk format is unchanged. - Several controller methods ALREADY took Beats and unwrapped it to shove into the command — the newtype was being discarded at the very boundary it existed to protect. TrimRange, for the domain-polymorphic case: a clip's content time is SECONDS for sampled audio but BEATS for MIDI, so a single newtype can't express it (there was even a comment in engine.rs saying so, and that rationalization is what let the bug through). A domain-tagged enum can. The engine rejects a range whose domain doesn't match the track, and the range is built from the clip (clip.trim_range()) so callers can't pick the wrong variant. ContentTime, for the trim fields: ClipInstance::trim_start/trim_end are content times, and were the last untyped f64 — the actual root of the split bug. ContentTime is deliberately a DEAD END: no .to_seconds(), no .to_beats(), no arithmetic with Seconds or Beats. Content times combine freely with each other (same clip, same domain — safe), so the ~100 passthrough sites cost nothing; the only exit is resolving against the clip that knows the domain (AudioClip::resolve_content_time / Document::resolve_content_time / ClipDuration::same_domain). Mixing domains no longer compiles. Two more live bugs the types surfaced: - ClipInstance::effective_duration_beats took a SECONDS clip duration and subtracted trim_start from it. For a TRIMMED MIDI clip that subtracted a beats offset from a seconds duration, so the clip's timeline length was wrong at any tempo but 60 BPM. Untrimmed clips happened to work, which is why it hid. It now takes a ClipDuration and resolves in the clip's own domain: beats content carries over directly (tempo- invariant), wall-clock content converts at the clip's position. Regression test asserts a clip trimmed to beats 2..6 is 4 beats long at 60/90/120 BPM. - Trim validation clamped a content-domain trim against a wall-clock gap. gap_to_content/content_to_secs now convert at the clip's position. Also folds two more copies of the backend add-logic into BackendContext::add_clip_instance (split and remove_clip_instances both re-add clips), so the trim/duration conversions live in exactly one place instead of four. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .github/workflows | ||
| daw-backend | ||
| docs | ||
| ffmpeg-blob-io | ||
| lightningbeam-core | ||
| lightningbeam-ui | ||
| nam-ffi | ||
| packaging | ||
| screenshots | ||
| scripts | ||
| src | ||
| tests | ||
| vendor | ||
| .gitignore | ||
| .gitmodules | ||
| ARCHITECTURE.md | ||
| BEAM_FILE_FORMAT.md | ||
| CONTRIBUTING.md | ||
| Changelog.md | ||
| LICENSE | ||
| README.md | ||
| beam_inspector.py | ||
| beam_inspector_README.md | ||
| create_release.sh | ||
| package.json | ||
| phone-ui-sketches.html | ||
| pnpm-lock.yaml | ||
README.md
Lightningbeam
A free and open-source 2D multimedia editor combining vector animation, audio production, and video editing in a single application.
Screenshots
Features
Vector Animation
- GPU-accelerated vector rendering with Vello
- Draw and animate vector shapes with keyframe-based timeline
- Non-destructive editing workflow
- Paint bucket tool for automatic fill detection
Audio Production
- Real-time multi-track audio recording and playback
- Node graph-based effects processing
- MIDI sequencing with synthesizers and samplers
- Comprehensive effects library (reverb, delay, EQ, compression, distortion, etc.)
- Custom audio engine with lock-free design for glitch-free playback
Video Editing
- Video timeline and editing with FFmpeg-based decoding
- GPU-accelerated waveform rendering with mipmaps
- Audio integration from video soundtracks
Technical Stack
Current Implementation (Rust UI)
- UI Framework: egui (immediate-mode GUI)
- GPU Rendering: Vello + wgpu (Vulkan/Metal/DirectX 12)
- Audio Engine: Custom real-time engine (
daw-backend)- cpal for cross-platform audio I/O
- symphonia for audio decoding
- dasp for node graph processing
- Video: FFmpeg 8 for encode/decode
- Platform: Cross-platform (Linux, macOS, Windows)
Legacy Implementation (Deprecated)
- Frontend: Vanilla JavaScript
- Backend: Rust (Tauri framework)
Project Status
Lightningbeam is developed on the main branch. The project has been rewritten from a Tauri/JavaScript prototype to a pure Rust application to eliminate IPC bottlenecks and achieve better performance for real-time video and audio processing.
Current Status:
- ✅ Core UI panes (Stage, Timeline, Asset Library, Info Panel, Toolbar)
- ✅ Drawing tools (Select, Draw, Rectangle, Ellipse, Paint Bucket, Transform)
- ✅ Undo/redo system
- ✅ GPU-accelerated vector rendering
- ✅ Audio engine with node graph processing
- ✅ GPU waveform rendering with mipmaps
- ✅ Video decoding integration
- ✅ Export system (video, image, audio, animated GIF, SVG)
- ✅ Node editor UI
- 🚧 Piano roll editor (in progress)
Getting Started
Download a prebuilt release
If you don't want to build from source, prebuilt binaries for each release are on the GitHub releases page. Download the build for your platform and run it directly.
Building from source
Prerequisites
- Rust (stable toolchain via rustup)
- System dependencies:
- Linux: ALSA development files, FFmpeg 8
- macOS: FFmpeg (via Homebrew)
- Windows: FFmpeg 8, Visual Studio with C++ tools
See docs/BUILDING.md for detailed setup instructions.
Building and Running
# Clone the repository
git clone https://github.com/skykooler/lightningbeam.git
# Or from Gitea
git clone https://git.skyler.io/skyler/lightningbeam.git
cd lightningbeam/lightningbeam-ui
# Build and run
cargo run
# Or build optimized release version
cargo build --release
Documentation
- CONTRIBUTING.md - Development setup and contribution guidelines
- ARCHITECTURE.md - System architecture overview
- docs/BUILDING.md - Detailed build instructions and troubleshooting
- docs/AUDIO_SYSTEM.md - Audio engine architecture and development
- docs/UI_SYSTEM.md - UI pane system and tool development
- docs/RENDERING.md - GPU rendering pipeline and shaders
Project History
Lightningbeam evolved from earlier multimedia editing projects I've worked on since 2010, including the FreeJam DAW. The JavaScript/Tauri prototype began in November 2023, and the Rust UI rewrite started in late 2024 to eliminate performance bottlenecks and provide a more integrated native experience.
Goals
Create a comprehensive FOSS alternative for 2D-focused multimedia work, integrating animation, audio, and video editing in a unified workflow. Lightningbeam aims to be:
- Fast: GPU-accelerated rendering and real-time audio processing
- Flexible: Node graph-based audio routing and modular synthesis
- Integrated: Seamless workflow across animation, audio, and video
- Open: Free and open-source, built on open standards
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
[License information to be added]


