Grand Diomande Research · Full HTML Reader

Phase 2 Implementation Plan – Scheduler & Safety

**Timeline:** Weeks 7-12 (6 weeks) **Status:** Foundation complete (BeatClock trait, Quantizer, SafetyPolicy) **Next:** Action queue, executor, MIDI/OSC integration

Embodied Trajectory Systems proposal experiment writeup candidate score 40 .md

Full Public Reader

Phase 2 Implementation Plan – Scheduler & Safety

## Overview
Phase 2 focuses on integrating Ableton Link synchronization, implementing quantized action execution with safety policies, and adding MIDI/OSC control interfaces. The scheduler coordinates deck operations with beat-synchronized timing while enforcing safety constraints.

Timeline: Weeks 7-12 (6 weeks)
Status: Foundation complete (BeatClock trait, Quantizer, SafetyPolicy)
Next: Action queue, executor, MIDI/OSC integration

---

Week 7 — Link Integration & Beat Clock ✅ (Foundation Complete)

### Completed ✅
- [x] BeatClock trait with `current_beat()`, `phase()`, `tempo_bpm()`, `is_synchronized()`, `num_peers()`
- [x] LocalBeatClock implementation for offline use
- [x] Helper methods: `time_to_next_beat()`, `time_to_next_bar()`
- [x] Unit tests for beat clock functionality

### Remaining Tasks
- [ ] 7.1 Link SDK FFI Integration (Requires Ableton Link SDK)
- [ ] Add Link SDK C++ bindings to `link-clock/src/ffi.rs`
- [ ] Create `LinkSession` wrapper struct
- [ ] Implement `LinkClock` struct implementing `BeatClock` trait
- [ ] Expose `beat_time()`, `phase()`, `is_connected()`, `num_peers()` methods
- [ ] Handle Link session lifecycle (create, enable, close)
- Dependencies: Ableton Link SDK license and C++ bindings
- Deliverable: `LinkClock` compiles and can query Link state

  • [ ] 7.2 Link Thread
  • [ ] Create `LinkThread` struct in `link-clock` crate
  • [ ] Poll Link state at configurable interval (e.g., 10ms)
  • [ ] Publish beat clock updates via SPSC ring (`BeatClockUpdate` message)
  • [ ] Handle Link tempo changes and peer connections/disconnections
  • [ ] Graceful shutdown on thread join
  • Dependencies: Link SDK integration (7.1)
  • Deliverable: `cargo run -p link-test` demonstrates Link sync with multiple instances

---

Week 8 — Quantized Action Executor

### Completed ✅
- [x] Quantizer with multiple resolutions (Beat, HalfBeat, QuarterBeat, etc.)
- [x] QuantizedAction result type with error tracking
- [x] Unit tests for quantization

### Remaining Tasks
- [ ] 8.1 Action Queue
- [ ] Define `Action` enum in `scheduler/src/action.rs`:

rust
    pub enum Action {
        PlayDeck { deck: DeckId, position: Option<SamplePosition> },
        StopDeck { deck: DeckId },
        SeekDeck { deck: DeckId, position: SamplePosition },
        SetCrossfader { position: f32 },
        SetEq { deck: DeckId, band: usize, gain_db: f32 },
        SetLimiter { threshold_db: f32 },
        LoadDeck { deck: DeckId, path: String },
    }
  • [ ] Create `ActionQueue` wrapper around SPSC ring (`ringbuf`)
  • [ ] Add `ActionTiming` enum: `Immediate`, `NextBeat`, `NextBar`, `CustomBeat(f64)`
  • [ ] Implement `ActionWithTiming` struct combining action + timing
  • [ ] Add queue capacity configuration (default: 64)
  • Deliverable: Action queue accepts and stores actions with timing preferences

- [ ] 8.2 Action Executor
- [ ] Create `ActionExecutor` struct in `scheduler/src/executor.rs`
- [ ] Fields: `action_queue`, `quantizer`, `clock`, `safety_policy`, `event_tx`
- [ ] Implement `execute_pending()` method:
1. Drain action queue
2. Check safety policy for each action
3. Quantize action timing based on `ActionTiming`
4. Schedule action for execution at quantized beat time
5. Convert to `ControlEvent` and send to engine
- [ ] Track scheduled actions in `Vec<ScheduledAction>` sorted by beat time
- [ ] Implement `tick()` method that executes actions whose beat time has arrived
- [ ] Handle immediate actions (bypass quantization)
- Dependencies: Action queue (8.1), Quantizer (✅), SafetyPolicy (✅)
- Deliverable: Integration test that queues actions, verifies quantization, asserts execution timing

  • [ ] 8.3 Integration Test
  • [ ] Create `tests/integration/action_executor.rs`
  • [ ] Test: Queue play action, verify quantization to next beat
  • [ ] Test: Queue multiple actions, verify execution order
  • [ ] Test: Immediate actions bypass quantization
  • [ ] Test: Actions respect safety policy constraints
  • Deliverable: All integration tests passing

---

Week 9 — Safety Policies Enhancement

### Completed ✅
- [x] DeckLockState state machine
- [x] SafetyPolicy with locks and cooldowns
- [x] Basic unit tests

### Remaining Tasks
- [ ] 9.1 Action Masks
- [ ] Create `ActionMask` bitfield in `scheduler/src/safety.rs`
- [ ] Define allowed actions per deck state:
- `Stopped`: Allow Play, Load, SetEq
- `Playing`: Allow Stop, Seek, Loop, SetEq
- `Primed`: Allow Play, Stop, Load
- [ ] Add `can_perform_action_with_mask()` method checking both lock and mask
- [ ] Update `SafetyPolicy` to use action masks
- Deliverable: Action masks prevent invalid state transitions

  • [ ] 9.2 Enhanced Safety Logic
  • [ ] Port DJ Agent safety predicates:
  • `can_play()`: Check deck not locked, not in cooldown, mask allows Play
  • `can_seek()`: Check deck playing, not locked, mask allows Seek
  • `can_loop()`: Check deck playing, not locked, mask allows Loop
  • [ ] Add `SafetyViolation` error type with reason
  • [ ] Return `Result<(), SafetyViolation>` from safety checks
  • [ ] Add telemetry tracking: lock events, cooldown triggers, rejected actions
  • Deliverable: Safety predicates match Python reference implementation
  • [ ] 9.3 Deck State Tracking
  • [ ] Enhance `EngineShadowState` to track more states:
  • Playing/stopped status
  • Current position
  • Loop state
  • Loaded track info
  • [ ] Update state from `ControlEvent` feedback
  • [ ] Use state for safety policy decisions
  • Deliverable: Safety policies use accurate deck state

---

Week 10 — Safety Regression Tests

### Tasks
- [ ] 10.1 Property-Based Tests
- [ ] Add `proptest` dependency to `scheduler/Cargo.toml`
- [ ] Port Python safety tests from `episode1/safety/`:
- Test: Deck lock transitions (Unlocked → Locked → Cooldown → Unlocked)
- Test: Cooldown enforcement (rapid actions blocked)
- Test: Action mask filtering (invalid actions rejected)
- Test: Concurrent action handling (thread safety)
- [ ] Create `tests/proptest/safety.rs` with property-based tests
- [ ] Test edge cases: rapid play/stop, seek during playback, crossfader during load
- Dependencies: proptest crate
- Deliverable: Test suite with 100

  • [ ] 10.2 Error Recovery
  • [ ] Implement `SafetyViolation` error type:
rust
    pub enum SafetyViolation {
        DeckLocked { deck: DeckId, reason: LockReason },
        CooldownActive { deck: DeckId, expires_at: Instant },
        ActionNotAllowed { deck: DeckId, action: ActionType, state: DeckState },
    }
  • [ ] Ensure scheduler continues after safety violations (log and continue)
  • [ ] Add `SafetyTelemetry` struct tracking:
  • Lock events count
  • Cooldown triggers count
  • Rejected actions count (by type)
  • [ ] Expose telemetry via `SafetyPolicy::telemetry()` method
  • Deliverable: No panics in safety-critical paths, telemetry reports safety events
  • [ ] 10.3 Integration Tests
  • [ ] Test: Rapid play/stop sequences
  • [ ] Test: Seek during playback
  • [ ] Test: Crossfader changes during deck load
  • [ ] Test: Multiple decks simultaneous operations
  • Deliverable: All integration tests passing, zero safety violations in normal operation

---

Week 11 — MIDI & OSC Control

### Tasks
- [ ] 11.1 MIDI Input Handler
- [ ] Review existing `midi-osc/src/midi_in.rs` implementation
- [ ] Enhance `MidiInputHandler`:
- Open virtual MIDI port using `midir` crate
- Listen for CC (Control Change) and Note messages
- Parse MIDI messages into `MidiMessage` enum
- [ ] Create `MidiMapping` struct:

rust
    pub struct MidiMapping {
        pub cc_number: Option<u8>,
        pub note_number: Option<u8>,
        pub action: Action,
        pub quantization: Option<QuantizationResolution>,
    }
  • [ ] Implement mapping lookup: MIDI message → Action
  • [ ] Add `MidiLearn` mode:
  • Record MIDI message → action binding
  • Store mappings in config file (YAML/JSON)
  • Load mappings on startup
  • Dependencies: midir crate (already in workspace)
  • Deliverable: `cargo run -p midi-osc --example midi_learn` demonstrates MIDI mapping
  • [ ] 11.2 OSC Server
  • [ ] Review existing `midi-osc/src/osc.rs` implementation
  • [ ] Enhance `OscServer`:
  • Listen on configurable UDP port (default: 8000)
  • Parse OSC messages using `rosc` or similar crate
  • Map OSC paths to actions:
  • `/deck/a/play` → `Action::PlayDeck { deck: A }`
  • `/deck/b/stop` → `Action::StopDeck { deck: B }`
  • `/crossfader` → `Action::SetCrossfader { position: f32 }`
  • `/eq/a/low` → `Action::SetEq { deck: A, band: 0, gain_db: f32 }`
  • Support both immediate and quantized execution via OSC message flags
  • [ ] Add OSC message validation (parameter types, ranges)
  • [ ] Handle OSC bundle messages
  • Dependencies: rosc crate (add to workspace if needed)
  • Deliverable: OSC client can trigger quantized deck operations
  • [ ] 11.3 MIDI/OSC Integration
  • [ ] Create `ControlInput` enum unifying MIDI and OSC inputs
  • [ ] Wire MIDI/OSC handlers to action queue
  • [ ] Add configuration for MIDI/OSC enable/disable
  • [ ] Handle MIDI/OSC errors gracefully (log and continue)
  • Deliverable: MIDI and OSC inputs flow through action queue to executor

---

Week 12 — Integration & Alpha Review

### Tasks
- [ ] 12.1 Scheduler Thread
- [ ] Create `SchedulerThread` struct in `scheduler/src/worker.rs`
- [ ] Thread responsibilities:
1. Poll Link clock (or LocalBeatClock) at 10ms interval
2. Drain action queue via `ActionExecutor`
3. Apply safety checks
4. Execute quantized actions
5. Send `ControlEvent`s to engine via command ring
6. Update `EngineShadowState` from feedback
- [ ] Add `SchedulerTelemetry` tracking:
- Action queue depth
- Quantization stats (min/mean/max error)
- Safety violation counts
- Execution latency
- [ ] Implement graceful shutdown (join handle, cleanup)
- Dependencies: Action executor (8.2), Link thread (7.2)
- Deliverable: Scheduler thread runs continuously, processes actions, sends events

  • [ ] 12.2 Engine Integration
  • [ ] Connect scheduler to `EngineController`:
  • Scheduler sends `ControlEvent`s → engine command ring
  • Engine sends state updates → scheduler shadow state
  • [ ] Ensure bidirectional communication works
  • [ ] Test end-to-end: MIDI input → scheduler → engine → audio output
  • Deliverable: Full pipeline operational
  • [ ] 12.3 Serato/Ableton Bridge Handshake (Stub)
  • [ ] Create `bridge` module in `scheduler/src/bridge.rs`
  • [ ] Define `BridgeState` enum: `Disconnected`, `Connecting`, `Connected`
  • [ ] Implement basic handshake protocol (stub for now)
  • [ ] Add message parsing skeleton
  • Note: Full implementation deferred to later phase
  • Deliverable: Bridge module compiles, handshake stub in place
  • [ ] 12.4 Alpha Demo
  • [ ] Create `examples/alpha_demo.rs` showcasing:
  • Link sync (if available) or local beat clock
  • Quantized actions (play deck at next beat)
  • Safety policies (rapid actions blocked)
  • MIDI control (if MIDI device available)
  • OSC control (via test client)
  • [ ] Run 5-minute soak test with randomized actions
  • [ ] Verify zero safety violations, stable operation
  • Deliverable: Alpha demo executable demonstrating all Phase 2 features
  • [ ] 12.5 Documentation
  • [ ] Update `docs/phase/phase-2.md` with:
  • Scheduler API usage guide
  • Safety policy configuration reference
  • MIDI/OSC setup instructions
  • Quantization resolution guide
  • [ ] Add code examples for common use cases
  • [ ] Document known limitations (Link SDK requirement, etc.)
  • Deliverable: Complete documentation for Phase 2 features

---

Dependencies & Blockers

### External Dependencies
1. Ableton Link SDK (Week 7)
- Status: Requires license approval
- Fallback: Use `LocalBeatClock` for offline development
- Action: Request SDK access, prepare FFI bindings

2. C++ FFI Bindings (Week 7)
- Status: Need to create bindings to Link C++ API
- Action: Use `bindgen` or manual FFI, create wrapper

### Internal Dependencies
- Action Queue (8.1) → Action Executor (8.2)
- Link SDK (7.1) → Link Thread (7.2)
- Action Executor (8.2) + Link Thread (7.2) → Scheduler Thread (12.1)
- MIDI/OSC (11.1, 11.2) → Scheduler Integration (12.1)

---

Testing Strategy

### Unit Tests
- [ ] BeatClock trait implementations
- [ ] Quantizer with all resolutions
- [ ] SafetyPolicy state transitions
- [ ] Action queue operations
- [ ] Action executor quantization

### Integration Tests
- [ ] End-to-end: MIDI → Scheduler → Engine
- [ ] End-to-end: OSC → Scheduler → Engine
- [ ] Quantized action timing accuracy
- [ ] Safety policy enforcement
- [ ] Concurrent action handling

### Property-Based Tests
- [ ] Safety policy state machine properties
- [ ] Quantization error bounds
- [ ] Action queue ordering guarantees

### Soak Tests
- [ ] 5-minute randomized action sequence
- [ ] Zero safety violations
- [ ] Stable Link sync (if available)
- [ ] No memory leaks

---

Success Criteria

### Week 7
- ✅ BeatClock trait implemented and tested
- [ ] Link SDK integrated (or LocalBeatClock fallback working)
- [ ] Link thread publishes updates

### Week 8
- ✅ Quantizer implemented and tested
- [ ] Action queue accepts and stores actions
- [ ] Action executor quantizes and schedules actions
- [ ] Integration tests passing

### Week 9
- ✅ SafetyPolicy implemented and tested
- [ ] Action masks prevent invalid transitions
- [ ] Enhanced safety predicates match Python reference

### Week 10
- [ ] Property-based tests cover all safety scenarios
- [ ] Zero panics in safety-critical paths
- [ ] Safety telemetry reports events

### Week 11
- [ ] MIDI input handler maps messages to actions
- [ ] MIDI learn mode records bindings
- [ ] OSC server accepts and parses messages
- [ ] MIDI/OSC integrated with action queue

### Week 12
- [ ] Scheduler thread runs continuously
- [ ] Full pipeline: Input → Scheduler → Engine → Audio
- [ ] Alpha demo showcases all features
- [ ] Documentation complete

---

Risk Mitigation

1. Link SDK Unavailable
- Mitigation: Use `LocalBeatClock` for development, add Link later
- Impact: Low (offline mode still functional)

2. FFI Complexity
- Mitigation: Start with simple bindings, iterate
- Impact: Medium (may delay Link integration)

3. Quantization Timing Accuracy
- Mitigation: Profile and optimize, use high-resolution timers
- Impact: Medium (affects user experience)

4. Safety Policy Performance
- Mitigation: Profile safety checks, optimize hot paths
- Impact: Low (safety checks are O(1))

---

Next Steps (Immediate)

1. Start Week 8 Tasks
- Implement Action enum and ActionQueue
- Create ActionExecutor with quantization
- Write integration tests

2. Prepare Link SDK Integration (if available)
- Review Link SDK documentation
- Create FFI bindings structure
- Test basic Link session creation

3. Enhance Safety Policies
- Add action masks
- Port Python safety predicates
- Expand test coverage

This plan provides a clear roadmap for completing Phase 2. Each task has dependencies, deliverables, and success criteria defined.

Promotion Decision

Attach run IDs, datasets, metrics, and reproduction commands.

Source Anchor

projects/Documentation/02-projects/echelon/phases/phase-2-plan.md

Detected Structure

Method · Evaluation · References · Code Anchors · Architecture