Awesome Go

go-audio-resampler

CategoryAudio and Music
SubcategoryAudio and Music
Stars11

Pure-Go, high-quality audio resampler with SIMD acceleration

About go-audio-resampler

This large README is shown as a plain-text preview. Read the full README at the source

<h1 align="center">go-audio-resampler</h1>

<p align="center">
  <em>The only pure Go, high-quality audio resampler (that I know of). No CGO, no C toolchain, no build headaches.</em>
</p>

<p align="center">
  <!-- Project status -->
  <img alt="Release" src="https://img.shields.io/github/v/release/tphakala/go-audio-resampler?sort=semver&label=release&color=blue">
  <a href="LICENSE"><img alt="License: LGPL-2.1" src="https://img.shields.io/badge/license-LGPL--2.1-blue.svg"></a>
  <img alt="Go version" src="https://img.shields.io/github/go-mod/go-version/tphakala/go-audio-resampler?label=go&logo=go&logoColor=white">
  <img alt="Pure Go, no CGO" src="https://img.shields.io/badge/pure%20Go-no%20CGO-00ADD8?logo=go&logoColor=white">
  <img alt="Platforms" src="https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows%20%7C%20WASM-lightgrey">
</p>

<p align="center">
  <!-- CI and code quality -->
  <a href="https://github.com/tphakala/go-audio-resampler/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/tphakala/go-audio-resampler/actions/workflows/ci.yml/badge.svg"></a>
  <a href="https://github.com/tphakala/go-audio-resampler/actions/workflows/govulncheck.yml"><img alt="govulncheck" src="https://github.com/tphakala/go-audio-resampler/actions/workflows/govulncheck.yml/badge.svg"></a>
  <a href="https://codecov.io/gh/tphakala/go-audio-resampler"><img alt="codecov" src="https://codecov.io/gh/tphakala/go-audio-resampler/branch/master/graph/badge.svg"></a>
  <a href="https://pkg.go.dev/github.com/tphakala/go-audio-resampler"><img alt="Go Reference" src="https://pkg.go.dev/badge/github.com/tphakala/go-audio-resampler.svg"></a>
  <a href="https://scorecard.dev/viewer/?uri=github.com/tphakala/go-audio-resampler"><img alt="OpenSSF Scorecard" src="https://api.scorecard.dev/projects/github.com/tphakala/go-audio-resampler/badge"></a>
</p>

<p align="center">
  <!-- Community -->
  <a href="https://github.com/sponsors/tphakala"><img alt="Sponsor" src="https://img.shields.io/github/sponsors/tphakala?label=Sponsor&logo=github&color=ea4aaa"></a>
  <a href="CONTRIBUTING.md"><img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"></a>
</p>

Based on [libsoxr](https://sourceforge.net/projects/soxr/) (the SoX Resampler library) by Rob Sykes, this is a complete Go reimplementation delivering professional-grade quality without any C code or external dependencies.

The library implements polyphase FIR filtering with Kaiser window design for professional-grade sample rate conversion, following the multi-stage architecture that makes libsoxr one of the highest-quality resamplers available.

## Contents

- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Quality Presets](#quality-presets)
- [Precision Modes (float32 vs float64)](#precision-modes-float32-vs-float64)
- [Architecture](#architecture)
- [API Reference](#api-reference)
- [Command-Line Tools](#command-line-tools)
- [Performance](#performance)
- [Dependencies](#dependencies)
- [Contributing](#contributing)
- [Sponsor](#sponsor)
- [License](#license)
- [Acknowledgments](#acknowledgments)

## Features

- **100% Pure Go** - No CGO, no external C libraries, no build complexity. Just `go get` and build anywhere
- **Cross-platform** - Works on Linux, macOS, Windows, ARM, WebAssembly - anywhere Go runs
- **Multiple quality presets** - From quick/low-latency to very high quality mastering (8-bit to 32-bit precision)
- **Selectable precision** - Generic float32/float64 processing paths with type-safe SIMD operations
- **Polyphase FIR filtering** - Efficient multi-stage resampling architecture with cubic coefficient interpolation
- **Kaiser window design** - Optimal stopband attenuation with configurable parameters
- **SIMD acceleration** - AVX2/SSE optimizations via [tphakala/simd](https://github.com/tphakala/simd) for both float32 and float64
- **Multi-channel support** - Process stereo, surround, and multi-channel audio (up to 256 channels)
- **Parallel channel processing** - ~1.7x speedup for stereo, up to 8x for 7.1 surround audio
- **Streaming API** - Process audio in chunks with proper state management
- **Zero-allocation streaming** - Caller-owned output buffers (`ProcessInto` / `ProcessFloat32Into`) for float32 and float64 hot paths
- **soxr-quality algorithms** - Implements libsoxr's multi-stage architecture for professional-grade quality

## Installation

```bash
go get github.com/tphakala/go-audio-resampler
```

Requires Go 1.26 or later.

## Quick Start

### Simple One-Shot Resampling

```go
package main

import (
    "fmt"
    "log"

    resampling "github.com/tphakala/go-audio-resampler"
)

func main() {
    // Resample mono audio from 44.1kHz to 48kHz
    input := generateSineWave(44100, 1000, 1.0) // 1 second of 1kHz sine

    output, err := resampling.ResampleMono(input, 44100, 48000, resampling.QualityHigh)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Resampled %d samples to %d samples\n", len(input), len(output))
}
```

### Streaming Resampling

```go
package main

import (
    "log"

    resampling "github.com/tphakala/go-audio-resampler"
)

func main() {
    // Create a resampler for CD to DAT conversion (mono; for multi-channel
    // audio use ProcessMulti, see "Multi-Channel Streaming" below)
    config := &resampling.Config{
        InputRate:  44100,
        OutputRate: 48000,
        Channels:   1,
        Quality:    resampling.QualitySpec{Preset: resampling.QualityHigh},
    }

    r, err := resampling.New(config)
    if err != nil {
        log.Fatal(err)
    }

    // Process audio in chunks
    for chunk := range audioChunks {
        output, err := r.Process(chunk)
        if err != nil {
            log.Fatal(err)
        }
        writeOutput(output)
    }

    // Flush remaining samples at end of stream
    final, err := r.Flush()
    if err != nil {
        log.Fatal(err)
    }
    writeOutput(final)
}
```

### Latency and Real-Time Streaming

A streaming resampler has a startup deficit: the internal filter needs a few samples of history before it can emit correctly filtered output, so the first `Process` calls in a stream withhold roughly `Latency()` samples that later calls make up. Callers that need a fixed number of output samples per callback, such as a portaudio or miniaudio audio callback, should sit a small FIFO between the resampler and the callback, primed with `Latency()` samples of silence. `Latency()` matches the measured deficit to within about 2 samples, so priming with it keeps callbacks fed in practice; any 1-2 sample shortfall self-heals because the deficit-driven buffer sizing catches up within the first few callbacks.

`Latency()` is available on `SimpleResampler` and `SimpleResamplerFloat32` (the `NewEngine`/`NewEngineFloat32` path). Resamplers built from `New(config)` expose the same figure through `GetLatency()` (and `GetInfo().Latency`): the startup deficit in output samples, accurate to within a few samples across the pipeline's stages, so it primes a FIFO the same way.

```go
r, err := resampling.NewEngineFloat32(44100, 48000, resampling.QualityHigh)
if err != nil {
    log.Fatal(err)
}
fifo := make([]float32, r.Latency()) // prime with the startup deficit

for chunk := range audioChunks {
    out, err := r.Process(chunk)
    if err != nil {
        log.Fatal(err)
    }
    fifo = append(fifo, out...)
    // deliver fixed-size slices from fifo to the audio callback here
}

// End of stream: drain the filter tail exactly once.
tail, err := r.Flush()
if err != nil {
    log.Fatal(err)
}
fifo = append(fifo, tail...)
```

`Flush` is end-of-stream only. It pushes padding through the filter to drain its tail, so calling it once per chunk instead of once at the very end injects that padding into the middle of the stream and produces audible clicks at every chunk boundary. Call `Flush` exactly once, after the last `Process` call for the stream; a second `Flush` call is a no-op that returns an empty slice. `Reset` discards all filter state and is for starting an unrelated stream, never for use between chunks of the same stream. Each `SimpleResampler` or `SimpleResamplerFloat32` processes one channel, so a multi-channel stream keeps one persistent instance per channel alive for the duration of the stream.

See [`examples/streaming`](examples/streaming/main.go) for the complete, runnable FIFO pattern.

### Zero-Allocation Streaming (`ProcessInto`)

For allocation-sensitive pipelines, use caller-owned output buffers:

```go
r, err := resampling.NewEngine(48000, 32000, resampling.QualityMedium)
if err != nil {
    log.Fatal(err)
}

for chunk := range audioChunks {
    out := make([]float64, r.EstimateOutput(len(chunk)))
    n, err := r.ProcessInto(chunk, out)
    if err != nil {
        log.Fatal(err)
    }
    writeOutput(out[:n]) // Only output[:n] is valid
}
```

Contract notes:

- `EstimateOutput(len(input))` returns a conservative upper bound for `ProcessInto`.
- `ProcessInto` returns `ErrBufferTooSmall` if `output` cannot hold results.
- On `ErrBufferTooSmall`, processing state is not advanced (safe to retry with a larger buffer).
- Samples beyond `output[:n]` are unspecified and should be ignored.

When using `New(config)`, access `ProcessInto` via type assertion:

```go
type processIntoResampler interface {
    ProcessInto(input, output []float64) (int, error)
    EstimateOutput(inputLen int) int
}

base, _ := resampling.New(config)
into, ok := base.(processIntoResampler)
if !ok {
    log.Fatal("ProcessInto not available")
}
```

#### Float32 zero-allocation streaming

For float32 audio, `NewEngineFloat32` exposes a float32-native `ProcessInto`. The
underlying engine is float32-native, so there is no float64 round-trip:

```go
r, err := resampling.NewEngineFloat32(48000, 32000, resampling.QualityMedium)
if err != nil {
    log.Fatal(err)
}

for chunk := range audioChunks {
    out := make([]float32, r.EstimateOutput(len(chunk)))
    n, err := r.ProcessInto(chunk, out)
    if err != nil {
        log.Fatal(err)
    }
    writeOutput(out[:n]) // Only out[:n] is valid
}
```

When using `New(config)`, the float32 caller-owned-output method is
`ProcessFloat32Into`. It runs through the float64 pipeline using reused scratch
buffers, so it also reports 0 allocs/op once warm. Access it via type assertion:

```go
type processFloat32IntoResampler interface {
    ProcessFloat32Into(input, output []float32) (int, error)
    EstimateOutput(inputLen int) int
}

base, _ := resampling.New(config)
into, ok := base.(processFloat32IntoResampler)
if !ok {
    log.Fatal("ProcessFloat32Into not available")
}
```

Both float32 methods follow the same contract as float64 `ProcessInto`: size the
output with `EstimateOutput(len(input))`, and on `ErrBufferTooSmall` no state is
advanced (safe to retry with a larger buffer).

### Convenience Functions

```go
// Common sample rate conversions
r, _ := resampling.NewCDtoDAT(resampling.QualityHigh)    // 44.1kHz -> 48kHz
r, _ := resampling.NewDATtoCD(resampling.QualityHigh)    // 48kHz -> 44.1kHz
r, _ := resampling.NewCDtoHiRes(resampling.QualityHigh)  // 44.1kHz -> 88.2kHz

// Simple mono/stereo resamplers
r, _ := resampling.NewSimple(inputRate, outputRate)
r, _ := resampling.NewStereo(inputRate, outputRate, resampling.QualityMedium)
r, _ := resampling.NewMultiChannel(inputRate, outputRate, 6, resampling.QualityHigh)

// Direct engine access for maximum performance
r, _ := resampling.NewEngine(44100, 48000, resampling.QualityHigh)
```

### Stereo Processing

```go
// Process stereo as separate channels
leftOut, rightOut, err := resampling.ResampleStereo(
    leftChannel, rightChannel,
    44100, 48000,
    resampling.QualityHigh,
)

// Interleave/deinterleave helpers
interleaved := resampling.InterleaveToStereo(left, right)
left, right := resampling.DeinterleaveFromStereo(interleaved)
```

### Multi-Channel Streaming

When streaming multi-channel audio with `ProcessMulti`, drain with `FlushMulti`
rather than `Flush`. `Flush` only drains channel 0, so it would silently drop the
delay-line tails of the remaining channels at end-of-stream:

```go
config := &resampling.Config{
    InputRate:  44100,
    OutputRate: 48000,
    Channels:   6,
    Quality:    resampling.QualitySpec{Preset: resampling.QualityHigh},
}
r, _ := resampling.New(config)

for chunk := range multiChannelChunks { // chunk is [][]float64, one slice per channel
    out, err := r.ProcessMulti(chunk)
    if err != nil {
        log.Fatal(err)
    }
    writeOutput(out)
}

// Drain every channel's tail (FlushMulti is an optional interface).
if mf, ok := r.(resampling.MultiFlusher); ok {
    tail, _ := mf.FlushMulti()
    writeOutput(tail)
}
```

## Quality Presets

| Preset            | Precision | Attenuation | Use Case                        |
| ----------------- | --------- | ----------- | ------------------------------- |
| `QualityQuick`    | 8-bit     | ~54 dB      | Preview, real-time with low CPU |
| `QualityLow`      | 16-bit    | ~102 dB     | Speech, low-bandwidth audio     |
| `QualityMedium`   | 16-bit    | ~102 dB     | General music playback          |
| `QualityHigh`     | 20-bit    | ~126 dB     | Studio production, streaming    |
| `QualityVeryHigh` | 28-bit    | ~175 dB     | Mastering, archival             |

### Quality Validation vs libsoxr

The following THD (Total Harmonic Distortion) measurements compare this library against libsoxr reference implementation for 44.1kHz → 48kHz conversion at 1kHz test tone:

| Preset   | libsoxr THD | Go THD     | Difference |
| -------- | ----------- | ---------- | ---------- |
| Low      | -146.25 dB  | -142.28 dB | +4.0 dB    |
| Medium   | -130.61 dB  | -129.79 dB | +0.8 dB    |
| High     | -155.19 dB  | -155.58 dB | -0.4 dB ✓  |
| VeryHigh | -162.22 dB  | -162.19 dB | +0.03 dB   |

_Negative difference means Go implementation has better THD (lower distortion)._ Go
figures come from the bundled quality-regression suite and are reproducible with
`go test ./internal/engine -run TestQualityRegression_THD -v`.

**Key findings:**

- **High and VeryHigh presets match or exceed libsoxr quality**, within 0.5 dB (High slightly better, VeryHigh essentially identical)
- **Low and Medium track libsoxr closely**: Medium is within ~1 dB and Low within ~4 dB, trading a little stopband headroom for lower passband ripple
- All presets achieve SNR (Signal-to-Noise Ratio) matching libsoxr within measurement tolerance
- Downsampling (e.g., 48kHz → 32kHz) shows substantially better THD (below -190 dB across presets) thanks to the anti-aliasing filters

> **Why does Low show better THD than Medium?** Both presets are 16-bit (same noise
> floor), so the difference is filter geometry, not precision. Low keeps its passband
> to 80% of Nyquist with a wide, gentle transition, which is easy to realize cleanly
> and yields excellent THD, but discards more high-frequency content. Medium extends
> the passband to 90% of Nyquist with a sharper transition: it preserves more bandwidth
> but leaks slightly more energy near Nyquist, so its single-tone THD is higher. Low
> and Medium are a bandwidth-versus-distortion trade, not a simple quality ranking, and
> libsoxr's own LQ/MQ recipes show the same ordering.

The table above uses 44.1kHz → 48kHz, an exact-rational ratio where the polyphase filter needs no sub-phase interpolation. Ratios that do engage the sub-phase Catmull-Rom interpolation banks are pinned separately (measured 2026-07-17, guarded by `TestQualityRegression_THD_ActiveInterpolation`):

| Conversion      | High       | Medium     | Low        |
| --------------- | ---------- | ---------- | ---------- |
| 32kHz → 44.1kHz | -162.05 dB | -134.95 dB | -144.22 dB |
| 44.1kHz → 64kHz | -151.63 dB | -135.03 dB | -139.47 dB |

### Custom Quality Settings

```go
config := &resampling.Config{
    InputRate:  44100,
    OutputRate: 48000,
    Channels:   1,
    Quality: resampling.QualitySpec{
        Preset:        resampling.QualityCustom,
        Precision:     24,          // Bits of precision
        PhaseResponse: 50,          // 0=minimum, 50=linear, 100=maximum
        PassbandEnd:   0.95,        // Preserve up to 95% of Nyquist
        StopbandBegin: 0.99,        // Attenuate above 99% of Nyquist
    },
}
```

## Precision Modes (float32 vs float64)

The library supports both float32 and float64 processing paths:

```go
// float64 for maximum precision (default)
r64, _ := resampling.NewEngine(44100, 48000, resampling.QualityHigh)
output64, _ := r64.Process(input64)

// float32 for ~2x SIMD throughput
r32, _ := resampling.NewEngineFloat32(44100, 48000, resampling.QualityHigh)
output32, _ := r32.Process(input32)
```

**Precision comparison (44.1kHz → 48kHz, High quality):**

| Mode    | THD @ 1kHz | Use Case                          |
| ------- | ---------- | --------------------------------- |
| float64 | -145.25 dB | Maximum precision, critical audio |
| float32 | -145.50 dB | Higher throughput, general use    |

Both modes achieve equivalent quality for most use cases. Use float32 when processing large amounts of audio data where throughput is more important than theoretical precision limits.

## Architecture

The library implements a multi-stage resampling architecture similar to libsoxr:

```text
Input -> [DFT Pre-Stage] -> [Polyphase FIR] -> Output
              (2x)            (fine ratio)
```

- **Integer ratios** (2x, 3x, etc.): Single DFT stage for efficiency
- **Non-integer ratios** (44.1kHz→48kHz): DFT pre-upsampling + polyphase stage
- **Polyphase decomposition**: Reduces computation by processing only needed output phases
- **Cubic coefficient interpolation**: Sub-phase interpolation for excellent high-frequency THD
- **Kaiser window**: Optimal FIR filter design with configurable stopband attenuation

## API Reference

### Core Types

```go
// Main resampler interface
type Resampler interface {
    Process(input []float64) ([]float64, error)
    ProcessFloat32(input []float32) ([]float32, error)
    ProcessMulti(input [][]float64) ([][]float64, error)
    Flush() ([]float64, error) // drains channel 0 only; see MultiFlusher
    GetLatency() int
    Reset()
    GetRatio() float64
}

// Optional interface for draining every channel of a multi-channel stream.
// Flush() only drains channel 0, so after ProcessMulti use FlushMulti to
// avoid dropping the delay-line tails of channels 1..N-1.
type MultiFlusher interface {
    FlushMulti() ([][]float64, error)
}

// Configuration
type Config struct {
    InputRate      float64      // Input sample rate in Hz
    OutputRate     float64      // Output sample rate in Hz
    Channels       int          // Number of audio channels
    Quality        QualitySpec  // Quality settings
    MaxInputSize   int          // Optional buffer size hint
    EnableSIMD     bool         // Enable SIMD optimizations
    EnableParallel bool         // Enable parallel channel processing
}
```

### Getting Resampler Info

```go
r, _ := resampling.New(config)
info := resampling.GetInfo(r)

fmt.Printf("Algorithm: %s\n", info.Algorithm)
fmt.Printf("Filter length: %d taps\n", info.FilterLength)
fmt.Printf("Latency: %d samples\n", info.Latency)
fmt.Printf("Memory: %d bytes\n", info.MemoryUsage)
fmt.Printf("SIMD: %v (%s)\n", info.SIMDEnabled, info.SIMDType)
```

## Command-Line Tools

### resample-wav

Resample WAV audio files:

```bash
# Build
go build -o bin/resample-wav ./cmd/resample-wav

# Usage
resample-wav -rate 48 input.wav output.wav              # Resample to 48kHz
resample-wav -rate 16 -quality high speech.wav out.wav  # Downsample speech
resample-wav -rate 96 music.wav hires.wav               # Upsample to hi-res
resample-wav -rate 48 -fast input.wav output.wav        # Float32 precision (~40% faster)
resample-wav -rate 48 -parallel=false input.wav out.wav # Disable parallel processing
```

Frequently Asked Questions

What is go-audio-resampler?

go-audio-resampler is a Audio and Music library for the Go programming language. Pure-Go, high-quality audio resampler with SIMD acceleration

How do I install go-audio-resampler?

Install go-audio-resampler with the Go module system using `go get tphakala/go-audio-resampler`. Check the repository for the current installation instructions.

What category does go-audio-resampler belong to?

go-audio-resampler is listed under Audio and Music, specifically Audio and Music.

← Back to Audio and Music