Skip to content

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.