Ariel
Ariel — Claude Context
Audio engine and MIDI I/O package. Depends on Horatio (types) and Touchstone (effects/DSP). Provides the real-time synthesizer and all MIDI plumbing for Prospero. Platforms: iOS 16+ / macOS 13+.
Files
Audio
| File |
Role |
AudioEngine.swift |
Lock-free polyphonic synth (44.1 kHz, up to 8 voices). PolyBLEP waveforms, per-voice glide, ADSR, vibrato/tremolo LFO. Integrated FX chain: Tone → Fuzz → Delay → Reverb (separate L/R instances). Ring buffer for main→audio thread commands |
AudioTypes.swift |
Waveform enum (sine, sawtooth, triangle, square), ModTarget enum (vibrato, tremolo) |
MIDI
| File |
Role |
MIDIRouter.swift |
Maps InputSource positions → MIDI messages via MIDIRoute configs. Active notes keyed by sourceID (not channel) — multiple inputs can share a channel. Auto noteOff on source loss |
MIDIController.swift |
CoreMIDI virtual source named "Prospero". Sends noteOn/Off, CC, channel pressure via MIDIReceived. Published isConnected |
MIDIClockReceiver.swift |
Listens for external MIDI clock (24 ticks/quarter). ClockBPMCalculator sliding window, 30–300 BPM bounds. Published bpm, transportState, connectedSourceCount |
MIDILearnSession.swift |
One-shot capture of incoming CC/note/pitchBend/pressure/program. States: idle → listening → captured/timedOut. Connects all MIDI sources on creation |
MIDIPacketParser.swift |
Raw MIDI packets → ParsedMIDIEvent structs. Handles all channel voice messages (0x80–0xEF), skips system (0xF0+) |
Key Implementation Details
- Lock-free audio:
AudioEngine uses a ring buffer (CommandBuffer) for main thread → render callback. Never block the render callback.
- Effect parameter routing: All effect params go through the command buffer. No main-thread
fxChain.setParameter() calls — prevents data races and click artifacts.
rtEffectsL/rtEffectsR: Audio-thread effect references set in start(), cleared in stop().
activeNotes keyed by sourceID: Critical — multiple hands can share a MIDI channel without killing each other's notes.
- PolyBLEP: Band-limited sawtooth and square waves. Square has 70% attenuation to match perceived loudness.
- ProxyFXChain: Main-thread wrapper that forwards parameter changes into the command buffer.
- MIDIRouter pure functions:
buildValidNotes(), selectNote(), positionCurve() are extracted as free functions for testability.
Tests
cd Packages/Ariel && swift test
Test files: AudioEngineTests, MIDIRouterTests, MIDIClockReceiverTests, MIDILearnSessionTests, MIDIPacketParserTests.
Rules
- Never call Touchstone DSPComponent methods directly from the render callback — use the command buffer.
@ObservationIgnored for all audio-thread state to avoid Swift 6.2 warnings. Prefix with _rt for audio-thread variables.
- Waveform generation must stay PolyBLEP — naive waveforms alias badly at musical frequencies.
- MIDIController's virtual source name "Prospero" is the user-visible MIDI device name — do not change without coordinating with the app.