diff --git a/go4k/bridge/bridge.go b/go4k/bridge/bridge.go index 6e127c4..031c8fa 100644 --- a/go4k/bridge/bridge.go +++ b/go4k/bridge/bridge.go @@ -3,6 +3,7 @@ package bridge import ( "errors" "fmt" + "strings" "github.com/vsariola/sointu/go4k" ) @@ -49,6 +50,27 @@ var opcodeTable = map[string]opTableEntry{ "in": opTableEntry{C.su_in_id, []string{"channel"}}, } +type RenderError struct { + errcode int +} + +func (e *RenderError) Error() string { + var reasons []string + if e.errcode&0x40 != 0 { + reasons = append(reasons, "FPU stack over/underflow") + } + if e.errcode&0x04 != 0 { + reasons = append(reasons, "FPU divide by zero") + } + if e.errcode&0x01 != 0 { + reasons = append(reasons, "FPU invalid operation") + } + if e.errcode&0x3800 != 0 { + reasons = append(reasons, "FPU stack push/pops are not balanced") + } + return "RenderError: " + strings.Join(reasons, ", ") +} + // Render renders until the buffer is full or the modulated time is reached, whichever // happens first. // Parameters: @@ -74,7 +96,7 @@ func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) { time := C.int(maxtime) errcode := int(C.su_render(synth, (*C.float)(&buffer[0]), &samples, &time)) if errcode > 0 { - return -1, -1, errors.New("RenderTime failed") + return int(samples), int(time), &RenderError{errcode: errcode} } return int(samples), int(time), nil } diff --git a/go4k/bridge/bridge_test.go b/go4k/bridge/bridge_test.go index eafa673..adc5c2b 100644 --- a/go4k/bridge/bridge_test.go +++ b/go4k/bridge/bridge_test.go @@ -67,3 +67,98 @@ func TestBridge(t *testing.T) { } } } + +func TestStackUnderflow(t *testing.T) { + patch := go4k.Patch{ + Instruments: []go4k.Instrument{ + go4k.Instrument{1, []go4k.Unit{ + go4k.Unit{"pop", map[string]int{}}, + }}}, + SampleOffsets: []go4k.SampleOffset{}, + DelayTimes: []int{}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = go4k.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to stack underflow") + } +} + +func TestStackBalancing(t *testing.T) { + patch := go4k.Patch{ + Instruments: []go4k.Instrument{ + go4k.Instrument{1, []go4k.Unit{ + go4k.Unit{"push", map[string]int{}}, + }}}, + SampleOffsets: []go4k.SampleOffset{}, + DelayTimes: []int{}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = go4k.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to unbalanced stack push/pop") + } +} + +func TestStackOverflow(t *testing.T) { + patch := go4k.Patch{ + Instruments: []go4k.Instrument{ + go4k.Instrument{1, []go4k.Unit{ + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + go4k.Unit{"pop", map[string]int{}}, + }}}, + SampleOffsets: []go4k.SampleOffset{}, + DelayTimes: []int{}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = go4k.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to stack overflow, despite balanced push/pops") + } +} + +func TestDivideByZero(t *testing.T) { + patch := go4k.Patch{ + Instruments: []go4k.Instrument{ + go4k.Instrument{1, []go4k.Unit{ + go4k.Unit{"loadval", map[string]int{"value": 128}}, + go4k.Unit{"invgain", map[string]int{"invgain": 0}}, + go4k.Unit{"pop", map[string]int{}}, + }}}, + SampleOffsets: []go4k.SampleOffset{}, + DelayTimes: []int{}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = go4k.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to divide by zero") + } +}