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
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`:
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:
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:
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