Skip to content

Commit

Permalink
added readme
Browse files Browse the repository at this point in the history
  • Loading branch information
dh1tw committed Dec 26, 2016
1 parent f5ba845 commit 3d953d2
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 64 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# libsamplerate binding for Golang

This is a [Golang](https://golang.org) binding for [libsamplerate](http://www.mega-nerd.com/SRC/index.html) (written in C), probably the best audio Sample Rate Converter available to today.

A classical use case is converting audio from a CD sample rate of 44.1kHz to the 48kHz sample rate used by DAT players.

libsamplerate is capable of arbitrary and time varying conversions (max sampling / upsampling by factor 256) and comes with 5 converters, allowing quality to be traded off against computation cost.

## API implementations
**gosamplerate** implements the following [libsamplerate](http://www.mega-nerd.com/SRC/index.html) API calls:

- [Simple API](http://www.mega-nerd.com/SRC/api_simple.html)
- [Full API](http://www.mega-nerd.com/SRC/api_full.html)
- [Most miscellaneous functions](http://www.mega-nerd.com/SRC/api_misc.html)

not (yet) implemented is:

- [Callback API](http://www.mega-nerd.com/SRC/api_callback.html)

## License
This library (**gosamplerate**) is published under the the permissive [BSD license](http://choosealicense.com/licenses/mit/). You can find a good comparison of Open Source Software licenses, including the BSD license at [choosealicense.com](http://choosealicense.com/licenses/)

libsamplerate has been [republished in 2016 under the 2-clause BSD license](http://www.mega-nerd.com/SRC/license.html).

## How to install samplerate

Make sure that you have [libsamplerate](http://www.mega-nerd.com/SRC/index.html) installed on your system.

On Mac or Linux it can be installed conveniently through your distro's packet manager.

### Linux:
using **apt** (Ubuntu), **yum** (Centos)...etc.
```bash
$ sudo apt install libsamplerate0
```

### MacOS
using [Homebrew](http://brew.sh):
```bash
$ brew install libsamplerate
```

### Install gosamplerate
```bash
$ go get github.com/dh1tw/gosamplerate
```

## Documentation
The API of **gosamplerate** can be found at [godoc.org](https://godoc.org/github.com/dh1tw/gosamplerate)

## Tests & Examples
The test coverage is close to 100%. The tests contain various examples on how to use **gosamplerate**.
66 changes: 57 additions & 9 deletions samplerate.go → gosamplerate.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// +build linux,cgo darwin,cgo

package samplerate
package gosamplerate

/*
#cgo CFLAGS: -I /usr/local/include
Expand Down Expand Up @@ -102,14 +102,14 @@ func (src *Src) Reset() error {
res := C.src_reset(src.srcState)
if res < 0 {
errMsg := fmt.Sprintf("Could not reset samplerate converter state: %s",
src.Error(int(res)))
Error(int(res)))
return errors.New(errMsg)
}
return nil
}

// Error Convert the error number into a string.
func (src *Src) Error(errNo int) string {
func Error(errNo int) string {
err := C.src_strerror(C.int(errNo))
return C.GoString(err)
}
Expand All @@ -120,17 +120,64 @@ func (src *Src) ErrorNo() int {
return int(errNo)
}

// Process processes the data provided by the caller using the sample rate object
func (src *Src) Process(data []float32, ratio float64, endOfInput bool) ([]float32, error) {
// Simple converts a single block of samples (one or more channels) in one go. The simple API is less
// capable than the full API (Process()). It must not be used if Audio shall be converted in junks. For full documentation see: http://www.mega-nerd.com/SRC/api_simple.html
func Simple(dataIn []float32, ratio float64, channels int, converterType int) ([]float32, error) {
cConverterType := C.int(converterType)
cChannels := C.int(channels)
cRatio := C.double(ratio)

dataInLength := len(dataIn)

inputBuffer := make([]C.float, dataInLength)
outputBuffer := make([]C.float, dataInLength*int(ratio)+20) // add some margin

// copy data into input buffer
for i, el := range dataIn {
inputBuffer[i] = C.float(el)
}

srcData := C.alloc_src_data()
defer C.free_src_data(srcData)

srcData.data_in = &inputBuffer[0]
srcData.data_out = &outputBuffer[0]
srcData.input_frames = C.long(dataInLength / channels)
srcData.output_frames = C.long(cap(outputBuffer) / channels)
srcData.src_ratio = cRatio

res := C.src_simple(srcData, cConverterType, cChannels)

if res != 0 {
errMsg := fmt.Sprintf("Error code: %d; %s", res, Error(int(res)))
return nil, errors.New(errMsg)
}

inputLength := len(data)
output := make([]float32, 0, srcData.output_frames_gen)

// fmt.Println("channels:", src.channels)
// fmt.Println("input frames", cSrcData.input_frames)
// fmt.Println("input used", cSrcData.input_frames_used)
// fmt.Println("output generated", cSrcData.output_frames_gen)

for i := 0; i < int(srcData.output_frames_gen*C.long(channels)); i++ {
output = append(output, float32(outputBuffer[i]))
}

return output, nil
}

// Process is known as the full API. It allows time varying sample rate conversion on streaming data on one or more channels. For full documentation see: http://www.mega-nerd.com/SRC/api_full.html
func (src *Src) Process(dataIn []float32, ratio float64, endOfInput bool) ([]float32, error) {

inputLength := len(dataIn)

if inputLength > len(src.inputBuffer) {
return nil, errors.New("data slice is larger than buffer")
}

// copy data into input buffer
for i, el := range data {
for i, el := range dataIn {
src.inputBuffer[i] = C.float(el)
}

Expand All @@ -148,7 +195,7 @@ func (src *Src) Process(data []float32, ratio float64, endOfInput bool) ([]float
res := C.src_process(src.srcState, src.srcData)

if res != 0 {
errMsg := fmt.Sprintf("Error code: %d; %s", res, src.Error(int(res)))
errMsg := fmt.Sprintf("Error code: %d; %s", res, Error(int(res)))
return nil, errors.New(errMsg)
}

Expand All @@ -174,7 +221,7 @@ func (src *Src) SetRatio(ratio float64) error {

res := C.src_set_ratio(src.srcState, C.double(ratio))
if res != 0 {
return errors.New(src.Error(int(res)))
return errors.New(Error(int(res)))
}
return nil
}
Expand Down Expand Up @@ -208,6 +255,7 @@ func GetDescription(converter C.int) (string, error) {

// GetVersion returns the version number of libsamplerate
func GetVersion() string {

cVersion := C.src_get_version()
return C.GoString(cVersion)
}
83 changes: 81 additions & 2 deletions fullapi_test.go → gosamplerate_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
package samplerate
package gosamplerate

import (
"reflect"
"strings"
"testing"
)

func TestGetConverterName(t *testing.T) {
name, err := GetName(SRC_LINEAR)
if err != nil {
t.Fatal(err)
}
if name != "Linear Interpolator" {
t.Fatal("Unexpected string")
}
}

func TestGetConverterNameError(t *testing.T) {
_, err := GetName(5)
if err == nil {
t.Fatal("expected Error")
}
if err.Error() != "unknown samplerate converter" {
t.Fatal("unexpected string")
}
}

func TestGetConverterDescription(t *testing.T) {
desc, err := GetDescription(SRC_LINEAR)
if err != nil {
t.Fatal(err)
}
if desc != "Linear interpolator, very fast, poor quality." {
t.Fatal("Unexpected string")
}
}

func TestGetConverterDescriptionError(t *testing.T) {
_, err := GetDescription(5)
if err == nil {
t.Fatal("expected Error")
}
if err.Error() != "unknown samplerate converter" {
t.Fatal("unexpected string")
}
}

func TestGetVersion(t *testing.T) {
version := GetVersion()
if !strings.Contains(version, "libsamplerate-") {
t.Fatal("Unexpected string")
}
}

func TestInitAndDestroy(t *testing.T) {
channels := 2
src, err := New(SRC_SINC_FASTEST, channels, 100)
Expand Down Expand Up @@ -41,6 +89,37 @@ func TestInvalidSrcObject(t *testing.T) {
}
}

func TestSimple(t *testing.T) {
input := []float32{0.1, -0.5, 0.3, 0.4, 0.1}
expectedOutput := []float32{0.1, 0.1, -0.10000001, -0.5, 0.033333343, 0.33333334, 0.4, 0.2}

output, err := Simple(input, 1.5, 1, SRC_LINEAR)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(output, expectedOutput) {
t.Log("input", input)
t.Log("output", output)
t.Fatal("unexpected output")
}
}

func TestSimpleError(t *testing.T) {

input := []float32{0.1, 0.9}
var invalidRatio float64 = -5.3

_, err := Simple(input, invalidRatio, 1, SRC_LINEAR)
if err == nil {
t.Fatal("expected Error")
}
if err.Error() != "Error code: 6; SRC ratio outside [1/256, 256] range." {
t.Log(err.Error())
t.Fatal("unexpected string")
}
}

func TestProcess(t *testing.T) {
src, err := New(SRC_LINEAR, 2, 100)
if err != nil {
Expand Down Expand Up @@ -181,7 +260,7 @@ func TestErrors(t *testing.T) {
t.Fatal("unexpected error number")
}

errString := src.Error(0)
errString := Error(0)
if errString != "No error." {
t.Fatal("unexpected Error string")
}
Expand Down
53 changes: 0 additions & 53 deletions samplerate_test.go

This file was deleted.

0 comments on commit 3d953d2

Please sign in to comment.