plays a file
- Needs a real asset — one per cue, in the app bundle
- Built around decoding, not around generated samples
- Retuning a cue means going back to an audio editor
- Bundle size grows with every sound you add
AVAudioEngine plays audio from an in-memory PCM buffer just as easily as from a bundled file — which means a UI sound generated in code needs no asset in your app bundle at all, and works the same from any SwiftUI view.
AVAudioPlayer is built to play a file from disk or memory in one shot — fine for a static asset, but it means shipping an audio file for every cue. AVAudioEngine with an AVAudioPlayerNode plays an AVAudioPCMBuffer you fill yourself, so a sound generated as raw samples plays exactly like a bundled one, with no file in your app bundle.
The graph and its clock. Created once, started once, and kept alive for the lifetime of the app.
let engine = AVAudioEngine()The source. Attached to the engine and connected to the mixer, then handed buffers whenever a sound should play.
engine.attach(player)The sound itself, as raw float samples you fill in code. This is the piece that replaces a file in your app bundle.
player.scheduleBuffer(buffer)The output. Connecting the player here is the entire routing step — no session juggling for a short UI cue.
engine.connect(player, to: engine.mainMixerNode)import AVFoundation
enum ClickSound {
static let engine = AVAudioEngine()
static let player = AVAudioPlayerNode()
static func start() {
engine.attach(player)
engine.connect(player, to: engine.mainMixerNode, format: nil)
try? engine.start()
}
static func play(buffer: AVAudioPCMBuffer) {
player.scheduleBuffer(buffer, at: nil, options: .interrupts)
if !player.isPlaying { player.play() }
}
}struct SaveButton: View {
var body: some View {
Button("Save") {
ClickSound.play(buffer: ClickSound.click)
}
.onAppear { ClickSound.start() }
}
}Treat this as an illustrative sketch, not a literal copy of what Blip Blip's export button produces — the real export is longer, parameter-driven, and bakes per-play randomized variation, harmonics, and any filter, drive, delay, or reverb stage in as literal constants. Shape a sound in the studio and the export writes that fuller version for you automatically. Building the same kind of tap sound for Android instead? The Kotlin and AudioTrack pattern follows the same idea.
Once. The pattern above keeps a single AVAudioEngine and AVAudioPlayerNode alive for the app's lifetime, attached and connected on first use, then reused for every play call — starting and tearing down the engine per tap adds latency you do not want on a button press.
Yes — AVAudioEngine is pure AVFoundation with no UIKit dependency, so calling the play function from a SwiftUI Button's action closure works exactly as it would from UIKit. Haptic feedback, such as SwiftUI's sensoryFeedback modifier or UIImpactFeedbackGenerator, is a separate, complementary API — it drives the Taptic Engine, not audio playback, so pair the two rather than expecting one to cover both.
Shape a sound in Blip Blip's studio — oscillator, filter, envelope, and any effects — then export it as Swift. The export writes the full AVAudioEngine setup above plus a function that fills an AVAudioPCMBuffer with the exact samples for that sound, including per-play pitch and timing variation, ready to paste into an Xcode project.
// blip blip — click
// swift · avaudioengine
import AVFoundation
private let sampleRate: Double = 44_100
private let duration: Double = 0.11
func makeClickBuffer() -> AVAudioPCMBuffer {
let format = AVAudioFormat(standardFormatWithSampleRate: sampleRate,
channels: 1)!
let frames = AVAudioFrameCount(sampleRate * duration)
let buffer = AVAudioPCMBuffer(pcmFormat: format,
frameCapacity: frames)!
buffer.frameLength = frames
…No. The exported Swift code fills an AVAudioPCMBuffer from generated sample data at runtime — there is nothing to add to your asset catalog or app bundle.
AVAudioEngine and AVAudioPlayerNode are shared AVFoundation APIs available on both platforms, so the same pattern applies to a macOS SwiftUI app without changes.
Yes — design the sound in the studio and use the Swift export to get a complete, ready-to-paste implementation, including the parts this page only sketches.
Open Blip Blip and give it a voice.