cursor having a crack at a wrapper module
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
# AudioTee.js
|
||||
|
||||
Node.js wrapper for [AudioTee](https://github.com/your-org/audiotee) - capture macOS system audio using Core Audio taps.
|
||||
|
||||
AudioTee.js provides a streaming interface to capture system audio in real-time, perfect for building applications that need to process audio from any running application on macOS.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎵 **Real-time system audio capture** using Core Audio taps
|
||||
- 📦 **Streaming interface** with Node.js EventEmitter API
|
||||
- ⚡ **High performance** binary protocol support
|
||||
- 🎛️ **Flexible configuration** - sample rates, chunk sizes, process filtering
|
||||
- 🔇 **Process-specific capture** - include/exclude specific applications
|
||||
- 📊 **Audio metadata** - format information and level monitoring
|
||||
- 🛡️ **Error handling** - graceful failure and process management
|
||||
|
||||
## Requirements
|
||||
|
||||
- **macOS 14.2+** (Sonoma or later)
|
||||
- **Node.js 14+**
|
||||
- **Audio recording permissions** (you'll be prompted on first use)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install audiotee-js
|
||||
```
|
||||
|
||||
The package will automatically download the appropriate AudioTee binary for your system during installation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```javascript
|
||||
const { AudioTeeStream } = require('audiotee-js');
|
||||
|
||||
// Create a stream with 16kHz sample rate (great for ASR)
|
||||
const stream = new AudioTeeStream({
|
||||
sampleRate: 16000,
|
||||
format: 'binary',
|
||||
chunkDuration: 0.2
|
||||
});
|
||||
|
||||
// Listen for audio metadata
|
||||
stream.on('metadata', (metadata) => {
|
||||
console.log('Audio format:', metadata);
|
||||
});
|
||||
|
||||
// Process audio chunks
|
||||
stream.on('audio', (packet) => {
|
||||
console.log(`Received ${packet.audioData.length} bytes of audio`);
|
||||
// packet.audioData is a Buffer containing raw PCM data
|
||||
// packet.timestamp, packet.duration, packet.peakAmplitude also available
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
stream.on('error', (error) => {
|
||||
console.error('AudioTee error:', error);
|
||||
});
|
||||
|
||||
// Start capturing
|
||||
stream.start();
|
||||
|
||||
// Stop when done
|
||||
// stream.stop();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### AudioTeeStream
|
||||
|
||||
The main class for capturing system audio.
|
||||
|
||||
#### Constructor
|
||||
|
||||
```javascript
|
||||
new AudioTeeStream(options)
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `format` | `string` | `'binary'` | Output format: `'json'`, `'binary'`, or `'auto'` |
|
||||
| `sampleRate` | `number` | `undefined` | Target sample rate (8000, 16000, 22050, 24000, 32000, 44100, 48000) |
|
||||
| `chunkDuration` | `number` | `0.2` | Audio chunk duration in seconds (max 5.0) |
|
||||
| `includeProcesses` | `number[]` | `[]` | Process IDs to capture (empty = all processes) |
|
||||
| `excludeProcesses` | `number[]` | `[]` | Process IDs to exclude |
|
||||
| `mute` | `boolean` | `false` | Mute processes being captured |
|
||||
| `binaryPath` | `string` | `auto` | Custom path to AudioTee binary |
|
||||
|
||||
#### Methods
|
||||
|
||||
- **`start()`** - Start audio capture, returns `this` for chaining
|
||||
- **`stop()`** - Stop audio capture
|
||||
- **`isActive()`** - Returns `true` if currently capturing
|
||||
- **`getMetadata()`** - Returns audio metadata (available after `metadata` event)
|
||||
|
||||
#### Events
|
||||
|
||||
- **`metadata`** - Audio format information
|
||||
- **`stream_start`** - Capture has started
|
||||
- **`audio`** - Audio data packet
|
||||
- **`stream_stop`** - Capture has stopped
|
||||
- **`log`** - Log messages from AudioTee
|
||||
- **`error`** - Error occurred
|
||||
- **`close`** - Process has closed
|
||||
|
||||
### Audio Packet Format
|
||||
|
||||
Audio events receive packets with this structure:
|
||||
|
||||
```javascript
|
||||
{
|
||||
timestamp: Date, // When this audio was captured
|
||||
duration: number, // Duration in seconds
|
||||
peakAmplitude: number, // Peak amplitude (0.0 - 1.0)
|
||||
audioData: Buffer // Raw PCM audio data
|
||||
}
|
||||
```
|
||||
|
||||
### Metadata Format
|
||||
|
||||
Metadata events provide audio format information:
|
||||
|
||||
```javascript
|
||||
{
|
||||
sample_rate: number, // e.g. 48000
|
||||
channels_per_frame: number,// Always 1 (mono)
|
||||
bits_per_channel: number, // e.g. 32
|
||||
is_float: boolean, // true for float32, false for int16
|
||||
encoding: string, // e.g. "pcm_f32le"
|
||||
capture_mode: string, // "audio"
|
||||
device_name: string|null, // Audio device name
|
||||
device_uid: string|null // Audio device UID
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Recording
|
||||
|
||||
```javascript
|
||||
const { AudioTeeStream } = require('audiotee-js');
|
||||
|
||||
const stream = new AudioTeeStream();
|
||||
|
||||
stream.on('metadata', console.log);
|
||||
stream.on('audio', (packet) => {
|
||||
console.log(`${packet.audioData.length} bytes, peak: ${packet.peakAmplitude}`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
```
|
||||
|
||||
### Save to WAV File
|
||||
|
||||
```javascript
|
||||
const fs = require('fs');
|
||||
const { AudioTeeStream } = require('audiotee-js');
|
||||
|
||||
const stream = new AudioTeeStream({
|
||||
sampleRate: 44100,
|
||||
format: 'binary'
|
||||
});
|
||||
|
||||
const output = fs.createWriteStream('recording.raw');
|
||||
|
||||
stream.on('audio', (packet) => {
|
||||
output.write(packet.audioData);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
|
||||
// Stop after 10 seconds
|
||||
setTimeout(() => {
|
||||
stream.stop();
|
||||
output.end();
|
||||
}, 10000);
|
||||
```
|
||||
|
||||
### Process-Specific Capture
|
||||
|
||||
```javascript
|
||||
const { AudioTeeStream } = require('audiotee-js');
|
||||
|
||||
// Only capture audio from Spotify (you'd need to find Spotify's PID)
|
||||
const spotifyPID = 1234; // Use Activity Monitor or `pgrep Spotify`
|
||||
|
||||
const stream = new AudioTeeStream({
|
||||
includeProcesses: [spotifyPID],
|
||||
mute: true // Don't play through speakers
|
||||
});
|
||||
|
||||
stream.on('audio', (packet) => {
|
||||
// Only Spotify's audio will be captured
|
||||
console.log('Spotify audio:', packet.audioData.length, 'bytes');
|
||||
});
|
||||
|
||||
stream.start();
|
||||
```
|
||||
|
||||
### Real-time ASR Integration
|
||||
|
||||
```javascript
|
||||
const { AudioTeeStream } = require('audiotee-js');
|
||||
|
||||
const stream = new AudioTeeStream({
|
||||
sampleRate: 16000, // Common ASR sample rate
|
||||
chunkDuration: 0.1, // Faster chunks for real-time
|
||||
format: 'binary'
|
||||
});
|
||||
|
||||
stream.on('audio', async (packet) => {
|
||||
// Send to your ASR service
|
||||
const transcript = await sendToASR(packet.audioData);
|
||||
if (transcript) {
|
||||
console.log('Transcription:', transcript);
|
||||
}
|
||||
});
|
||||
|
||||
stream.start();
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the included test to verify everything works:
|
||||
|
||||
```bash
|
||||
# Basic interactive test
|
||||
npm test
|
||||
|
||||
# Quick automated test
|
||||
npm test quick
|
||||
```
|
||||
|
||||
The test will capture audio for a few seconds and display statistics.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission Denied
|
||||
|
||||
AudioTee requires microphone permissions. You'll see a system dialog on first use - make sure to allow access.
|
||||
|
||||
### Binary Not Found
|
||||
|
||||
If you see "AudioTee binary not found", try rebuilding:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### No Audio Captured
|
||||
|
||||
- Check that audio is actually playing on your system
|
||||
- Verify you have the latest macOS version (14.2+)
|
||||
- Try running the Swift AudioTee directly to isolate the issue
|
||||
|
||||
## Development
|
||||
|
||||
### Building from Source
|
||||
|
||||
```bash
|
||||
# Clone and build the parent AudioTee project first
|
||||
git clone https://github.com/your-org/audiotee.git
|
||||
cd audiotee
|
||||
swift build -c release
|
||||
|
||||
# Then build the Node.js package
|
||||
cd audiotee-js
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Testing Changes
|
||||
|
||||
```bash
|
||||
npm test # Run basic test
|
||||
npm run lint # Check code style
|
||||
npm run clean # Clean build artifacts
|
||||
```
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- **Binary format** is more efficient than JSON for high-throughput applications
|
||||
- **Lower chunk durations** increase CPU usage but reduce latency
|
||||
- **Sample rate conversion** adds processing overhead - use native rates when possible
|
||||
- The AudioTee binary uses real-time audio threads for minimal latency
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) file.
|
||||
|
||||
## Related Projects
|
||||
|
||||
- [AudioTee](https://github.com/your-org/audiotee) - The underlying Swift CLI tool
|
||||
- [node-core-audio](https://github.com/ZECTBynmo/node-core-audio) - Alternative Node.js audio library
|
||||
- [AudioCap](https://github.com/insidegui/AudioCap) - macOS audio capture inspiration
|
||||
Reference in New Issue
Block a user