├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── gosamplerate.go └── gosamplerate_test.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Cross Platform build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | linux: 7 | runs-on: ubuntu-22.04 8 | strategy: 9 | matrix: 10 | go: ["1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "1.12", "1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19", "1.20", "1.21"] 11 | steps: 12 | - 13 | name: Set up Go ${{ matrix.go }} 14 | uses: actions/setup-go@v4 15 | id: go 16 | with: 17 | go-version: ${{ matrix.go }} 18 | - 19 | name: Checkout source code 20 | uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - 24 | name: Install dependencies 25 | run: | 26 | sudo apt-get install -y libsamplerate0 libsamplerate0-dev 27 | - 28 | name: Compile 29 | run: go build 30 | - 31 | name: Execute Unit Tests 32 | run: go test ./... 33 | 34 | macos: 35 | runs-on: macos-latest 36 | strategy: 37 | matrix: 38 | go: ["1.11", "1.12", "1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19", "1.20", "1.21"] 39 | steps: 40 | - 41 | name: Set up Go ${{ matrix.go }} 42 | uses: actions/setup-go@v4 43 | id: go 44 | with: 45 | go-version: ${{ matrix.go }} 46 | - 47 | name: Checkout source code 48 | uses: actions/checkout@v4 49 | with: 50 | submodules: true 51 | - 52 | name: Install dependencies 53 | run: brew install libsamplerate 54 | - 55 | name: Compile 56 | run: go build 57 | - 58 | name: Execute Unit Tests 59 | run: go test ./... 60 | 61 | 62 | update_code_coverage: 63 | runs-on: ubuntu-22.04 64 | steps: 65 | - 66 | name: Set up Go 1.21 67 | uses: actions/setup-go@v4 68 | id: go 69 | with: 70 | go-version: 1.21 71 | - 72 | name: Checkout source code 73 | uses: actions/checkout@v4 74 | with: 75 | submodules: true 76 | - 77 | name: Install dependencies 78 | run: | 79 | sudo apt-get install -y libsamplerate0 libsamplerate0-dev 80 | - 81 | name: Execute Unit Tests 82 | run: go test -race -coverprofile=coverage.out -covermode=atomic 83 | - 84 | name: Upload coverage to Codecov 85 | uses: codecov/codecov-action@v3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) [2017], [Tobias Wellnitz] 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libsamplerate binding for Golang 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/dh1tw/gosamplerate)](https://goreportcard.com/report/github.com/dh1tw/gosamplerate) 3 | [![Cross Platform build](https://github.com/dh1tw/gosamplerate/actions/workflows/build.yml/badge.svg)](https://github.com/dh1tw/gosamplerate/actions/workflows/build.yml) 4 | [![codecov](https://codecov.io/gh/dh1tw/gosamplerate/graph/badge.svg?token=5vIJOLA5hI)](https://codecov.io/gh/dh1tw/gosamplerate) 5 | 6 | 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. 7 | 8 | A classical use case is converting audio from a CD sample rate of 44.1kHz to the 48kHz sample rate used by DAT players. 9 | 10 | 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. 11 | 12 | ## API implementations 13 | **gosamplerate** implements the following [libsamplerate](http://www.mega-nerd.com/SRC/index.html) API calls: 14 | 15 | - [Simple API](http://www.mega-nerd.com/SRC/api_simple.html) 16 | - [Full API](http://www.mega-nerd.com/SRC/api_full.html) 17 | - [Most miscellaneous functions](http://www.mega-nerd.com/SRC/api_misc.html) 18 | 19 | not (yet) implemented is: 20 | 21 | - [Callback API](http://www.mega-nerd.com/SRC/api_callback.html) 22 | 23 | ## License 24 | 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/) 25 | 26 | libsamplerate has been [republished in 2016 under the 2-clause BSD license](http://www.mega-nerd.com/SRC/license.html). 27 | 28 | ## How to install samplerate 29 | 30 | Make sure that you have [libsamplerate](http://www.mega-nerd.com/SRC/index.html) installed on your system. 31 | 32 | On Mac or Linux it can be installed conveniently through your distro's packet manager. 33 | 34 | ### Linux: 35 | using **apt** (Ubuntu), **yum** (Centos)...etc. 36 | ```bash 37 | $ sudo apt install libsamplerate0 38 | ``` 39 | 40 | ### MacOS 41 | using [Homebrew](http://brew.sh): 42 | ```bash 43 | $ brew install libsamplerate 44 | ``` 45 | 46 | ### Install gosamplerate 47 | ```bash 48 | $ go get github.com/dh1tw/gosamplerate 49 | ``` 50 | 51 | ## Documentation 52 | The API of **gosamplerate** can be found at [godoc.org](https://godoc.org/github.com/dh1tw/gosamplerate). 53 | The documentation of libsamplerate (necessary in order to fully understand the API) can be found 54 | [here](http://www.mega-nerd.com/SRC/index.html). 55 | 56 | ## Tests & Examples 57 | The test coverage is close to 100%. The tests contain various examples on how to use **gosamplerate**. -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dh1tw/gosamplerate 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /gosamplerate.go: -------------------------------------------------------------------------------- 1 | // Package gosamplerate is a golang binding for libsamplerate (audio sample rate converter) 2 | package gosamplerate 3 | 4 | /* 5 | 6 | #cgo pkg-config: samplerate 7 | 8 | #include 9 | #include 10 | 11 | SRC_DATA *alloc_src_data(float *data_in, float *data_out, 12 | long output_frames, double src_ratio) { 13 | 14 | SRC_DATA *src_data = malloc(sizeof(SRC_DATA)); 15 | src_data->data_in = data_in; 16 | src_data->data_out = data_out; 17 | src_data->output_frames = output_frames; 18 | src_data->src_ratio = src_ratio; 19 | return src_data; 20 | } 21 | 22 | void free_src_data(SRC_DATA *p){ 23 | free(p); 24 | } 25 | 26 | int run_src_simple(float *data_in, float *data_out, 27 | long input_frames, long output_frames, 28 | double src_ratio, int converter_type, int channels, 29 | long *output_frames_gen) { 30 | SRC_DATA src_data; 31 | src_data.data_in = data_in; 32 | src_data.data_out = data_out; 33 | src_data.input_frames = input_frames; 34 | src_data.output_frames = output_frames; 35 | src_data.src_ratio = src_ratio; 36 | int res = src_simple(&src_data, converter_type, channels); 37 | *output_frames_gen = src_data.output_frames_gen; 38 | return res; 39 | } 40 | 41 | */ 42 | import "C" 43 | 44 | import ( 45 | "errors" 46 | "fmt" 47 | "math" 48 | ) 49 | 50 | // Src struct holding the data for the full API 51 | type Src struct { 52 | srcState *C.SRC_STATE 53 | srcData *C.SRC_DATA 54 | channels C.long 55 | inputBuffer []C.float 56 | outputBuffer []C.float 57 | } 58 | 59 | const ( 60 | SRC_SINC_BEST_QUALITY = C.SRC_SINC_BEST_QUALITY 61 | SRC_SINC_MEDIUM_QUALITY = C.SRC_SINC_MEDIUM_QUALITY 62 | SRC_SINC_FASTEST = C.SRC_SINC_FASTEST 63 | SRC_ZERO_ORDER_HOLD = C.SRC_ZERO_ORDER_HOLD 64 | SRC_LINEAR = C.SRC_LINEAR 65 | ) 66 | 67 | //New initializes the converter object and returns a reference to it. 68 | func New(converterType int, channels int, bufferLen int) (Src, error) { 69 | cConverter := C.int(converterType) 70 | cChannels := C.int(channels) 71 | var cErr *C.int 72 | 73 | src_state := C.src_new(cConverter, cChannels, cErr) 74 | if src_state == nil { 75 | return Src{}, errors.New("Could not initialize samplerate converter object") 76 | } 77 | 78 | inputBuffer := make([]C.float, bufferLen) 79 | outputBuffer := make([]C.float, bufferLen) 80 | 81 | cData := C.alloc_src_data( 82 | &inputBuffer[0], 83 | &outputBuffer[0], 84 | C.long(len(outputBuffer)/channels), 85 | 1, 86 | ) 87 | 88 | src := Src{ 89 | srcState: src_state, 90 | srcData: cData, 91 | channels: C.long(cChannels), 92 | inputBuffer: inputBuffer, 93 | outputBuffer: outputBuffer, 94 | } 95 | 96 | return src, nil 97 | } 98 | 99 | // Delete cleans up all internal allocations. 100 | func Delete(src Src) error { 101 | srcState := C.src_delete(src.srcState) 102 | C.free_src_data(src.srcData) 103 | if srcState == nil { 104 | return nil 105 | } 106 | return errors.New("Could not delete object; It did not exist") 107 | } 108 | 109 | // GetChannels gets the current channel count. 110 | func (src *Src) GetChannels() (int, error) { 111 | 112 | // for version < 1.9 113 | return int(src.channels), nil 114 | 115 | // with version 1.9 src_get_channels was added 116 | // cChannels := C.src_get_channels(src.srcState) 117 | // if cChannels < 0 { 118 | // return int(cChannels), errors.New("invalid channel count") 119 | // } 120 | // return int(cChannels), nil 121 | } 122 | 123 | // Reset the internal SRC state. It does not modify the quality settings. 124 | // It does not free any memory allocations. 125 | func (src *Src) Reset() error { 126 | res := C.src_reset(src.srcState) 127 | if res < 0 { 128 | errMsg := fmt.Sprintf("Could not reset samplerate converter state: %s", 129 | Error(int(res))) 130 | return errors.New(errMsg) 131 | } 132 | return nil 133 | } 134 | 135 | // Error Convert the error number into a string. 136 | func Error(errNo int) string { 137 | err := C.src_strerror(C.int(errNo)) 138 | return C.GoString(err) 139 | } 140 | 141 | //ErrorNo return an error number 142 | func (src *Src) ErrorNo() int { 143 | errNo := C.src_error(src.srcState) 144 | return int(errNo) 145 | } 146 | 147 | // Simple converts a single block of samples (one or more channels) in one go. The simple API is less 148 | // capable than the full API (Process()). It must not be used if Audio shall be converted in chunks. For full documentation see: http://www.mega-nerd.com/SRC/api_simple.html 149 | func Simple(dataIn []float32, ratio float64, channels int, converterType int) ([]float32, error) { 150 | if ratio <= 0 { 151 | return nil, fmt.Errorf("Error code: 6; SRC ratio outside [1/256, 256] range.") 152 | } 153 | outputBuffer := make([]float32, len(dataIn)*int(math.Ceil(ratio))) 154 | var outputFramesGen C.long 155 | res := C.run_src_simple( 156 | (*C.float)(&dataIn[0]), 157 | (*C.float)(&outputBuffer[0]), 158 | C.long(len(dataIn)/channels), 159 | C.long(cap(outputBuffer)/channels), 160 | C.double(ratio), 161 | C.int(converterType), 162 | C.int(channels), 163 | &outputFramesGen) 164 | if res != 0 { 165 | errMsg := fmt.Sprintf("Error code: %d; %s", res, Error(int(res))) 166 | return nil, errors.New(errMsg) 167 | } 168 | return outputBuffer[:int(outputFramesGen)*channels], nil 169 | } 170 | 171 | // 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 172 | func (src *Src) Process(dataIn []float32, ratio float64, endOfInput bool) ([]float32, error) { 173 | 174 | inputLength := len(dataIn) 175 | 176 | if inputLength > len(src.inputBuffer) { 177 | return nil, errors.New("data slice is larger than buffer") 178 | } 179 | 180 | // copy data into input buffer 181 | for i, el := range dataIn { 182 | src.inputBuffer[i] = C.float(el) 183 | } 184 | 185 | var cEndOfInput = C.int(0) 186 | if endOfInput { 187 | cEndOfInput = 1 188 | } else { 189 | cEndOfInput = 0 190 | } 191 | 192 | src.srcData.input_frames = C.long(inputLength) / src.channels 193 | src.srcData.end_of_input = cEndOfInput 194 | src.srcData.src_ratio = C.double(ratio) 195 | 196 | res := C.src_process(src.srcState, src.srcData) 197 | 198 | if res != 0 { 199 | errMsg := fmt.Sprintf("Error code: %d; %s", res, Error(int(res))) 200 | return nil, errors.New(errMsg) 201 | } 202 | 203 | output := make([]float32, 0, src.srcData.output_frames_gen) 204 | 205 | // fmt.Println("channels:", src.channels) 206 | // fmt.Println("input frames", cSrcData.input_frames) 207 | // fmt.Println("input used", cSrcData.input_frames_used) 208 | // fmt.Println("output generated", cSrcData.output_frames_gen) 209 | 210 | for i := 0; i < int(src.srcData.output_frames_gen*src.channels); i++ { 211 | output = append(output, float32(src.outputBuffer[i])) 212 | } 213 | 214 | return output, nil 215 | } 216 | 217 | // SetRatio sets the samplerate conversion ratio between input and output samples. 218 | // Normally, when using (src *SRC) Process or the callback, the library will try to smoothly 219 | // transition between the conversion ratio of the last call and the conversion ratio of the next 220 | // call. This function bypasses this behaviour and achieves a step response in the conversion rate. 221 | func (src *Src) SetRatio(ratio float64) error { 222 | 223 | res := C.src_set_ratio(src.srcState, C.double(ratio)) 224 | if res != 0 { 225 | return errors.New(Error(int(res))) 226 | } 227 | return nil 228 | } 229 | 230 | // IsValidRatio returns True is ratio is a valid conversion ratio, False otherwise. 231 | func IsValidRatio(ratio float64) bool { 232 | res := C.src_is_valid_ratio(C.double(ratio)) 233 | if res == 1 { 234 | return true 235 | } 236 | return false 237 | } 238 | 239 | // GetName returns the name of a sample rate converter 240 | func GetName(converter C.int) (string, error) { 241 | cConverterName := C.src_get_name(converter) 242 | if cConverterName == nil { 243 | return "", errors.New("unknown samplerate converter") 244 | } 245 | return C.GoString(cConverterName), nil 246 | } 247 | 248 | // GetDescription returns the description of a sample rate converter 249 | func GetDescription(converter C.int) (string, error) { 250 | cConverterDescription := C.src_get_description(converter) 251 | if cConverterDescription == nil { 252 | return "", errors.New("unknown samplerate converter") 253 | } 254 | return C.GoString(cConverterDescription), nil 255 | } 256 | 257 | // GetVersion returns the version number of libsamplerate 258 | func GetVersion() string { 259 | 260 | cVersion := C.src_get_version() 261 | return C.GoString(cVersion) 262 | } 263 | -------------------------------------------------------------------------------- /gosamplerate_test.go: -------------------------------------------------------------------------------- 1 | package gosamplerate 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestGetConverterName(t *testing.T) { 10 | name, err := GetName(SRC_LINEAR) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | if name != "Linear Interpolator" { 15 | t.Fatal("Unexpected string") 16 | } 17 | } 18 | 19 | func TestGetConverterNameError(t *testing.T) { 20 | _, err := GetName(5) 21 | if err == nil { 22 | t.Fatal("expected Error") 23 | } 24 | if err.Error() != "unknown samplerate converter" { 25 | t.Fatal("unexpected string") 26 | } 27 | } 28 | 29 | func TestGetConverterDescription(t *testing.T) { 30 | desc, err := GetDescription(SRC_LINEAR) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | if desc != "Linear interpolator, very fast, poor quality." { 35 | t.Fatal("Unexpected string") 36 | } 37 | } 38 | 39 | func TestGetConverterDescriptionError(t *testing.T) { 40 | _, err := GetDescription(5) 41 | if err == nil { 42 | t.Fatal("expected Error") 43 | } 44 | if err.Error() != "unknown samplerate converter" { 45 | t.Fatal("unexpected string") 46 | } 47 | } 48 | 49 | func TestGetVersion(t *testing.T) { 50 | version := GetVersion() 51 | if !strings.Contains(version, "libsamplerate-") { 52 | t.Fatal("Unexpected string") 53 | } 54 | } 55 | 56 | func TestInitAndDestroy(t *testing.T) { 57 | channels := 2 58 | src, err := New(SRC_SINC_FASTEST, channels, 100) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | chs, err := src.GetChannels() 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if chs != channels { 68 | t.Fatal("unexpected amount of channels") 69 | } 70 | 71 | err = src.Reset() 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | err = Delete(src) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | } 81 | 82 | func TestInvalidSrcObject(t *testing.T) { 83 | _, err := New(5, 2, 100) 84 | if err == nil { 85 | t.Fatal("expected Error") 86 | } 87 | if err.Error() != "Could not initialize samplerate converter object" { 88 | t.Log("unexpected Error string") 89 | } 90 | } 91 | 92 | func TestSimple(t *testing.T) { 93 | input := []float32{0.1, -0.5, 0.3, 0.4, 0.1} 94 | expectedOutput := []float32{0.1, 0.1, -0.10000001, -0.5, 0.033333343, 0.33333334, 0.4, 0.2} 95 | 96 | output, err := Simple(input, 1.5, 1, SRC_LINEAR) 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | 101 | if !closeEnough(output, expectedOutput) { 102 | t.Log("input", input) 103 | t.Log("output", output) 104 | t.Log("expectedOutput", expectedOutput) 105 | t.Fatal("unexpected output") 106 | } 107 | } 108 | 109 | func TestSimpleLessThanOne(t *testing.T) { 110 | var input []float32 111 | for i := 0; i < 10; i++ { 112 | input = append(input, 0.1, -0.5, 0.3, 0.4, 0.1) 113 | } 114 | expectedOutput := []float32{0.1, -0.5, 0.4, 0.1, 0.3, 0.1, -0.5, 0.4, 0.1, 0.3, 0.1, -0.5, 0.4, 0.1, 0.3, 0.1, -0.5, 0.4, 0.1, 0.3, 0.1, -0.5, 0.4, 0.1, 0.3} 115 | 116 | output, err := Simple(input, 0.5, 1, SRC_LINEAR) 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | 121 | if !reflect.DeepEqual(output, expectedOutput) { 122 | t.Log("input", input) 123 | t.Log("output", output) 124 | t.Fatal("unexpected output") 125 | } 126 | } 127 | 128 | func TestSimpleError(t *testing.T) { 129 | 130 | input := []float32{0.1, 0.9} 131 | var invalidRatio float64 = -5.3 132 | 133 | _, err := Simple(input, invalidRatio, 1, SRC_LINEAR) 134 | if err == nil { 135 | t.Fatal("expected Error") 136 | } 137 | if err.Error() != "Error code: 6; SRC ratio outside [1/256, 256] range." { 138 | t.Log(err.Error()) 139 | t.Fatal("unexpected string") 140 | } 141 | } 142 | 143 | func TestProcess(t *testing.T) { 144 | src, err := New(SRC_LINEAR, 2, 100) 145 | if err != nil { 146 | t.Fatal(err) 147 | } 148 | 149 | input := []float32{0.1, -0.5, 0.2, -0.3} 150 | output, err := src.Process(input, 2.0, false) 151 | if err != nil { 152 | t.Fatal(err) 153 | } 154 | expOutput := []float32{0.1, -0.5, 0.1, -0.5, 0.1, -0.5, 0.15, -0.4} 155 | 156 | if !reflect.DeepEqual(output, expOutput) { 157 | t.Log("input:", input) 158 | t.Log("output:", output) 159 | t.Fatal("unexpected output") 160 | } 161 | 162 | err = Delete(src) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | } 167 | 168 | func TestProcessWithEndOfInputFlagSet(t *testing.T) { 169 | src, err := New(SRC_SINC_FASTEST, 2, 100) 170 | if err != nil { 171 | t.Fatal(err) 172 | } 173 | 174 | input := []float32{0.1, -0.5, 0.2, -0.3} 175 | output, err := src.Process(input, 2.0, true) 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | expOutput := []float32{0.11488709, 180 | -0.46334597, 0.18373828, -0.48996875, 0.1821644, 181 | -0.32879135, 0.10804618, -0.11150829} 182 | 183 | if !reflect.DeepEqual(output, expOutput) { 184 | t.Log("input:", input) 185 | t.Log("output:", output) 186 | t.Fatal("unexpected output") 187 | } 188 | 189 | err = Delete(src) 190 | if err != nil { 191 | t.Fatal(err) 192 | } 193 | } 194 | 195 | func TestProcessDataSliceBiggerThanInputBuffer(t *testing.T) { 196 | src, err := New(SRC_LINEAR, 1, 100) 197 | if err != nil { 198 | t.Fatal(err) 199 | } 200 | 201 | input := make([]float32, 150) 202 | _, err = src.Process(input, 150.0, true) 203 | if err == nil { 204 | t.Fatal("expected Error") 205 | } 206 | if err.Error() != "data slice is larger than buffer" { 207 | t.Log("unexpected Error string") 208 | } 209 | } 210 | 211 | func TestProcessErrorWithInvalidRatio(t *testing.T) { 212 | src, err := New(SRC_LINEAR, 1, 100) 213 | if err != nil { 214 | t.Fatal(err) 215 | } 216 | 217 | input := make([]float32, 100) 218 | _, err = src.Process(input, -5, true) 219 | if err == nil { 220 | t.Fatal("expected Error") 221 | } 222 | if err.Error() != "Error code: 6; SRC ratio outside [1/256, 256] range." { 223 | t.Log(err.Error()) 224 | t.Log("unexpected Error string") 225 | } 226 | } 227 | 228 | func TestGetChannels(t *testing.T) { 229 | channels := 2 230 | src, err := New(SRC_SINC_FASTEST, channels, 100) 231 | if err != nil { 232 | t.Fatal(err) 233 | } 234 | chLength, err := src.GetChannels() 235 | if err != nil { 236 | t.Fatal(err) 237 | } else if chLength != channels { 238 | t.Fatal("unexpected channel length") 239 | } 240 | } 241 | 242 | func TestSetRatio(t *testing.T) { 243 | src, err := New(SRC_LINEAR, 1, 10) 244 | if err != nil { 245 | t.Fatal(err) 246 | } 247 | if err = src.SetRatio(25.0); err != nil { 248 | t.Fatal("unexpected result; should be valid conversion rate") 249 | } 250 | } 251 | 252 | func TestSetRatioInvalid(t *testing.T) { 253 | src, err := New(SRC_LINEAR, 1, 10) 254 | if err != nil { 255 | t.Fatal(err) 256 | } 257 | err = src.SetRatio(-5) 258 | if err == nil { 259 | t.Fatal("expected Error") 260 | } 261 | } 262 | 263 | func TestIsValidRatio(t *testing.T) { 264 | if !IsValidRatio(5) { 265 | t.Fatal("unexpected result; should be valid") 266 | } 267 | 268 | if IsValidRatio(-1) { 269 | t.Fatal("unexpected result; should be invalid") 270 | } 271 | 272 | if IsValidRatio(257) { 273 | t.Fatal("unexpected result; should be invalid") 274 | } 275 | } 276 | 277 | func TestErrors(t *testing.T) { 278 | channels := 2 279 | src, err := New(SRC_SINC_FASTEST, channels, 100) 280 | if err != nil { 281 | t.Fatal(err) 282 | } 283 | 284 | errNo := src.ErrorNo() 285 | if errNo != 0 { 286 | t.Fatal("unexpected error number") 287 | } 288 | 289 | errString := Error(0) 290 | if errString != "No error." { 291 | t.Fatal("unexpected Error string") 292 | } 293 | 294 | err = Delete(src) 295 | if err != nil { 296 | t.Fatal(err) 297 | } 298 | } 299 | 300 | func closeEnough(a, b []float32) bool { 301 | if len(a) != len(b) { 302 | return false 303 | } 304 | for i, v := range a { 305 | if v-b[i] > 0.00001 { 306 | return false 307 | } 308 | } 309 | return true 310 | } 311 | --------------------------------------------------------------------------------