# Buzz

High-level audio library for Bun using ALSA with streaming support and voice activity detection.

## Features

- Play audio files with repeat option
- Generate and play multi-frequency tones (dial tones, DTMF, etc.)
- Stream audio playback with buffer tracking
- Record audio to stream or file (WAV)
- Volume control via ALSA mixer
- Device discovery and selection
- Voice activity detection via RMS calculation
- Type-safe TypeScript API with namespace types
- Zero external dependencies (uses ALSA `aplay` and `arecord`)

## Requirements

- Bun 1.0+
- ALSA utilities (`aplay`, `arecord`, `amixer`)
- Linux system with ALSA support
- TypeScript 5.2+

## Quick Start

```typescript
import Buzz from "./buzz"

// Play an audio file
const player = await Buzz.player()
const playback = await player.play("./sounds/greeting.wav")
await playback.finished()

// Generate a dial tone
const dialTone = await player.playTone([350, 440], Infinity) // infinite duration
await Buzz.sleep(3000)
await dialTone.stop()

// Record audio
const recorder = await Buzz.recorder()
const recording = recorder.start()

for await (const chunk of recording.stream()) {
  const rms = Buzz.calculateRMS(chunk)
  if (rms > 5000) {
    console.log("Speech detected!")
  }
}
```

## API

### Buzz Module

#### `Buzz.player(label?, format?)`

Create a player. Omit `label` to use the default playback device.

```typescript
const player = await Buzz.player()                           // default device
const player = await Buzz.player(undefined, { sampleRate: 16000 }) // default device with custom format
const player = await Buzz.player("USB Audio")                // specific device
const player = await Buzz.player("Speaker", { sampleRate: 44100 }) // specific device with format
```

#### `Buzz.recorder(label?, format?)`

Create a recorder. Omit `label` to use the default capture device.

```typescript
const recorder = await Buzz.recorder()                       // default device
const recorder = await Buzz.recorder(undefined, { sampleRate: 16000 }) // default device with custom format
const recorder = await Buzz.recorder("USB Microphone")       // specific device
```

#### `Buzz.setVolume(volume, label?)`

Set playback volume (0.0 to 1.0).

```typescript
await Buzz.setVolume(0.5)           // 50% on default device
await Buzz.setVolume(0.8, "Speaker") // 80% on specific device
```

#### `Buzz.getVolume(label?)`

Get current playback volume.

```typescript
const volume = await Buzz.getVolume() // returns 0.0 to 1.0
```

#### `Buzz.listDevices()`

List all available audio devices.

```typescript
const devices = await Buzz.listDevices()
// [
//   { id: 'plughw:0,0', card: 0, device: 0, label: 'bcm2835 Headphones', type: 'playback' },
//   { id: 'plughw:1,0', card: 1, device: 0, label: 'USB Audio', type: 'capture' }
// ]
```

#### `Buzz.calculateRMS(audioChunk)`

Calculate root mean square (RMS) for voice activity detection.

```typescript
const chunk: Uint8Array = // ... audio data
const rms = Buzz.calculateRMS(chunk)
if (rms > 5000) {
  console.log("Voice detected!")
}
```

### Player

#### `player.play(filePath, options?)`

Play an audio file (WAV format).

```typescript
const playback = await player.play("./sounds/beep.wav")
const playback = await player.play("./music.wav", { repeat: true })

// Wait for playback to finish
await playback.finished()

// Stop playback
await playback.stop()
```

Returns: `Buzz.Playback`

Options:
- `repeat?: boolean` - Loop the file indefinitely (default: false)

#### `player.playTone(frequencies, duration)`

Generate and play a tone with one or more frequencies.

```typescript
// Dial tone (350 Hz + 440 Hz)
const dialTone = await player.playTone([350, 440], Infinity)

// DTMF "1" key (697 Hz + 1209 Hz) for 200ms
const dtmf = await player.playTone([697, 1209], 200)

// Single frequency beep
const beep = await player.playTone([440], 1000) // 440 Hz for 1 second
```

Returns: `Buzz.Playback`

#### `player.playStream()`

Create a streaming playback handle for real-time audio.

```typescript
const stream = player.playStream()

// Write audio chunks
stream.write(audioChunk1)
stream.write(audioChunk2)

// Check if buffer is empty
if (stream.bufferEmptyFor > 1000) {
  console.log("Buffer empty for 1+ seconds")
}

// Stop streaming
await stream.stop()
```

Returns: `Buzz.StreamingPlayback`

### Recorder

#### `recorder.start()`

Start recording to a stream.

```typescript
const recording = recorder.start()

for await (const chunk of recording.stream()) {
  // Process audio chunks (Uint8Array)
  console.log("Received", chunk.byteLength, "bytes")
}
```

Returns: `Buzz.StreamingRecording`

#### `recorder.start(outputFile)`

Start recording to a WAV file.

```typescript
const recording = recorder.start("./output.wav")

// Stop when done
await Bun.sleep(5000)
await recording.stop()
```

Returns: `Buzz.FileRecording`

## Types

All types are available under the `Buzz` namespace:

```typescript
Buzz.AudioFormat        // { format?, sampleRate?, channels? }
Buzz.Device            // { id, card, device, label, type }
Buzz.Playback          // { isPlaying, stop(), finished() }
Buzz.StreamingPlayback // { isPlaying, write(), stop(), bufferEmptyFor }
Buzz.StreamingRecording // { isRecording, stream(), stop() }
Buzz.FileRecording     // { isRecording, stop() }
Buzz.Player            // Player class type
Buzz.Recorder          // Recorder class type
```

## Audio Format

Default format: `S16_LE`, 16000 Hz, mono

```typescript
type AudioFormat = {
  format?: string      // e.g., "S16_LE", "S32_LE"
  sampleRate?: number  // e.g., 16000, 44100, 48000
  channels?: number    // 1 = mono, 2 = stereo
}
```

Common formats:
- **Phone quality**: `{ sampleRate: 8000, channels: 1 }`
- **Voice/AI**: `{ sampleRate: 16000, channels: 1 }` (default)
- **CD quality**: `{ sampleRate: 44100, channels: 2 }`
- **Professional**: `{ sampleRate: 48000, channels: 2 }`

## Examples

### Voice Activity Detection

```typescript
import Buzz from "./buzz"

const recorder = await Buzz.recorder()
const player = await Buzz.player()

const recording = recorder.start()
let talking = false

for await (const chunk of recording.stream()) {
  const rms = Buzz.calculateRMS(chunk)

  if (rms > 5000 && !talking) {
    console.log("🗣️  Started talking")
    talking = true
  } else if (rms < 1000 && talking) {
    console.log("🤫 Stopped talking")
    talking = false
  }
}
```

### Streaming Playback with Buffer Tracking

```typescript
import Buzz from "./buzz"

const player = await Buzz.player()
const stream = player.playStream()

// Simulate receiving audio chunks from network
const chunks = [chunk1, chunk2, chunk3] // Uint8Array[]

for (const chunk of chunks) {
  stream.write(chunk)

  // Wait until buffer is nearly empty before requesting more
  while (stream.bufferEmptyFor < 500) {
    await Bun.sleep(100)
  }
}

await stream.stop()
```

### Dial Tone with Voice Detection

```typescript
import Buzz from "./buzz"

await Buzz.setVolume(0.4)

const player = await Buzz.player()
const recorder = await Buzz.recorder()

// Play dial tone
const dialTone = await player.playTone([350, 440], Infinity)

// Wait for voice
const recording = recorder.start()
const vadThreshold = 5000

for await (const chunk of recording.stream()) {
  const rms = Buzz.calculateRMS(chunk)

  if (rms > vadThreshold) {
    console.log("Voice detected, stopping dial tone")
    await dialTone.stop()
    break
  }
}
```

### Play Sound Effects

```typescript
import Buzz from "./buzz"

const player = await Buzz.player()

// Play multiple sounds in sequence
const sounds = ["./start.wav", "./beep.wav", "./end.wav"]

for (const sound of sounds) {
  const playback = await player.play(sound)
  await playback.finished()
}
```

### Background Music Loop

```typescript
import Buzz from "./buzz"

const player = await Buzz.player()

// Play background music on repeat
const bgMusic = await player.play("./background.wav", { repeat: true })

// Stop after 30 seconds
await Bun.sleep(30000)
await bgMusic.stop()
```

### Record to File

```typescript
import Buzz from "./buzz"

const recorder = await Buzz.recorder()

console.log("Recording for 10 seconds...")
const recording = recorder.start("./output.wav")

await Bun.sleep(10000)
await recording.stop()

console.log("Saved to output.wav")
```

### Multi-Device Setup

```typescript
import Buzz from "./buzz"

// List all devices
const devices = await Buzz.listDevices()
console.log("Available devices:", devices)

// Use specific devices
const speaker = await Buzz.player("Speaker")
const mic = await Buzz.recorder("USB Microphone")

// Independent volume control
await Buzz.setVolume(0.8, "Speaker")
await Buzz.setVolume(1.0, "Headphones")
```

## Architecture

### ALSA Backend

Buzz wraps ALSA command-line tools (`aplay`, `arecord`) via Bun's subprocess API:

- **Playback**: Spawns `aplay` with stdin pipe for streaming or file path for file playback
- **Recording**: Spawns `arecord` with stdout pipe for streaming or file path for WAV output
- **Volume**: Uses `amixer` for volume control

**Benefits:**

- **Simple**: No C bindings or FFI required
- **Reliable**: ALSA tools are battle-tested
- **Flexible**: Full format support (sample rates, channels, encodings)
- **Portable**: Works on any Linux system with ALSA

### Streaming Architecture

Streaming playback uses Bun's subprocess stdin pipe:

1. Spawn `aplay` with raw audio format and stdin input
2. Write audio chunks to process stdin as they arrive
3. Track buffer duration based on bytes written
4. Calculate `bufferEmptyFor` using performance timestamps

This enables:
- Real-time playback of network streams (WebSocket, API responses)
- Buffer management for smooth playback
- Low-latency audio (<100ms with proper buffering)

### Voice Activity Detection

`calculateRMS()` computes the root mean square of audio samples:

```
RMS = sqrt(sum(sample²) / count)
```

This provides a simple but effective measure of audio energy:
- Silence: RMS < 1000
- Noise: RMS 1000-5000
- Speech: RMS > 5000

Adjust thresholds based on your microphone and environment.

## Device Selection

### By Default (Recommended)

```typescript
const player = await Buzz.player()
const recorder = await Buzz.recorder()
```

Uses ALSA default device (usually correct).

### By Label

```typescript
const devices = await Buzz.listDevices()
// Find device with label containing "USB"
const usbDevice = devices.find(d => d.label.includes("USB"))

const player = await Buzz.player(usbDevice.label)
```

Useful for multi-device setups (USB audio, HDMI, headphones).

## Error Handling

```typescript
try {
  const player = await Buzz.player()
} catch (err) {
  if (err.message.includes("No playback devices found")) {
    console.error("No audio output devices available")
  }
}

try {
  await Buzz.setVolume(0.5)
} catch (err) {
  if (err.message.includes("Failed to set volume")) {
    console.error("Could not control volume (check mixer permissions)")
  }
}
```

## Troubleshooting

### No devices found

Check ALSA devices:

```bash
aplay -l  # list playback devices
arecord -l  # list capture devices
```

### Volume control fails

Check mixer controls:

```bash
amixer scontrols
amixer sget Master
```

### Crackling or distortion

Try different buffer sizes by adjusting format:

```typescript
const player = await Buzz.player(undefined, {
  sampleRate: 16000,
  channels: 1,
  format: "S16_LE"
})
```

### Device already in use

Only one process can use an ALSA device at a time. Stop other audio applications or use PulseAudio/PipeWire for mixing.

## Design Philosophy

- **Simple by default** - `player()` and `recorder()` work out of the box without arguments
- **Streaming-first** - Built for real-time audio (AI voice, telephony, WebRTC)
- **Type-safe** - Namespace types provide autocomplete and compile-time safety
- **Flexible** - Support for files, tones, and streams
- **Minimal dependencies** - Uses standard ALSA tools, no native bindings

## Performance

- **Latency**: ~50-100ms for streaming playback (depends on buffering)
- **CPU**: Minimal overhead (subprocess spawning + pipe I/O)
- **Memory**: Efficient streaming (no need to load entire files)
- **Voice detection**: `calculateRMS()` is fast (~1µs per chunk on modern hardware)

## References

- [ALSA documentation](https://www.alsa-project.org/wiki/Main_Page)
- [Bun subprocess API](https://bun.sh/docs/api/spawn)
- [Audio sample formats](https://en.wikipedia.org/wiki/Audio_bit_depth)
