Change bridge.go so that there is just SetPatch(...) function, instead of having to SetCommands, SetValues etc.

Now the patch definition in bridge_test.go and test_envelope.asm appear quite similar, so it's clear how they are related.
This commit is contained in:
Veikko Sariola 2020-10-24 16:15:15 +03:00
parent 1abc6f22d5
commit aa133b4606
2 changed files with 135 additions and 93 deletions

View File

@ -1,19 +1,33 @@
package bridge
import "fmt"
import "unsafe"
import "math"
import (
"errors"
"math"
)
// #cgo CFLAGS: -I"${SRCDIR}/../include"
// #cgo LDFLAGS: "${SRCDIR}/../build/src/libsointu.a"
// #include <sointu.h>
import "C"
import "errors"
type SynthState = C.SynthState
// SynthState contains the entire state of sointu sound engine
type SynthState C.SynthState // hide C.Synthstate, explicit cast is still possible if needed
// Opcode is a single byte, representing the virtual machine commands used in Sointu
type Opcode byte
// Unit includes command (filter, oscillator, envelope etc.) and its parameters
type Unit struct {
Command Opcode
Params []byte
}
// Instrument includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument
type Instrument struct {
NumVoices int
Units []Unit
}
var ( // cannot be const as the rhs are not known at compile-time
Add = Opcode(C.su_add_id)
Addp = Opcode(C.su_addp_id)
@ -47,10 +61,12 @@ var ( // cannot be const as the rhs are not known at compile-time
In = Opcode(C.su_in_id)
)
// Stereo returns the stereo version of any (mono or stereo) opcode
func (o Opcode) Stereo() Opcode {
return Opcode(byte(o) | 1) // set lowest bit
}
// Mono returns the mono version of any (mono or stereo) opcode
func (o Opcode) Mono() Opcode {
return Opcode(byte(o) & 0xFE) // clear lowest bit
}
@ -73,38 +89,64 @@ func (s *SynthState) Render(buffer []float32,maxRows int,callback func()) (int,
maxSamples := len(buffer) / 2
remaining := maxSamples
for i := 0; i < maxRows; i++ {
remaining = int(C.su_render_samples(s,C.int(remaining),(*C.float)(&buffer[2*(maxSamples-remaining)])))
if (remaining >= 0) { // values >= 0 mean that row end was reached
remaining = int(C.su_render_samples((*C.SynthState)(s), C.int(remaining), (*C.float)(&buffer[2*(maxSamples-remaining)])))
if remaining >= 0 { // values >= 0 mean that row end was reached
callback()
}
if (remaining <= 0) { // values <= 0 mean that buffer is full, ready to return
break;
if remaining <= 0 { // values <= 0 mean that buffer is full, ready to return
break
}
}
return maxSamples - remaining, nil
}
func (s *SynthState) SetCommands(c [2048]Opcode) {
pk := *((*[2048]C.uchar)(unsafe.Pointer(&c)))
s.Commands = pk
func (s *SynthState) SetPatch(patch []Instrument) error {
totalVoices := 0
commands := make([]Opcode, 0)
values := make([]byte, 0)
for _, instr := range patch {
if len(instr.Units) > 63 {
return errors.New("An instrument can have a maximum of 63 units")
}
func (s *SynthState) SetValues(c [16384]byte) {
pk := *((*[16384]C.uchar)(unsafe.Pointer(&c)))
s.Values = pk
if instr.NumVoices < 1 {
return errors.New("Each instrument must have at least 1 voice")
}
for _, unit := range instr.Units {
commands = append(commands, unit.Command)
values = append(values, unit.Params...)
}
commands = append(commands, Advance)
totalVoices += instr.NumVoices
}
if totalVoices > 32 {
return errors.New("Sointu does not support more than 32 concurrent voices")
}
if len(commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo
return errors.New("The patch would result in more than 2048 commands")
}
if len(values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
return errors.New("The patch would result in more than 16384 values")
}
cs := (*C.SynthState)(s)
for i := range commands {
cs.Commands[i] = (C.uchar)(commands[i])
}
for i := range values {
cs.Values[i] = (C.uchar)(values[i])
}
cs.NumVoices = C.uint(totalVoices)
return nil
}
func (s *SynthState) Trigger(voice int, note int) {
fmt.Printf("Calling Trigger...\n")
s.Synth.Voices[voice] = C.Voice{}
s.Synth.Voices[voice].Note = C.int(note)
fmt.Printf("Returning from Trigger...\n")
cs := (*C.SynthState)(s)
cs.Synth.Voices[voice] = C.Voice{}
cs.Synth.Voices[voice].Note = C.int(note)
}
func (s *SynthState) Release(voice int) {
fmt.Printf("Calling Release...\n")
s.Synth.Voices[voice].Release = 1
fmt.Printf("Returning from Release...\n")
cs := (*C.SynthState)(s)
cs.Synth.Voices[voice].Release = 1
}
func NewSynthState() *SynthState {

View File

@ -3,11 +3,12 @@ package bridge_test
import (
"bytes"
"encoding/binary"
"github.com/vsariola/sointu/bridge"
"io/ioutil"
"path"
"runtime"
"testing"
"github.com/vsariola/sointu/bridge"
)
const BPM = 100
@ -20,20 +21,19 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
// const bufsize = su_max_samples * 2
func TestBridge(t *testing.T) {
commands := [2048]bridge.Opcode{
bridge.Envelope, bridge.Envelope, bridge.Out.Stereo(), bridge.Advance}
values := [16384]byte{64, 64, 64, 80, 128, // envelope 1
95, 64, 64, 80, 128, // envelope 2
128}
s := bridge.NewSynthState()
s.SetCommands(commands)
s.SetValues(values)
s.NumVoices = 1
s.Synth.Voices[0].Note = 64
s.SamplesPerRow = SAMPLES_PER_ROW * 8 // this song is two blocks of 8 rows, release during second
s.SetPatch([]bridge.Instrument{
bridge.Instrument{1, []bridge.Unit{
bridge.Unit{bridge.Envelope, []byte{64, 64, 64, 80, 128}},
bridge.Unit{bridge.Envelope, []byte{95, 64, 64, 80, 128}},
bridge.Unit{bridge.Out.Stereo(), []byte{128}},
}},
})
s.Trigger(0, 64)
s.SamplesPerRow = SAMPLES_PER_ROW * 8 // this song is two blocks of 8 rows, release before second block start
buffer := make([]float32, 2*su_max_samples)
n, err := s.Render(buffer, 2, func() {
s.Synth.Voices[0].Release = 1
s.Release(0)
})
if n < su_max_samples {
t.Fatalf("could not fill the whole buffer, %v samples rendered, %v expected", n, su_max_samples)