mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-18 21:14:31 -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
@ -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
|
||||
// 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.
|
||||
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)
|
||||
// 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 {
|
||||
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)
|
||||
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 {
|
||||
return int(samples), int(time), &RenderError{errcode: errcode}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func TestRenderSamples(t *testing.T) {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
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])
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
t.Fatalf("Play failed: %v", err)
|
||||
}
|
||||
@ -134,7 +134,7 @@ func TestStackUnderflow(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
buffer := make(sointu.AudioBuffer, 1)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
t.Fatalf("rendering should have failed due to stack underflow")
|
||||
@ -150,7 +150,7 @@ func TestStackBalancing(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
buffer := make(sointu.AudioBuffer, 1)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
t.Fatalf("rendering should have failed due to unbalanced stack push/pop")
|
||||
@ -183,7 +183,7 @@ func TestStackOverflow(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
buffer := make(sointu.AudioBuffer, 1)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
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 {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
buffer := make(sointu.AudioBuffer, 1)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
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)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "..", "tests", "expected_output", rawname))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
||||
expected := make([]float32, len(expectedb)/4)
|
||||
expected := make(sointu.AudioBuffer, len(expectedb)/8)
|
||||
buf := bytes.NewReader(expectedb)
|
||||
err = binary.Read(buf, binary.LittleEndian, &expected)
|
||||
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))
|
||||
}
|
||||
for i, v := range expected {
|
||||
if math.IsNaN(float64(buffer[i])) || math.Abs(float64(v-buffer[i])) > 1e-6 {
|
||||
t.Fatalf("error bigger than 1e-6 detected, at sample position %v", i)
|
||||
for j, s := range v {
|
||||
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
|
||||
}
|
||||
|
||||
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() {
|
||||
if err := recover(); err != nil {
|
||||
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 = append(stack, []float32{0, 0, 0, 0}...)
|
||||
synth := &s.synth
|
||||
for time < maxtime && len(buffer) > 1 {
|
||||
for time < maxtime && len(buffer) > 0 {
|
||||
commandInstr := s.bytePatch.Commands
|
||||
valuesInstr := s.bytePatch.Values
|
||||
commands, values := commandInstr, valuesInstr
|
||||
@ -580,11 +580,10 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
||||
if len(stack) > 4 {
|
||||
return samples, time, errors.New("stack not empty")
|
||||
}
|
||||
buffer[0] = synth.outputs[0]
|
||||
buffer[1] = synth.outputs[1]
|
||||
buffer[0][0], buffer[0][1] = synth.outputs[0], synth.outputs[1]
|
||||
synth.outputs[0] = 0
|
||||
synth.outputs[1] = 0
|
||||
buffer = buffer[2:]
|
||||
buffer = buffer[1:]
|
||||
samples++
|
||||
time++
|
||||
s.synth.globalTime++
|
||||
|
@ -18,6 +18,8 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const errorThreshold = 1e-2
|
||||
|
||||
func TestAllRegressionTests(t *testing.T) {
|
||||
_, myname, _, _ := runtime.Caller(0)
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
t.Fatalf("Play failed: %v", err)
|
||||
}
|
||||
@ -80,7 +82,7 @@ func TestStackUnderflow(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
buffer := make(sointu.AudioBuffer, 1)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
t.Fatalf("rendering should have failed due to stack underflow")
|
||||
@ -96,20 +98,20 @@ func TestStackBalancing(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
}
|
||||
buffer := make([]float32, 2)
|
||||
buffer := make(sointu.AudioBuffer, 1)
|
||||
err = sointu.Render(synth, buffer)
|
||||
if err == nil {
|
||||
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)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
||||
expected := make([]float32, len(expectedb)/4)
|
||||
expected := make(sointu.AudioBuffer, len(expectedb)/8)
|
||||
buf := bytes.NewReader(expectedb)
|
||||
err = binary.Read(buf, binary.LittleEndian, &expected)
|
||||
if err != nil {
|
||||
@ -121,14 +123,16 @@ func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
||||
firsterr := -1
|
||||
errs := 0
|
||||
for i, v := range expected[1 : len(expected)-1] {
|
||||
if math.IsNaN(float64(buffer[i])) || (math.Abs(float64(v-buffer[i])) > 1e-2 &&
|
||||
math.Abs(float64(v-buffer[i+1])) > 1e-2 && math.Abs(float64(v-buffer[i+2])) > 1e-2) {
|
||||
errs++
|
||||
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)
|
||||
for j, s := range v {
|
||||
if math.IsNaN(float64(buffer[i][j])) || (math.Abs(float64(s-buffer[i][j])) > errorThreshold &&
|
||||
math.Abs(float64(s-buffer[i+1][j])) > errorThreshold && math.Abs(float64(s-buffer[i+2][j])) > errorThreshold) {
|
||||
errs++
|
||||
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 %v detected, first at sample position %v", errorThreshold, firsterr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user