OOLOI.ORG
Menu

Architectural decision Record 0014: PIECE-WALKER

​ADR-0014: Piece-Walker Temporal Coordination System

# ADR: Piece-Walker Temporal Coordination System

## Status: Accepted

## Context

Ooloi requires sophisticated traversal capabilities for its hierarchical musical structures to support critical applications including MIDI playback, visual layout, attachment resolution, and collaborative editing. The system faces several key challenges:

1. **Temporal Coordination**: Musical pieces require traversal that respects temporal ordering - all events at measure N must be processed before any events at measure N+1 across all voices and instruments.

2. **Polyphonic Complexity**: Modern musical scores contain multiple simultaneous voices that must be synchronized during traversal while maintaining their individual structural integrity.

3. **Scope-Limited Operations**: Different operations require traversal of specific subsets of the piece (single instrument, staff, voice, or measure range) without processing irrelevant portions.

4. **Performance Requirements**: Large orchestral scores with hundreds of thousands of musical elements demand efficient traversal that can terminate early and filter elements without full tree processing.

5. **Multiple Application Domains**: The same traversal mechanism must support fundamentally different operations including MIDI event generation, visual layout calculations, attachment endpoint resolution, and collaborative change propagation.

6. **Memory Constraints**: Professional scores can contain millions of elements, requiring memory-efficient processing that avoids intermediate collections.

## Decision

We implement a comprehensive piece-walker system based on temporal coordination principles, using transducer-based traversal with boundary-VPD scope limiting. The system provides "measure N across all voices before measure N+1" traversal guarantees while supporting efficient filtering, early termination, and scope limitation.

## Transducer Architecture Decision

### Why Clojure Transducers?

Clojure transducers provide the optimal solution for Ooloi's piece-walker due to several critical advantages:

**1. Composable Processing Pipelines**
Transducers enable elegant composition of operations without intermediate collections:
```clojure
(comp (filter pitch?)
      (map pitch->midi-note)
      (take-while #(< (:time %) end-time)))
```
This composes filtering, transformation, and early termination into a single efficient pipeline.

**2. Memory Efficiency**
Unlike sequence operations that create intermediate collections, transducers process elements one at a time, crucial for large orchestral scores:
- No intermediate vectors for filtered results
- Constant memory usage regardless of piece size
- Streaming-compatible for real-time applications

**3. Early Termination Support**
Transducers naturally support early termination through `take`, `take-while`, etc., essential for:
- MIDI playback of score sections
- Layout calculations that stop at page boundaries
- Interactive editing that processes only visible measures

**4. Process-Agnostic Design**
The same transducer works with different collection types and processing contexts:
- Lazy sequences for exploratory analysis
- Reduced operations for aggregation
- Channel processing for concurrent applications

### Alternative Approaches Considered and Rejected

**1. Sequence-Based Processing (map, filter, etc.)**
```clojure
;; Rejected approach
(->> (get-all-items piece)
     (filter pitch?)
     (map pitch->midi-note)
     (take 1000))
```
**Why Rejected:**
- Creates intermediate collections at each step
- Memory usage grows linearly with piece size
- No early termination until final step
- Poor performance for large orchestral scores

**2. Recursive Tree Walking with Higher-Order Functions**
```clojure
;; Rejected approach
(defn walk-tree [node processor filter-fn]
  (when (filter-fn node)
    (processor node))
  (doseq [child (get-children node)]
    (walk-tree child processor filter-fn)))
```
**Why Rejected:**
- Difficult to compose multiple operations
- No built-in early termination
- Temporal coordination requires complex state management
- Hard to unit test individual transformation steps

**3. Visitor Pattern Implementation**
```clojure
;; Rejected approach
(defprotocol ItemVisitor
  (visit-pitch [this pitch])
  (visit-chord [this chord])
  (visit-rest [this rest]))
```
**Why Rejected:**
- Object-oriented approach conflicts with Clojure's functional philosophy
- Poor composability - each visitor is monolithic
- Difficult to implement early termination
- Complex to extend for new musical element types

**4. Core.async Channels**
```clojure
;; Rejected approach
(defn walk-piece-async [piece]
  (let [ch (chan 1000)]
    (go (doseq [item (get-all-items piece)]
          (>! ch item)))
    ch))
```
**Why Rejected:**
- Unnecessary complexity for synchronous operations
- Channel buffering decisions complicate memory management
- Temporal coordination requires complex channel coordination
- Overkill for most piece-walker use cases

**5. Custom Iterator/Generator Protocol**
```clojure
;; Rejected approach
(defprotocol PieceIterator
  (next-item [this])
  (has-next? [this]))
```
**Why Rejected:**
- Stateful design conflicts with functional programming principles
- Poor composability with other Clojure operations
- Complex to implement temporal coordination correctly
- Difficult to test and reason about state transitions

### Single Unified Transducer Architecture

**Critical Design Decision: One Transducer for All Applications**

Rather than creating separate traversal systems for MIDI generation, visual layout, attachment resolution, and analysis, Ooloi implements a **single unified piece-walker transducer** that serves all these domains. This architectural choice provides several crucial advantages:

**1. Temporal Consistency Across All Operations**
Every application automatically inherits the same temporal coordination guarantee:
```clojure
;; MIDI, layout, and attachment resolution all use the same temporal ordering
(piece-walker piece boundary-vpd midi-transducer)    ; Temporal order preserved
(piece-walker piece boundary-vpd layout-transducer)  ; Same temporal order
(piece-walker piece boundary-vpd attach-transducer)  ; Same temporal order
```

**2. Reduced Complexity and Maintenance**
- Single algorithm to test, debug, and optimize
- No risk of inconsistent temporal behavior between different systems
- Unified API for all musical traversal needs
- Single point of optimization benefits all applications

**3. Composable Cross-Domain Operations**
Applications can combine concerns within a single traversal:
```clojure
;; Generate MIDI while calculating layout metrics
(piece-walker piece nil
  (comp (map (juxt item->midi-event calculate-width))
        (filter first)  ; Only items that generate MIDI
        (map (fn [[midi width]] {:midi midi :width width}))))
```

**4. Guaranteed Behavioral Consistency**
- MIDI playback and visual display show identical temporal ordering
- Attachment resolution finds the same endpoints regardless of context
- Analysis results are consistent with user's visual experience
- Eliminates entire classes of synchronization bugs

**Why Not Separate Systems?**

Alternative approaches with domain-specific traversals would create:
- **Temporal Drift**: MIDI and layout could process measures in different orders
- **Maintenance Burden**: Multiple algorithms requiring separate optimization
- **Inconsistent Behavior**: Users experiencing different temporal behavior in different contexts
- **Duplicate Code**: Similar traversal logic repeated across domains
- **Testing Complexity**: Need to verify temporal consistency across multiple systems

### Transducer-Specific Advantages for Musical Applications

**1. Temporal Coordination Preservation**
Transducers maintain the temporal ordering guarantee while allowing arbitrary transformations:
```clojure
(piece-walker piece 
  {:boundary-vpd [:musicians 0]}
  (comp (filter chord?)
        (map extract-harmony)))
;; Still processes measure 1 chords before measure 2 chords
```

**2. MIDI Streaming Compatibility**
Transducers naturally support real-time MIDI generation:
```clojure
(piece-walker piece nil
  (comp (map item->midi-event)
        (remove nil?)
        (take-while #(< (:time %) playback-end))))
```

**3. Layout Calculation Optimization**
Visual layout can terminate early when page capacity is reached:
```clojure
(piece-walker piece nil
  (comp (map calculate-item-width)
        (scan +)  ; Running width total
        (take-while #(< % page-width))))
```

**4. Attachment Resolution Efficiency**
Find attachment endpoints without processing entire piece:
```clojure
(piece-walker piece {:boundary-vpd attachment-scope}
  (comp (filter #(= (:end-id %) target-id))
        (take 1)))  ; Stop after finding endpoint
```

## Detailed Design

### 1. Core Algorithm: Temporal Breadth-First Traversal

The piece-walker implements temporal coordination through a two-phase process:

**Phase 1: Parallel Measure Grouping**
```clojure
(find-parallel-measures piece boundary-vpd)
```
- Discovers all measures at each temporal position across the specified scope
- Groups measures by their measure number (1, 2, 3, etc.)
- Returns sequence of measure groups in temporal order

**Phase 2: Temporal Item Processing**
```clojure
(walk-items grouped-measures transducer)
```
- Processes each measure group completely before advancing to the next
- Applies transducer to all items within each measure group
- Maintains temporal ordering guarantee throughout processing

### 2. Boundary-VPD Scope Limiting

The system uses Vector Path Descriptors (VPDs) to define traversal boundaries:

```clojure
;; Full piece traversal
{:boundary-vpd nil}

;; Single musician traversal  
{:boundary-vpd [:musicians 2]}

;; Single instrument traversal
{:boundary-vpd [:musicians 0 :instruments 1]}

;; Single staff traversal
{:boundary-vpd [:musicians 0 :instruments 1 :staves 0]}
```

Boundary-VPDs provide precise control over traversal scope while maintaining the temporal coordination guarantee within the specified boundary.

### 3. Transducer Integration Examples

```clojure
;; MIDI generation with early termination
(piece-walker piece 
  {:boundary-vpd [:musicians 0]} 
  (comp (filter rhythmic-item?)
        (map item->midi-event)
        (remove nil?)
        (take-while #(< (:time %) end-time))))

;; Harmonic analysis with filtering
(piece-walker piece
  {:boundary-vpd nil}
  (comp (filter chord?)
        (map extract-harmony)
        (distinct)))

;; Layout calculation with running totals
(piece-walker piece
  {:boundary-vpd [:musicians 0 :instruments 0]}
  (comp (map calculate-width)
        (scan +)
        (take-while #(< % page-width))))
```

### 4. Deep Traversal Support

The walker performs deep traversal within each measure, finding all nested items including:
- Direct measure items (rests, pitches, chords)
- Tuplet-contained items (items within tuplets)
- Tremolando-contained items (items within tremolandos)
- Arbitrarily nested structures

This ensures complete temporal coordination across all musical elements regardless of their nesting level.

## Key Applications

### 1. MIDI Playback Generation

The temporal coordination guarantee directly solves MIDI's core requirement for time-ordered event streams:

- **Temporal Order**: All MIDI events at measure N emit before any at measure N+1
- **Polyphonic Support**: Natural handling of multiple simultaneous voices
- **Partial Playback**: Boundary-VPD enables instrument/staff-specific playback
- **Real-time Streaming**: Transducer composition supports efficient event generation

### 2. Visual Layout Calculation

The walker provides the foundation for professional music engraving:

- **System Layout**: Temporal coordination ensures measures align properly across staves
- **Page Breaking**: Early termination when page capacity is reached
- **Scope-Limited Layout**: Boundary-VPD enables partial re-layout after edits
- **Performance Optimization**: Avoid processing off-screen content

### 3. Attachment Resolution

Musical attachments (slurs, hairpins, ties) require endpoint resolution across temporal boundaries:

- **Cross-Measure Attachments**: Temporal traversal finds endpoints in later measures
- **Scope Awareness**: Boundary-VPD prevents attachment resolution outside relevant scope
- **Efficient Processing**: Early termination when all endpoints are found

### 4. Collaborative Editing

Multi-user editing requires precise change propagation:

- **Temporal Change Ordering**: Ensure changes apply in correct temporal sequence
- **Scope-Limited Updates**: Boundary-VPD enables efficient partial updates
- **Conflict Resolution**: Temporal coordination assists in resolving editing conflicts

### 5. Analysis and Export

Musical analysis tools require comprehensive piece traversal:

- **Harmonic Analysis**: Temporal coordination for chord progression analysis
- **Statistical Analysis**: Complete piece traversal with filtering
- **Format Export**: MusicXML, MIDI, and other format generation

## Rationale

1. **Temporal Coordination**: The "measure N before measure N+1" guarantee is fundamental to musical applications and distinguishes this system from generic tree traversal.

2. **Transducer Architecture**: Leverages Clojure's composable, efficient data transformation model while maintaining the temporal guarantee.

3. **Memory Efficiency**: Transducers avoid intermediate collections, crucial for large orchestral scores.

4. **Composability**: Different operations compose naturally without performance penalties.

5. **Boundary-VPD Integration**: Reuses Ooloi's existing VPD addressing system for intuitive scope specification.

6. **Performance Focus**: Early termination and filtering capabilities handle large orchestral scores efficiently.

7. **Multi-Application Support**: Single traversal mechanism serves diverse use cases from MIDI to layout to analysis.

## Consequences

### Positive

- **Unified Traversal Model**: Single system serves all temporal coordination needs
- **Memory Efficient**: Constant memory usage regardless of piece size
- **Performance Optimized**: Handles large scores with early termination and filtering
- **Composable Operations**: Transducer architecture enables flexible operation composition
- **Precise Scope Control**: Boundary-VPD provides fine-grained traversal control
- **MIDI-Ready Architecture**: Directly enables professional-quality MIDI playback
- **Layout Foundation**: Provides basis for sophisticated visual layout algorithms
- **Streaming Compatible**: Supports real-time applications without modification

### Negative

- **Learning Curve**: Developers must understand both temporal coordination and transducer principles
- **Complexity**: More sophisticated than simple tree traversal or sequence operations
- **Debugging**: Transducer pipelines can be harder to debug than explicit loops

### Neutral

- **Specialized Design**: Optimized for musical applications rather than general tree traversal
- **VPD Dependency**: Requires understanding of Ooloi's VPD addressing system
- **Functional Paradigm**: Benefits developers familiar with functional programming concepts

## Implementation Notes

1. **Core Functions**: `piece-walker`, `find-parallel-measures`, `walk-items` provide the primary API
2. **Helper Utilities**: `find-all-measures-in-scope`, `get-measures-at-number` support the core algorithm
3. **Comprehensive Testing**: 66 tests ensure correctness across complex musical scenarios
4. **Integration Points**: Designed for integration with attachment resolution, MIDI generation, and layout systems
5. **Performance Characteristics**: Constant memory usage, O(n) time complexity where n is items processed (not total items in piece)

## Future Considerations

1. **Performance Optimization**: Profile and optimize for extremely large orchestral scores
2. **Streaming Support**: Investigate lazy evaluation for real-time applications
3. **Parallel Processing**: Implement concurrent processing of measure groups using `pmap` with STM coordination for orchestral scores
4. **Enhanced Filtering**: Develop domain-specific filtering predicates for common operations
5. **Caching Strategies**: Cache parallel measure groupings for repeated operations on same piece sections
6. **Integration Extensions**: Develop specialized APIs for MIDI, layout, and attachment resolution applications
7. **Transducer Library**: Build collection of music-specific transducers for common operations

## Related ADRs

- **ADR-0010 Pure Trees**: Provides the foundational tree structure that piece-walker traverses
- **ADR-0008 VPDs**: Defines the Vector Path Descriptor system used for boundary specification
- **ADR-0012 Persisting Pieces**: Establishes the ID reference system that piece-walker respects during traversal

The piece-walker system represents a fundamental advancement in Ooloi's architecture, providing the temporal coordination foundation essential for professional music notation software while leveraging Clojure's transducer model for optimal performance, composability, and memory efficiency.
Home
​Overview
Documentation
About
Contact
FrankenScore is a modern, open-source music notation software designed to handle complex musical scores with ease. It is designed to be a flexible and powerful music notation software tool providing professional, extremely high-quality results. The core functionality includes inputting music notation, formatting scores and their parts, and printing them. Additional features can be added as plugins, allowing for a modular and customizable user experience.​
  • Home
  • Overview
    • Background and History
    • Project Goals
    • Introduction for Musicians
    • Introduction for Programmers
    • Introduction for Anti-Capitalists
    • Technical Comparison
  • Documentation
    • Architectural Decision Log >
      • Choice of Clojure
      • Separation of Frontend and Backend
      • Adoption of gRPC
      • Plugins
      • STM for Concurrency
      • JavaFX & Skija
      • SMuFL
      • Nippy
      • Vector Path Descriptors
      • Collaborative Features
      • Trees and Circles
      • Shared Structure
      • Persisting Pieces
      • Slur Formatting
      • Piece Walker
    • Backend src README
    • Development Plan
    • License
    • Code of Conduct
  • About
  • Contact
  • Home
  • Overview
    • Background and History
    • Project Goals
    • Introduction for Musicians
    • Introduction for Programmers
    • Introduction for Anti-Capitalists
    • Technical Comparison
  • Documentation
    • Architectural Decision Log >
      • Choice of Clojure
      • Separation of Frontend and Backend
      • Adoption of gRPC
      • Plugins
      • STM for Concurrency
      • JavaFX & Skija
      • SMuFL
      • Nippy
      • Vector Path Descriptors
      • Collaborative Features
      • Trees and Circles
      • Shared Structure
      • Persisting Pieces
      • Slur Formatting
      • Piece Walker
    • Backend src README
    • Development Plan
    • License
    • Code of Conduct
  • About
  • Contact