Skip to content

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

  1. Check System Preferences > Sound > Input
  2. Ensure microphone permissions are granted
  3. List devices with coremusic device list --input

Recording is Silent

  1. Check input device is selected correctly
  2. Verify microphone is not muted
  3. Test with Audio MIDI Setup app
  4. Check input gain/volume

Permission Denied

macOS requires microphone permission:

  1. Go to System Preferences > Security & Privacy > Privacy
  2. Select Microphone
  3. Enable permission for Terminal or your Python app

Next Steps

See Also