Audio Playback¶
This tutorial covers audio playback using coremusic, from simple file playback to real-time streaming.
Prerequisites¶
- coremusic installed and built
- Basic Python knowledge
- Audio files to play (WAV, AIFF, MP3, M4A, etc.)
Simple File Playback¶
Using AudioPlayer (Recommended)¶
The AudioPlayer class provides the easiest way to play audio files:
import time
from coremusic.base import AudioPlayer
# Create player and load file
player = AudioPlayer()
player.load_file("audio.wav")
player.setup_output()
# Start playback
player.play()
# Wait for playback to complete
while player.is_playing():
time.sleep(0.1)
print("Playback complete!")
Playback with Progress¶
Monitor playback progress with a progress bar:
import sys
import time
from coremusic.audio import AudioFile
from coremusic.base import AudioPlayer
def play_with_progress(filepath):
"""Play audio file with progress display."""
# AudioPlayer does not report duration, so read it from the file
with AudioFile(filepath) as audio:
duration = audio.duration
player = AudioPlayer()
player.load_file(filepath)
player.setup_output()
print(f"Playing: {filepath}")
print(f"Duration: {duration:.2f}s")
player.play()
while player.is_playing():
progress = player.get_progress()
current_time = progress * duration
# Display progress bar
bar_width = 40
filled = int(bar_width * progress)
bar = '=' * filled + '-' * (bar_width - filled)
sys.stdout.write(f'\r[{bar}] {current_time:.1f}s / {duration:.1f}s')
sys.stdout.flush()
time.sleep(0.1)
print('\nDone!')
play_with_progress("audio.wav")
Looping Playback¶
For continuous looping:
import time
from coremusic.base import AudioPlayer
def play_looped(filepath, num_loops=3):
"""Play audio file multiple times."""
player = AudioPlayer()
player.load_file(filepath)
player.setup_output()
for i in range(num_loops):
print(f"Loop {i + 1}/{num_loops}")
player.play()
while player.is_playing():
time.sleep(0.1)
# Rewind for the next pass
player.reset_playback()
print("Looping complete!")
play_looped("audio.wav", num_loops=2)
Or let the player loop for you, and stop it when you are done:
player = AudioPlayer()
player.load_file("audio.wav")
player.setup_output()
# Let the player loop by itself, and stop it when you have had enough
player.set_looping(True)
player.play()
time.sleep(1.0)
player.stop()
Using the CLI¶
The coremusic CLI provides quick playback:
# Simple playback
coremusic audio play music.wav
# Looping playback
coremusic audio play music.wav --loop
# List audio devices
coremusic device list
Streaming Playback¶
AudioPlayer reads the file for you. When you need to produce the samples
yourself - a synthesiser, a decoder, a live effect - use
AudioOutputStream, which pulls blocks from a generator you supply:
import time
from coremusic.audio import AudioFile
from coremusic.audio.streaming import AudioOutputStream
def make_file_generator(path, channels):
"""Return generator(frame_count) -> bytes, reading a file on demand."""
audio = AudioFile(path)
audio.open()
position = 0
total = audio.packet_count
scale = 1.0 / 32768.0
def generate(frame_count):
nonlocal position
import struct
data, count = audio.read_packets(position, min(frame_count, total - position))
position += count
if count == 0:
return b""
# The file is 16-bit; the stream wants interleaved float32
samples = struct.unpack(f"<{len(data) // 2}h", data)
return struct.pack(f"<{len(samples)}f", *[s * scale for s in samples])
return generate, audio
generate, audio = make_file_generator("audio.wav", channels=2)
stream = AudioOutputStream(channels=2, sample_rate=44100.0, buffer_size=512)
stream.set_generator(generate)
stream.start()
print(f"Streaming, latency {stream.latency * 1000:.1f}ms")
time.sleep(1.0)
stream.stop()
audio.close()
The generator returns interleaved float32 as raw bytes and must return
promptly: it runs on the audio thread, where a slow call becomes an audible
dropout. stream.latency reports the round-trip latency of the configured
buffer size.
Async Playback¶
For non-blocking playback in async applications:
import asyncio
from coremusic.audio import AsyncAudioFile
async def async_playback(filepath):
"""Non-blocking audio file reading."""
async with AsyncAudioFile(filepath) as audio:
print(f"Duration: {audio.duration:.2f}s")
# Stream chunks asynchronously
total = 0
async for chunk in audio.read_chunks_async(chunk_size=4096):
# Process each chunk without blocking
total += len(chunk)
await asyncio.sleep(0) # Yield to event loop
print(f"Read {total:,} bytes")
# Run async playback
asyncio.run(async_playback("audio.wav"))
Playback with Effects¶
Route audio through AudioUnit effects during playback:
import time
from coremusic.audio import AudioEffectsChain
def run_reverb_chain(seconds):
"""Build reverb -> output and run it."""
chain = AudioEffectsChain()
chain.open()
reverb_node = chain.add_effect_by_name("AUReverb2")
output_node = chain.add_output()
chain.connect(reverb_node, output_node)
chain.initialize()
try:
chain.start()
time.sleep(seconds)
finally:
chain.stop()
chain.dispose()
run_reverb_chain(0.5)
That chain processes live input. To hear a file through an effect, render it through the plugin and play the result:
from coremusic.audio import AudioFile
from coremusic.audio.audiounit_host import AudioUnitPlugin
# To hear a file through an effect rather than a live input, render it:
# read the samples, push them through the plugin, and play or write the result.
with AudioFile("audio.wav") as audio:
samples = audio.read_as_numpy()
with AudioUnitPlugin.from_name("AUMatrixReverb") as reverb:
reverb['Dry/Wet Mix'] = 40.0
block = bytes(512 * 2 * 4) # one block of float32 stereo silence
processed = reverb.process(block)
print(f"processed {len(processed)} bytes")
Device Selection¶
Play to a specific audio device:
from coremusic.audio import AudioDeviceManager
def list_output_devices():
"""List available output devices."""
devices = AudioDeviceManager.get_output_devices()
print("Output Devices:")
for device in devices:
default = AudioDeviceManager.get_default_output_device()
marker = " (default)" if default and device.uid == default.uid else ""
print(f" {device.name} [{device.uid}]{marker}")
return devices
list_output_devices()
CoreAudio routes playback to the default output device, so selecting a device means making it the default for the duration:
from coremusic.audio import AudioDeviceManager
def play_to_device(filepath, device_name):
"""Send playback to a specific device by making it the default."""
device = AudioDeviceManager.find_device_by_name(device_name)
if device is None or not device.has_output():
print(f"Device not found: {device_name}")
return
print(f"Playing to: {device.name}")
previous = AudioDeviceManager.get_default_output_device()
AudioDeviceManager.set_default_output_device(device)
try:
from coremusic.shortcuts import play
play(filepath)
finally:
if previous is not None:
AudioDeviceManager.set_default_output_device(previous)
Volume Control¶
Control playback volume:
from coremusic.audio import AudioDeviceManager
device = AudioDeviceManager.get_default_output_device()
# Not every device exposes a software volume control; get_volume() returns
# None when it does not.
level = device.get_volume() if device else None
if level is not None:
print(f"Output volume: {level:.2f}")
device.set_volume(level)
else:
print("This device has no software volume control.")
Error Handling¶
Handle playback errors gracefully:
import time
from pathlib import Path
from coremusic.base import AudioPlayer
from coremusic.exceptions import AudioFileError, AudioQueueError, CoreAudioError
def safe_play(filepath):
"""Play audio with comprehensive error handling."""
# Check file exists
if not Path(filepath).exists():
print(f"Error: File not found: {filepath}")
return False
try:
player = AudioPlayer()
player.load_file(filepath)
player.setup_output()
player.play()
while player.is_playing():
time.sleep(0.1)
return True
except AudioFileError as e:
print(f"Audio file error: {e}")
return False
except AudioQueueError as e:
print(f"Audio queue error: {e}")
return False
except CoreAudioError as e:
# Every coremusic error derives from this one
print(f"CoreAudio error: {e}")
return False
# Use with error handling
success = safe_play("audio.wav")
print(f"Playback {'succeeded' if success else 'failed'}")
Complete Example: Music Player¶
A simple command-line music player:
import sys
import time
from pathlib import Path
from coremusic.audio import AudioFile
from coremusic.base import AudioPlayer
class SimpleMusicPlayer:
"""Simple command-line music player."""
def __init__(self):
self.player = AudioPlayer()
self.duration = 0.0
def load(self, filepath):
"""Load audio file."""
if not Path(filepath).exists():
raise FileNotFoundError(f"File not found: {filepath}")
with AudioFile(filepath) as audio:
self.duration = audio.duration
self.player.load_file(filepath)
self.player.setup_output()
print(f"Loaded: {filepath}")
print(f"Duration: {self.duration:.2f}s")
def play(self):
"""Start playback."""
self.player.play()
print("Playing...")
def stop(self):
"""Stop playback."""
self.player.stop()
print("Stopped")
def rewind(self):
"""Return to the start of the file."""
self.player.reset_playback()
def get_status(self):
"""Get current playback status."""
progress = self.player.get_progress()
return {
'playing': self.player.is_playing(),
'progress': progress,
'current_time': progress * self.duration,
'duration': self.duration,
}
def main():
if len(sys.argv) < 2:
print("Usage: python music_player.py <audio_file>")
sys.exit(1)
player = SimpleMusicPlayer()
try:
player.load(sys.argv[1])
player.play()
# Simple playback loop
while player.player.is_playing():
status = player.get_status()
bar_width = 30
filled = int(bar_width * status['progress'])
bar = '=' * filled + '-' * (bar_width - filled)
sys.stdout.write(
f"\r[{bar}] {status['current_time']:.1f}s / {status['duration']:.1f}s"
)
sys.stdout.flush()
time.sleep(0.1)
print("\nPlayback complete!")
except FileNotFoundError as e:
print(f"Error: {e}")
sys.exit(1)
except KeyboardInterrupt:
print("\nInterrupted")
player.stop()
if __name__ == "__main__":
main()
Next Steps¶
- Audio Recording - Record audio from input devices
- Audio Processing Cookbook - Process and manipulate audio
- AudioUnit Hosting Cookbook - Use AudioUnit effects
See Also¶
- API Reference - Complete API reference
- Real-Time Audio Cookbook - Real-time audio techniques