mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
refactor: use [][2] as audio buffers, instead of []float32
Throughout sointu, we assume stereo audiobuffers, but were passing around []float32. This had several issues, including len(buf)/2 and numSamples*2 type of length conversion in many places. Also, it caused one bug in a test case, causing it to succeed when it should have not (the test had +-1 when it should have had +-2). This refactoring makes it impossible to have odd length buffer issues.
This commit is contained in:
parent
bb0d4d6800
commit
38e9007bf8
7
audio.go
7
audio.go
@ -1,9 +1,14 @@
|
|||||||
package sointu
|
package sointu
|
||||||
|
|
||||||
|
// AudioBuffer is a buffer of stereo audio samples of variable length, each
|
||||||
|
// sample represented by a slice of [2]float32. [0] is left channel, [1] is
|
||||||
|
// right
|
||||||
|
type AudioBuffer [][2]float32
|
||||||
|
|
||||||
// AudioOutput represents something where we can send audio e.g. audio output.
|
// AudioOutput represents something where we can send audio e.g. audio output.
|
||||||
// WriteAudio should block if not ready to accept audio e.g. buffer full.
|
// WriteAudio should block if not ready to accept audio e.g. buffer full.
|
||||||
type AudioOutput interface {
|
type AudioOutput interface {
|
||||||
WriteAudio(buffer []float32) error
|
WriteAudio(buffer AudioBuffer) error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@ import (
|
|||||||
//
|
//
|
||||||
// If pcm16 is set to true, the samples in the WAV-file will be 16-bit signed
|
// If pcm16 is set to true, the samples in the WAV-file will be 16-bit signed
|
||||||
// integers; otherwise the samples will be 32-bit floats
|
// integers; otherwise the samples will be 32-bit floats
|
||||||
func Wav(buffer []float32, pcm16 bool) ([]byte, error) {
|
func Wav(buffer AudioBuffer, pcm16 bool) ([]byte, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
wavHeader(len(buffer), pcm16, buf)
|
wavHeader(len(buffer)*2, pcm16, buf)
|
||||||
err := rawToBuffer(buffer, pcm16, buf)
|
err := rawToBuffer(buffer, pcm16, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Wav failed: %v", err)
|
return nil, fmt.Errorf("Wav failed: %v", err)
|
||||||
@ -27,7 +27,7 @@ func Wav(buffer []float32, pcm16 bool) ([]byte, error) {
|
|||||||
//
|
//
|
||||||
// If pcm16 is set to true, the samples will be 16-bit signed integers;
|
// If pcm16 is set to true, the samples will be 16-bit signed integers;
|
||||||
// otherwise the samples will be 32-bit floats
|
// otherwise the samples will be 32-bit floats
|
||||||
func Raw(buffer []float32, pcm16 bool) ([]byte, error) {
|
func Raw(buffer AudioBuffer, pcm16 bool) ([]byte, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
err := rawToBuffer(buffer, pcm16, buf)
|
err := rawToBuffer(buffer, pcm16, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -36,12 +36,13 @@ func Raw(buffer []float32, pcm16 bool) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rawToBuffer(data []float32, pcm16 bool, buf *bytes.Buffer) error {
|
func rawToBuffer(data AudioBuffer, pcm16 bool, buf *bytes.Buffer) error {
|
||||||
var err error
|
var err error
|
||||||
if pcm16 {
|
if pcm16 {
|
||||||
int16data := make([]int16, len(data))
|
int16data := make([][2]int16, len(data))
|
||||||
for i, v := range data {
|
for i, v := range data {
|
||||||
int16data[i] = int16(clamp(int(v*math.MaxInt16), math.MinInt16, math.MaxInt16))
|
int16data[i][0] = int16(clamp(int(v[0]*math.MaxInt16), math.MinInt16, math.MaxInt16))
|
||||||
|
int16data[i][1] = int16(clamp(int(v[1]*math.MaxInt16), math.MinInt16, math.MaxInt16))
|
||||||
}
|
}
|
||||||
err = binary.Write(buf, binary.LittleEndian, int16data)
|
err = binary.Write(buf, binary.LittleEndian, int16data)
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
|
"github.com/vsariola/sointu"
|
||||||
"github.com/vsariola/sointu/cmd"
|
"github.com/vsariola/sointu/cmd"
|
||||||
"github.com/vsariola/sointu/oto"
|
"github.com/vsariola/sointu/oto"
|
||||||
"github.com/vsariola/sointu/tracker"
|
"github.com/vsariola/sointu/tracker"
|
||||||
@ -61,7 +62,7 @@ func main() {
|
|||||||
output := audioContext.Output()
|
output := audioContext.Output()
|
||||||
defer output.Close()
|
defer output.Close()
|
||||||
go func() {
|
go func() {
|
||||||
buf := make([]float32, 2048)
|
buf := make(sointu.AudioBuffer, 1024)
|
||||||
ctx := NullContext{}
|
ctx := NullContext{}
|
||||||
for {
|
for {
|
||||||
player.Process(buf, ctx)
|
player.Process(buf, ctx)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/vsariola/sointu"
|
||||||
"github.com/vsariola/sointu/cmd"
|
"github.com/vsariola/sointu/cmd"
|
||||||
"github.com/vsariola/sointu/tracker"
|
"github.com/vsariola/sointu/tracker"
|
||||||
"github.com/vsariola/sointu/tracker/gioui"
|
"github.com/vsariola/sointu/tracker/gioui"
|
||||||
@ -66,7 +67,7 @@ func init() {
|
|||||||
tracker.SetInstrEnlarged(true) // start the vsti with the instrument editor enlarged
|
tracker.SetInstrEnlarged(true) // start the vsti with the instrument editor enlarged
|
||||||
go tracker.Main()
|
go tracker.Main()
|
||||||
context := VSTIProcessContext{make([]vst2.MIDIEvent, 100), h}
|
context := VSTIProcessContext{make([]vst2.MIDIEvent, 100), h}
|
||||||
buf := make([]float32, 2048)
|
buf := make(sointu.AudioBuffer, 1024)
|
||||||
return vst2.Plugin{
|
return vst2.Plugin{
|
||||||
UniqueID: PLUGIN_ID,
|
UniqueID: PLUGIN_ID,
|
||||||
Version: version,
|
Version: version,
|
||||||
@ -79,13 +80,13 @@ func init() {
|
|||||||
ProcessFloatFunc: func(in, out vst2.FloatBuffer) {
|
ProcessFloatFunc: func(in, out vst2.FloatBuffer) {
|
||||||
left := out.Channel(0)
|
left := out.Channel(0)
|
||||||
right := out.Channel(1)
|
right := out.Channel(1)
|
||||||
if len(buf) < out.Frames*2 {
|
if len(buf) < out.Frames {
|
||||||
buf = append(buf, make([]float32, out.Frames*2-len(buf))...)
|
buf = append(buf, make(sointu.AudioBuffer, out.Frames-len(buf))...)
|
||||||
}
|
}
|
||||||
buf = buf[:out.Frames*2]
|
buf = buf[:out.Frames]
|
||||||
player.Process(buf, &context)
|
player.Process(buf, &context)
|
||||||
for i := 0; i < out.Frames; i++ {
|
for i := 0; i < out.Frames; i++ {
|
||||||
left[i], right[i] = buf[i*2], buf[i*2+1]
|
left[i], right[i] = buf[i][0], buf[i][1]
|
||||||
}
|
}
|
||||||
context.events = context.events[:0]
|
context.events = context.events[:0]
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,8 @@ package oto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/vsariola/sointu"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FloatBufferTo16BitLE is a naive helper method to convert []float32 buffers to
|
// FloatBufferTo16BitLE is a naive helper method to convert []float32 buffers to
|
||||||
@ -9,17 +11,22 @@ import (
|
|||||||
//
|
//
|
||||||
// Appends the encoded bytes into "to" slice, allowing you to preallocate the
|
// Appends the encoded bytes into "to" slice, allowing you to preallocate the
|
||||||
// capacity or just use nil
|
// capacity or just use nil
|
||||||
func FloatBufferTo16BitLE(from []float32, to []byte) []byte {
|
func FloatBufferTo16BitLE(from sointu.AudioBuffer, to []byte) []byte {
|
||||||
for _, v := range from {
|
for _, v := range from {
|
||||||
var uv int16
|
left := to16BitSample(v[0])
|
||||||
if v < -1.0 {
|
right := to16BitSample(v[1])
|
||||||
uv = -math.MaxInt16 // we are a bit lazy: -1.0 is encoded as -32767, as this makes math easier, and -32768 is unused
|
to = append(to, byte(left&255), byte(left>>8), byte(right&255), byte(right>>8))
|
||||||
} else if v > 1.0 {
|
|
||||||
uv = math.MaxInt16
|
|
||||||
} else {
|
|
||||||
uv = int16(v * math.MaxInt16)
|
|
||||||
}
|
|
||||||
to = append(to, byte(uv&255), byte(uv>>8))
|
|
||||||
}
|
}
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert float32 to int16, clamping to min and max
|
||||||
|
func to16BitSample(v float32) int16 {
|
||||||
|
if v < -1.0 {
|
||||||
|
return -math.MaxInt16
|
||||||
|
}
|
||||||
|
if v > 1.0 {
|
||||||
|
return math.MaxInt16
|
||||||
|
}
|
||||||
|
return int16(v * math.MaxInt16)
|
||||||
|
}
|
||||||
|
@ -4,11 +4,12 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vsariola/sointu"
|
||||||
"github.com/vsariola/sointu/oto"
|
"github.com/vsariola/sointu/oto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFloatBufferToBytes(t *testing.T) {
|
func TestFloatBufferToBytes(t *testing.T) {
|
||||||
floats := []float32{0, 0.000489128, 0, 0.0019555532, 0, 0.0043964, 0, 0.007806882, 0, 0.012180306, 0, 0.017508084, 0, 0.023779746, 0, 0.030982954, 0, 0.039103523, 0, 0.04812544, 0, 0.05803088, 0, 0.068800256, 0, 0.08041221, 0, 0.09284368, 0, 0.10606992, 0, 0.120064534, 0, 0.13479951, 0, 0.1502453, 0, 0.16637078, 0, 0.18314338, 0, 0.20052913, 0, 0.21849263, 0, 0.23699719, 0, 0.2560048, 0, 0.27547634, 0, 0.29537144, 0, 0.31564865, 0, 0.33626547, 0, 0.35717854, 0, 0.37834346, 0, 0.39971504, 0, 0.4212474, 0, 0.4428938, 0, 0.46460703, 0, 0.48633927, 0, 0.50804216, 0, 0.52966696, 0, 0.5511646, 0, 0.57248586, 0, 0.5935812, 0, 0.6144009, 0, 0.63489544, 0, 0.6550152, 0, 0.67471063, 0, 0.6939326, 0, 0.712632, 0, 0.7307603, 0, 0.7482692, 0, 0.7651111, 0, 0.7812389}
|
floats := sointu.AudioBuffer{{0, 0.000489128}, {0, 0.0019555532}, {0, 0.0043964}, {0, 0.007806882}, {0, 0.012180306}, {0, 0.017508084}, {0, 0.023779746}, {0, 0.030982954}, {0, 0.039103523}, {0, 0.04812544}, {0, 0.05803088}, {0, 0.068800256}, {0, 0.08041221}, {0, 0.09284368}, {0, 0.10606992}, {0, 0.120064534}, {0, 0.13479951}, {0, 0.1502453}, {0, 0.16637078}, {0, 0.18314338}, {0, 0.20052913}, {0, 0.21849263}, {0, 0.23699719}, {0, 0.2560048}, {0, 0.27547634}, {0, 0.29537144}, {0, 0.31564865}, {0, 0.33626547}, {0, 0.35717854}, {0, 0.37834346}, {0, 0.39971504}, {0, 0.4212474}, {0, 0.4428938}, {0, 0.46460703}, {0, 0.48633927}, {0, 0.50804216}, {0, 0.52966696}, {0, 0.5511646}, {0, 0.57248586}, {0, 0.5935812}, {0, 0.6144009}, {0, 0.63489544}, {0, 0.6550152}, {0, 0.67471063}, {0, 0.6939326}, {0, 0.712632}, {0, 0.7307603}, {0, 0.7482692}, {0, 0.7651111}, {0, 0.7812389}}
|
||||||
bytes := []byte{0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x8f, 0x1, 0x0, 0x0, 0x3d, 0x2, 0x0, 0x0, 0xb, 0x3, 0x0, 0x0, 0xf7, 0x3, 0x0, 0x0, 0x1, 0x5, 0x0, 0x0, 0x28, 0x6, 0x0, 0x0, 0x6d, 0x7, 0x0, 0x0, 0xce, 0x8, 0x0, 0x0, 0x4a, 0xa, 0x0, 0x0, 0xe2, 0xb, 0x0, 0x0, 0x93, 0xd, 0x0, 0x0, 0x5e, 0xf, 0x0, 0x0, 0x40, 0x11, 0x0, 0x0, 0x3b, 0x13, 0x0, 0x0, 0x4b, 0x15, 0x0, 0x0, 0x71, 0x17, 0x0, 0x0, 0xaa, 0x19, 0x0, 0x0, 0xf7, 0x1b, 0x0, 0x0, 0x55, 0x1e, 0x0, 0x0, 0xc4, 0x20, 0x0, 0x0, 0x42, 0x23, 0x0, 0x0, 0xce, 0x25, 0x0, 0x0, 0x66, 0x28, 0x0, 0x0, 0xa, 0x2b, 0x0, 0x0, 0xb7, 0x2d, 0x0, 0x0, 0x6d, 0x30, 0x0, 0x0, 0x29, 0x33, 0x0, 0x0, 0xeb, 0x35, 0x0, 0x0, 0xb0, 0x38, 0x0, 0x0, 0x77, 0x3b, 0x0, 0x0, 0x3f, 0x3e, 0x0, 0x0, 0x7, 0x41, 0x0, 0x0, 0xcb, 0x43, 0x0, 0x0, 0x8c, 0x46, 0x0, 0x0, 0x46, 0x49, 0x0, 0x0, 0xf9, 0x4b, 0x0, 0x0, 0xa4, 0x4e, 0x0, 0x0, 0x43, 0x51, 0x0, 0x0, 0xd6, 0x53, 0x0, 0x0, 0x5c, 0x56, 0x0, 0x0, 0xd2, 0x58, 0x0, 0x0, 0x36, 0x5b, 0x0, 0x0, 0x88, 0x5d, 0x0, 0x0, 0xc6, 0x5f, 0x0, 0x0, 0xee, 0x61, 0x0, 0x0, 0xfe, 0x63}
|
bytes := []byte{0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x8f, 0x1, 0x0, 0x0, 0x3d, 0x2, 0x0, 0x0, 0xb, 0x3, 0x0, 0x0, 0xf7, 0x3, 0x0, 0x0, 0x1, 0x5, 0x0, 0x0, 0x28, 0x6, 0x0, 0x0, 0x6d, 0x7, 0x0, 0x0, 0xce, 0x8, 0x0, 0x0, 0x4a, 0xa, 0x0, 0x0, 0xe2, 0xb, 0x0, 0x0, 0x93, 0xd, 0x0, 0x0, 0x5e, 0xf, 0x0, 0x0, 0x40, 0x11, 0x0, 0x0, 0x3b, 0x13, 0x0, 0x0, 0x4b, 0x15, 0x0, 0x0, 0x71, 0x17, 0x0, 0x0, 0xaa, 0x19, 0x0, 0x0, 0xf7, 0x1b, 0x0, 0x0, 0x55, 0x1e, 0x0, 0x0, 0xc4, 0x20, 0x0, 0x0, 0x42, 0x23, 0x0, 0x0, 0xce, 0x25, 0x0, 0x0, 0x66, 0x28, 0x0, 0x0, 0xa, 0x2b, 0x0, 0x0, 0xb7, 0x2d, 0x0, 0x0, 0x6d, 0x30, 0x0, 0x0, 0x29, 0x33, 0x0, 0x0, 0xeb, 0x35, 0x0, 0x0, 0xb0, 0x38, 0x0, 0x0, 0x77, 0x3b, 0x0, 0x0, 0x3f, 0x3e, 0x0, 0x0, 0x7, 0x41, 0x0, 0x0, 0xcb, 0x43, 0x0, 0x0, 0x8c, 0x46, 0x0, 0x0, 0x46, 0x49, 0x0, 0x0, 0xf9, 0x4b, 0x0, 0x0, 0xa4, 0x4e, 0x0, 0x0, 0x43, 0x51, 0x0, 0x0, 0xd6, 0x53, 0x0, 0x0, 0x5c, 0x56, 0x0, 0x0, 0xd2, 0x58, 0x0, 0x0, 0x36, 0x5b, 0x0, 0x0, 0x88, 0x5d, 0x0, 0x0, 0xc6, 0x5f, 0x0, 0x0, 0xee, 0x61, 0x0, 0x0, 0xfe, 0x63}
|
||||||
converted := oto.FloatBufferTo16BitLE(floats, nil)
|
converted := oto.FloatBufferTo16BitLE(floats, nil)
|
||||||
for i, v := range converted {
|
for i, v := range converted {
|
||||||
@ -23,13 +24,14 @@ func TestFloatBufferToBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFloatBufferToBytesLimits(t *testing.T) {
|
func TestFloatBufferToBytesLimits(t *testing.T) {
|
||||||
floats := []float32{0, 1, -1, 0.999, -0.999}
|
floats := sointu.AudioBuffer{{0, 1}, {-1, 0.999}, {-0.999, 0}}
|
||||||
bytes := []byte{
|
bytes := []byte{
|
||||||
0x0, 0x0,
|
0x0, 0x0,
|
||||||
0xFF, 0x7F, // float 1 = 0x7FFF = 0111111111111111
|
0xFF, 0x7F, // float 1 = 0x7FFF = 0111111111111111
|
||||||
0x01, 0x80, // float -1 = 0x8001 = 1000000000000001
|
0x01, 0x80, // float -1 = 0x8001 = 1000000000000001
|
||||||
0xDE, 0x7F, // float 0.999 = 0x7FDE = 0111111111011110
|
0xDE, 0x7F, // float 0.999 = 0x7FDE = 0111111111011110
|
||||||
0x22, 0x80, // float -0.999 = 0x8022 = 1000000000100010
|
0x22, 0x80, // float -0.999 = 0x8022 = 1000000000100010
|
||||||
|
0x0, 0x0,
|
||||||
}
|
}
|
||||||
converted := oto.FloatBufferTo16BitLE(floats, nil)
|
converted := oto.FloatBufferTo16BitLE(floats, nil)
|
||||||
for i, v := range converted {
|
for i, v := range converted {
|
||||||
|
@ -36,7 +36,7 @@ func (c *OtoContext) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Play implements the audio.Player interface for OtoPlayer
|
// Play implements the audio.Player interface for OtoPlayer
|
||||||
func (o *OtoOutput) WriteAudio(floatBuffer []float32) (err error) {
|
func (o *OtoOutput) WriteAudio(floatBuffer sointu.AudioBuffer) (err error) {
|
||||||
// we reuse the old capacity tmpBuffer by setting its length to zero. then,
|
// we reuse the old capacity tmpBuffer by setting its length to zero. then,
|
||||||
// we save the tmpBuffer so we can reuse it next time
|
// we save the tmpBuffer so we can reuse it next time
|
||||||
o.tmpBuffer = FloatBufferTo16BitLE(floatBuffer, o.tmpBuffer[:0])
|
o.tmpBuffer = FloatBufferTo16BitLE(floatBuffer, o.tmpBuffer[:0])
|
||||||
|
26
synth.go
26
synth.go
@ -10,12 +10,12 @@ import (
|
|||||||
type Synth interface {
|
type Synth interface {
|
||||||
// Render tries to fill a stereo signal buffer with sound from the
|
// Render tries to fill a stereo signal buffer with sound from the
|
||||||
// synthesizer, until either the buffer is full or a given number of
|
// synthesizer, until either the buffer is full or a given number of
|
||||||
// timesteps is advanced. Normally, 1 sample = 1 unit of time, but
|
// timesteps is advanced. Normally, 1 sample = 1 unit of time, but speed
|
||||||
// speed modulations may change this. It returns the number of samples
|
// modulations may change this. It returns the number of samples filled (in
|
||||||
// filled (! in stereo samples, so the buffer will have 2 * sample floats),
|
// stereo samples i.e. number of elements of AudioBuffer filled), the
|
||||||
// the number of sync outputs written, the number of time steps time
|
// number of sync outputs written, the number of time steps time advanced,
|
||||||
// advanced, and a possible error.
|
// and a possible error.
|
||||||
Render(buffer []float32, maxtime int) (sample int, time int, err error)
|
Render(buffer AudioBuffer, maxtime int) (sample int, time int, err error)
|
||||||
|
|
||||||
// Update recompiles a patch, but should maintain as much as possible of its
|
// Update recompiles a patch, but should maintain as much as possible of its
|
||||||
// state as reasonable. For example, filters should keep their state and
|
// state as reasonable. For example, filters should keep their state and
|
||||||
@ -40,12 +40,12 @@ type SynthService interface {
|
|||||||
|
|
||||||
// Render fills an stereo audio buffer using a Synth, disregarding all syncs and
|
// Render fills an stereo audio buffer using a Synth, disregarding all syncs and
|
||||||
// time limits.
|
// time limits.
|
||||||
func Render(synth Synth, buffer []float32) error {
|
func Render(synth Synth, buffer AudioBuffer) error {
|
||||||
s, _, err := synth.Render(buffer, math.MaxInt32)
|
s, _, err := synth.Render(buffer, math.MaxInt32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("sointu.Render failed: %v", err)
|
return fmt.Errorf("sointu.Render failed: %v", err)
|
||||||
}
|
}
|
||||||
if s != len(buffer)/2 {
|
if s != len(buffer) {
|
||||||
return errors.New("in sointu.Render, synth.Render should have filled the whole buffer but did not")
|
return errors.New("in sointu.Render, synth.Render should have filled the whole buffer but did not")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -57,7 +57,7 @@ func Render(synth Synth, buffer []float32) error {
|
|||||||
// created. The default behaviour during runtime rendering is to leave them
|
// created. The default behaviour during runtime rendering is to leave them
|
||||||
// playing, meaning that envelopes start attacking right away unless an explicit
|
// playing, meaning that envelopes start attacking right away unless an explicit
|
||||||
// note release is put to every track.
|
// note release is put to every track.
|
||||||
func Play(synthService SynthService, song Song, release bool) ([]float32, error) {
|
func Play(synthService SynthService, song Song, release bool) (AudioBuffer, error) {
|
||||||
err := song.Validate()
|
err := song.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -75,9 +75,9 @@ func Play(synthService SynthService, song Song, release bool) ([]float32, error)
|
|||||||
for i := range curVoices {
|
for i := range curVoices {
|
||||||
curVoices[i] = song.Score.FirstVoiceForTrack(i)
|
curVoices[i] = song.Score.FirstVoiceForTrack(i)
|
||||||
}
|
}
|
||||||
initialCapacity := song.Score.LengthInRows() * song.SamplesPerRow() * 2
|
initialCapacity := song.Score.LengthInRows() * song.SamplesPerRow()
|
||||||
buffer := make([]float32, 0, initialCapacity)
|
buffer := make(AudioBuffer, 0, initialCapacity)
|
||||||
rowbuffer := make([]float32, song.SamplesPerRow()*2)
|
rowbuffer := make(AudioBuffer, song.SamplesPerRow())
|
||||||
for row := 0; row < song.Score.LengthInRows(); row++ {
|
for row := 0; row < song.Score.LengthInRows(); row++ {
|
||||||
patternRow := row % song.Score.RowsPerPattern
|
patternRow := row % song.Score.RowsPerPattern
|
||||||
pattern := row / song.Score.RowsPerPattern
|
pattern := row / song.Score.RowsPerPattern
|
||||||
@ -116,7 +116,7 @@ func Play(synthService SynthService, song Song, release bool) ([]float32, error)
|
|||||||
return buffer, fmt.Errorf("render failed: %v", err)
|
return buffer, fmt.Errorf("render failed: %v", err)
|
||||||
}
|
}
|
||||||
rowtime += time
|
rowtime += time
|
||||||
buffer = append(buffer, rowbuffer[:samples*2]...)
|
buffer = append(buffer, rowbuffer[:samples]...)
|
||||||
if tries > 100 {
|
if tries > 100 {
|
||||||
return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow)
|
return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow)
|
||||||
}
|
}
|
||||||
|
@ -95,13 +95,13 @@ func NewPlayer(synthService sointu.SynthService, playerMessages chan<- PlayerMes
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Process(buffer []float32, context PlayerProcessContext) {
|
func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext) {
|
||||||
p.processMessages(context)
|
p.processMessages(context)
|
||||||
midi, midiOk := context.NextEvent()
|
midi, midiOk := context.NextEvent()
|
||||||
frame := 0
|
frame := 0
|
||||||
|
|
||||||
if p.recording && p.recordingNoteArrived {
|
if p.recording && p.recordingNoteArrived {
|
||||||
p.recordingFrames += len(buffer) / 2
|
p.recordingFrames += len(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldBuffer := buffer
|
oldBuffer := buffer
|
||||||
@ -110,11 +110,11 @@ func (p *Player) Process(buffer []float32, context PlayerProcessContext) {
|
|||||||
for midiOk && frame >= midi.Frame {
|
for midiOk && frame >= midi.Frame {
|
||||||
if p.recording {
|
if p.recording {
|
||||||
if !p.recordingNoteArrived {
|
if !p.recordingNoteArrived {
|
||||||
p.recordingFrames = len(buffer) / 2
|
p.recordingFrames = len(buffer)
|
||||||
p.recordingNoteArrived = true
|
p.recordingNoteArrived = true
|
||||||
}
|
}
|
||||||
midiTotalFrame := midi
|
midiTotalFrame := midi
|
||||||
midiTotalFrame.Frame = p.recordingFrames - len(buffer)/2
|
midiTotalFrame.Frame = p.recordingFrames - len(buffer)
|
||||||
p.recordingEvents = append(p.recordingEvents, midiTotalFrame)
|
p.recordingEvents = append(p.recordingEvents, midiTotalFrame)
|
||||||
}
|
}
|
||||||
if midi.On {
|
if midi.On {
|
||||||
@ -124,7 +124,7 @@ func (p *Player) Process(buffer []float32, context PlayerProcessContext) {
|
|||||||
}
|
}
|
||||||
midi, midiOk = context.NextEvent()
|
midi, midiOk = context.NextEvent()
|
||||||
}
|
}
|
||||||
framesUntilMidi := len(buffer) / 2
|
framesUntilMidi := len(buffer)
|
||||||
if delta := midi.Frame - frame; midiOk && delta < framesUntilMidi {
|
if delta := midi.Frame - frame; midiOk && delta < framesUntilMidi {
|
||||||
framesUntilMidi = delta
|
framesUntilMidi = delta
|
||||||
}
|
}
|
||||||
@ -138,14 +138,14 @@ func (p *Player) Process(buffer []float32, context PlayerProcessContext) {
|
|||||||
var rendered, timeAdvanced int
|
var rendered, timeAdvanced int
|
||||||
var err error
|
var err error
|
||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
rendered, timeAdvanced, err = p.synth.Render(buffer[:framesUntilMidi*2], timeUntilRowAdvance)
|
rendered, timeAdvanced, err = p.synth.Render(buffer[:framesUntilMidi], timeUntilRowAdvance)
|
||||||
} else {
|
} else {
|
||||||
mx := framesUntilMidi
|
mx := framesUntilMidi
|
||||||
if timeUntilRowAdvance < mx {
|
if timeUntilRowAdvance < mx {
|
||||||
mx = timeUntilRowAdvance
|
mx = timeUntilRowAdvance
|
||||||
}
|
}
|
||||||
for i := 0; i < mx*2; i++ {
|
for i := 0; i < mx; i++ {
|
||||||
buffer[i] = 0
|
buffer[i] = [2]float32{}
|
||||||
}
|
}
|
||||||
rendered = mx
|
rendered = mx
|
||||||
timeAdvanced = mx
|
timeAdvanced = mx
|
||||||
@ -154,7 +154,7 @@ func (p *Player) Process(buffer []float32, context PlayerProcessContext) {
|
|||||||
p.synth = nil
|
p.synth = nil
|
||||||
p.trySend(PlayerCrashMessage{fmt.Errorf("synth.Render: %w", err)})
|
p.trySend(PlayerCrashMessage{fmt.Errorf("synth.Render: %w", err)})
|
||||||
}
|
}
|
||||||
buffer = buffer[rendered*2:]
|
buffer = buffer[rendered:]
|
||||||
frame += rendered
|
frame += rendered
|
||||||
p.rowtime += timeAdvanced
|
p.rowtime += timeAdvanced
|
||||||
for i := range p.samplesSinceEvent {
|
for i := range p.samplesSinceEvent {
|
||||||
|
@ -3,6 +3,8 @@ package tracker
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/vsariola/sointu"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Volume represents an average and peak volume measurement, in decibels. 0 dB =
|
// Volume represents an average and peak volume measurement, in decibels. 0 dB =
|
||||||
@ -25,14 +27,14 @@ type Volume struct {
|
|||||||
//
|
//
|
||||||
// minVolume and maxVolume are hard limits in decibels to prevent negative
|
// minVolume and maxVolume are hard limits in decibels to prevent negative
|
||||||
// infinities for volumes
|
// infinities for volumes
|
||||||
func (v *Volume) Analyze(buffer []float32, tau float64, attack float64, release float64, minVolume float64, maxVolume float64) error {
|
func (v *Volume) Analyze(buffer sointu.AudioBuffer, tau float64, attack float64, release float64, minVolume float64, maxVolume float64) error {
|
||||||
alpha := 1 - math.Exp(-1.0/(tau*44100)) // from https://en.wikipedia.org/wiki/Exponential_smoothing
|
alpha := 1 - math.Exp(-1.0/(tau*44100)) // from https://en.wikipedia.org/wiki/Exponential_smoothing
|
||||||
alphaAttack := 1 - math.Exp(-1.0/(attack*44100))
|
alphaAttack := 1 - math.Exp(-1.0/(attack*44100))
|
||||||
alphaRelease := 1 - math.Exp(-1.0/(release*44100))
|
alphaRelease := 1 - math.Exp(-1.0/(release*44100))
|
||||||
var err error
|
var err error
|
||||||
for j := 0; j < 2; j++ {
|
for j := 0; j < 2; j++ {
|
||||||
for i := 0; i < len(buffer); i += 2 {
|
for i := 0; i < len(buffer); i++ {
|
||||||
sample2 := float64(buffer[i+j] * buffer[i+j])
|
sample2 := float64(buffer[i][j] * buffer[i][j])
|
||||||
if math.IsNaN(sample2) {
|
if math.IsNaN(sample2) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errors.New("NaN detected in master output")
|
err = errors.New("NaN detected in master output")
|
||||||
|
@ -79,15 +79,15 @@ func Synth(patch sointu.Patch, bpm int) (*BridgeSynth, error) {
|
|||||||
// time > maxtime, as it is modulated and the time could advance by 2 or more, so the loop
|
// time > maxtime, as it is modulated and the time could advance by 2 or more, so the loop
|
||||||
// exit condition would fire when the time is already past maxtime.
|
// exit condition would fire when the time is already past maxtime.
|
||||||
// Under no conditions, nsamples >= len(buffer)/2 i.e. guaranteed to never overwrite the buffer.
|
// Under no conditions, nsamples >= len(buffer)/2 i.e. guaranteed to never overwrite the buffer.
|
||||||
func (bridgesynth *BridgeSynth) Render(buffer []float32, maxtime int) (int, int, error) {
|
func (bridgesynth *BridgeSynth) Render(buffer sointu.AudioBuffer, maxtime int) (int, int, error) {
|
||||||
synth := (*C.Synth)(bridgesynth)
|
synth := (*C.Synth)(bridgesynth)
|
||||||
// TODO: syncBuffer is not getting passed to cgo; do we want to even try to support the syncing with the native bridge
|
// TODO: syncBuffer is not getting passed to cgo; do we want to even try to support the syncing with the native bridge
|
||||||
if len(buffer)%1 == 1 {
|
if len(buffer)%1 == 1 {
|
||||||
return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length")
|
return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length")
|
||||||
}
|
}
|
||||||
samples := C.int(len(buffer) / 2)
|
samples := C.int(len(buffer))
|
||||||
time := C.int(maxtime)
|
time := C.int(maxtime)
|
||||||
errcode := int(C.su_render(synth, (*C.float)(&buffer[0]), &samples, &time))
|
errcode := int(C.su_render(synth, (*C.float)(&buffer[0][0]), &samples, &time))
|
||||||
if errcode > 0 {
|
if errcode > 0 {
|
||||||
return int(samples), int(time), &RenderError{errcode: errcode}
|
return int(samples), int(time), &RenderError{errcode: errcode}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func TestRenderSamples(t *testing.T) {
|
|||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
synth.Trigger(0, 64)
|
synth.Trigger(0, 64)
|
||||||
buffer := make([]float32, 2*su_max_samples)
|
buffer := make(sointu.AudioBuffer, su_max_samples)
|
||||||
err = sointu.Render(synth, buffer[:len(buffer)/2])
|
err = sointu.Render(synth, buffer[:len(buffer)/2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("first render gave an error")
|
t.Fatalf("first render gave an error")
|
||||||
@ -96,7 +96,7 @@ func TestAllRegressionTests(t *testing.T) {
|
|||||||
t.Fatalf("could not parse the .yml file: %v", err)
|
t.Fatalf("could not parse the .yml file: %v", err)
|
||||||
}
|
}
|
||||||
buffer, err := sointu.Play(bridge.BridgeService{}, song, false)
|
buffer, err := sointu.Play(bridge.BridgeService{}, song, false)
|
||||||
buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
|
buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()] // extend to the nominal length always.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Play failed: %v", err)
|
t.Fatalf("Play failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -134,7 +134,7 @@ func TestStackUnderflow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
buffer := make([]float32, 2)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = sointu.Render(synth, buffer)
|
err = sointu.Render(synth, buffer)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("rendering should have failed due to stack underflow")
|
t.Fatalf("rendering should have failed due to stack underflow")
|
||||||
@ -150,7 +150,7 @@ func TestStackBalancing(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
buffer := make([]float32, 2)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = sointu.Render(synth, buffer)
|
err = sointu.Render(synth, buffer)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("rendering should have failed due to unbalanced stack push/pop")
|
t.Fatalf("rendering should have failed due to unbalanced stack push/pop")
|
||||||
@ -183,7 +183,7 @@ func TestStackOverflow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
buffer := make([]float32, 2)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = sointu.Render(synth, buffer)
|
err = sointu.Render(synth, buffer)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("rendering should have failed due to stack overflow, despite balanced push/pops")
|
t.Fatalf("rendering should have failed due to stack overflow, despite balanced push/pops")
|
||||||
@ -200,20 +200,20 @@ func TestDivideByZero(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
buffer := make([]float32, 2)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = sointu.Render(synth, buffer)
|
err = sointu.Render(synth, buffer)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("rendering should have failed due to divide by zero")
|
t.Fatalf("rendering should have failed due to divide by zero")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
func compareToRawFloat32(t *testing.T, buffer sointu.AudioBuffer, rawname string) {
|
||||||
_, filename, _, _ := runtime.Caller(0)
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "..", "tests", "expected_output", rawname))
|
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "..", "tests", "expected_output", rawname))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot read expected: %v", err)
|
t.Fatalf("cannot read expected: %v", err)
|
||||||
}
|
}
|
||||||
expected := make([]float32, len(expectedb)/4)
|
expected := make(sointu.AudioBuffer, len(expectedb)/8)
|
||||||
buf := bytes.NewReader(expectedb)
|
buf := bytes.NewReader(expectedb)
|
||||||
err = binary.Read(buf, binary.LittleEndian, &expected)
|
err = binary.Read(buf, binary.LittleEndian, &expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -223,8 +223,10 @@ func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
|||||||
t.Fatalf("buffer length mismatch, got %v, expected %v", len(buffer), len(expected))
|
t.Fatalf("buffer length mismatch, got %v, expected %v", len(buffer), len(expected))
|
||||||
}
|
}
|
||||||
for i, v := range expected {
|
for i, v := range expected {
|
||||||
if math.IsNaN(float64(buffer[i])) || math.Abs(float64(v-buffer[i])) > 1e-6 {
|
for j, s := range v {
|
||||||
t.Fatalf("error bigger than 1e-6 detected, at sample position %v", i)
|
if math.IsNaN(float64(buffer[i][j])) || math.Abs(float64(s-buffer[i][j])) > 1e-6 {
|
||||||
|
t.Fatalf("error bigger than 1e-6 detected, at sample position %v", i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ func (s *Interpreter) Update(patch sointu.Patch, bpm int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time int, renderError error) {
|
func (s *Interpreter) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, time int, renderError error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
renderError = fmt.Errorf("render panicced: %v", err)
|
renderError = fmt.Errorf("render panicced: %v", err)
|
||||||
@ -150,7 +150,7 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
|||||||
stack := s.stack[:]
|
stack := s.stack[:]
|
||||||
stack = append(stack, []float32{0, 0, 0, 0}...)
|
stack = append(stack, []float32{0, 0, 0, 0}...)
|
||||||
synth := &s.synth
|
synth := &s.synth
|
||||||
for time < maxtime && len(buffer) > 1 {
|
for time < maxtime && len(buffer) > 0 {
|
||||||
commandInstr := s.bytePatch.Commands
|
commandInstr := s.bytePatch.Commands
|
||||||
valuesInstr := s.bytePatch.Values
|
valuesInstr := s.bytePatch.Values
|
||||||
commands, values := commandInstr, valuesInstr
|
commands, values := commandInstr, valuesInstr
|
||||||
@ -580,11 +580,10 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
|||||||
if len(stack) > 4 {
|
if len(stack) > 4 {
|
||||||
return samples, time, errors.New("stack not empty")
|
return samples, time, errors.New("stack not empty")
|
||||||
}
|
}
|
||||||
buffer[0] = synth.outputs[0]
|
buffer[0][0], buffer[0][1] = synth.outputs[0], synth.outputs[1]
|
||||||
buffer[1] = synth.outputs[1]
|
|
||||||
synth.outputs[0] = 0
|
synth.outputs[0] = 0
|
||||||
synth.outputs[1] = 0
|
synth.outputs[1] = 0
|
||||||
buffer = buffer[2:]
|
buffer = buffer[1:]
|
||||||
samples++
|
samples++
|
||||||
time++
|
time++
|
||||||
s.synth.globalTime++
|
s.synth.globalTime++
|
||||||
|
@ -18,6 +18,8 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const errorThreshold = 1e-2
|
||||||
|
|
||||||
func TestAllRegressionTests(t *testing.T) {
|
func TestAllRegressionTests(t *testing.T) {
|
||||||
_, myname, _, _ := runtime.Caller(0)
|
_, myname, _, _ := runtime.Caller(0)
|
||||||
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.yml"))
|
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.yml"))
|
||||||
@ -42,7 +44,7 @@ func TestAllRegressionTests(t *testing.T) {
|
|||||||
t.Fatalf("could not parse the .yml file: %v", err)
|
t.Fatalf("could not parse the .yml file: %v", err)
|
||||||
}
|
}
|
||||||
buffer, err := sointu.Play(vm.SynthService{}, song, false)
|
buffer, err := sointu.Play(vm.SynthService{}, song, false)
|
||||||
buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
|
buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()] // extend to the nominal length always.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Play failed: %v", err)
|
t.Fatalf("Play failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -80,7 +82,7 @@ func TestStackUnderflow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
buffer := make([]float32, 2)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = sointu.Render(synth, buffer)
|
err = sointu.Render(synth, buffer)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("rendering should have failed due to stack underflow")
|
t.Fatalf("rendering should have failed due to stack underflow")
|
||||||
@ -96,20 +98,20 @@ func TestStackBalancing(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
buffer := make([]float32, 2)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = sointu.Render(synth, buffer)
|
err = sointu.Render(synth, buffer)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("rendering should have failed due to unbalanced stack push/pop")
|
t.Fatalf("rendering should have failed due to unbalanced stack push/pop")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
func compareToRawFloat32(t *testing.T, buffer sointu.AudioBuffer, rawname string) {
|
||||||
_, filename, _, _ := runtime.Caller(0)
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot read expected: %v", err)
|
t.Fatalf("cannot read expected: %v", err)
|
||||||
}
|
}
|
||||||
expected := make([]float32, len(expectedb)/4)
|
expected := make(sointu.AudioBuffer, len(expectedb)/8)
|
||||||
buf := bytes.NewReader(expectedb)
|
buf := bytes.NewReader(expectedb)
|
||||||
err = binary.Read(buf, binary.LittleEndian, &expected)
|
err = binary.Read(buf, binary.LittleEndian, &expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -121,14 +123,16 @@ func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
|||||||
firsterr := -1
|
firsterr := -1
|
||||||
errs := 0
|
errs := 0
|
||||||
for i, v := range expected[1 : len(expected)-1] {
|
for i, v := range expected[1 : len(expected)-1] {
|
||||||
if math.IsNaN(float64(buffer[i])) || (math.Abs(float64(v-buffer[i])) > 1e-2 &&
|
for j, s := range v {
|
||||||
math.Abs(float64(v-buffer[i+1])) > 1e-2 && math.Abs(float64(v-buffer[i+2])) > 1e-2) {
|
if math.IsNaN(float64(buffer[i][j])) || (math.Abs(float64(s-buffer[i][j])) > errorThreshold &&
|
||||||
errs++
|
math.Abs(float64(s-buffer[i+1][j])) > errorThreshold && math.Abs(float64(s-buffer[i+2][j])) > errorThreshold) {
|
||||||
if firsterr == -1 {
|
errs++
|
||||||
firsterr = i
|
if firsterr == -1 {
|
||||||
}
|
firsterr = i
|
||||||
if errs > 200 { // we are again quite liberal with rounding errors, as different platforms have minor differences in floating point rounding
|
}
|
||||||
t.Fatalf("more than 200 errors bigger than 1e-2 detected, first at sample position %v", firsterr)
|
if errs > 200 { // we are again quite liberal with rounding errors, as different platforms have minor differences in floating point rounding
|
||||||
|
t.Fatalf("more than 200 errors bigger than %v detected, first at sample position %v", errorThreshold, firsterr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user