From e1cf57918e753e965f2b98f6d22e2e911ff01e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Thu, 26 Feb 2026 01:08:16 +0100 Subject: [PATCH] Feat: WIP terse code documentation --- TODO.md | 263 ++++++++++++++++++++++++ build.rs | 2 + crates/forth/src/compiler.rs | 1 + crates/forth/src/ops.rs | 1 + crates/forth/src/theory/chords.rs | 5 + crates/forth/src/theory/mod.rs | 2 + crates/forth/src/theory/scales.rs | 5 + crates/forth/src/types.rs | 9 + crates/forth/src/vm.rs | 4 + crates/forth/src/words/compile.rs | 2 + crates/forth/src/words/core.rs | 4 +- crates/forth/src/words/effects.rs | 4 +- crates/forth/src/words/midi.rs | 3 +- crates/forth/src/words/mod.rs | 7 + crates/forth/src/words/music.rs | 3 +- crates/forth/src/words/sequencing.rs | 3 +- crates/forth/src/words/sound.rs | 3 +- crates/markdown/src/highlighter.rs | 4 + crates/markdown/src/lib.rs | 2 + crates/markdown/src/parser.rs | 11 +- crates/markdown/src/theme.rs | 4 + crates/project/src/file.rs | 10 +- crates/project/src/lib.rs | 4 + crates/project/src/project.rs | 34 ++- crates/project/src/share.rs | 5 + crates/ratatui/src/category_list.rs | 5 + crates/ratatui/src/confirm.rs | 3 + crates/ratatui/src/editor.rs | 6 + crates/ratatui/src/file_browser.rs | 3 + crates/ratatui/src/hint_bar.rs | 3 + crates/ratatui/src/lissajous.rs | 3 + crates/ratatui/src/list_select.rs | 3 + crates/ratatui/src/modal.rs | 3 + crates/ratatui/src/nav_minimap.rs | 2 + crates/ratatui/src/props_form.rs | 3 + crates/ratatui/src/sample_browser.rs | 5 + crates/ratatui/src/scope.rs | 4 + crates/ratatui/src/scroll_indicators.rs | 4 + crates/ratatui/src/search_bar.rs | 3 + crates/ratatui/src/section_header.rs | 3 + crates/ratatui/src/sparkles.rs | 3 + crates/ratatui/src/spectrum.rs | 3 + crates/ratatui/src/text_input.rs | 3 + crates/ratatui/src/vu_meter.rs | 3 + crates/ratatui/src/waveform.rs | 3 + src/main.rs | 39 ++++ website/src/pages/index.astro | 21 +- 47 files changed, 499 insertions(+), 24 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..f6a15d9 --- /dev/null +++ b/TODO.md @@ -0,0 +1,263 @@ +# Rustdoc & Cleanup Review + +## Workflow + +**Strictly one file at a time, in list order.** When the user says "review" (or +similar), process the next unchecked file — never skip ahead, never batch. + +1. Read the file. +2. Read any imports, callers, or sibling files needed to understand what the code + does and how it fits in the codebase. Gathering context is encouraged. +3. Apply the changes described below. +4. **`cargo build`** to confirm nothing broke. +5. Check the file off in this list. +6. Stop. Wait for the user before moving to the next file. + +## What to do + +1. **Add `//!` module doc** at the top if missing — one or two lines explaining what + the module does and its role in the crate. +2. **Add `///` on public items** (structs, enums, functions, traits, type aliases). + Keep it to one line when possible. Document struct fields only when the name alone + is not self-explanatory. +3. **Light cleanup** — remove dead code, fix misleading names, apply trivial + simplifications. No behavior changes. +4. **`cargo build`** after each file to confirm nothing broke. + +## What NOT to do + +- No comments on private internals unless truly obscure. +- No fluff ("This struct represents…"). Be direct. +- No feature changes, refactoring sprees, or reformatting of unrelated code. +- No over-commenting. If the code is clear, leave it alone. + +## Style + +Sparse, english, imperative where possible. Match the tone of the existing codebase. + +--- + +- [x] build.rs +- [x] crates/forth/src/compiler.rs +- [x] crates/forth/src/lib.rs +- [x] crates/forth/src/ops.rs +- [x] crates/forth/src/theory/chords.rs +- [x] crates/forth/src/theory/mod.rs +- [x] crates/forth/src/theory/scales.rs +- [x] crates/forth/src/types.rs +- [x] crates/forth/src/vm.rs +- [x] crates/forth/src/words/compile.rs +- [x] crates/forth/src/words/core.rs +- [x] crates/forth/src/words/effects.rs +- [x] crates/forth/src/words/midi.rs +- [x] crates/forth/src/words/mod.rs +- [x] crates/forth/src/words/music.rs +- [x] crates/forth/src/words/sequencing.rs +- [x] crates/forth/src/words/sound.rs +- [x] crates/markdown/src/highlighter.rs +- [x] crates/markdown/src/lib.rs +- [x] crates/markdown/src/parser.rs +- [x] crates/markdown/src/theme.rs +- [x] crates/project/src/file.rs +- [x] crates/project/src/lib.rs +- [x] crates/project/src/project.rs +- [x] crates/project/src/share.rs +- [x] crates/ratatui/src/category_list.rs +- [x] crates/ratatui/src/confirm.rs +- [x] crates/ratatui/src/editor.rs +- [x] crates/ratatui/src/file_browser.rs +- [x] crates/ratatui/src/hint_bar.rs +- [ ] crates/ratatui/src/lib.rs +- [ ] crates/ratatui/src/lissajous.rs +- [ ] crates/ratatui/src/list_select.rs +- [ ] crates/ratatui/src/modal.rs +- [ ] crates/ratatui/src/nav_minimap.rs +- [ ] crates/ratatui/src/props_form.rs +- [ ] crates/ratatui/src/sample_browser.rs +- [ ] crates/ratatui/src/scope.rs +- [ ] crates/ratatui/src/scroll_indicators.rs +- [ ] crates/ratatui/src/search_bar.rs +- [ ] crates/ratatui/src/section_header.rs +- [ ] crates/ratatui/src/sparkles.rs +- [ ] crates/ratatui/src/spectrum.rs +- [ ] crates/ratatui/src/text_input.rs +- [ ] crates/ratatui/src/theme/build.rs +- [ ] crates/ratatui/src/theme/catppuccin_latte.rs +- [ ] crates/ratatui/src/theme/catppuccin_mocha.rs +- [ ] crates/ratatui/src/theme/dracula.rs +- [ ] crates/ratatui/src/theme/eden.rs +- [ ] crates/ratatui/src/theme/ember.rs +- [ ] crates/ratatui/src/theme/everforest.rs +- [ ] crates/ratatui/src/theme/fairyfloss.rs +- [ ] crates/ratatui/src/theme/fauve.rs +- [ ] crates/ratatui/src/theme/georges.rs +- [ ] crates/ratatui/src/theme/gruvbox_dark.rs +- [ ] crates/ratatui/src/theme/hot_dog_stand.rs +- [ ] crates/ratatui/src/theme/iceberg.rs +- [ ] crates/ratatui/src/theme/jaipur.rs +- [ ] crates/ratatui/src/theme/kanagawa.rs +- [ ] crates/ratatui/src/theme/letz_light.rs +- [ ] crates/ratatui/src/theme/mod.rs +- [ ] crates/ratatui/src/theme/monochrome_black.rs +- [ ] crates/ratatui/src/theme/monochrome_white.rs +- [ ] crates/ratatui/src/theme/monokai.rs +- [ ] crates/ratatui/src/theme/nord.rs +- [ ] crates/ratatui/src/theme/palette.rs +- [ ] crates/ratatui/src/theme/pitch_black.rs +- [ ] crates/ratatui/src/theme/rose_pine.rs +- [ ] crates/ratatui/src/theme/tokyo_night.rs +- [ ] crates/ratatui/src/theme/transform.rs +- [ ] crates/ratatui/src/theme/tropicalia.rs +- [ ] crates/ratatui/src/vu_meter.rs +- [ ] crates/ratatui/src/waveform.rs +- [ ] plugins/baseview/src/clipboard.rs +- [ ] plugins/baseview/src/event.rs +- [ ] plugins/baseview/src/gl/macos.rs +- [ ] plugins/baseview/src/gl/mod.rs +- [ ] plugins/baseview/src/gl/win.rs +- [ ] plugins/baseview/src/gl/x11.rs +- [ ] plugins/baseview/src/gl/x11/errors.rs +- [ ] plugins/baseview/src/keyboard.rs +- [ ] plugins/baseview/src/lib.rs +- [ ] plugins/baseview/src/macos/keyboard.rs +- [ ] plugins/baseview/src/macos/mod.rs +- [ ] plugins/baseview/src/macos/view.rs +- [ ] plugins/baseview/src/macos/window.rs +- [ ] plugins/baseview/src/mouse_cursor.rs +- [ ] plugins/baseview/src/win/cursor.rs +- [ ] plugins/baseview/src/win/drop_target.rs +- [ ] plugins/baseview/src/win/hook.rs +- [ ] plugins/baseview/src/win/keyboard.rs +- [ ] plugins/baseview/src/win/mod.rs +- [ ] plugins/baseview/src/win/window.rs +- [ ] plugins/baseview/src/window_info.rs +- [ ] plugins/baseview/src/window_open_options.rs +- [ ] plugins/baseview/src/window.rs +- [ ] plugins/baseview/src/x11/cursor.rs +- [ ] plugins/baseview/src/x11/event_loop.rs +- [ ] plugins/baseview/src/x11/keyboard.rs +- [ ] plugins/baseview/src/x11/mod.rs +- [ ] plugins/baseview/src/x11/visual_info.rs +- [ ] plugins/baseview/src/x11/window.rs +- [ ] plugins/baseview/src/x11/xcb_connection.rs +- [ ] plugins/cagire-plugins/src/editor.rs +- [ ] plugins/cagire-plugins/src/lib.rs +- [ ] plugins/cagire-plugins/src/main.rs +- [ ] plugins/cagire-plugins/src/params.rs +- [ ] plugins/egui-baseview/src/lib.rs +- [ ] plugins/egui-baseview/src/renderer.rs +- [ ] plugins/egui-baseview/src/renderer/opengl.rs +- [ ] plugins/egui-baseview/src/renderer/opengl/renderer.rs +- [ ] plugins/egui-baseview/src/translate.rs +- [ ] plugins/egui-baseview/src/window.rs +- [ ] plugins/nih-plug-egui/src/editor.rs +- [ ] plugins/nih-plug-egui/src/lib.rs +- [ ] plugins/nih-plug-egui/src/resizable_window.rs +- [ ] plugins/nih-plug-egui/src/widgets.rs +- [ ] plugins/nih-plug-egui/src/widgets/generic_ui.rs +- [ ] plugins/nih-plug-egui/src/widgets/param_slider.rs +- [ ] plugins/nih-plug-egui/src/widgets/util.rs +- [ ] src/app/clipboard.rs +- [ ] src/app/dispatch.rs +- [ ] src/app/editing.rs +- [ ] src/app/mod.rs +- [ ] src/app/navigation.rs +- [ ] src/app/persistence.rs +- [ ] src/app/scripting.rs +- [ ] src/app/sequencer.rs +- [ ] src/app/staging.rs +- [ ] src/app/undo.rs +- [ ] src/bin/desktop/main.rs +- [ ] src/block_renderer.rs +- [ ] src/commands.rs +- [ ] src/engine/audio.rs +- [ ] src/engine/dispatcher.rs +- [ ] src/engine/link.rs +- [ ] src/engine/mod.rs +- [ ] src/engine/realtime.rs +- [ ] src/engine/sequencer.rs +- [ ] src/engine/timing.rs +- [ ] src/init.rs +- [ ] src/input_egui.rs +- [ ] src/input/engine_page.rs +- [ ] src/input/help_page.rs +- [ ] src/input/main_page.rs +- [ ] src/input/mod.rs +- [ ] src/input/modal.rs +- [ ] src/input/mouse.rs +- [ ] src/input/options_page.rs +- [ ] src/input/panel.rs +- [ ] src/input/patterns_page.rs +- [ ] src/lib.rs +- [ ] src/main.rs +- [ ] src/midi.rs +- [ ] src/model/categories.rs +- [ ] src/model/demos.rs +- [ ] src/model/docs.rs +- [ ] src/model/mod.rs +- [ ] src/model/onboarding.rs +- [ ] src/model/script.rs +- [ ] src/page.rs +- [ ] src/services/clipboard.rs +- [ ] src/services/dict_nav.rs +- [ ] src/services/euclidean.rs +- [ ] src/services/help_nav.rs +- [ ] src/services/mod.rs +- [ ] src/services/pattern_editor.rs +- [ ] src/services/stack_preview.rs +- [ ] src/settings.rs +- [ ] src/state/audio.rs +- [ ] src/state/color_scheme.rs +- [ ] src/state/editor.rs +- [ ] src/state/effects.rs +- [ ] src/state/file_browser.rs +- [ ] src/state/live_keys.rs +- [ ] src/state/mod.rs +- [ ] src/state/modal.rs +- [ ] src/state/mute.rs +- [ ] src/state/options.rs +- [ ] src/state/panel.rs +- [ ] src/state/patterns_nav.rs +- [ ] src/state/playback.rs +- [ ] src/state/project.rs +- [ ] src/state/sample_browser.rs +- [ ] src/state/ui.rs +- [ ] src/state/undo.rs +- [ ] src/theme.rs +- [ ] src/views/dict_view.rs +- [ ] src/views/engine_view.rs +- [ ] src/views/help_view.rs +- [ ] src/views/highlight.rs +- [ ] src/views/keybindings.rs +- [ ] src/views/main_view.rs +- [ ] src/views/mod.rs +- [ ] src/views/options_view.rs +- [ ] src/views/patterns_view.rs +- [ ] src/views/render.rs +- [ ] src/views/title_view.rs +- [ ] src/widgets/mod.rs +- [ ] tests/forth.rs +- [ ] tests/forth/arithmetic.rs +- [ ] tests/forth/case_statement.rs +- [ ] tests/forth/chords.rs +- [ ] tests/forth/comparison.rs +- [ ] tests/forth/context.rs +- [ ] tests/forth/control_flow.rs +- [ ] tests/forth/definitions.rs +- [ ] tests/forth/errors.rs +- [ ] tests/forth/euclidean.rs +- [ ] tests/forth/generator.rs +- [ ] tests/forth/harmony.rs +- [ ] tests/forth/harness.rs +- [ ] tests/forth/intervals.rs +- [ ] tests/forth/list_words.rs +- [ ] tests/forth/midi.rs +- [ ] tests/forth/notes.rs +- [ ] tests/forth/quotations.rs +- [ ] tests/forth/ramps.rs +- [ ] tests/forth/randomness.rs +- [ ] tests/forth/sound.rs +- [ ] tests/forth/stack.rs +- [ ] tests/forth/temporal.rs +- [ ] tests/forth/variables.rs +- [ ] xtask/src/main.rs diff --git a/build.rs b/build.rs index 08c5870..40d4ac1 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,5 @@ +//! Build script — embeds Windows application resources (icon, metadata). + fn main() { #[cfg(windows)] { diff --git a/crates/forth/src/compiler.rs b/crates/forth/src/compiler.rs index bd6d208..cac8b45 100644 --- a/crates/forth/src/compiler.rs +++ b/crates/forth/src/compiler.rs @@ -15,6 +15,7 @@ enum Token { Word(String, SourceSpan), } +/// Compile Forth source text into an executable Op sequence. pub(super) fn compile_script(input: &str, dict: &Dictionary) -> Result, String> { let tokens = tokenize(input); compile(&tokens, dict) diff --git a/crates/forth/src/ops.rs b/crates/forth/src/ops.rs index d0c81e9..7439dcf 100644 --- a/crates/forth/src/ops.rs +++ b/crates/forth/src/ops.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use super::types::SourceSpan; +/// Single VM instruction produced by the compiler. #[derive(Clone, Debug, PartialEq)] pub enum Op { PushInt(i64, Option), diff --git a/crates/forth/src/theory/chords.rs b/crates/forth/src/theory/chords.rs index caf1180..ddc4b53 100644 --- a/crates/forth/src/theory/chords.rs +++ b/crates/forth/src/theory/chords.rs @@ -1,8 +1,12 @@ +//! Chord definitions as semitone interval arrays. + +/// Named chord with its interval pattern. pub struct Chord { pub name: &'static str, pub intervals: &'static [i64], } +/// All built-in chord types. pub static CHORDS: &[Chord] = &[ // Triads Chord { @@ -169,6 +173,7 @@ pub static CHORDS: &[Chord] = &[ }, ]; +/// Find a chord's intervals by name. pub fn lookup(name: &str) -> Option<&'static [i64]> { CHORDS.iter().find(|c| c.name == name).map(|c| c.intervals) } diff --git a/crates/forth/src/theory/mod.rs b/crates/forth/src/theory/mod.rs index 901a041..3c9d267 100644 --- a/crates/forth/src/theory/mod.rs +++ b/crates/forth/src/theory/mod.rs @@ -1,3 +1,5 @@ +//! Music theory data — chord and scale lookup tables. + pub mod chords; mod scales; diff --git a/crates/forth/src/theory/scales.rs b/crates/forth/src/theory/scales.rs index 62f0d99..a3bac98 100644 --- a/crates/forth/src/theory/scales.rs +++ b/crates/forth/src/theory/scales.rs @@ -1,8 +1,12 @@ +//! Scale definitions as semitone offset arrays. + +/// Named scale with its semitone pattern. pub struct Scale { pub name: &'static str, pub pattern: &'static [i64], } +/// All built-in scale types. pub static SCALES: &[Scale] = &[ Scale { name: "major", @@ -125,6 +129,7 @@ pub static SCALES: &[Scale] = &[ }, ]; +/// Find a scale's pattern by name. pub fn lookup(name: &str) -> Option<&'static [i64]> { SCALES.iter().find(|s| s.name == name).map(|s| s.pattern) } diff --git a/crates/forth/src/types.rs b/crates/forth/src/types.rs index 95c0fa0..d635d32 100644 --- a/crates/forth/src/types.rs +++ b/crates/forth/src/types.rs @@ -14,12 +14,14 @@ pub trait CcAccess: Send + Sync { fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8; } +/// Byte range in source text. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct SourceSpan { pub start: u32, pub end: u32, } +/// Concrete value resolved from a nondeterministic op, used for trace annotations. #[derive(Clone, Debug)] pub enum ResolvedValue { Int(i64), @@ -39,6 +41,7 @@ impl ResolvedValue { } } +/// Spans and resolved values collected during a single evaluation, used for UI highlighting. #[derive(Clone, Debug, Default)] pub struct ExecutionTrace { pub executed_spans: Vec, @@ -46,6 +49,7 @@ pub struct ExecutionTrace { pub resolved: Vec<(SourceSpan, ResolvedValue)>, } +/// Per-step sequencer state passed into the VM. pub struct StepContext<'a> { pub step: usize, pub beat: f64, @@ -72,13 +76,18 @@ impl StepContext<'_> { } } +/// Underlying map for user-defined variables. pub type VariablesMap = HashMap; +/// Shared variable store, swapped atomically after each step. pub type Variables = Arc>; +/// Shared user-defined word dictionary. pub type Dictionary = Arc>>>; +/// Shared random number generator. pub type Rng = Arc>; pub type Stack = Mutex>; pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(&'static str, Value)]); +/// Stack value in the Forth VM. #[derive(Clone, Debug)] pub enum Value { Int(i64, Option), diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 6497820..f0911a9 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -14,6 +14,7 @@ use super::types::{ Value, Variables, VariablesMap, }; +/// Forth VM instance. Holds the stack, variables, dictionary, and RNG. pub struct Forth { stack: Stack, vars: Variables, @@ -45,12 +46,14 @@ impl Forth { self.global_params.lock().clear(); } + /// Evaluate a Forth script and return audio command strings. pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result, String> { let (outputs, var_writes) = self.evaluate_impl(script, ctx, None)?; self.apply_var_writes(var_writes); Ok(outputs) } + /// Evaluate and collect an execution trace for UI highlighting. pub fn evaluate_with_trace( &self, script: &str, @@ -62,6 +65,7 @@ impl Forth { Ok(outputs) } + /// Evaluate and return both outputs and pending variable writes (without applying them). pub fn evaluate_raw( &self, script: &str, diff --git a/crates/forth/src/words/compile.rs b/crates/forth/src/words/compile.rs index 8b92402..8e75d46 100644 --- a/crates/forth/src/words/compile.rs +++ b/crates/forth/src/words/compile.rs @@ -1,3 +1,5 @@ +//! Word-to-Op translation: maps Forth word names to compiled instructions. + use std::sync::Arc; use crate::ops::Op; diff --git a/crates/forth/src/words/core.rs b/crates/forth/src/words/core.rs index f511b98..a7306a1 100644 --- a/crates/forth/src/words/core.rs +++ b/crates/forth/src/words/core.rs @@ -1,6 +1,6 @@ -use super::{Word, WordCompile::*}; +//! Word metadata for core language primitives (stack, arithmetic, logic, variables, definitions). -// Stack, Arithmetic, Comparison, Logic, Control, Variables, Definitions +use super::{Word, WordCompile::*}; pub(super) const WORDS: &[Word] = &[ // Stack manipulation Word { diff --git a/crates/forth/src/words/effects.rs b/crates/forth/src/words/effects.rs index 99802a4..d885551 100644 --- a/crates/forth/src/words/effects.rs +++ b/crates/forth/src/words/effects.rs @@ -1,6 +1,6 @@ -use super::{Word, WordCompile::*}; +//! Word metadata for audio effect parameters (filter, envelope, reverb, delay, lo-fi, stereo, mod FX). -// Filter, Envelope, Reverb, Delay, Lo-fi, Stereo, Mod FX +use super::{Word, WordCompile::*}; pub(super) const WORDS: &[Word] = &[ // Envelope Word { diff --git a/crates/forth/src/words/midi.rs b/crates/forth/src/words/midi.rs index 53087f8..84dc08a 100644 --- a/crates/forth/src/words/midi.rs +++ b/crates/forth/src/words/midi.rs @@ -1,6 +1,7 @@ +//! MIDI word definitions: channel, CC, pitch bend, transport, and device routing. + use super::{Word, WordCompile::*}; -// MIDI pub(super) const WORDS: &[Word] = &[ Word { name: "chan", diff --git a/crates/forth/src/words/mod.rs b/crates/forth/src/words/mod.rs index bb16323..4e26575 100644 --- a/crates/forth/src/words/mod.rs +++ b/crates/forth/src/words/mod.rs @@ -1,3 +1,5 @@ +//! Built-in word definitions and lookup for the Forth VM. + mod compile; mod core; mod effects; @@ -11,6 +13,7 @@ use std::sync::LazyLock; pub(crate) use compile::compile_word; +/// How a word is compiled into ops. #[derive(Clone, Copy)] pub enum WordCompile { Simple, @@ -19,6 +22,7 @@ pub enum WordCompile { Probability(f64), } +/// Metadata for a built-in Forth word. #[derive(Clone, Copy)] pub struct Word { pub name: &'static str, @@ -31,6 +35,7 @@ pub struct Word { pub varargs: bool, } +/// All built-in words, aggregated from every category module. pub static WORDS: LazyLock> = LazyLock::new(|| { let mut words = Vec::new(); words.extend_from_slice(self::core::WORDS); @@ -42,6 +47,7 @@ pub static WORDS: LazyLock> = LazyLock::new(|| { words }); +/// Index mapping word names and aliases to their definitions. static WORD_MAP: LazyLock> = LazyLock::new(|| { let mut map = HashMap::with_capacity(WORDS.len() * 2); for word in WORDS.iter() { @@ -53,6 +59,7 @@ static WORD_MAP: LazyLock> = LazyLock::new( map }); +/// Find a word by name or alias. pub fn lookup_word(name: &str) -> Option<&'static Word> { WORD_MAP.get(name).copied() } diff --git a/crates/forth/src/words/music.rs b/crates/forth/src/words/music.rs index adbf0a7..609f1b3 100644 --- a/crates/forth/src/words/music.rs +++ b/crates/forth/src/words/music.rs @@ -1,6 +1,7 @@ +//! Word definitions for music theory, harmony, and chord construction. + use super::{Word, WordCompile::*}; -// Music, Chord pub(super) const WORDS: &[Word] = &[ // Music Word { diff --git a/crates/forth/src/words/sequencing.rs b/crates/forth/src/words/sequencing.rs index 584e48b..c1dda6c 100644 --- a/crates/forth/src/words/sequencing.rs +++ b/crates/forth/src/words/sequencing.rs @@ -1,6 +1,7 @@ +//! Word metadata for sequencing: probability, timing, context queries, generators. + use super::{Word, WordCompile::*}; -// Time, Context, Probability, Generator, Desktop pub(super) const WORDS: &[Word] = &[ // Probability Word { diff --git a/crates/forth/src/words/sound.rs b/crates/forth/src/words/sound.rs index 1c9ed0a..3a3e7b9 100644 --- a/crates/forth/src/words/sound.rs +++ b/crates/forth/src/words/sound.rs @@ -1,6 +1,7 @@ +//! Word metadata for sound commands, sample/oscillator params, FM, modulation, and LFO. + use super::{Word, WordCompile::*}; -// Sound, Oscillator, Sample, Wavetable, FM, Modulation, LFO pub(super) const WORDS: &[Word] = &[ // Sound Word { diff --git a/crates/markdown/src/highlighter.rs b/crates/markdown/src/highlighter.rs index 4e5856d..c2c378a 100644 --- a/crates/markdown/src/highlighter.rs +++ b/crates/markdown/src/highlighter.rs @@ -1,9 +1,13 @@ +//! Syntax highlighting trait for fenced code blocks in markdown. + use ratatui::style::Style; +/// Produce styled spans from a single line of source code. pub trait CodeHighlighter { fn highlight(&self, line: &str) -> Vec<(Style, String)>; } +/// Pass-through highlighter that applies no styling. pub struct NoHighlight; impl CodeHighlighter for NoHighlight { diff --git a/crates/markdown/src/lib.rs b/crates/markdown/src/lib.rs index 54c484f..fb58817 100644 --- a/crates/markdown/src/lib.rs +++ b/crates/markdown/src/lib.rs @@ -1,3 +1,5 @@ +//! Parse markdown into styled ratatui lines with pluggable syntax highlighting. + mod highlighter; mod parser; mod theme; diff --git a/crates/markdown/src/parser.rs b/crates/markdown/src/parser.rs index 5a8d3ad..7d19826 100644 --- a/crates/markdown/src/parser.rs +++ b/crates/markdown/src/parser.rs @@ -1,3 +1,5 @@ +//! Parse markdown text into styled ratatui lines with syntax-highlighted code blocks. + use minimad::{Composite, CompositeStyle, Compound, Line, TableRow}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line as RLine, Span}; @@ -5,17 +7,20 @@ use ratatui::text::{Line as RLine, Span}; use crate::highlighter::CodeHighlighter; use crate::theme::MarkdownTheme; +/// Span of lines within a parsed document that form a fenced code block. pub struct CodeBlock { pub start_line: usize, pub end_line: usize, pub source: String, } +/// Result of parsing a markdown string: styled lines and extracted code blocks. pub struct ParsedMarkdown { pub lines: Vec>, pub code_blocks: Vec, } +/// Parse markdown text into themed, syntax-highlighted ratatui lines. pub fn parse( md: &str, theme: &T, @@ -44,7 +49,7 @@ pub fn parse( let close_block = |start: Option, source: &mut Vec, blocks: &mut Vec, - lines: &Vec>| { + lines: &[RLine<'static>]| { if let Some(start) = start { blocks.push(CodeBlock { start_line: start, @@ -118,7 +123,7 @@ pub fn parse( ParsedMarkdown { lines, code_blocks } } -pub fn preprocess_markdown(md: &str) -> String { +fn preprocess_markdown(md: &str) -> String { let mut out = String::with_capacity(md.len()); for line in md.lines() { let line = convert_dash_lists(line); @@ -162,7 +167,7 @@ pub fn preprocess_markdown(md: &str) -> String { out } -pub fn convert_dash_lists(line: &str) -> String { +fn convert_dash_lists(line: &str) -> String { let trimmed = line.trim_start(); if let Some(rest) = trimmed.strip_prefix("- ") { let indent = line.len() - trimmed.len(); diff --git a/crates/markdown/src/theme.rs b/crates/markdown/src/theme.rs index e259508..7011167 100644 --- a/crates/markdown/src/theme.rs +++ b/crates/markdown/src/theme.rs @@ -1,5 +1,8 @@ +//! Style provider trait for markdown rendering. + use ratatui::style::{Color, Modifier, Style}; +/// Style provider for each markdown element type. pub trait MarkdownTheme { fn h1(&self) -> Style; fn h2(&self) -> Style; @@ -16,6 +19,7 @@ pub trait MarkdownTheme { fn table_row_odd(&self) -> Color; } +/// Fallback theme with hardcoded terminal colors, used in tests. pub struct DefaultTheme; impl MarkdownTheme for DefaultTheme { diff --git a/crates/project/src/file.rs b/crates/project/src/file.rs index 119b4f0..8069d0d 100644 --- a/crates/project/src/file.rs +++ b/crates/project/src/file.rs @@ -1,3 +1,5 @@ +//! JSON-based project file persistence with versioned format. + use std::fs; use std::io; use std::path::{Path, PathBuf}; @@ -7,9 +9,9 @@ use serde::{Deserialize, Serialize}; use crate::project::{Bank, Project}; const VERSION: u8 = 1; -pub const EXTENSION: &str = "cagire"; +const EXTENSION: &str = "cagire"; -pub fn ensure_extension(path: &Path) -> PathBuf { +fn ensure_extension(path: &Path) -> PathBuf { if path.extension().map(|e| e == EXTENSION).unwrap_or(false) { path.to_path_buf() } else { @@ -62,6 +64,7 @@ impl From for Project { } } +/// Error returned by project save/load operations. #[derive(Debug)] pub enum FileError { Io(io::Error), @@ -91,6 +94,7 @@ impl From for FileError { } } +/// Write a project to disk as pretty-printed JSON, returning the final path. pub fn save(project: &Project, path: &Path) -> Result { let path = ensure_extension(path); let file = ProjectFile::from(project); @@ -99,11 +103,13 @@ pub fn save(project: &Project, path: &Path) -> Result { Ok(path) } +/// Read a project from a `.cagire` file on disk. pub fn load(path: &Path) -> Result { let json = fs::read_to_string(path)?; load_str(&json) } +/// Parse a project from a JSON string. pub fn load_str(json: &str) -> Result { let file: ProjectFile = serde_json::from_str(json)?; if file.version > VERSION { diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index e2f1572..d1a8066 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -4,9 +4,13 @@ mod file; mod project; pub mod share; +/// Maximum number of banks in a project. pub const MAX_BANKS: usize = 32; +/// Maximum number of patterns per bank. pub const MAX_PATTERNS: usize = 32; +/// Maximum number of steps per pattern. pub const MAX_STEPS: usize = 1024; +/// Default pattern length in steps. pub const DEFAULT_LENGTH: usize = 16; pub use file::{load, load_str, save, FileError}; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 487dbe5..da9c2b2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{DEFAULT_LENGTH, MAX_BANKS, MAX_PATTERNS, MAX_STEPS}; +/// Speed multiplier for a pattern, expressed as a rational fraction. #[derive(Clone, Copy, PartialEq, Eq)] pub struct PatternSpeed { pub num: u8, @@ -37,10 +38,12 @@ impl PatternSpeed { Self::OCTO, ]; + /// Return the speed as a floating-point multiplier. pub fn multiplier(&self) -> f64 { self.num as f64 / self.denom as f64 } + /// Format as a human-readable label (e.g. "2x", "1/4x"). pub fn label(&self) -> String { if self.denom == 1 { format!("{}x", self.num) @@ -49,6 +52,7 @@ impl PatternSpeed { } } + /// Return the next faster preset, or self if already at maximum. pub fn next(&self) -> Self { let current = self.multiplier(); Self::PRESETS @@ -58,6 +62,7 @@ impl PatternSpeed { .unwrap_or(*self) } + /// Return the next slower preset, or self if already at minimum. pub fn prev(&self) -> Self { let current = self.multiplier(); Self::PRESETS @@ -68,6 +73,7 @@ impl PatternSpeed { .unwrap_or(*self) } + /// Parse a speed label like "2x" or "1/4x" into a `PatternSpeed`. pub fn from_label(s: &str) -> Option { let s = s.trim().trim_end_matches('x'); if let Some((num, denom)) = s.split_once('/') { @@ -139,6 +145,7 @@ impl<'de> Deserialize<'de> for PatternSpeed { } } +/// Quantization grid for launching patterns. #[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] pub enum LaunchQuantization { Immediate, @@ -151,6 +158,7 @@ pub enum LaunchQuantization { } impl LaunchQuantization { + /// Human-readable label for display. pub fn label(&self) -> &'static str { match self { Self::Immediate => "Immediate", @@ -162,6 +170,7 @@ impl LaunchQuantization { } } + /// Cycle to the next longer quantization, clamped at `Bars8`. pub fn next(&self) -> Self { match self { Self::Immediate => Self::Beat, @@ -173,6 +182,7 @@ impl LaunchQuantization { } } + /// Cycle to the next shorter quantization, clamped at `Immediate`. pub fn prev(&self) -> Self { match self { Self::Immediate => Self::Immediate, @@ -185,6 +195,7 @@ impl LaunchQuantization { } } +/// How a pattern synchronizes when launched: restart or phase-lock. #[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] pub enum SyncMode { #[default] @@ -193,6 +204,7 @@ pub enum SyncMode { } impl SyncMode { + /// Human-readable label for display. pub fn label(&self) -> &'static str { match self { Self::Reset => "Reset", @@ -200,6 +212,7 @@ impl SyncMode { } } + /// Toggle between Reset and PhaseLock. pub fn toggle(&self) -> Self { match self { Self::Reset => Self::PhaseLock, @@ -208,6 +221,7 @@ impl SyncMode { } } +/// What happens when a pattern finishes: loop, stop, or chain to another. #[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] pub enum FollowUp { #[default] @@ -217,6 +231,7 @@ pub enum FollowUp { } impl FollowUp { + /// Human-readable label for display. pub fn label(&self) -> &'static str { match self { Self::Loop => "Loop", @@ -225,6 +240,7 @@ impl FollowUp { } } + /// Cycle forward through follow-up modes. pub fn next_mode(&self) -> Self { match self { Self::Loop => Self::Stop, @@ -233,6 +249,7 @@ impl FollowUp { } } + /// Cycle backward through follow-up modes. pub fn prev_mode(&self) -> Self { match self { Self::Loop => Self::Chain { bank: 0, pattern: 0 }, @@ -246,6 +263,7 @@ fn is_default_follow_up(f: &FollowUp) -> bool { *f == FollowUp::default() } +/// Single step in a pattern, holding a Forth script and optional metadata. #[derive(Clone, Serialize, Deserialize)] pub struct Step { pub active: bool, @@ -257,10 +275,12 @@ pub struct Step { } impl Step { + /// True if all fields are at their default values. pub fn is_default(&self) -> bool { self.active && self.script.is_empty() && self.source.is_none() && self.name.is_none() } + /// True if the script is non-empty. pub fn has_content(&self) -> bool { !self.script.is_empty() } @@ -277,6 +297,7 @@ impl Default for Step { } } +/// Sequence of steps with playback settings (speed, quantization, sync, follow-up). #[derive(Clone)] pub struct Pattern { pub steps: Vec, @@ -447,14 +468,17 @@ impl Default for Pattern { } impl Pattern { + /// Borrow a step by index. pub fn step(&self, index: usize) -> Option<&Step> { self.steps.get(index) } + /// Mutably borrow a step by index. pub fn step_mut(&mut self, index: usize) -> Option<&mut Step> { self.steps.get_mut(index) } + /// Set the active length, clamped to `[1, MAX_STEPS]`. pub fn set_length(&mut self, length: usize) { let length = length.clamp(1, MAX_STEPS); while self.steps.len() < length { @@ -463,6 +487,7 @@ impl Pattern { self.length = length; } + /// Follow the source chain from `index` to find the originating step. pub fn resolve_source(&self, index: usize) -> usize { let mut current = index; for _ in 0..self.steps.len() { @@ -479,20 +504,22 @@ impl Pattern { index } + /// Return the script at the resolved source of `index`. pub fn resolve_script(&self, index: usize) -> Option<&str> { let source_idx = self.resolve_source(index); self.steps.get(source_idx).map(|s| s.script.as_str()) } + /// Count active-length steps that have a script or a source reference. pub fn content_step_count(&self) -> usize { self.steps[..self.length] .iter() .filter(|s| s.has_content() || s.source.is_some()) .count() } - } +/// Collection of patterns forming a bank. #[derive(Clone, Serialize, Deserialize)] pub struct Bank { pub patterns: Vec, @@ -501,6 +528,7 @@ pub struct Bank { } impl Bank { + /// Count patterns that contain at least one non-empty step. pub fn content_pattern_count(&self) -> usize { self.patterns .iter() @@ -518,6 +546,7 @@ impl Default for Bank { } } +/// Top-level project: banks, tempo, sample paths, and prelude script. #[derive(Clone, Serialize, Deserialize)] pub struct Project { pub banks: Vec, @@ -548,14 +577,17 @@ impl Default for Project { } impl Project { + /// Borrow a pattern by bank and pattern index. pub fn pattern_at(&self, bank: usize, pattern: usize) -> &Pattern { &self.banks[bank].patterns[pattern] } + /// Mutably borrow a pattern by bank and pattern index. pub fn pattern_at_mut(&mut self, bank: usize, pattern: usize) -> &mut Pattern { &mut self.banks[bank].patterns[pattern] } + /// Pad banks, patterns, and steps to their maximum sizes after deserialization. pub fn normalize(&mut self) { self.banks.resize_with(MAX_BANKS, Bank::default); for bank in &mut self.banks { diff --git a/crates/project/src/share.rs b/crates/project/src/share.rs index b7b472f..804583f 100644 --- a/crates/project/src/share.rs +++ b/crates/project/src/share.rs @@ -11,6 +11,7 @@ use crate::{Bank, Pattern}; const PATTERN_PREFIX: &str = "cgr:"; const BANK_PREFIX: &str = "cgrb:"; +/// Error during pattern or bank import/export. #[derive(Debug)] pub enum ShareError { InvalidPrefix, @@ -67,18 +68,22 @@ fn decode(text: &str, prefix: &str) -> Result Result { encode(pattern, PATTERN_PREFIX) } +/// Decode a `cgr:` string back into a pattern. pub fn import(text: &str) -> Result { decode(text, PATTERN_PREFIX) } +/// Encode a bank as a shareable `cgrb:` string. pub fn export_bank(bank: &Bank) -> Result { encode(bank, BANK_PREFIX) } +/// Decode a `cgrb:` string back into a bank. pub fn import_bank(text: &str) -> Result { decode(text, BANK_PREFIX) } diff --git a/crates/ratatui/src/category_list.rs b/crates/ratatui/src/category_list.rs index 7dda8aa..e9646ff 100644 --- a/crates/ratatui/src/category_list.rs +++ b/crates/ratatui/src/category_list.rs @@ -1,3 +1,5 @@ +//! Collapsible categorized list widget with section headers. + use ratatui::layout::Rect; use ratatui::style::{Color, Modifier, Style}; use ratatui::widgets::{Block, Borders, List, ListItem}; @@ -5,17 +7,20 @@ use ratatui::Frame; use crate::theme; +/// Entry in a category list: either a section header or a leaf item. pub struct CategoryItem<'a> { pub label: &'a str, pub is_section: bool, pub collapsed: bool, } +/// What is currently selected: a leaf item or a section header. pub enum Selection { Item(usize), Section(usize), } +/// Scrollable list with collapsible section headers. pub struct CategoryList<'a> { items: &'a [CategoryItem<'a>], selection: Selection, diff --git a/crates/ratatui/src/confirm.rs b/crates/ratatui/src/confirm.rs index 89914e9..42e4627 100644 --- a/crates/ratatui/src/confirm.rs +++ b/crates/ratatui/src/confirm.rs @@ -1,3 +1,5 @@ +//! Yes/No confirmation dialog widget. + use crate::theme; use ratatui::layout::{Alignment, Constraint, Layout, Rect}; use ratatui::style::Style; @@ -7,6 +9,7 @@ use ratatui::Frame; use super::ModalFrame; +/// Modal dialog with Yes/No buttons. pub struct ConfirmModal<'a> { title: &'a str, message: &'a str, diff --git a/crates/ratatui/src/editor.rs b/crates/ratatui/src/editor.rs index 64d71de..32c2e51 100644 --- a/crates/ratatui/src/editor.rs +++ b/crates/ratatui/src/editor.rs @@ -1,3 +1,5 @@ +//! Script editor widget with completion, search, and sample finder popups. + use std::cell::Cell; use crate::theme; @@ -10,8 +12,10 @@ use ratatui::{ }; use tui_textarea::TextArea; +/// Callback that syntax-highlights a single line, returning styled spans (bool = annotation). pub type Highlighter<'a> = &'a dyn Fn(usize, &str) -> Vec<(Style, String, bool)>; +/// Metadata for a single autocomplete entry. #[derive(Clone)] pub struct CompletionCandidate { pub name: String, @@ -78,6 +82,7 @@ impl SearchState { } } +/// Multi-line text editor backed by tui_textarea. pub struct Editor { text: TextArea<'static>, completion: CompletionState, @@ -702,6 +707,7 @@ impl Editor { } } +/// Score a fuzzy match of `query` against `target`. Lower is better; `None` if no match. pub fn fuzzy_match(query: &str, target: &str) -> Option { let target_lower: Vec = target.to_lowercase().chars().collect(); let query_lower: Vec = query.to_lowercase().chars().collect(); diff --git a/crates/ratatui/src/file_browser.rs b/crates/ratatui/src/file_browser.rs index 6aa10c4..6b69716 100644 --- a/crates/ratatui/src/file_browser.rs +++ b/crates/ratatui/src/file_browser.rs @@ -1,3 +1,5 @@ +//! File/directory browser modal widget. + use crate::theme; use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Color, Style}; @@ -7,6 +9,7 @@ use ratatui::Frame; use super::ModalFrame; +/// Modal listing files and directories with a filter input line. pub struct FileBrowserModal<'a> { title: &'a str, input: &'a str, diff --git a/crates/ratatui/src/hint_bar.rs b/crates/ratatui/src/hint_bar.rs index 340cab2..5f75648 100644 --- a/crates/ratatui/src/hint_bar.rs +++ b/crates/ratatui/src/hint_bar.rs @@ -1,8 +1,11 @@ +//! Bottom-bar keyboard hint renderer. + use ratatui::text::{Line, Span}; use ratatui::style::Style; use crate::theme; +/// Build a styled line of key/action pairs for the hint bar. pub fn hint_line(pairs: &[(&str, &str)]) -> Line<'static> { let theme = theme::get(); let key_style = Style::default().fg(theme.hint.key); diff --git a/crates/ratatui/src/lissajous.rs b/crates/ratatui/src/lissajous.rs index 5a9885d..c04f5dc 100644 --- a/crates/ratatui/src/lissajous.rs +++ b/crates/ratatui/src/lissajous.rs @@ -1,3 +1,5 @@ +//! Lissajous XY oscilloscope widget using braille characters. + use crate::theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; @@ -9,6 +11,7 @@ thread_local! { static PATTERNS: RefCell> = const { RefCell::new(Vec::new()) }; } +/// XY oscilloscope plotting left vs right channels as a Lissajous curve. pub struct Lissajous<'a> { left: &'a [f32], right: &'a [f32], diff --git a/crates/ratatui/src/list_select.rs b/crates/ratatui/src/list_select.rs index 80ee26e..dbc35f1 100644 --- a/crates/ratatui/src/list_select.rs +++ b/crates/ratatui/src/list_select.rs @@ -1,3 +1,5 @@ +//! Scrollable single-select list widget with cursor highlight. + use crate::theme; use ratatui::layout::Rect; use ratatui::style::{Modifier, Style}; @@ -5,6 +7,7 @@ use ratatui::text::{Line, Span}; use ratatui::widgets::Paragraph; use ratatui::Frame; +/// Scrollable list with a highlighted cursor and selected-item marker. pub struct ListSelect<'a> { items: &'a [String], selected: usize, diff --git a/crates/ratatui/src/modal.rs b/crates/ratatui/src/modal.rs index 0fe92ad..f5cf520 100644 --- a/crates/ratatui/src/modal.rs +++ b/crates/ratatui/src/modal.rs @@ -1,9 +1,12 @@ +//! Centered modal frame with border and title. + use crate::theme; use ratatui::layout::Rect; use ratatui::style::{Color, Style}; use ratatui::widgets::{Block, Borders, Clear, Paragraph}; use ratatui::Frame; +/// Centered modal overlay with titled border. pub struct ModalFrame<'a> { title: &'a str, width: u16, diff --git a/crates/ratatui/src/nav_minimap.rs b/crates/ratatui/src/nav_minimap.rs index f04232d..260977e 100644 --- a/crates/ratatui/src/nav_minimap.rs +++ b/crates/ratatui/src/nav_minimap.rs @@ -1,3 +1,5 @@ +//! Page navigation minimap showing a 3x2 grid of tiles. + use crate::theme; use ratatui::layout::{Alignment, Rect}; use ratatui::style::Style; diff --git a/crates/ratatui/src/props_form.rs b/crates/ratatui/src/props_form.rs index 1314f78..ba1fcba 100644 --- a/crates/ratatui/src/props_form.rs +++ b/crates/ratatui/src/props_form.rs @@ -1,3 +1,5 @@ +//! Vertical label/value property form renderer. + use ratatui::layout::Rect; use ratatui::style::{Modifier, Style}; use ratatui::widgets::Paragraph; @@ -5,6 +7,7 @@ use ratatui::Frame; use crate::theme; +/// Render a vertical list of label/value pairs with selection highlight. pub fn render_props_form(frame: &mut Frame, area: Rect, fields: &[(&str, &str, bool)]) { let theme = theme::get(); diff --git a/crates/ratatui/src/sample_browser.rs b/crates/ratatui/src/sample_browser.rs index 3cad4e3..faa8f55 100644 --- a/crates/ratatui/src/sample_browser.rs +++ b/crates/ratatui/src/sample_browser.rs @@ -1,3 +1,5 @@ +//! Tree-view sample browser with search filtering. + use crate::theme; use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Modifier, Style}; @@ -5,6 +7,7 @@ use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Paragraph}; use ratatui::Frame; +/// Node type in the sample tree. #[derive(Clone, Copy)] pub enum TreeLineKind { Root { expanded: bool }, @@ -12,6 +15,7 @@ pub enum TreeLineKind { File, } +/// A single row in the sample browser tree. #[derive(Clone)] pub struct TreeLine { pub depth: u8, @@ -21,6 +25,7 @@ pub struct TreeLine { pub index: usize, } +/// Tree-view browser for navigating sample folders. pub struct SampleBrowser<'a> { entries: &'a [TreeLine], cursor: usize, diff --git a/crates/ratatui/src/scope.rs b/crates/ratatui/src/scope.rs index ce27abd..b839e47 100644 --- a/crates/ratatui/src/scope.rs +++ b/crates/ratatui/src/scope.rs @@ -1,3 +1,5 @@ +//! Oscilloscope waveform widget using braille characters. + use crate::theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; @@ -9,12 +11,14 @@ thread_local! { static PATTERNS: RefCell> = const { RefCell::new(Vec::new()) }; } +/// Rendering direction for the oscilloscope. #[derive(Clone, Copy)] pub enum Orientation { Horizontal, Vertical, } +/// Single-channel oscilloscope using braille dot plotting. pub struct Scope<'a> { data: &'a [f32], orientation: Orientation, diff --git a/crates/ratatui/src/scroll_indicators.rs b/crates/ratatui/src/scroll_indicators.rs index 565c78b..bc193d5 100644 --- a/crates/ratatui/src/scroll_indicators.rs +++ b/crates/ratatui/src/scroll_indicators.rs @@ -1,13 +1,17 @@ +//! Up/down arrow scroll indicators for bounded lists. + use ratatui::layout::Rect; use ratatui::style::{Color, Style}; use ratatui::widgets::Paragraph; use ratatui::Frame; +/// Horizontal alignment for scroll indicators. pub enum IndicatorAlign { Center, Right, } +/// Render up/down scroll arrows when content overflows. pub fn render_scroll_indicators( frame: &mut Frame, area: Rect, diff --git a/crates/ratatui/src/search_bar.rs b/crates/ratatui/src/search_bar.rs index be3e527..3f5f529 100644 --- a/crates/ratatui/src/search_bar.rs +++ b/crates/ratatui/src/search_bar.rs @@ -1,3 +1,5 @@ +//! Inline search bar with active/inactive styling. + use ratatui::layout::Rect; use ratatui::style::Style; use ratatui::text::{Line, Span}; @@ -6,6 +8,7 @@ use ratatui::Frame; use crate::theme; +/// Render a `/query` search bar. pub fn render_search_bar(frame: &mut Frame, area: Rect, query: &str, active: bool) { let theme = theme::get(); let style = if active { diff --git a/crates/ratatui/src/section_header.rs b/crates/ratatui/src/section_header.rs index f6eae07..d1cd41c 100644 --- a/crates/ratatui/src/section_header.rs +++ b/crates/ratatui/src/section_header.rs @@ -1,3 +1,5 @@ +//! Section header with horizontal divider for engine-view panels. + use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::widgets::Paragraph; @@ -5,6 +7,7 @@ use ratatui::Frame; use crate::theme; +/// Render a section title with a horizontal divider below it. pub fn render_section_header(frame: &mut Frame, title: &str, focused: bool, area: Rect) { let theme = theme::get(); let [header_area, divider_area] = diff --git a/crates/ratatui/src/sparkles.rs b/crates/ratatui/src/sparkles.rs index 644c62f..b2d42b8 100644 --- a/crates/ratatui/src/sparkles.rs +++ b/crates/ratatui/src/sparkles.rs @@ -1,3 +1,5 @@ +//! Decorative particle effect using random Unicode glyphs. + use crate::theme; use rand::Rng; use ratatui::buffer::Buffer; @@ -14,6 +16,7 @@ struct Sparkle { life: u8, } +/// Animated sparkle particles for visual flair. #[derive(Default)] pub struct Sparkles { sparkles: Vec, diff --git a/crates/ratatui/src/spectrum.rs b/crates/ratatui/src/spectrum.rs index dea5ff8..c60cce1 100644 --- a/crates/ratatui/src/spectrum.rs +++ b/crates/ratatui/src/spectrum.rs @@ -1,3 +1,5 @@ +//! 32-band frequency spectrum bar display. + use crate::theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; @@ -6,6 +8,7 @@ use ratatui::widgets::Widget; const BLOCKS: [char; 8] = ['\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}', '\u{2588}']; +/// 32-band spectrum analyzer using block characters. pub struct Spectrum<'a> { data: &'a [f32; 32], gain: f32, diff --git a/crates/ratatui/src/text_input.rs b/crates/ratatui/src/text_input.rs index 1df2fbe..5c67886 100644 --- a/crates/ratatui/src/text_input.rs +++ b/crates/ratatui/src/text_input.rs @@ -1,3 +1,5 @@ +//! Single-line text input modal with optional hint. + use crate::theme; use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Color, Style}; @@ -7,6 +9,7 @@ use ratatui::Frame; use super::ModalFrame; +/// Modal dialog with a single-line text input. pub struct TextInputModal<'a> { title: &'a str, input: &'a str, diff --git a/crates/ratatui/src/vu_meter.rs b/crates/ratatui/src/vu_meter.rs index a23076f..f2b2767 100644 --- a/crates/ratatui/src/vu_meter.rs +++ b/crates/ratatui/src/vu_meter.rs @@ -1,3 +1,5 @@ +//! Stereo VU meter with dB-scaled level display. + use crate::theme; use ratatui::buffer::Buffer; use ratatui::layout::Rect; @@ -8,6 +10,7 @@ const DB_MIN: f32 = -48.0; const DB_MAX: f32 = 3.0; const DB_RANGE: f32 = DB_MAX - DB_MIN; +/// Stereo VU meter displaying left/right levels in dB. pub struct VuMeter { left: f32, right: f32, diff --git a/crates/ratatui/src/waveform.rs b/crates/ratatui/src/waveform.rs index 943711d..3404f1c 100644 --- a/crates/ratatui/src/waveform.rs +++ b/crates/ratatui/src/waveform.rs @@ -1,3 +1,5 @@ +//! Filled waveform display using braille characters. + use crate::scope::Orientation; use crate::theme; use ratatui::buffer::Buffer; @@ -10,6 +12,7 @@ thread_local! { static PATTERNS: RefCell> = const { RefCell::new(Vec::new()) }; } +/// Filled waveform renderer using braille dot plotting. pub struct Waveform<'a> { data: &'a [f32], orientation: Orientation, diff --git a/src/main.rs b/src/main.rs index 42f473a..77bb330 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,26 @@ struct Args { buffer: Option, } +#[cfg(unix)] +fn redirect_stderr() -> Option { + use std::os::fd::FromRawFd; + + let mut fds = [0i32; 2]; + if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 { + return None; + } + unsafe { + libc::dup2(fds[1], libc::STDERR_FILENO); + libc::close(fds[1]); + libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK); + Some(std::fs::File::from_raw_fd(fds[0])) + } +} + fn main() -> io::Result<()> { + #[cfg(unix)] + let mut stderr_pipe = redirect_stderr(); + #[cfg(unix)] engine::realtime::lock_memory(); @@ -171,6 +190,26 @@ fn main() -> io::Result<()> { app.ui.flash(&err, 3000, state::FlashKind::Error); } + #[cfg(unix)] + if let Some(ref mut pipe) = stderr_pipe { + use std::io::Read; + let max_len = terminal.size().map(|s| s.width as usize).unwrap_or(80).saturating_sub(16); + let mut buf = [0u8; 1024]; + while let Ok(n) = pipe.read(&mut buf) { + if n == 0 { + break; + } + let text = String::from_utf8_lossy(&buf[..n]); + for line in text.lines() { + let line = line.trim(); + if !line.is_empty() { + let capped = if line.len() > max_len { &line[..max_len] } else { line }; + app.ui.flash(capped, 5000, state::FlashKind::Error); + } + } + } + } + app.playback.playing = playing.load(Ordering::Relaxed); while let Ok(midi_cmd) = midi_rx.try_recv() { diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 6afff6b..8e4c61d 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -2,6 +2,7 @@ import fs from 'node:fs'; const cargo = fs.readFileSync('../Cargo.toml', 'utf-8'); const version = cargo.match(/\[workspace\.package\]\s*\nversion\s*=\s*"([^"]+)"/)?.[1]; +const DL = 'https://dlcagire.raphaelforment.fr'; --- @@ -60,30 +61,30 @@ const version = cargo.match(/\[workspace\.package\]\s*\nversion\s*=\s*"([^"]+)"/ macOS (Universal) - .pkg + .pkg macOS (ARM) - .app - .tar.gz + .app + .tar.gz macOS (Intel) - .app - .tar.gz + .app + .tar.gz Windows - .exe - .zip + .exe + .zip Linux - .deb - .tar.gz + .deb + .tar.gz -

All releases are available on GitHub. You can also compile the software yourself by getting it from Cargo!

+

Source code and issue tracker on GitHub. You can also compile the software yourself from source!

About