mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-19 13:34:34 -04:00
feat: Delays and samples are now working through the bridge.
One should call bridge.Init() once during the initialization of the program to load the static sample table. On linux, bridge.Init() does nothing.
This commit is contained in:
@ -17,6 +17,8 @@ func ParseAsm(reader io.Reader) (*Song, error) {
|
||||
tracks := make([]Track, 0)
|
||||
var patch Patch
|
||||
var instr Instrument
|
||||
var delayTimes []int
|
||||
var sampleOffsets [][]int
|
||||
paramReg, err := regexp.Compile(`([a-zA-Z]\w*)\s*\(\s*([0-9]+)\s*\)`) // matches FOO(42), groups "FOO" and "42"
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -158,6 +160,20 @@ func ParseAsm(reader io.Reader) (*Song, error) {
|
||||
instr = Instrument{NumVoices: ints[0], Units: []Unit{}}
|
||||
case "END_INSTRUMENT":
|
||||
patch = append(patch, instr)
|
||||
case "DELTIME":
|
||||
ints, err := parseNumbers(rest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range ints {
|
||||
delayTimes = append(delayTimes, v)
|
||||
}
|
||||
case "SAMPLE_OFFSET":
|
||||
ints, err := parseNumbers(rest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sampleOffsets = append(sampleOffsets, ints)
|
||||
}
|
||||
if unittype, ok := unitNameMap[word]; ok {
|
||||
instrMatch := wordReg.FindStringSubmatch(rest)
|
||||
@ -214,6 +230,12 @@ func ParseAsm(reader io.Reader) (*Song, error) {
|
||||
} else {
|
||||
parameters["pop"] = 0
|
||||
}
|
||||
} else if unittype == "delay" {
|
||||
if flags["NOTETRACKING"] {
|
||||
parameters["notetracking"] = 1
|
||||
} else {
|
||||
parameters["notetracking"] = 0
|
||||
}
|
||||
}
|
||||
unit := Unit{Type: unittype, Stereo: stereo, Parameters: parameters}
|
||||
instr.Units = append(instr.Units, unit)
|
||||
@ -221,6 +243,26 @@ func ParseAsm(reader io.Reader) (*Song, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range patch {
|
||||
for u := range patch[i].Units {
|
||||
if patch[i].Units[u].Type == "delay" {
|
||||
s := patch[i].Units[u].Parameters["delay"]
|
||||
e := patch[i].Units[u].Parameters["count"]
|
||||
if patch[i].Units[u].Stereo {
|
||||
e *= 2 // stereo delays use 'count' number of delaytimes, but for both channels
|
||||
}
|
||||
patch[i].Units[u].DelayTimes = append(patch[i].Units[u].DelayTimes, delayTimes[s:e]...)
|
||||
delete(patch[i].Units[u].Parameters, "delay")
|
||||
delete(patch[i].Units[u].Parameters, "count")
|
||||
} else if patch[i].Units[u].Type == "oscillator" && patch[i].Units[u].Parameters["type"] == Sample {
|
||||
sampleno := patch[i].Units[u].Parameters["color"]
|
||||
patch[i].Units[u].Parameters["start"] = sampleOffsets[sampleno][0]
|
||||
patch[i].Units[u].Parameters["loopstart"] = sampleOffsets[sampleno][1]
|
||||
patch[i].Units[u].Parameters["looplength"] = sampleOffsets[sampleno][2]
|
||||
delete(patch[i].Units[u].Parameters, "color")
|
||||
}
|
||||
}
|
||||
}
|
||||
s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch, SongLength: -1}
|
||||
return &s, nil
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func TestAllAsmFiles(t *testing.T) {
|
||||
bridge.Init()
|
||||
_, myname, _, _ := runtime.Caller(0)
|
||||
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.asm"))
|
||||
if err != nil {
|
||||
@ -27,9 +28,6 @@ func TestAllAsmFiles(t *testing.T) {
|
||||
basename := filepath.Base(filename)
|
||||
testname := strings.TrimSuffix(basename, path.Ext(basename))
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
if strings.Contains(testname, "delay") || strings.Contains(testname, "sample") {
|
||||
return // delays and samples are not implemented yet in the bridge, so skip them for now
|
||||
}
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read the .asm file: %v", filename)
|
||||
|
@ -34,7 +34,7 @@ var opcodeTable = map[string]opTableEntry{
|
||||
"filter": opTableEntry{C.su_filter_id, []string{"frequency", "resonance"}},
|
||||
"clip": opTableEntry{C.su_clip_id, []string{}},
|
||||
"pan": opTableEntry{C.su_pan_id, []string{"panning"}},
|
||||
"delay": opTableEntry{C.su_delay_id, []string{"pregain", "dry", "feedback", "damp", "damp", "delay", "count"}},
|
||||
"delay": opTableEntry{C.su_delay_id, []string{"pregain", "dry", "feedback", "damp", "delaycount"}},
|
||||
"compressor": opTableEntry{C.su_compres_id, []string{"attack", "release", "invgain", "threshold", "ratio"}},
|
||||
"speed": opTableEntry{C.su_speed_id, []string{}},
|
||||
"out": opTableEntry{C.su_out_id, []string{"gain"}},
|
||||
@ -80,6 +80,9 @@ func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) {
|
||||
}
|
||||
|
||||
func Synth(patch go4k.Patch) (*C.Synth, error) {
|
||||
s := new(C.Synth)
|
||||
sampleno := 0
|
||||
delaytimeno := 0
|
||||
totalVoices := 0
|
||||
commands := make([]byte, 0)
|
||||
values := make([]byte, 0)
|
||||
@ -99,13 +102,31 @@ func Synth(patch go4k.Patch) (*C.Synth, error) {
|
||||
}
|
||||
commands = append(commands, byte(opCode))
|
||||
for _, paramname := range val.parameterList {
|
||||
if pval, ok := unit.Parameters[paramname]; ok {
|
||||
if unit.Type == "delay" && paramname == "count" {
|
||||
pval = pval*2 - 1
|
||||
if val, ok := unit.Parameters["notetracking"]; ok && val == 1 {
|
||||
pval++
|
||||
}
|
||||
if unit.Type == "delay" && paramname == "delaycount" {
|
||||
if unit.Stereo && len(unit.DelayTimes)%2 != 0 {
|
||||
return nil, errors.New("Stereo delays should have even number of delaytimes")
|
||||
}
|
||||
values = append(values, byte(delaytimeno))
|
||||
for _, v := range unit.DelayTimes {
|
||||
s.DelayTimes[delaytimeno] = C.ushort(v)
|
||||
delaytimeno++
|
||||
}
|
||||
count := len(unit.DelayTimes)
|
||||
if unit.Stereo {
|
||||
count /= 2
|
||||
}
|
||||
count = count*2 - 1
|
||||
if unit.Parameters["notetracking"] == 1 {
|
||||
count++
|
||||
}
|
||||
values = append(values, byte(count))
|
||||
} else if unit.Type == "oscillator" && unit.Parameters["type"] == go4k.Sample && paramname == "color" {
|
||||
values = append(values, byte(sampleno))
|
||||
s.SampleOffsets[sampleno].Start = (C.uint)(unit.Parameters["start"])
|
||||
s.SampleOffsets[sampleno].LoopStart = (C.ushort)(unit.Parameters["loopstart"])
|
||||
s.SampleOffsets[sampleno].LoopLength = (C.ushort)(unit.Parameters["looplength"])
|
||||
sampleno++
|
||||
} else if pval, ok := unit.Parameters[paramname]; ok {
|
||||
values = append(values, byte(pval))
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unit parameter undefined: %v (at instrument %v, unit %v)", paramname, insid, unitid)
|
||||
@ -178,7 +199,6 @@ func Synth(patch go4k.Patch) (*C.Synth, error) {
|
||||
if len(values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
|
||||
return nil, errors.New("The patch would result in more than 16384 values")
|
||||
}
|
||||
s := new(C.Synth)
|
||||
for i := range commands {
|
||||
s.Commands[i] = (C.uchar)(commands[i])
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
||||
func TestBridge(t *testing.T) {
|
||||
patch := []go4k.Instrument{
|
||||
go4k.Instrument{1, []go4k.Unit{
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
|
||||
go4k.Unit{"out", true, map[string]int{"gain": 128}},
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}, []int{}},
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}, []int{}},
|
||||
go4k.Unit{"out", true, map[string]int{"gain": 128}, []int{}},
|
||||
}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
|
6
go4k/bridge/init_nonwin.go
Normal file
6
go4k/bridge/init_nonwin.go
Normal file
@ -0,0 +1,6 @@
|
||||
// +build !windows
|
||||
|
||||
package bridge
|
||||
|
||||
func Init() {
|
||||
}
|
12
go4k/bridge/init_win.go
Normal file
12
go4k/bridge/init_win.go
Normal file
@ -0,0 +1,12 @@
|
||||
// +build windows
|
||||
|
||||
package bridge
|
||||
|
||||
// #cgo CFLAGS: -I"${SRCDIR}/../../include/sointu"
|
||||
// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a"
|
||||
// #include <sointu.h>
|
||||
import "C"
|
||||
|
||||
func Init() {
|
||||
C.su_load_gmdls() // GM.DLS is an windows specific sound bank so samples work currently only on windows
|
||||
}
|
@ -10,6 +10,7 @@ type Unit struct {
|
||||
Type string
|
||||
Stereo bool
|
||||
Parameters map[string]int
|
||||
DelayTimes []int
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -2,12 +2,13 @@ package go4k_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/vsariola/sointu/go4k"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/vsariola/sointu/go4k"
|
||||
)
|
||||
|
||||
const expectedMarshaled = "{\"BPM\":100,\"Patterns\":[\"QABEACAAAABLAE4AAAAAAA==\"],\"Tracks\":[{\"NumVoices\":1,\"Sequence\":\"AA==\"}],\"SongLength\":0,\"Patch\":[{\"NumVoices\":1,\"Units\":[{\"Type\":\"envelope\",\"Stereo\":false,\"Parameters\":{\"attack\":32,\"decay\":32,\"gain\":128,\"release\":64,\"sustain\":64}},{\"Type\":\"oscillator\",\"Stereo\":false,\"Parameters\":{\"color\":96,\"detune\":64,\"flags\":64,\"gain\":128,\"phase\":0,\"shape\":64,\"transpose\":64}},{\"Type\":\"mulp\",\"Stereo\":false,\"Parameters\":{}},{\"Type\":\"envelope\",\"Stereo\":false,\"Parameters\":{\"attack\":32,\"decay\":32,\"gain\":128,\"release\":64,\"sustain\":64}},{\"Type\":\"oscillator\",\"Stereo\":false,\"Parameters\":{\"color\":64,\"detune\":64,\"flags\":64,\"gain\":128,\"phase\":64,\"shape\":96,\"transpose\":72}},{\"Type\":\"mulp\",\"Stereo\":false,\"Parameters\":{}},{\"Type\":\"out\",\"Stereo\":true,\"Parameters\":{\"gain\":128}}]}]}"
|
||||
const expectedMarshaled = "{\"BPM\":100,\"Patterns\":[\"QABEACAAAABLAE4AAAAAAA==\"],\"Tracks\":[{\"NumVoices\":1,\"Sequence\":\"AA==\"}],\"SongLength\":0,\"Patch\":[{\"NumVoices\":1,\"Units\":[{\"Type\":\"envelope\",\"Stereo\":false,\"Parameters\":{\"attack\":32,\"decay\":32,\"gain\":128,\"release\":64,\"sustain\":64},\"DelayTimes\":[]},{\"Type\":\"oscillator\",\"Stereo\":false,\"Parameters\":{\"color\":96,\"detune\":64,\"flags\":64,\"gain\":128,\"phase\":0,\"shape\":64,\"transpose\":64},\"DelayTimes\":[]},{\"Type\":\"mulp\",\"Stereo\":false,\"Parameters\":{},\"DelayTimes\":[]},{\"Type\":\"envelope\",\"Stereo\":false,\"Parameters\":{\"attack\":32,\"decay\":32,\"gain\":128,\"release\":64,\"sustain\":64},\"DelayTimes\":[]},{\"Type\":\"oscillator\",\"Stereo\":false,\"Parameters\":{\"color\":64,\"detune\":64,\"flags\":64,\"gain\":128,\"phase\":64,\"shape\":96,\"transpose\":72},\"DelayTimes\":[]},{\"Type\":\"mulp\",\"Stereo\":false,\"Parameters\":{},\"DelayTimes\":[]},{\"Type\":\"out\",\"Stereo\":true,\"Parameters\":{\"gain\":128},\"DelayTimes\":[]}]}]}"
|
||||
|
||||
var testSong = go4k.Song{
|
||||
BPM: 100,
|
||||
@ -18,13 +19,13 @@ var testSong = go4k.Song{
|
||||
SongLength: 0,
|
||||
Patch: go4k.Patch{
|
||||
go4k.Instrument{NumVoices: 1, Units: []go4k.Unit{
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
{"oscillator", false, map[string]int{"transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "flags": 0x40}},
|
||||
{"mulp", false, map[string]int{}},
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
{"oscillator", false, map[string]int{"transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "flags": 0x40}},
|
||||
{"mulp", false, map[string]int{}},
|
||||
{"out", true, map[string]int{"gain": 128}},
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
||||
{"oscillator", false, map[string]int{"transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "flags": 0x40}, []int{}},
|
||||
{"mulp", false, map[string]int{}, []int{}},
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
||||
{"oscillator", false, map[string]int{"transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "flags": 0x40}, []int{}},
|
||||
{"mulp", false, map[string]int{}, []int{}},
|
||||
{"out", true, map[string]int{"gain": 128}, []int{}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
@ -23,14 +23,14 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
||||
// const bufsize = su_max_samples * 2
|
||||
|
||||
func TestPlayer(t *testing.T) {
|
||||
patch := go4k.Patch{go4k.Instrument{1, []go4k.Unit{
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
go4k.Unit{"oscillator", false, map[string]int{"transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}},
|
||||
go4k.Unit{"mulp", false, map[string]int{}},
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
go4k.Unit{"oscillator", false, map[string]int{"transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}},
|
||||
go4k.Unit{"mulp", false, map[string]int{}},
|
||||
go4k.Unit{"out", true, map[string]int{"gain": 128}},
|
||||
patch := []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
||||
go4k.Unit{"oscillator", false, map[string]int{"transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}, []int{}},
|
||||
go4k.Unit{"mulp", false, map[string]int{}, []int{}},
|
||||
go4k.Unit{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
||||
go4k.Unit{"oscillator", false, map[string]int{"transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}, []int{}},
|
||||
go4k.Unit{"mulp", false, map[string]int{}, []int{}},
|
||||
go4k.Unit{"out", true, map[string]int{"gain": 128}, []int{}},
|
||||
}}}
|
||||
patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}
|
||||
tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
|
||||
|
@ -15,13 +15,13 @@ var defaultSong = go4k.Song{
|
||||
SongLength: 0,
|
||||
Patch: go4k.Patch{
|
||||
go4k.Instrument{NumVoices: 2, Units: []go4k.Unit{
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
{"oscillator", false, map[string]int{"transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine}},
|
||||
{"mulp", false, map[string]int{}},
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
{"oscillator", false, map[string]int{"transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine}},
|
||||
{"mulp", false, map[string]int{}},
|
||||
{"out", true, map[string]int{"gain": 128}},
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
||||
{"oscillator", false, map[string]int{"transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine}, []int{}},
|
||||
{"mulp", false, map[string]int{}, []int{}},
|
||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
||||
{"oscillator", false, map[string]int{"transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine}, []int{}},
|
||||
{"mulp", false, map[string]int{}, []int{}},
|
||||
{"out", true, map[string]int{"gain": 128}, []int{}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user