mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
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:
parent
1abc6f22d5
commit
aa133b4606
202
bridge/bridge.go
202
bridge/bridge.go
@ -1,58 +1,74 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
import "unsafe"
|
"errors"
|
||||||
import "math"
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
// #cgo CFLAGS: -I"${SRCDIR}/../include"
|
// #cgo CFLAGS: -I"${SRCDIR}/../include"
|
||||||
// #cgo LDFLAGS: "${SRCDIR}/../build/src/libsointu.a"
|
// #cgo LDFLAGS: "${SRCDIR}/../build/src/libsointu.a"
|
||||||
// #include <sointu.h>
|
// #include <sointu.h>
|
||||||
import "C"
|
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
|
type Opcode byte
|
||||||
|
|
||||||
var ( // cannot be const as the rhs are not known at compile-time
|
// Unit includes command (filter, oscillator, envelope etc.) and its parameters
|
||||||
Add = Opcode(C.su_add_id)
|
type Unit struct {
|
||||||
Addp = Opcode(C.su_addp_id)
|
Command Opcode
|
||||||
Pop = Opcode(C.su_pop_id)
|
Params []byte
|
||||||
Loadnote = Opcode(C.su_loadnote_id)
|
|
||||||
Mul = Opcode(C.su_mul_id)
|
|
||||||
Mulp = Opcode(C.su_mulp_id)
|
|
||||||
Push = Opcode(C.su_push_id)
|
|
||||||
Xch = Opcode(C.su_xch_id)
|
|
||||||
Distort = Opcode(C.su_distort_id)
|
|
||||||
Hold = Opcode(C.su_hold_id)
|
|
||||||
Crush = Opcode(C.su_crush_id)
|
|
||||||
Gain = Opcode(C.su_gain_id)
|
|
||||||
Invgain = Opcode(C.su_invgain_id)
|
|
||||||
Filter = Opcode(C.su_filter_id)
|
|
||||||
Clip = Opcode(C.su_clip_id)
|
|
||||||
Pan = Opcode(C.su_pan_id)
|
|
||||||
Delay = Opcode(C.su_delay_id)
|
|
||||||
Compres = Opcode(C.su_compres_id)
|
|
||||||
Advance = Opcode(C.su_advance_id)
|
|
||||||
Speed = Opcode(C.su_speed_id)
|
|
||||||
Out = Opcode(C.su_out_id)
|
|
||||||
Outaux = Opcode(C.su_outaux_id)
|
|
||||||
Aux = Opcode(C.su_aux_id)
|
|
||||||
Send = Opcode(C.su_send_id)
|
|
||||||
Envelope = Opcode(C.su_envelope_id)
|
|
||||||
Noise = Opcode(C.su_noise_id)
|
|
||||||
Oscillat = Opcode(C.su_oscillat_id)
|
|
||||||
Loadval = Opcode(C.su_loadval_id)
|
|
||||||
Receive = Opcode(C.su_receive_id)
|
|
||||||
In = Opcode(C.su_in_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (o Opcode) Stereo() Opcode {
|
|
||||||
return Opcode(byte(o) | 1) // set lowest bit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
Pop = Opcode(C.su_pop_id)
|
||||||
|
Loadnote = Opcode(C.su_loadnote_id)
|
||||||
|
Mul = Opcode(C.su_mul_id)
|
||||||
|
Mulp = Opcode(C.su_mulp_id)
|
||||||
|
Push = Opcode(C.su_push_id)
|
||||||
|
Xch = Opcode(C.su_xch_id)
|
||||||
|
Distort = Opcode(C.su_distort_id)
|
||||||
|
Hold = Opcode(C.su_hold_id)
|
||||||
|
Crush = Opcode(C.su_crush_id)
|
||||||
|
Gain = Opcode(C.su_gain_id)
|
||||||
|
Invgain = Opcode(C.su_invgain_id)
|
||||||
|
Filter = Opcode(C.su_filter_id)
|
||||||
|
Clip = Opcode(C.su_clip_id)
|
||||||
|
Pan = Opcode(C.su_pan_id)
|
||||||
|
Delay = Opcode(C.su_delay_id)
|
||||||
|
Compres = Opcode(C.su_compres_id)
|
||||||
|
Advance = Opcode(C.su_advance_id)
|
||||||
|
Speed = Opcode(C.su_speed_id)
|
||||||
|
Out = Opcode(C.su_out_id)
|
||||||
|
Outaux = Opcode(C.su_outaux_id)
|
||||||
|
Aux = Opcode(C.su_aux_id)
|
||||||
|
Send = Opcode(C.su_send_id)
|
||||||
|
Envelope = Opcode(C.su_envelope_id)
|
||||||
|
Noise = Opcode(C.su_noise_id)
|
||||||
|
Oscillat = Opcode(C.su_oscillat_id)
|
||||||
|
Loadval = Opcode(C.su_loadval_id)
|
||||||
|
Receive = Opcode(C.su_receive_id)
|
||||||
|
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 {
|
func (o Opcode) Mono() Opcode {
|
||||||
return Opcode(byte(o) & 0xFE) // clear lowest bit
|
return Opcode(byte(o) & 0xFE) // clear lowest bit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render tries to fill the buffer with samples rendered by Sointu.
|
// Render tries to fill the buffer with samples rendered by Sointu.
|
||||||
@ -66,53 +82,79 @@ func (o Opcode) Mono() Opcode {
|
|||||||
// callback called every time a row advances. Won't get called if you have
|
// callback called every time a row advances. Won't get called if you have
|
||||||
// not set SamplesPerRow explicitly.
|
// not set SamplesPerRow explicitly.
|
||||||
// Returns the number samples rendered, len(buffer)/2 in the typical case where buffer was filled
|
// Returns the number samples rendered, len(buffer)/2 in the typical case where buffer was filled
|
||||||
func (s *SynthState) Render(buffer []float32,maxRows int,callback func()) (int, error) {
|
func (s *SynthState) Render(buffer []float32, maxRows int, callback func()) (int, error) {
|
||||||
if len(buffer) % 1 == 1 {
|
if len(buffer)%1 == 1 {
|
||||||
return -1, errors.New("Render writes stereo signals, so buffer should have even length")
|
return -1, errors.New("Render writes stereo signals, so buffer should have even length")
|
||||||
}
|
}
|
||||||
maxSamples := len(buffer) / 2
|
maxSamples := len(buffer) / 2
|
||||||
remaining := maxSamples
|
remaining := maxSamples
|
||||||
for i := 0; i < maxRows; i++ {
|
for i := 0; i < maxRows; i++ {
|
||||||
remaining = int(C.su_render_samples(s,C.int(remaining),(*C.float)(&buffer[2*(maxSamples-remaining)])))
|
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
|
if remaining >= 0 { // values >= 0 mean that row end was reached
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
if (remaining <= 0) { // values <= 0 mean that buffer is full, ready to return
|
if remaining <= 0 { // values <= 0 mean that buffer is full, ready to return
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return maxSamples - remaining, nil
|
return maxSamples - remaining, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SynthState) SetCommands(c [2048]Opcode) {
|
func (s *SynthState) SetPatch(patch []Instrument) error {
|
||||||
pk := *((*[2048]C.uchar)(unsafe.Pointer(&c)))
|
totalVoices := 0
|
||||||
s.Commands = pk
|
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")
|
||||||
|
}
|
||||||
|
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) SetValues(c [16384]byte) {
|
func (s *SynthState) Trigger(voice int, note int) {
|
||||||
pk := *((*[16384]C.uchar)(unsafe.Pointer(&c)))
|
cs := (*C.SynthState)(s)
|
||||||
s.Values = pk
|
cs.Synth.Voices[voice] = C.Voice{}
|
||||||
}
|
cs.Synth.Voices[voice].Note = C.int(note)
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SynthState) Release(voice int) {
|
func (s *SynthState) Release(voice int) {
|
||||||
fmt.Printf("Calling Release...\n")
|
cs := (*C.SynthState)(s)
|
||||||
s.Synth.Voices[voice].Release = 1
|
cs.Synth.Voices[voice].Release = 1
|
||||||
fmt.Printf("Returning from Release...\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSynthState() *SynthState {
|
func NewSynthState() *SynthState {
|
||||||
s := new(SynthState)
|
s := new(SynthState)
|
||||||
s.RandSeed = 1
|
s.RandSeed = 1
|
||||||
// The default behaviour will be to have rows/beats disabled i.e.
|
// The default behaviour will be to have rows/beats disabled i.e.
|
||||||
// fill the whole buffer every call. This is a lot better default
|
// fill the whole buffer every call. This is a lot better default
|
||||||
// behaviour than leaving this 0 (Render would never render anything)
|
// behaviour than leaving this 0 (Render would never render anything)
|
||||||
s.SamplesPerRow = math.MaxInt32
|
s.SamplesPerRow = math.MaxInt32
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,12 @@ package bridge_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/vsariola/sointu/bridge"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vsariola/sointu/bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
const BPM = 100
|
const BPM = 100
|
||||||
@ -20,20 +21,19 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
|||||||
// const bufsize = su_max_samples * 2
|
// const bufsize = su_max_samples * 2
|
||||||
|
|
||||||
func TestBridge(t *testing.T) {
|
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 := bridge.NewSynthState()
|
||||||
s.SetCommands(commands)
|
s.SetPatch([]bridge.Instrument{
|
||||||
s.SetValues(values)
|
bridge.Instrument{1, []bridge.Unit{
|
||||||
s.NumVoices = 1
|
bridge.Unit{bridge.Envelope, []byte{64, 64, 64, 80, 128}},
|
||||||
s.Synth.Voices[0].Note = 64
|
bridge.Unit{bridge.Envelope, []byte{95, 64, 64, 80, 128}},
|
||||||
s.SamplesPerRow = SAMPLES_PER_ROW * 8 // this song is two blocks of 8 rows, release during second
|
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)
|
buffer := make([]float32, 2*su_max_samples)
|
||||||
n,err := s.Render(buffer,2,func() {
|
n, err := s.Render(buffer, 2, func() {
|
||||||
s.Synth.Voices[0].Release = 1
|
s.Release(0)
|
||||||
})
|
})
|
||||||
if n < su_max_samples {
|
if n < su_max_samples {
|
||||||
t.Fatalf("could not fill the whole buffer, %v samples rendered, %v expected", n, su_max_samples)
|
t.Fatalf("could not fill the whole buffer, %v samples rendered, %v expected", n, su_max_samples)
|
||||||
|
Loading…
Reference in New Issue
Block a user