blip
swiftui sound effects,
without a bundled file.
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.
why AVAudioEngine instead of AVAudioPlayer?
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.
set it up once, then just feed it buffers.
This is the shape of the technique done by hand: a single engine and player node, started once, then handed a fresh buffer on every tap.
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() }
}
}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.
is the engine set up once or per sound?
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.
does this work from a SwiftUI view?
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.
where does the PCM buffer come from?
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.
the useful details.
Do I need to add an audio file to my Xcode project?
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.
Does this work on both iOS and macOS?
AVAudioEngine and AVAudioPlayerNode are shared AVFoundation APIs available on both platforms, so the same pattern applies to a macOS SwiftUI app without changes.
Can I try this without writing any Swift myself?
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.
ready to make
some noise?
Open Blip Blip and give it a voice.