npm package: @pojagi/opus-decoder-wasm
Description: RFC 7845-compliant Ogg/Opus decoder for WebAssembly with multichannel support
Author: tjb1982
Latest version: 0.0.1

# @pojagi/opus-decoder-wasm

RFC 7845-compliant Ogg/Opus decoder compiled to WebAssembly.

> ⚠️ **Beta Software**: This package is in early development (v0.0.x). The API is stable but it has not been extensively tested in production. Please report any issues you encounter.

## Overview

This package provides a complete Ogg/Opus audio decoder that runs in the browser via WebAssembly. It correctly implements the full RFC 7845 specification, including sample-accurate start and end trimming that many JavaScript decoders get wrong.

## Features

- **RFC 7845 Compliant**: Proper pre-skip trimming at the start and granule-based end trimming for sample-accurate decoding
- **Multichannel Support**: Handles up to 255 channels using the Opus multistream decoder API
- **Pure Rust/WASM**: No native dependencies or toolchain required at runtime
- **Self-Contained**: Zero external module imports—works with any bundler (Vite, webpack, etc.)
- **TypeScript**: Full type definitions included

## How It Works

The decoder is built entirely in Rust and compiled to WebAssembly:

- **[unopus](https://crates.io/crates/unopus)**: The reference libopus 1.3.1 implementation, mechanically transpiled from C to pure Rust using c2rust. This provides bit-exact compatibility with native libopus while requiring no C toolchain or native libraries.
- **[ogg](https://crates.io/crates/ogg)**: Pure Rust Ogg container demuxer for parsing the Ogg bitstream.
- **[wasm-bindgen](https://crates.io/crates/wasm-bindgen)**: Generates JavaScript bindings for the WASM module.

### Why unopus?

Most WASM Opus decoders either:
1. Use Emscripten to compile libopus, which introduces dependencies on an `env` module that bundlers can't resolve
2. Use pure JavaScript implementations that may have subtle differences from the reference

By using `unopus` (libopus transpiled to Rust), we get:
- **Bit-exact decoding** matching the reference C implementation
- **No native toolchain** required for building
- **Self-contained WASM** with no external imports
- **~187KB** module size (gzipped: ~95KB)

## Building

Requires:
- Rust with `wasm32-unknown-unknown` target: `rustup target add wasm32-unknown-unknown`
- wasm-pack: `cargo install wasm-pack`

```bash
./build.sh
```

## Usage

### High-Level API

```typescript
import { OggOpusDecoder } from '@pojagi/opus-decoder-wasm/wrapper';

const decoder = new OggOpusDecoder();
await decoder.ready;

const result = await decoder.decode(oggOpusData);
console.log(`Decoded ${result.samplesDecoded} samples at ${result.sampleRate}Hz`);
console.log(`Channels: ${result.channelData.length}`);
```

### Low-Level API

```typescript
import { initialize, decode, getInfo } from '@pojagi/opus-decoder-wasm';

// Initialize WASM module (required once)
await initialize();

// Decode a complete Ogg/Opus file
const audio = await decode(oggOpusData);
// audio.channelData: Float32Array[] - one array per channel
// audio.sampleRate: number - always 48000 for Opus
// audio.numberOfChannels: number
// audio.length: number - samples per channel

// Get file metadata without decoding
const info = await getInfo(oggOpusData);
// info.channels, info.preSkip, info.finalGranulePosition, etc.
```

## API Reference

### OggOpusDecoder

High-level decoder class with a familiar API.

```typescript
class OggOpusDecoder {
    ready: Promise<void>;
    
    decode(data: Uint8Array): Promise<{
        channelData: Float32Array[];
        samplesDecoded: number;
        sampleRate: number;
        errors: never[];
    }>;
    
    reset(): Promise<void>;
    free(): Promise<void>;
}
```

### DecodedAudio

```typescript
interface DecodedAudio {
    channelData: Float32Array[];  // One array per channel
    sampleRate: number;           // Always 48000
    numberOfChannels: number;
    length: number;               // Samples per channel
}
```

### OggOpusInfo

```typescript
interface OggOpusInfo {
    channels: number;
    preSkip: number;
    inputSampleRate: number;      // Original sample rate (informational)
    outputGain: number;           // Gain in dB
    channelMappingFamily: number; // 0, 1, or 255
    packetCount: number;
    finalGranulePosition: bigint;
    expectedOutputSamples: bigint;
}
```

## RFC 7845 Compliance

This decoder properly implements:

1. **Pre-skip trimming**: Discards the encoder delay samples specified in the OpusHead
2. **End trimming**: Uses the final page's granule position to discard padding samples at the end
3. **Channel mapping**: Supports mapping families 0, 1, and 255 for proper multichannel decoding

These details are critical for gapless playback and accurate sample counts, especially when decoding audio segments for streaming applications.

## License

MIT