Metadata-Version: 2.4
Name: pysysaudio
Version: 0.2.0
Summary: System audio recording for macOS and Windows
Author-email: "Scott J. Goldman" <scottjg@umich.edu>
License-Expression: MIT
Project-URL: Homepage, https://github.com/scottjg/pysysaudio
Project-URL: Repository, https://github.com/scottjg/pysysaudio
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Multimedia :: Sound/Audio :: Capture/Recording
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: black>=22.0; extra == "dev"
Requires-Dist: mypy>=0.990; extra == "dev"
Dynamic: license-file

# pysysaudio

A Python library for recording system audio on **macOS** and **Windows**.

## Features

- 🎵 Record system audio (all sounds playing on your system)
- 🪟 **Cross-platform**: macOS (CoreAudio process taps) and Windows (WASAPI)
- 🎚️ Configurable sample rate and channel count
- 🔧 Configurable output formats: bytes, NumPy, or MLX arrays
- 🎛️ Automatic resampling and channel remixing with each platform's native resampler, for both streams and WAV files

## Requirements

### macOS
- macOS 14.2 (Sonoma) or later
- Python 3.11+
- System audio recording permission (macOS will prompt on first use)

### Windows
- Windows 10 or later
- Python 3.11+
- Visual Studio C++ build tools (for installation from source)

## Installation

### From PyPI (Recommended)

```bash
pip install pysysaudio
```

Pre-built wheels are available for:
- **macOS**: 14.2+ - Intel, Apple Silicon, and Universal2
- **Windows**: 10+ (64-bit)
- **Python**: 3.11, 3.12, 3.13, 3.14, 3.15

### From Source

```bash
git clone https://github.com/scottjg/pysysaudio.git
cd pysysaudio
pip install .
```

### For Development

```bash
pip install -e ".[dev]"
```

## Quick Start

### Basic Recording

```python
from pysysaudio import SystemAudioRecorder
import time

# Create a recorder instance
recorder = SystemAudioRecorder(sample_rate=48000, channels=2)

# Start recording
recorder.start_recording("output.wav")

# Record for 10 seconds
time.sleep(10)

# Stop and save
output_path = recorder.stop_recording()
print(f"Recording saved to: {output_path}")
```

### Using Context Manager

```python
from pysysaudio import SystemAudioRecorder
import time

with SystemAudioRecorder() as recorder:
    recorder.start_recording("output.wav")
    time.sleep(10)
    # Automatically stops when context exits
```

### Real-time Audio Streaming

Stream audio chunks in real-time for processing, analysis, or streaming to APIs.

Choose your output format:

```python
from pysysaudio import SystemAudioRecorder

# Option 1: bytes format (default, no dependencies)
recorder = SystemAudioRecorder(format="bytes", dtype="int16")
recorder.start_recording()

for audio_data in recorder.stream():
    # audio_data is bytes (PCM int16)
    print(f"Got {len(audio_data)} bytes")
    if some_condition:
        break

recorder.stop_recording()

# Option 2: NumPy arrays (requires numpy)
recorder = SystemAudioRecorder(format="numpy", dtype="float32")
recorder.start_recording("output.wav")  # Save to file AND stream

for audio_chunk in recorder.stream():
    # audio_chunk is a numpy.ndarray with shape (frames, channels)
    print(f"NumPy: shape={audio_chunk.shape}, mean={audio_chunk.mean()}")
    if some_condition:
        break

recorder.stop_recording()

# Option 3: MLX arrays (requires mlx, Apple Silicon optimized)
import mlx.core as mx

recorder = SystemAudioRecorder(format="mlx", dtype="float32")
recorder.start_recording()  # Streaming only (no file)

for audio_chunk in recorder.stream():
    # audio_chunk is an mlx.core.array, runs on GPU/Neural Engine
    mean = mx.mean(audio_chunk)
    print(f"MLX: shape={audio_chunk.shape}, mean={float(mean)}")
    if some_condition:
        break

recorder.stop_recording()
```

### Check Permission Status

```python
from pysysaudio import SystemAudioRecorder

# Check if system audio recording permission is granted
if SystemAudioRecorder.check_permission():
    print("Permission granted!")
else:
    print("Please grant system audio recording permission in System Settings")
```

## API Reference

### `SystemAudioRecorder`

Main class for recording system audio.

#### Constructor

```python
SystemAudioRecorder(
    sample_rate: int = 48000, 
    channels: int = 2,
    format: Literal["bytes", "numpy", "mlx"] = "bytes",
    dtype: Literal["float32", "int16", "int32"] = "float32",
    buffer_seconds: float = 2.0
)
```

- `sample_rate`: Audio sample rate in Hz (default: 48000)
- `channels`: Number of audio channels - 1 for mono, 2 for stereo (default: 2)
- `format`: Format for audio data yielded by `stream()` (default: "bytes")
  - `"bytes"`: Raw PCM data as bytes (no dependencies)
  - `"numpy"`: NumPy ndarray with shape (frames, channels), so mono is (frames, 1) (requires `numpy`)
  - `"mlx"`: MLX array with shape (frames, channels), so mono is (frames, 1) (requires `mlx`, Apple Silicon)
- `dtype`: Sample data type for output audio (default: "float32")
  - `"float32"`: 32-bit float in range [-1.0, 1.0]
  - `"int16"`: 16-bit signed integer (PCM16, range [-32768, 32767])
  - `"int32"`: 32-bit signed integer (PCM32)
- `buffer_seconds`: How much captured audio to hold between the capture thread and `stream()` before chunks are dropped (default: 2.0)

#### Methods

##### `start_recording(output_path: Optional[str] = None)`

Start recording system audio.

- `output_path`: Optional path where the WAV file will be saved. If `None`, audio is only available via `stream()`. The file is written at the requested `sample_rate` and `channels` as 16-bit PCM, whatever `dtype` is set to.

You can save to a file, stream audio via `stream()`, or both simultaneously.

##### `stream(timeout: Optional[float] = 0.1) -> Iterator[AudioData]`

Generator that yields audio chunks as they are recorded.

- `timeout`: Timeout in seconds for waiting for new chunks (default: 0.1). `None` means wait indefinitely.

**Returns**: Iterator yielding audio data in the format specified during initialization (bytes, numpy array, or mlx array).

Chunks are read from a native capture queue and converted on the calling thread. The platform's audio thread never runs Python, so heavy processing inside the loop cannot stall capture. If the loop falls more than `buffer_seconds` behind, chunks are dropped and `dropped_frames` counts them. The generator ends once recording has stopped and the remaining chunks have been drained.

**Example**:
```python
recorder = SystemAudioRecorder(format="numpy")
recorder.start_recording()
for chunk in recorder.stream():
    # Process audio chunk
    if done:
        break
recorder.stop_recording()
```

##### `stop_recording() -> Optional[str]`

Stop recording and finalize the audio file.

Returns the path to the saved audio file, or empty string if no file was created (streaming-only mode).

##### `is_recording() -> bool`

Check if recording is currently in progress.

##### `dropped_frames -> int` (property)

Frames of captured audio discarded because `stream()` fell more than `buffer_seconds` behind. Counted at the capture sample rate. Resets on each `start_recording()`; after `stop_recording()` it holds the final count for the last recording.

##### `check_permission() -> bool` (static)

Check if system audio recording permission has been granted.

Returns `True` if permission is granted, `False` otherwise.

## Permissions

On first use, macOS will prompt you to grant system audio recording permission to your application/terminal.

To grant permission:
1. macOS will show a permission dialog on first use
2. Or manually: System Settings → Privacy & Security → Screen & System Audio Recording
3. Enable permission for your Python interpreter or Terminal

## How It Works

`pysysaudio` uses platform-native APIs for optimal performance:

### macOS
Uses CoreAudio process taps to capture system audio (requires macOS 14.2+). Resampling uses CoreAudio's AudioConverter at its highest quality setting.

### Windows  
Uses WASAPI (Windows Audio Session API) in loopback mode to capture audio from the default output device. Resampling uses the Media Foundation audio resampler with its longest filter.

### Format conversion
When the system's format differs from the one you asked for, audio is converted in this order: channels are remixed (averaged to mono, duplicated from mono, otherwise truncated or padded with silence), then resampled, then encoded as `dtype`. Conversion runs in native code on the thread that iterates `stream()`, never on the audio thread. NumPy is only needed for `format="numpy"` and `format="mlx"`.

## Development

### Building

```bash
pip install -e ".[dev]"
```

## License

MIT License - see LICENSE file for details
