diff options
Diffstat (limited to 'docs/design/musical-model.md')
| -rw-r--r-- | docs/design/musical-model.md | 321 |
1 files changed, 0 insertions, 321 deletions
diff --git a/docs/design/musical-model.md b/docs/design/musical-model.md deleted file mode 100644 index f7b941a..0000000 --- a/docs/design/musical-model.md +++ /dev/null @@ -1,321 +0,0 @@ -# Musical Model and Duration System - -**Last Updated:** 2025-11-15 - -## Overview - -This document describes how the CRDT represents musical time and rhythm, focusing on the relationship between cells, rows, and musical durations. - -## Core Concepts - -### Row Duration (Normalized) - -All rows have a **normalized duration of 1**. This is the internal representation used by the CRDT for calculations and validation. - -```rust -pub struct Row { - id: DerivedId, - cells: Vec<Cell>, - expected_duration: Ratio<u32>, // Always Ratio::new(1, 1) -} -``` - -### Base Cells and Musical Duration - -The **musical duration** of a row is determined by two factors: - -1. **base_cells_per_row** - How many base cells fit in the normalized row duration of 1 -2. **Base cell musical duration** - What musical duration each base cell represents (e.g., quarter note, eighth note) - -**Example 1: One measure of 4/4 time** -``` -base_cells_per_row = 16 -base cell duration = sixteenth note -row musical duration = 16 sixteenth notes = 1 measure of 4/4 -``` - -**Example 2: Two measures of 4/4 time** -``` -base_cells_per_row = 16 -base cell duration = eighth note -row musical duration = 16 eighth notes = 2 measures of 4/4 -``` - -**Example 3: One measure of 3/4 time** -``` -base_cells_per_row = 12 -base cell duration = sixteenth note -row musical duration = 12 sixteenth notes = 1 measure of 3/4 -``` - -## Cell Duration System - -### Rational Number Representation - -Cell durations are stored as exact ratios using Rust's `num_rational::Ratio<u32>`: - -```rust -use num_rational::Ratio; - -pub struct Cell { - id: DerivedId, - duration: Ratio<u32>, // Fraction of the normalized row duration -} -``` - -**Why ratios?** -- Musical durations are inherently rational (1/4, 1/8, 1/3, etc.) -- No floating-point rounding errors -- Exact validation that cell durations sum to row duration -- Natural representation for subdivision arithmetic - -### Base Cell Duration Calculation - -For a row with `base_cells_per_row` base cells: - -```rust -let base_cell_duration = Ratio::new(1, base_cells_per_row); - -// Examples: -// base_cells_per_row = 16 → each cell is 1/16 of the row -// base_cells_per_row = 12 → each cell is 1/12 of the row -``` - -### Subdivision Duration Calculation - -When subdividing N cells into M new cells, the total duration is preserved: - -```rust -// Original: N cells, each with duration d -let total_duration = n_cells * cell_duration; - -// After subdivision: M cells -let new_cell_duration = total_duration / m_cells; - -// Example: 4 cells of 1/16 → 3 triplets -// total = 4 * (1/16) = 4/16 = 1/4 -// new = (1/4) / 3 = 1/12 per cell -``` - -**Musical interpretation:** -``` -4 sixteenth notes → 3 eighth-note triplets -Duration: 1/4 of row → 1/4 of row (preserved) -Each new cell: 1/12 of row -``` - -## Duration Invariants - -### Row Duration Preservation - -**Invariant:** The sum of all non-deleted cell durations in a row must always equal the row's expected duration. - -```rust -fn validate_row_duration(row: &Row) -> Result<(), Error> { - let actual: Ratio<u32> = row.cells.iter() - .filter(|c| !c.deleted) - .map(|c| c.duration) - .sum(); - - if actual != row.expected_duration { - return Err(Error::DurationMismatch { - expected: row.expected_duration, - actual, - }); - } - - Ok(()) -} -``` - -### Subdivision Preservation - -**Invariant:** A `ChangeSubdivisions` operation must preserve the total duration of the cells it replaces. - -```rust -// Before subdivision -let old_duration: Ratio<u32> = old_cells.iter() - .map(|c| c.duration) - .sum(); - -// After subdivision -let new_duration: Ratio<u32> = new_cells.iter() - .map(|c| c.duration) - .sum(); - -assert_eq!(old_duration, new_duration); -``` - -## Musical Duration Mapping - -While the CRDT uses normalized durations (fractions of 1), the UI needs to display actual musical durations. - -### Common Mappings - -If base cell = quarter note: -``` -CRDT Duration → Musical Duration -1/1 → whole note -1/2 → half note -1/4 → quarter note -1/8 → eighth note -1/16 → sixteenth note -1/3 → quarter note triplet (3 per half note) -1/6 → eighth note triplet -1/12 → sixteenth note triplet -``` - -If base cell = eighth note: -``` -CRDT Duration → Musical Duration -1/2 → whole note -1/4 → half note -1/8 → quarter note -1/16 → eighth note -1/32 → sixteenth note -``` - -### Converting to Musical Duration - -```rust -pub struct MusicalDuration { - pub note_value: NoteValue, // Quarter, Eighth, etc. - pub dots: u8, // Dotted notes - pub tuplet: Option<Tuplet>, // Triplets, quintuplets, etc. -} - -pub enum NoteValue { - Whole, - Half, - Quarter, - Eighth, - Sixteenth, -} - -pub struct Tuplet { - pub notes: u32, // 3 for triplet, 5 for quintuplet - pub in_space_of: u32, // Usually notes - 1 -} - -fn to_musical_duration( - cell_duration: Ratio<u32>, - base_cell_musical_duration: NoteValue, -) -> MusicalDuration { - // Implementation depends on base cell duration and ratio - // Example: if base cell is sixteenth note and duration is 1/12, - // that's a sixteenth note triplet - todo!() -} -``` - -## Design Rationale - -### Why Normalized Row Duration? - -**Benefits:** -1. **Simplifies CRDT logic** - All rows have the same expected duration (1) -2. **Musical flexibility** - Same CRDT can represent different time signatures -3. **Clear separation** - CRDT handles ratios, UI handles musical interpretation -4. **Easier validation** - Always check if durations sum to 1 - -**Trade-off:** Requires mapping layer between CRDT and musical display - -### Why Not Store Musical Durations Directly? - -Storing "quarter note" or "eighth note" in the CRDT would: -- Couple the CRDT to Western musical notation -- Make subdivision calculations more complex -- Require context about time signature for validation -- Limit future extensions (non-Western music, variable time signatures) - -The normalized approach keeps the CRDT generic and the musical interpretation separate. - -## Future Considerations - -### Time Signature Changes - -Currently, the time signature is implicit in `base_cells_per_row`. Future enhancements might: -- Store explicit time signature metadata on rows -- Support time signature changes mid-document -- Validate subdivisions against time signature rules - -### Tempo and Absolute Time - -The CRDT currently deals with relative durations. Future work might add: -- Tempo markers (BPM) -- Conversion to absolute time (milliseconds) -- Synchronization with audio playback - -### Non-Standard Divisions - -Some musical contexts require unusual divisions: -- Nested tuplets (triplets of quintuplets) -- Irrational rhythms -- Microtiming adjustments - -The ratio system can represent any rational duration, supporting most musical use cases. - -## References - -- **CRDT Design:** `./crdt-design.md` -- **Conflict Resolution:** `./branch-based-merge.md` -- **Implementation:** `/crdt/src/lib.rs` - -## Examples - -### Example 1: Creating a Row - -```rust -// Create a row with 16 base cells (normalized duration 1) -// Musical interpretation: 16 sixteenth notes = 1 measure of 4/4 -let op = OpPayload::CreateGrid { - rows: 1, - base_cells_per_row: 16, -}; - -// Each cell has duration 1/16 -let cell_duration = Ratio::new(1, 16); -``` - -### Example 2: Subdividing into Triplets - -```rust -// Start with 4 cells, each 1/16 (= 1 quarter note) -let old_cells = vec![ - Cell { duration: Ratio::new(1, 16), .. }, - Cell { duration: Ratio::new(1, 16), .. }, - Cell { duration: Ratio::new(1, 16), .. }, - Cell { duration: Ratio::new(1, 16), .. }, -]; - -// Total duration = 4/16 = 1/4 -let total = Ratio::new(4, 16); - -// Subdivide into 3 triplets -// Each triplet = (1/4) / 3 = 1/12 -let new_cells = vec![ - Cell { duration: Ratio::new(1, 12), .. }, - Cell { duration: Ratio::new(1, 12), .. }, - Cell { duration: Ratio::new(1, 12), .. }, -]; - -// Validation: 3 * (1/12) = 3/12 = 1/4 ✓ -``` - -### Example 3: Concurrent Subdivisions - -```rust -// Initial row: 16 cells of 1/16 each -// Alice subdivides cells [0,1,2,3] into 3 triplets (each 1/12) -// Bob subdivides cells [2,3,4,5] into 5 quintuplets (each 1/20) - -// Conflict: cells 2 and 3 are targeted by both operations -// After both ops apply: -// - Cells 0,1 deleted by Alice -// - Cells 2,3,4,5 deleted by Bob -// - Cells 2,3 deleted by BOTH (conflict!) -// - Row has: original cells 6-15 + Alice's 3 triplets + Bob's 5 quintuplets -// - Duration: 10*(1/16) + 3*(1/12) + 5*(1/20) = incorrect! - -// This is why we need conflict detection -``` |
