An actor-based sound effect library for iOS and macOS. SwiftfulSoundEffects manages multiple AVAudioPlayers with round-robin playback for simultaneous sound effects.
- Thread-safe actor-based design with modern Swift concurrency
- Simple synchronous API with async behavior internalized
- Simultaneous playback via configurable player pools per sound
- Round-robin player selection for overlapping sound effects
- Memory management with selective teardown per sound
- Optional logging for analytics integration
Details (Click to expand)
Add SwiftfulSoundEffects to your project.
https://github.com/SwiftfulThinking/SwiftfulSoundEffects.git
Import the package.
import SwiftfulSoundEffectsCreate a SoundEffectManager instance.
// Basic setup
let soundEffectManager = SoundEffectManager()
// With optional logger for analytics
let soundEffectManager = SoundEffectManager(logger: yourLogger)Details (Click to expand)
Prepare a sound effect, then play it.
let url = Bundle.main.url(forResource: "coin", withExtension: "wav")!
// Prepare the player
soundEffectManager.prepareSoundEffect(url: url)
// Play when needed
soundEffectManager.playSoundEffect(url: url)
// Clean up when done (optional)
soundEffectManager.tearDownSoundEffect(url: url)For sounds that overlap (e.g. rapid coin collection), add simultaneous players.
soundEffectManager.prepareSoundEffect(url: url, simultaneousPlayers: 6)Details (Click to expand)
All public methods are synchronous (nonisolated) with async behavior handled internally.
// Preparation (required before playing)
func prepareSoundEffect(url: URL, simultaneousPlayers: Int = 1, volume: Float = 1)
// Playback
func playSoundEffect(url: URL)
// Memory management (optional)
func tearDownSoundEffect(url: URL)prepareSoundEffect() — Required before playing
- Creates one or more
AVAudioPlayerinstances for the given URL simultaneousPlayerscontrols how many players are created per sound- With 1 player (default): calling
playSoundEffect()while already playing restarts the sound - With multiple players: supports overlapping playback of the same sound via round-robin
volumesets playback volume from0.0(mute) to1.0(max)
playSoundEffect() — Core functionality
- Selects the next player for the URL using round-robin
- If the selected player is already playing, playback restarts from the beginning
- Thread-safe: can be called from any context
tearDownSoundEffect() — Optional memory management
- Stops and removes all players for the given URL
- Call during screen disappear or when a sound is no longer needed
Details (Click to expand)
By default, each sound gets 1 player. If you playSoundEffect() while it's still playing, the sound restarts.
To support overlapping playback (e.g. rapid coin sounds), prepare with multiple players:
// 1 player — playSoundEffect() restarts if already playing
soundEffectManager.prepareSoundEffect(url: coinURL)
// 6 players — supports up to 6 overlapping plays
soundEffectManager.prepareSoundEffect(url: coinURL, simultaneousPlayers: 6)Players are selected round-robin. If all players are busy, the next one restarts.
Details (Click to expand)
Create an enum to manage your sound files (not included in the package).
enum SoundEffectFile: String, Equatable {
case coin
case pop
case success
var fileName: String {
switch self {
case .coin: return "Coin.wav"
case .pop: return "Pop.wav"
case .success: return "Success.wav"
}
}
var url: URL {
let path = Bundle.main.path(forResource: fileName, ofType: nil)!
return URL(fileURLWithPath: path)
}
}Then use it with the manager:
soundEffectManager.prepareSoundEffect(url: SoundEffectFile.coin.url, simultaneousPlayers: 4)
soundEffectManager.playSoundEffect(url: SoundEffectFile.coin.url)Details (Click to expand)
SwiftfulSoundEffects supports optional logging for analytics.
// Implement the SoundEffectLogger protocol
class MyAnalytics: SoundEffectLogger {
func trackEvent(event: SoundEffectLogEvent) {
print("SoundEffect: \(event.eventName)")
}
func addUserProperties(dict: [String: Any], isHighPriority: Bool) {
// Add user properties for analytics
}
}
// Initialize with logger
let soundEffectManager = SoundEffectManager(logger: MyAnalytics())Or use SwiftfulLogging directly.
let logManager = LogManager(services: [
ConsoleService(printParameters: true),
FirebaseCrashlyticsService(),
MixpanelService()
])
let soundEffectManager = SoundEffectManager(logger: logManager)Details (Click to expand)
- Always call prepareSoundEffect() before playSoundEffect() — play does nothing if no players exist
- Use tearDownSoundEffect() during screen disappear or memory warnings
- Choose simultaneousPlayers wisely — more players = more memory, but supports overlapping
- Use lower volume for ambient or background sounds to blend with other audio
This package includes a .claude/swiftful-sound-effects-rules.md with usage guidelines and integration patterns for projects using Claude Code.
- iOS 16.0+
- macOS 12.0+
SwiftfulSoundEffects is available under the MIT license.