diff --git a/README.md b/README.md index ab1c336..d131ad8 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,59 @@ -``` -T E X T S T E P -``` +
+ +# T E X T S T E P + +**A terminal-based step sequencer, drum machine, and synthesizer built entirely in Rust.** -A terminal-based step sequencer, drum machine, and synthesizer built in Rust. All DSP from scratch — no samples, no external audio libraries. Just your terminal and your speakers. +[![Rust](https://img.shields.io/badge/Rust-1.70%2B-orange?logo=rust&logoColor=white)](https://www.rust-lang.org/) +[![License: GPL v2](https://img.shields.io/badge/License-GPL_v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![Platform: macOS](https://img.shields.io/badge/Platform-macOS-lightgrey?logo=apple&logoColor=white)](https://www.apple.com/macos/) +[![Audio: CoreAudio](https://img.shields.io/badge/Audio-CoreAudio-green)](https://developer.apple.com/documentation/coreaudio) +[![TUI: ratatui](https://img.shields.io/badge/TUI-ratatui-purple)](https://ratatui.rs/) +[![Lines of Code](https://img.shields.io/badge/Lines_of_Code-17k-informational)]() +[![Tests](https://img.shields.io/badge/Tests-23_passing-brightgreen)]() + ![TextStep Demo](assets/demo.gif) -## What Is This +
+ +--- -TextStep is a TUI music production tool with: +## Features -- **8 drum tracks** — Kick, Snare, Closed HiHat, Open HiHat, Ride, Clap, Cowbell, Tom — each fully synthesized with 8 tweakable sound parameters -- **Polyphonic synth** — 2 oscillators + sub, 2 ADSR envelopes, resonant filter, LFO with 6 waveforms, collapsible UI section -- **32-step sequencer** with 10 patterns and 8 kit slots -- **Send effects chain** — Schroeder reverb, tempo-synced filtered delay, tube saturator, SSL-style glue compressor -- **Live performance** — drum pads, real-time recording, pattern queuing, per-pattern BPM -- **Mouse support** — click the grid, drag parameters Ableton-style, audition sounds from the activity bar -- **Project system** — save/load `.tsp` files, standalone kit export, preset browser +- **8 Drum Tracks** — Kick, Snare, Closed HiHat, Open HiHat, Ride, Clap, Cowbell, Tom — each fully synthesized with 8 tweakable sound parameters +- **Polyphonic Synth** — 2 oscillators + sub, 2 ADSR envelopes, resonant filter, LFO with 6 waveforms, collapsible UI section +- **32-Step Sequencer** — 10 patterns and 8 kit slots with per-pattern BPM and swing +- **Send Effects Chain** — Schroeder reverb, tempo-synced filtered delay, tube saturator, SSL-style glue compressor +- **Live Performance** — drum pads, real-time recording, pattern queuing, per-pattern BPM +- **Mouse Support** — click the grid, drag parameters Ableton-style, audition sounds from the activity bar +- **Project System** — save/load `.tsp` files, standalone kit export, preset browser +- **Spectrum Analyzer** — real-time FFT spectrum display and VU meter with 90s Hi-Fi LED aesthetic Ships with **10 demo patterns** ready to play: House, Chicago House, Brit House, French House, Dirty House, Trance, Techno, Drum & Bass, Trap, and Moombahton. -### Color Palette +## Getting Started -Hardware/synthwave aesthetic — amber for data, cyan for transport, pink for focus, gold for queued state. All rendered with UTF-8 block characters on a dark background. +### Prerequisites -## Build & Run +- [Rust](https://www.rust-lang.org/tools/install) 1.70 or later +- macOS with CoreAudio (primary target) +### Build & Run Requires Rust (1.70+). macOS with CoreAudio is the primary target; Linux with ALSA is also supported. ```bash +# Clone the repository +git clone https://github.com/illobo/textStep.git +cd textStep + +# Build and run (release mode recommended for audio performance) cargo build --release cargo run --release ``` +### Run Tests On Linux, install ALSA development headers first: ```bash @@ -43,7 +63,7 @@ sudo apt-get install libasound2-dev # Debian/Ubuntu Run the tests: ```bash -cargo test +cargo test # 23 tests, runs in <1s ``` ### Pre-built Binaries @@ -145,7 +165,28 @@ Projects are stored as JSON in `~/Library/Application Support/textstep/projects/ ## Architecture -Two-thread model: +``` +┌──────────────────────────────────────────────────┐ +│ UI Thread │ +│ ┌────────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ ratatui │ │ crossterm│ │ App State │ │ +│ │ renderer │ │ events │ │ (app.rs) │ │ +│ └────────────┘ └──────────┘ └──────────────┘ │ +│ │ +│ crossbeam channels (lock-free, bounded) │ +│ ▼ UiToAudio ▲ AudioToUi │ +│ │ +│ Audio Thread │ +│ ┌────────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ Sequencer │ │ Voices │ │ Effects │ │ +│ │ Clock │ │ Drum+Syn │ │ Rev/Dly/Comp │ │ +│ └────────────┘ └──────────┘ └──────────────┘ │ +│ │ │ +│ cpal/CoreAudio │ +└──────────────────────────────────────────────────┘ +``` + +**Two-thread model:** - **UI thread** — ratatui + crossterm for rendering and input at ~60fps - **Audio thread** — cpal/CoreAudio callback running all DSP per-sample @@ -156,25 +197,112 @@ Communication is lock-free via bounded crossbeam channels. The audio thread neve Every sound is synthesized in real-time with no external DSP dependencies: -- **Drum voices**: TR-808/909-inspired kicks (sine + pitch envelope + resonant impulse), noise-blended snares, 6-oscillator metallic banks for hats and rides (using Mutable Instruments Plaits-style inharmonic ratios), ring-modulated open hats, bandpass claps, detuned pulse cowbells, FM toms -- **Synth voice**: dual oscillators, sub, noise, two ADSR envelopes, resonant SVF filter, 6-waveform LFO -- **Effects**: Schroeder/Freeverb reverb (4 comb + 2 allpass), tempo-synced filtered delay, asymmetric tube saturator, feedforward RMS glue compressor with soft knee -- **Primitives**: 1-pole HP/LP filters, state-variable filter, xorshift32 noise, tanh waveshaping +- **Drum voices** — TR-808/909-inspired kicks (sine + pitch envelope + resonant impulse), noise-blended snares, 6-oscillator metallic banks for hats and rides (Mutable Instruments Plaits-style inharmonic ratios), ring-modulated open hats, bandpass claps, detuned pulse cowbells, FM toms +- **Synth voice** — dual oscillators, sub, noise, two ADSR envelopes, resonant SVF filter, 6-waveform LFO +- **Effects** — Schroeder/Freeverb reverb (4 comb + 2 allpass), tempo-synced filtered delay, asymmetric tube saturator, feedforward RMS glue compressor with soft knee +- **Primitives** — 1-pole HP/LP filters, state-variable filter, xorshift32 noise, tanh waveshaping + +### Source Map + +| Directory | Purpose | +|-----------|---------| +| `src/` | Core: entry point, app state, input handling, messages | +| `src/ui/` | Rendering: layout, theme, grids, knobs, transport, spectrum | +| `src/audio/` | DSP: engine, clock, drum/synth voices, effects, mixer, FFT | +| `src/sequencer/` | Data: patterns, transport, project serialization | +| `src/presets/` | Preset browser: drum/synth sounds and patterns by genre | + +### Color Palette + +Hardware/synthwave aesthetic — all rendered with UTF-8 block characters on a dark background. + +| Color | Hex | Usage | +|-------|-----|-------| +| Amber | `#e8a838` | Active steps, gauge fills, primary data | +| Cyan | `#61dafb` | Transport state, beat LEDs, playhead, focused borders | +| Pink | `#ff6b9d` | Focus/selection, current track, record | +| Gold | `#ffd700` | Queued patterns, warnings | + +## Dependencies + +| Crate | Version | Purpose | +|-------|---------|---------| +| `ratatui` | 0.29 | TUI rendering framework | +| `crossterm` | 0.28 | Terminal backend (events, raw mode) | +| `cpal` | 0.15 | Cross-platform audio I/O (CoreAudio) | +| `crossbeam-channel` | 0.5 | Lock-free bounded MPSC channels | +| `serde` + `serde_json` | 1 | Project serialization (JSON) | + +No other runtime dependencies. -### Dependencies +## Project Structure ``` -ratatui 0.29 TUI rendering -crossterm 0.28 Terminal backend -cpal 0.15 Audio I/O (CoreAudio) -crossbeam-channel 0.5 Lock-free channels -serde + serde_json Project serialization +textStep/ +├── src/ +│ ├── main.rs # Entry point, thread spawning, event loop +│ ├── app.rs # Application state, focus, modals +│ ├── keys.rs # Keyboard input handler +│ ├── mouse.rs # Mouse input handler (mirrors UI layout) +│ ├── messages.rs # Cross-thread message enums +│ ├── params.rs # Shared effect parameters +│ ├── ui/ +│ │ ├── mod.rs # Top-level render dispatch +│ │ ├── layout.rs # Layout constants (single source of truth) +│ │ ├── theme.rs # Color palette and styling +│ │ ├── transport_bar.rs # Transport controls rendering +│ │ ├── drum_grid.rs # 8×32 drum step grid +│ │ ├── synth_grid.rs # Synth step grid with note display +│ │ ├── knobs.rs # Drum parameter sliders +│ │ ├── synth_knobs.rs # Synth parameter groups +│ │ ├── waveform.rs # Spectrum analyzer / VU meter +│ │ ├── splash.rs # Boot animation +│ │ └── help_overlay.rs # Keyboard shortcut reference +│ ├── audio/ +│ │ ├── engine.rs # Audio callback and voice management +│ │ ├── clock.rs # Beat/step timing with swing +│ │ ├── drum_voice.rs # 8 drum synth voices (all DSP) +│ │ ├── synth_voice.rs # Polyphonic synth DSP +│ │ ├── effects.rs # Reverb, delay, compressor, saturator +│ │ ├── mixer.rs # Channel mixing, mute/solo +│ │ ├── display_buffer.rs # Lock-free audio→UI waveform buffer +│ │ └── fft.rs # FFT for spectrum analyzer +│ ├── sequencer/ +│ │ ├── drum_pattern.rs # Drum pattern data (8 tracks × 32 steps) +│ │ ├── synth_pattern.rs # Synth pattern data and parameters +│ │ ├── transport.rs # Transport state (BPM, play, swing) +│ │ └── project.rs # Project serialization (.tsp JSON) +│ └── presets/ +│ ├── mod.rs # Preset browser state machine +│ ├── drum_presets.rs # Drum sound presets +│ ├── synth_presets.rs # Synth sound presets +│ ├── pattern_presets.rs # Drum pattern presets by genre +│ └── synth_pattern_presets.rs # Synth pattern presets +├── assets/ +│ └── demo.gif # Demo recording +├── Cargo.toml +├── BLUEPRINT.md # Full technical documentation +└── LICENSE # GPL v2 ``` -No other runtime dependencies. +## Contributing + +Contributions are welcome! Please ensure: -See [BLUEPRINT.md](BLUEPRINT.md) for full technical documentation. +1. All tests pass: `cargo test` +2. Code compiles without warnings: `cargo build --release` +3. Follow the existing code style and comment conventions + +See [BLUEPRINT.md](BLUEPRINT.md) for full technical documentation and architecture details. ## License -[GNU General Public License v2.0](LICENSE) +This project is licensed under the [GNU General Public License v2.0](LICENSE). + +--- + +
+ +**Built with Rust** · **All DSP from scratch** · **Zero audio dependencies** + +
diff --git a/src/audio/drum_voice.rs b/src/audio/drum_voice.rs index 38dcda9..208f83f 100644 --- a/src/audio/drum_voice.rs +++ b/src/audio/drum_voice.rs @@ -1,5 +1,6 @@ -// Drum voice trait and all 8 voice implementations -// Hand-rolled DSP — no external crate dependencies. +//! Drum voice DSP implementations (8 tracks). +//! All synthesis is hand-rolled: oscillators, filters, envelopes, noise. +//! No external DSP crate dependencies. use crate::sequencer::drum_pattern::DrumTrackParams; diff --git a/src/audio/effects.rs b/src/audio/effects.rs index 46a8da3..88dc5cc 100644 --- a/src/audio/effects.rs +++ b/src/audio/effects.rs @@ -1,14 +1,11 @@ -// Audio effects: Reverb, Delay, Glue Compressor -// Reverb/Delay ported from zicbox applyReverb.h with adaptations for Rust. +//! Send effects: Schroeder reverb, tempo-synced delay, tube saturator, RMS glue compressor. +//! Reverb/delay ported from zicbox applyReverb.h with adaptations for Rust. -// --------------------------------------------------------------------------- -// Reverb: 4 parallel comb filters + 2 series allpass filters -// --------------------------------------------------------------------------- - -// Base lengths tuned for 44100 Hz; scaled by sample_rate / 44100.0 +// Base comb/allpass lengths tuned for 44100 Hz; scaled by sample_rate / 44100.0 const BASE_COMB_LENGTHS: [usize; 4] = [1117, 1301, 1571, 1787]; const BASE_ALLPASS_LENGTHS: [usize; 2] = [557, 443]; +/// Schroeder reverb: 4 parallel comb filters feeding 2 series allpass filters. pub struct ReverbEffect { comb_buf: Vec, comb_lengths: [usize; 4], diff --git a/src/audio/engine.rs b/src/audio/engine.rs index bf5c353..daadd02 100644 --- a/src/audio/engine.rs +++ b/src/audio/engine.rs @@ -1,4 +1,4 @@ -// AudioEngine: cpal callback, voice mixing, send effects, message handling +//! Audio engine: cpal callback, voice management, LFO, message dispatch. use std::sync::Arc; @@ -16,9 +16,8 @@ use crate::sequencer::drum_pattern::{DrumPattern, DrumTrackId, NUM_DRUM_TRACKS}; use crate::sequencer::synth_pattern::{SynthPattern, LFO_DEST_FIELDS, lfo_division_multiplier}; use crate::sequencer::transport::{PlayState, Transport}; -// --------------------------------------------------------------------------- -// LFO: tempo-synced, global (shared across synth voices) -// --------------------------------------------------------------------------- +/// Tempo-synced global LFO shared across synth voices. +/// Supports sine, triangle, saw (up/down), square, and exponential decay waveforms. struct Lfo { phase: f64, } @@ -76,6 +75,8 @@ impl Lfo { } } +/// Core audio engine running on the audio thread. +/// Owns all voices, effects, the sequencer clock, and handles messages from the UI thread. pub struct AudioEngine { sample_rate: f64, clock: SequencerClock, diff --git a/src/audio/synth_voice.rs b/src/audio/synth_voice.rs index 542da12..f0ea39c 100644 --- a/src/audio/synth_voice.rs +++ b/src/audio/synth_voice.rs @@ -1,5 +1,5 @@ -// Paraphonic 2-oscillator synthesizer voice with sub oscillator. -// Hand-rolled DSP — no external crate dependencies. +//! Polyphonic synth voice: dual oscillators, sub, noise, 2 ADSR envelopes, +//! SVF filter, tempo-synced LFO. Hand-rolled DSP, no external crate dependencies. use crate::sequencer::synth_pattern::SynthParams; @@ -368,9 +368,8 @@ impl Filter24dB { } } -// --------------------------------------------------------------------------- -// SynthVoice — paraphonic 2-oscillator + sub -// --------------------------------------------------------------------------- +/// Paraphonic synthesizer voice with dual oscillators, sub oscillator, +/// two ADSR envelopes (amplitude + filter), and a state-variable filter. pub struct SynthVoice { sample_rate: f32, osc1: Oscillator, diff --git a/src/keys.rs b/src/keys.rs index 005aba4..f52364b 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,4 +1,4 @@ -// Keyboard input mapping +//! Keyboard event handler: maps key events to app state changes. use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; @@ -10,6 +10,7 @@ use crate::sequencer::project::{NUM_KITS, NUM_PATTERNS}; use crate::sequencer::synth_pattern::{SynthControlField, MAX_STEPS as SYNTH_MAX_STEPS}; use crate::sequencer::transport::{PlayState, RecordMode}; +/// How much a parameter changes per arrow-key press (0.02 = 50 steps for full range). const PARAM_INCREMENT: f32 = 0.02; /// Map drum pad keys (bottom row) to track indices. diff --git a/src/main.rs b/src/main.rs index 05093c6..0c2491a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +//! Entry point: spawns audio thread, runs UI event loop with ~60fps rendering. + mod app; mod audio; mod keys; @@ -18,6 +20,9 @@ use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; use ratatui::backend::CrosstermBackend; use ratatui::Terminal; +/// Sets up cross-thread channels, starts the audio stream, initializes the +/// terminal, and enters the main UI loop. The audio stream is kept alive +/// until this function returns. fn main() -> io::Result<()> { // Create channels for UI <-> Audio communication let (tx_to_audio, rx_from_ui) = crossbeam_channel::bounded(64); diff --git a/src/messages.rs b/src/messages.rs index 0aae4bd..d141bde 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,10 +1,12 @@ -// UiToAudio and AudioToUi message enums +//! Lock-free message types for cross-thread communication between UI and audio. use crate::params::EffectParams; use crate::sequencer::drum_pattern::{DrumPattern, DrumTrackId}; use crate::sequencer::synth_pattern::SynthPattern; use crate::sequencer::transport::Transport; +/// Commands sent from the UI thread to the audio thread. +/// Sent via a bounded crossbeam channel (capacity 64). pub enum UiToAudio { SetTransport(Transport), SetDrumPattern(DrumPattern), @@ -15,6 +17,8 @@ pub enum UiToAudio { ReleaseSynth, // release synth envelopes } +/// Notifications sent from the audio thread back to the UI. +/// Sent via a bounded crossbeam channel (capacity 16). pub enum AudioToUi { PlaybackPosition { global_step: usize, diff --git a/src/mouse.rs b/src/mouse.rs index 26effa6..d8a041a 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -1,4 +1,5 @@ -// Mouse input handling +//! Mouse event handler. Hit-testing layout MUST mirror ui/mod.rs — uses shared +//! constants from ui/layout.rs. use std::time::Instant; @@ -18,7 +19,8 @@ const DOUBLE_CLICK_MS: u128 = 300; /// 0.04 = full range in ~25 rows of drag. const DRAG_SENSITIVITY: f32 = 0.04; -/// Main mouse event handler. +/// Routes mouse events (click, scroll, drag) to the appropriate UI section. +/// Layout rectangles are recomputed from `term_size` to match rendering. pub fn handle_mouse(app: &mut App, event: MouseEvent, term_size: Rect) { // Ignore mouse during splash or modals if app.ui.splash.phase != crate::app::SplashPhase::Done { diff --git a/src/params.rs b/src/params.rs index da4bd6f..4732660 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,7 +1,9 @@ -// Shared parameter types +//! Shared effect parameters used by both UI and audio engine. use serde::{Deserialize, Serialize}; +/// Master effect and mix bus parameters, serialized with each project. +/// All values are normalized to 0.0..1.0. #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct EffectParams { pub reverb_amount: f32, // 0.0-1.0: feedback/decay diff --git a/src/presets/drum_presets.rs b/src/presets/drum_presets.rs index 8d0ebb0..d85b74a 100644 --- a/src/presets/drum_presets.rs +++ b/src/presets/drum_presets.rs @@ -1,3 +1,5 @@ +//! Hand-crafted drum sound presets organized by category (808, 909, Acoustic, Lo-Fi, etc.). + use crate::sequencer::drum_pattern::DrumTrackId; use crate::sequencer::project::DrumSoundParams; use super::DrumSoundPreset; diff --git a/src/presets/mod.rs b/src/presets/mod.rs index 7f5b4a8..d839a57 100644 --- a/src/presets/mod.rs +++ b/src/presets/mod.rs @@ -1,3 +1,6 @@ +//! Preset browser: categories, merge modes, and state machine for browsing +//! drum/synth sound and pattern presets. + pub mod drum_presets; pub mod pattern_presets; pub mod synth_pattern_presets; @@ -24,8 +27,7 @@ pub struct SynthSoundPreset { pub params: SynthParams, } -// ── Preset Browser State ───────────────────────────────────────────────────── - +/// Which kind of preset the browser is targeting. #[derive(Clone, Debug, PartialEq)] pub enum PresetTarget { DrumSound(usize), // track index @@ -40,6 +42,7 @@ pub enum PatternMergeMode { Layer, } +/// State machine for the preset browser modal: tracks target, category, and selection index. #[derive(Clone, Debug, PartialEq)] pub struct PresetBrowserState { pub target: PresetTarget, diff --git a/src/presets/pattern_presets.rs b/src/presets/pattern_presets.rs index 49199c9..0e59b3a 100644 --- a/src/presets/pattern_presets.rs +++ b/src/presets/pattern_presets.rs @@ -1,3 +1,5 @@ +//! Drum pattern presets: genre-specific step patterns (House, Techno, DnB, etc.). + use crate::sequencer::drum_pattern::NUM_DRUM_TRACKS; pub struct PatternPreset { diff --git a/src/presets/synth_pattern_presets.rs b/src/presets/synth_pattern_presets.rs index a58d53e..188c68a 100644 --- a/src/presets/synth_pattern_presets.rs +++ b/src/presets/synth_pattern_presets.rs @@ -1,3 +1,5 @@ +//! Synth pattern presets: melodic sequences organized by genre and style. + use crate::sequencer::synth_pattern::MAX_STEPS; pub struct SynthPatternPreset { diff --git a/src/presets/synth_presets.rs b/src/presets/synth_presets.rs index e41afa2..c181a5e 100644 --- a/src/presets/synth_presets.rs +++ b/src/presets/synth_presets.rs @@ -1,3 +1,5 @@ +//! Synth sound presets: pads, leads, basses, keys, and FX organized by category. + use crate::sequencer::synth_pattern::SynthParams; use super::SynthSoundPreset; diff --git a/src/sequencer/drum_pattern.rs b/src/sequencer/drum_pattern.rs index 01bb02f..22c2c3e 100644 --- a/src/sequencer/drum_pattern.rs +++ b/src/sequencer/drum_pattern.rs @@ -1,8 +1,9 @@ -// DrumPattern: 8 tracks x 32 steps +//! Drum pattern data: 8 tracks x 32 steps with per-track synthesis parameters. pub const NUM_DRUM_TRACKS: usize = 8; pub const MAX_STEPS: usize = 32; +/// Identifies one of the 8 drum tracks (Kick, Snare, CHH, OHH, Ride, Clap, Cowbell, Tom). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DrumTrackId { Kick = 0, @@ -135,6 +136,7 @@ pub const TRACK_IDS: [DrumTrackId; NUM_DRUM_TRACKS] = [ DrumTrackId::Tom, ]; +/// A drum pattern: 8 tracks of boolean step data plus per-track synthesis parameters. #[derive(Clone, Copy, Debug)] pub struct DrumPattern { pub steps: [[bool; MAX_STEPS]; NUM_DRUM_TRACKS], diff --git a/src/sequencer/project.rs b/src/sequencer/project.rs index d19e6ee..a9ead3f 100644 --- a/src/sequencer/project.rs +++ b/src/sequencer/project.rs @@ -1,4 +1,5 @@ -// Project: bundles a drum kit + 10 patterns into a single saveable unit. +//! Project serialization: bundles kit sounds, synth parameters, and 10 patterns +//! as JSON (.tsp files). use serde::{Deserialize, Serialize}; use std::path::PathBuf; diff --git a/src/sequencer/synth_pattern.rs b/src/sequencer/synth_pattern.rs index dcb6b21..10ff9eb 100644 --- a/src/sequencer/synth_pattern.rs +++ b/src/sequencer/synth_pattern.rs @@ -1,11 +1,10 @@ -// SynthPattern: monophonic synth sequencer data model +//! Synth pattern data: 32 steps with note/velocity/length, plus full synth parameter set. use serde::{Serialize, Deserialize}; pub const MAX_STEPS: usize = 32; -// --- SynthStep --- - +/// A single synth sequencer step: MIDI note, velocity (0 = off), and length in steps. #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct SynthStep { pub note: u8, // MIDI note number (0-127). 60 = C4 @@ -42,8 +41,8 @@ impl SynthStep { } } -// --- SynthParams --- - +/// Complete synth parameter set: dual oscillators, sub, two ADSR envelopes, +/// filter with its own envelope, LFO routing, and send effect levels. #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct SynthParams { // Oscillator 1 diff --git a/src/sequencer/transport.rs b/src/sequencer/transport.rs index e7686e2..8e37174 100644 --- a/src/sequencer/transport.rs +++ b/src/sequencer/transport.rs @@ -1,5 +1,6 @@ -// Transport state, BPM, loop config +//! Transport state: play/pause/stop, BPM, loop configuration, swing amount. +/// Sequencer playback state. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PlayState { Stopped, @@ -13,6 +14,7 @@ pub enum RecordMode { On, } +/// Per-section loop length settings (8/16/24/32 steps for drum and synth independently). #[derive(Clone, Copy, Debug)] pub struct LoopConfig { pub enabled: bool, @@ -30,6 +32,7 @@ impl Default for LoopConfig { } } +/// Master transport: play state, tempo (BPM), record mode, loop config, and swing. #[derive(Clone, Copy, Debug)] pub struct Transport { pub state: PlayState, diff --git a/src/ui/drum_grid.rs b/src/ui/drum_grid.rs index 25ebcd8..74c5e1a 100644 --- a/src/ui/drum_grid.rs +++ b/src/ui/drum_grid.rs @@ -1,4 +1,4 @@ -// Drum step grid widget (8x32) with spaced squares and hardware borders +//! Drum step grid: 8 tracks x 32 steps with playhead, cursor, and mute/solo indicators. use ratatui::Frame; use ratatui::layout::Rect; @@ -14,6 +14,8 @@ use crate::ui::theme; /// Track name column: ">Cowbell " or " Cowbell " = 9 chars (padded to longest name) const NAME_WIDTH: usize = 9; +/// Renders the 8-track drum grid with step indicators, track names, +/// mute/solo buttons, playhead column, and cursor highlight. pub fn render_drum_grid(f: &mut Frame, area: Rect, app: &App) { let focused_grid = app.ui.focus == FocusSection::DrumGrid; let border_style = theme::focus_border_style(focused_grid); diff --git a/src/ui/help_overlay.rs b/src/ui/help_overlay.rs index 1e6fed4..e7038e1 100644 --- a/src/ui/help_overlay.rs +++ b/src/ui/help_overlay.rs @@ -1,4 +1,4 @@ -// Key binding reference panel (3-column layout, renders inline below sequencer) +//! Help overlay: 3-column keyboard shortcut reference panel. use ratatui::Frame; use ratatui::layout::Rect; @@ -54,6 +54,8 @@ fn row3<'a>( Line::from(v) } +/// Renders the help panel with three columns of key-binding entries +/// covering transport, navigation, editing, and pattern management shortcuts. pub fn render_help(f: &mut Frame, area: Rect) { let block = Block::default() .title(" Key Bindings (? to close) ") diff --git a/src/ui/knobs.rs b/src/ui/knobs.rs index c664ce8..19b5c3f 100644 --- a/src/ui/knobs.rs +++ b/src/ui/knobs.rs @@ -1,4 +1,4 @@ -// Knobs panel: vertical slider bars for the selected drum track's parameters +//! Drum parameter panel: vertical slider bars for the selected track's sound parameters. use ratatui::Frame; use ratatui::layout::Rect; @@ -19,6 +19,8 @@ const SLIDER_LABELS: [&str; 11] = [ /// Number of vertical bar rows in each slider. const BAR_ROWS: usize = 5; +/// Renders vertical slider columns for the currently selected drum track's +/// parameters (Tune, Sweep, Color, Snap, Filter, Drive, Decay, Volume, etc.). pub fn render_knobs(f: &mut Frame, area: Rect, app: &App) { let focused = app.ui.focus == FocusSection::Knobs; let border_style = theme::focus_border_style(focused); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 0e7b92b..2c369d9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,5 @@ +//! Top-level render dispatch: computes layout, renders all sections, handles modals. + pub mod drum_grid; pub mod help_overlay; pub mod knobs; diff --git a/src/ui/splash.rs b/src/ui/splash.rs index e68d878..3aa5a22 100644 --- a/src/ui/splash.rs +++ b/src/ui/splash.rs @@ -1,4 +1,4 @@ -// Splash screen: Hardware-themed boot sequence with amber/cyan palette +//! Splash screen animation: typewriter logo, matrix rain reveal, boot sequence. use ratatui::Frame; use ratatui::layout::Rect; @@ -54,6 +54,8 @@ fn logo_total_chars() -> usize { line.chars().count() * LOGO_ROWS } +/// Draws the splash screen: animated block-letter logo with typewriter effect, +/// matrix rain columns, and a simulated boot log sequence. pub fn render_splash(f: &mut Frame, area: Rect, splash: &SplashState) { match splash.phase { SplashPhase::SlideIn | SplashPhase::Hold => { diff --git a/src/ui/synth_grid.rs b/src/ui/synth_grid.rs index 02f1ea3..c037df2 100644 --- a/src/ui/synth_grid.rs +++ b/src/ui/synth_grid.rs @@ -1,4 +1,4 @@ -// Synth step sequencer: 32-step row with note names +//! Synth step grid: 32-step note sequencer with multi-step note visualization. use ratatui::Frame; use ratatui::layout::Rect; @@ -14,6 +14,8 @@ use crate::ui::theme; /// Track name column width, matching drum grid (padded to longest name "Cowbell") const NAME_WIDTH: usize = 9; +/// Renders the synth step row with note names, velocity shading, +/// multi-step continuation bars, and playhead/cursor highlights. pub fn render_synth_grid(f: &mut Frame, area: Rect, app: &App) { let focused = app.ui.focus == FocusSection::SynthGrid; let border_style = theme::focus_border_style(focused); @@ -84,7 +86,10 @@ pub fn render_synth_grid(f: &mut Frame, area: Rect, app: &App) { let playback_step = app.ui.synth_playback_step; let is_playing = app.transport.state == PlayState::Playing; - let mut covered_until: Option = None; // end step index of a multi-step note + // Multi-step note tracking: `covered_until` holds the last step index covered by + // the current note's length. Steps within that range render as continuation bars + // (`is_continuation`) instead of new note heads, with an end-cap on the final step. + let mut covered_until: Option = None; let mut cover_bg: Option = None; // background color for continuation cells for s in 0..MAX_STEPS { diff --git a/src/ui/synth_knobs.rs b/src/ui/synth_knobs.rs index 5563819..b3834fe 100644 --- a/src/ui/synth_knobs.rs +++ b/src/ui/synth_knobs.rs @@ -1,4 +1,4 @@ -// Synth knobs panel: grouped layout with vertical sliders and packed ADSR bars +//! Synth parameter panel: grouped knobs for OSC, ENV, FILT, LFO, and AMP sections. use ratatui::Frame; use ratatui::layout::{Constraint, Direction, Layout, Rect}; @@ -14,6 +14,9 @@ use crate::ui::theme; const SLIDER_ROWS: usize = 5; // ── Row group definitions ──────────────────────────────────────────────────── +// Each constant below lists the `SynthControlField` variants for one sub-section. +// These arrays drive both the rendering order in `render_synth_knobs` and the +// keyboard/mouse navigation mapping in `mouse.rs`, so both files must stay in sync. const OSC1_SLIDERS: &[SynthControlField] = &[ SynthControlField::Osc1Tune, @@ -61,6 +64,8 @@ const ADSR_LABELS: &[&str] = &["A", "D", "S", "R"]; // ── Main render ────────────────────────────────────────────────────────────── +/// Renders the synth parameter panel with grouped slider/ADSR sections +/// laid out in four row groups: OSC, ENV+FILT, LFO, and AMP. pub fn render_synth_knobs(f: &mut Frame, area: Rect, app: &App) { let focused = app.ui.focus == FocusSection::SynthControls; let border_style = theme::focus_border_style(focused); diff --git a/src/ui/transport_bar.rs b/src/ui/transport_bar.rs index ebe4ed7..447ae38 100644 --- a/src/ui/transport_bar.rs +++ b/src/ui/transport_bar.rs @@ -1,5 +1,4 @@ -// Play/Pause/Stop, BPM display, LED, loop controls, pattern indicator -// Hardware/Synthwave themed transport bar +//! Transport control bar: play/pause state, BPM, beat LEDs, swing, pattern/kit selectors. use ratatui::Frame; use ratatui::layout::Rect; @@ -15,6 +14,8 @@ use crate::ui::theme; const PATTERN_KEYS: [char; 10] = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p']; const KIT_KEYS: [char; 8] = ['1', '2', '3', '4', '5', '6', '7', '8']; +/// Draws the transport bar: play state, BPM, beat LEDs, swing, record toggle, +/// pattern/kit selectors, loop indicators, and master level gauges. pub fn render_transport(f: &mut Frame, area: Rect, app: &App) { let focused = app.ui.focus == FocusSection::Transport; let border_style = theme::focus_border_style(focused);