A declarative Swift DSL for real-time audio processing, built on Elementary Audio.
Swift Elementary Audio provides a modern, type-safe Swift API for building audio processing graphs. It wraps the powerful Elementary Audio C++ DSP engine and exposes it through an intuitive declarative syntax.
- Declarative DSL - Build audio graphs using Swift result builders
- Type-Safe - Compile-time checked node types and properties
- 40+ Built-in Nodes - Oscillators, filters, delays, effects, and more
- Real-Time Safe - Designed for glitch-free audio processing
- Async/Await - Modern Swift concurrency for engine lifecycle
- Custom Nodes - Extend with your own DSP implementations
- Swift 5.10+
- macOS 14+ / iOS 15+
- Xcode 15.3+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/yourusername/swift-elementary-audio", from: "1.0.0")
]import ElementaryAudio
// Create an audio engine
let engine = try await AudioEngine()
// Render a simple sine wave
try await engine.render {
El.cycle(440) * 0.5 // 440Hz sine at 50% volume
}
// Start playback
try await engine.start()try await engine.render {
let modulator = El.cycle(220) * 200
El.cycle(440 + modulator) * 0.3
}try await engine.render {
let lfo = El.cycle(0.5) * 500 + 1000
El.blepsaw(110)
.lowpass(frequency: lfo, q: 4)
.gain(0.4)
}try await engine.render {
El.cycle(440) * 0.3 // Left channel
El.cycle(550) * 0.3 // Right channel
}try await engine.render {
let dry = El.cycle(440) * 0.3
let wet = El.delay(44100, El.const(22050), dry) * 0.5
dry + wet
}try await engine.render {
let trigger = El.phasor(4) // 4Hz trigger
let notes: [Double] = [261.63, 293.66, 329.63, 349.23] // C D E F
let freq = El.seq(trigger, notes)
El.cycle(freq) * 0.3
}El.cycle(freq)- Sine waveEl.phasor(freq)- Ramp (0 to 1)El.blepsaw(freq)- Band-limited sawtoothEl.blepsquare(freq)- Band-limited squareEl.bleptriangle(freq)- Band-limited triangleEl.noise()- White noise
El.svf(mode, freq, q, input)- State variable filterEl.pole(coef, input)- One-pole lowpassEl.env(attack, release, input)- Envelope follower
El.z(input)- Single sample delayEl.sdelay(size, input)- Fixed delayEl.delay(size, time, input)- Variable delay
El.sin(x),El.cos(x),El.tan(x)- TrigonometryEl.tanh(x)- Soft clippingEl.abs(x),El.sqrt(x),El.exp(x)- Basic math
El.latch(trigger, input)- Sample and holdEl.counter(gate)- Event counterEl.seq(trigger, values)- Step sequencer
El.meter(name, input)- Level meterEl.scope(name, size, input)- Oscilloscope
El.tapIn(name, input)- Create feedback pointEl.tapOut(name)- Read feedback (one block delay)
Signals support fluent method chaining:
El.blepsaw(110)
.lowpass(frequency: 2000, q: 4)
.delayed(samples: 4410)
.gain(0.5)
.metered(name: "output")Implement the CustomAudioNode protocol:
struct GainNode: CustomAudioNode {
static let nodeType = "customGain"
let nodeId = NodeID()
var children: [any AudioNode] = []
var properties: NodeProperties = [:]
private var gain: Float = 1.0
init(id: NodeID, sampleRate: Double, blockSize: Int) {}
mutating func setProperty(_ key: String, value: PropertyValue) -> Bool {
if key == "gain", let v = value.numberValue {
gain = Float(v)
return true
}
return false
}
func process(context: AudioProcessContext) {
for i in 0..<context.numSamples {
context.outputData[i] = context.inputData[0][i] * gain
}
}
}┌─────────────────────────────────────────┐
│ Swift DSL Layer │
│ (El, Signal, AudioGraphBuilder, etc.) │
├─────────────────────────────────────────┤
│ Instruction Encoder │
│ (Encodes graphs to C++ commands) │
├─────────────────────────────────────────┤
│ C++ Elementary Runtime │
│ (High-performance audio processing) │
└─────────────────────────────────────────┘
- Property updates are thread-safe (use atomic operations)
- The
process()method runs on the real-time audio thread - Avoid allocations, locks, or I/O in real-time code
MIT License - see LICENSE file for details.
Built on Elementary Audio by Nick Thompson.