Feat: WIP terse code documentation

This commit is contained in:
2026-02-26 01:08:16 +01:00
parent 71bd09d5ea
commit e1cf57918e
47 changed files with 499 additions and 24 deletions

View File

@@ -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<Self> {
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<Step>,
@@ -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<Pattern>,
@@ -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<Bank>,
@@ -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 {