Audio Recording¶
This tutorial covers recording audio from input devices using coremusic.
Prerequisites¶
- coremusic installed and built
- A working audio input device (built-in microphone, USB audio interface, etc.)
- Basic Python knowledge
Simple Recording¶
Using the CLI¶
The easiest way to record is via the command line:
# Record for 10 seconds
coremusic audio record -o recording.wav --duration 10
# Record with specific settings
coremusic audio record -o recording.wav --duration 10 --sample-rate 48000 --channels 1
# List input devices
coremusic device list --input
Using AudioRecorder¶
For programmatic recording:
from coremusic.capi import AudioRecorder
def record_audio(output_path, duration_seconds):
"""Record audio to a WAV file."""
# The recorder allocates its buffer up front, so the maximum duration is
# fixed when the input is set up.
recorder = AudioRecorder(sample_rate=44100.0, channels=2)
recorder.setup_input(duration=duration_seconds)
print(f"Recording for {duration_seconds} seconds...")
print("Press Ctrl+C to stop early")
recorder.start()
try:
# run_loop() pumps the CoreAudio run loop; without it no buffers
# arrive, because capture is driven from this thread.
while recorder.is_recording():
recorder.run_loop(0.1)
print(f"Recording: {recorder.get_recorded_duration():.1f}s", end='\r')
except KeyboardInterrupt:
print("\nStopped early")
recorder.stop()
recorder.save_to_file(output_path)
print(f"\nSaved to: {output_path}")
record_audio("my_recording.wav", duration_seconds=1)
Recording with Progress¶
Display recording progress:
import sys
from coremusic.capi import AudioRecorder
def record_with_progress(output_path, duration):
"""Record with visual progress bar."""
recorder = AudioRecorder(sample_rate=44100.0, channels=2)
recorder.setup_input(duration=duration)
print(f"Recording: {output_path}")
print(f"Duration: {duration}s")
print()
recorder.start()
try:
while recorder.is_recording():
recorder.run_loop(0.05)
progress = recorder.get_progress()
elapsed = recorder.get_recorded_duration()
bar_width = 40
filled = int(bar_width * progress)
bar = '=' * filled + '-' * (bar_width - filled)
sys.stdout.write(f'\r[{bar}] {elapsed:.1f}s')
sys.stdout.flush()
except KeyboardInterrupt:
print("\nStopped by user")
recorder.stop()
recorder.save_to_file(output_path)
print(f"\nRecording saved to: {output_path}")
record_with_progress("recording.wav", duration=1)
Device Selection¶
List Input Devices¶
from coremusic.audio import AudioDeviceManager
def list_input_devices():
"""List all available input devices."""
devices = AudioDeviceManager.get_input_devices()
print("Input Devices:")
print("-" * 50)
for device in devices:
print(f"Name: {device.name}")
print(f" UID: {device.uid}")
print(f" Channels: {device.channel_count('input')}")
print(f" Sample Rate: {device.sample_rate}")
print()
return devices
input_devices = list_input_devices()
Record from Specific Device¶
from coremusic.audio import AudioDeviceManager
from coremusic.capi import AudioRecorder
def record_from_device(device_name, output_path, duration):
"""Record from a specific audio device."""
device = AudioDeviceManager.find_device_by_name(device_name)
if device is None or not device.has_input():
print(f"Device not found: {device_name}")
return
print(f"Recording from: {device.name}")
# Capture follows the default input device, so select it first
previous = AudioDeviceManager.get_default_input_device()
AudioDeviceManager.set_default_input_device(device)
try:
recorder = AudioRecorder(
sample_rate=device.sample_rate,
channels=min(device.channel_count('input'), 2),
)
recorder.setup_input(duration=duration)
recorder.start()
while recorder.is_recording():
recorder.run_loop(0.1)
recorder.stop()
recorder.save_to_file(output_path)
print(f"Saved to: {output_path}")
finally:
if previous is not None:
AudioDeviceManager.set_default_input_device(previous)
if input_devices:
record_from_device(input_devices[0].name, "device_recording.wav", duration=0.5)
Recording Formats¶
Different Sample Rates¶
from coremusic.capi import AudioRecorder
def record_high_quality(output_path, duration):
"""Record at a professional sample rate.
The recorder always captures 32-bit float, which is what the AudioQueue
hands over; convert on the way out if you need another depth.
"""
recorder = AudioRecorder(sample_rate=96000.0, channels=2)
recorder.setup_input(duration=duration)
print("Recording at 96kHz...")
capture(recorder, output_path)
print(f"High-quality recording saved to: {output_path}")
record_high_quality("hq_recording.wav", duration=0.5)
Mono Recording¶
from coremusic.capi import AudioRecorder
def record_mono(output_path, duration):
"""Record single channel (mono) audio."""
recorder = AudioRecorder(sample_rate=44100.0, channels=1)
recorder.setup_input(duration=duration)
print("Recording mono...")
capture(recorder, output_path)
print(f"Mono recording saved to: {output_path}")
record_mono("mono_recording.wav", duration=0.5)
Real-Time Monitoring¶
AudioRecorder captures into a buffer; it does not report levels while it
runs. To meter the input live, read it with AudioInputStream, which hands
each block to a callback as it arrives:
import sys
import time
from coremusic.audio.streaming import AudioInputStream
levels = [0.0, 0.0]
def measure(audio_data, frame_count):
"""Called on the audio thread for every captured block.
Keep it short: this runs in real time, so do the display elsewhere.
"""
import numpy as np
if frame_count == 0:
return
peak = np.max(np.abs(audio_data), axis=0)
levels[0] = float(peak[0])
levels[1] = float(peak[-1])
stream = AudioInputStream(channels=2, sample_rate=44100.0, buffer_size=512)
stream.add_callback(measure)
try:
stream.start()
except RuntimeError as e:
# macOS refuses the input stream until the app has microphone permission
print(e)
raise SystemExit(0) from None
print("Monitoring input levels")
print("=" * 50)
deadline = time.monotonic() + 1.0
while time.monotonic() < deadline:
meter_width = 20
left_bar = '|' * int(levels[0] * meter_width)
right_bar = '|' * int(levels[1] * meter_width)
sys.stdout.write(f'\rL:{left_bar:<20} R:{right_bar:<20}')
sys.stdout.flush()
time.sleep(0.05)
stream.stop()
print(f"\nOverruns: {stream.overruns}")
Recording to NumPy Array¶
For processing, record directly to a NumPy array:
from coremusic.base import NUMPY_AVAILABLE
if NUMPY_AVAILABLE:
import numpy as np
from coremusic.capi import AudioRecorder
def record_to_numpy(duration, sample_rate=44100.0, channels=2):
"""Record audio and return it as a NumPy array."""
recorder = AudioRecorder(sample_rate=sample_rate, channels=channels)
recorder.setup_input(duration=duration)
recorder.start()
while recorder.is_recording():
recorder.run_loop(0.05)
recorder.stop()
# The recorder captures interleaved float32
raw = recorder.get_audio_data()
audio = np.frombuffer(raw, dtype=np.float32).reshape(-1, channels)
return audio, sample_rate
# Record and process
audio, sr = record_to_numpy(duration=0.5)
print(f"Recorded {len(audio)} frames at {sr}Hz")
print(f"Peak amplitude: {np.max(np.abs(audio)):.4f}")
Error Handling¶
Handle recording errors gracefully:
from pathlib import Path
from coremusic.audio import AudioDeviceManager
from coremusic.capi import AudioRecorder
from coremusic.exceptions import AudioDeviceError, AudioQueueError
def safe_record(output_path, duration):
"""Record with comprehensive error handling."""
# Check output directory exists
output_dir = Path(output_path).parent
if not output_dir.exists():
output_dir.mkdir(parents=True)
# An absent input device is the most common failure, and is worth
# reporting before touching CoreAudio
if not AudioDeviceManager.get_input_devices():
print("No audio input device available")
return False
try:
recorder = AudioRecorder(sample_rate=44100.0, channels=2)
recorder.setup_input(duration=duration)
print(f"Recording to: {output_path}")
recorder.start()
while recorder.is_recording():
recorder.run_loop(0.1)
recorder.stop()
if not recorder.has_audio_content():
# Silence usually means macOS denied microphone access
print("Warning: recording is silent - check microphone permissions")
recorder.save_to_file(output_path)
# Verify file was created
if Path(output_path).exists():
size = Path(output_path).stat().st_size
print(f"Recording saved ({size / 1024:.1f} KB)")
return True
print("Error: Recording file not created")
return False
except AudioQueueError as e:
print(f"Audio queue error: {e}")
return False
except AudioDeviceError as e:
print(f"Audio device error: {e}")
print("Check that an input device is available")
return False
except PermissionError as e:
print(f"Permission error: {e}")
print("Check microphone permissions in System Settings")
return False
safe_record("safe_recording.wav", duration=0.5)
Complete Example: Voice Recorder¶
A complete voice recorder application:
import sys
from datetime import datetime
from pathlib import Path
from coremusic.capi import AudioRecorder
class VoiceRecorder:
"""Simple voice recorder with multiple recordings."""
def __init__(self, output_dir="recordings"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
def generate_filename(self):
"""Generate unique filename with timestamp."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
return self.output_dir / f"recording_{timestamp}.wav"
def record(self, duration):
"""Record for `duration` seconds, or until interrupted."""
output_path = self.generate_filename()
recorder = AudioRecorder(sample_rate=44100.0, channels=1) # mono for voice
recorder.setup_input(duration=duration)
print(f"Recording: {output_path.name}")
print(f"Duration: {duration}s (Ctrl+C to stop early)")
recorder.start()
try:
while recorder.is_recording():
recorder.run_loop(0.1)
elapsed = recorder.get_recorded_duration()
print(f" Recording... {elapsed:.1f}s", end='\r')
except KeyboardInterrupt:
pass
recorder.stop()
recorder.save_to_file(str(output_path))
print(f"\nRecorded {recorder.get_recorded_duration():.1f}s "
f"to {output_path.name}")
return output_path
def list_recordings(self):
"""List all recordings."""
recordings = sorted(
self.output_dir.glob("*.wav"),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
print(f"\nRecordings in {self.output_dir}:")
print("-" * 50)
for rec in recordings:
size = rec.stat().st_size / 1024
mtime = datetime.fromtimestamp(rec.stat().st_mtime)
print(f" {rec.name} ({size:.1f} KB) - {mtime:%Y-%m-%d %H:%M}")
return recordings
def main():
recorder = VoiceRecorder()
duration = float(sys.argv[1]) if len(sys.argv) > 1 else 10.0
recorder.record(duration=duration)
recorder.list_recordings()
if __name__ == "__main__":
main()
Troubleshooting¶
No Input Device Found¶
- Check System Preferences > Sound > Input
- Ensure microphone permissions are granted
- List devices with
coremusic device list --input
Recording is Silent¶
- Check input device is selected correctly
- Verify microphone is not muted
- Test with Audio MIDI Setup app
- Check input gain/volume
Permission Denied¶
macOS requires microphone permission:
- Go to System Preferences > Security & Privacy > Privacy
- Select Microphone
- Enable permission for Terminal or your Python app
Next Steps¶
- Audio Playback - Play back your recordings
- Audio Processing Cookbook - Process recorded audio
- Real-Time Audio Cookbook - Real-time audio monitoring
See Also¶
- API Reference - Complete API reference
- CLI Guide - CLI recording commands