summaryrefslogtreecommitdiff
path: root/docs/design/musical-model.md
diff options
context:
space:
mode:
authorJosh Kingsley <josh@joshkingsley.me>2025-11-23 17:27:34 +0200
committerJosh Kingsley <josh@joshkingsley.me>2025-11-23 17:27:34 +0200
commiteb96b8eba0faf07adc5be4463759ec8b64ddc0e0 (patch)
tree014b7405ca5d55066ca2bba4aba2caa561571970 /docs/design/musical-model.md
parentbe5cae1beb714680f56d7598b8f5c4816000cb1d (diff)
chore: remove docs
Diffstat (limited to 'docs/design/musical-model.md')
-rw-r--r--docs/design/musical-model.md321
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
-```