mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-18 13:04:25 -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)
|
tracks := make([]Track, 0)
|
||||||
var patch Patch
|
var patch Patch
|
||||||
var instr Instrument
|
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"
|
paramReg, err := regexp.Compile(`([a-zA-Z]\w*)\s*\(\s*([0-9]+)\s*\)`) // matches FOO(42), groups "FOO" and "42"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -158,6 +160,20 @@ func ParseAsm(reader io.Reader) (*Song, error) {
|
|||||||
instr = Instrument{NumVoices: ints[0], Units: []Unit{}}
|
instr = Instrument{NumVoices: ints[0], Units: []Unit{}}
|
||||||
case "END_INSTRUMENT":
|
case "END_INSTRUMENT":
|
||||||
patch = append(patch, instr)
|
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 {
|
if unittype, ok := unitNameMap[word]; ok {
|
||||||
instrMatch := wordReg.FindStringSubmatch(rest)
|
instrMatch := wordReg.FindStringSubmatch(rest)
|
||||||
@ -214,6 +230,12 @@ func ParseAsm(reader io.Reader) (*Song, error) {
|
|||||||
} else {
|
} else {
|
||||||
parameters["pop"] = 0
|
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}
|
unit := Unit{Type: unittype, Stereo: stereo, Parameters: parameters}
|
||||||
instr.Units = append(instr.Units, unit)
|
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}
|
s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch, SongLength: -1}
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAllAsmFiles(t *testing.T) {
|
func TestAllAsmFiles(t *testing.T) {
|
||||||
|
bridge.Init()
|
||||||
_, myname, _, _ := runtime.Caller(0)
|
_, myname, _, _ := runtime.Caller(0)
|
||||||
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.asm"))
|
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.asm"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -27,9 +28,6 @@ func TestAllAsmFiles(t *testing.T) {
|
|||||||
basename := filepath.Base(filename)
|
basename := filepath.Base(filename)
|
||||||
testname := strings.TrimSuffix(basename, path.Ext(basename))
|
testname := strings.TrimSuffix(basename, path.Ext(basename))
|
||||||
t.Run(testname, func(t *testing.T) {
|
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)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot read the .asm file: %v", filename)
|
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"}},
|
"filter": opTableEntry{C.su_filter_id, []string{"frequency", "resonance"}},
|
||||||
"clip": opTableEntry{C.su_clip_id, []string{}},
|
"clip": opTableEntry{C.su_clip_id, []string{}},
|
||||||
"pan": opTableEntry{C.su_pan_id, []string{"panning"}},
|
"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"}},
|
"compressor": opTableEntry{C.su_compres_id, []string{"attack", "release", "invgain", "threshold", "ratio"}},
|
||||||
"speed": opTableEntry{C.su_speed_id, []string{}},
|
"speed": opTableEntry{C.su_speed_id, []string{}},
|
||||||
"out": opTableEntry{C.su_out_id, []string{"gain"}},
|
"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) {
|
func Synth(patch go4k.Patch) (*C.Synth, error) {
|
||||||
|
s := new(C.Synth)
|
||||||
|
sampleno := 0
|
||||||
|
delaytimeno := 0
|
||||||
totalVoices := 0
|
totalVoices := 0
|
||||||
commands := make([]byte, 0)
|
commands := make([]byte, 0)
|
||||||
values := make([]byte, 0)
|
values := make([]byte, 0)
|
||||||
@ -99,13 +102,31 @@ func Synth(patch go4k.Patch) (*C.Synth, error) {
|
|||||||
}
|
}
|
||||||
commands = append(commands, byte(opCode))
|
commands = append(commands, byte(opCode))
|
||||||
for _, paramname := range val.parameterList {
|
for _, paramname := range val.parameterList {
|
||||||
if pval, ok := unit.Parameters[paramname]; ok {
|
if unit.Type == "delay" && paramname == "delaycount" {
|
||||||
if unit.Type == "delay" && paramname == "count" {
|
if unit.Stereo && len(unit.DelayTimes)%2 != 0 {
|
||||||
pval = pval*2 - 1
|
return nil, errors.New("Stereo delays should have even number of delaytimes")
|
||||||
if val, ok := unit.Parameters["notetracking"]; ok && val == 1 {
|
|
||||||
pval++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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))
|
values = append(values, byte(pval))
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Unit parameter undefined: %v (at instrument %v, unit %v)", paramname, insid, unitid)
|
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
|
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")
|
return nil, errors.New("The patch would result in more than 16384 values")
|
||||||
}
|
}
|
||||||
s := new(C.Synth)
|
|
||||||
for i := range commands {
|
for i := range commands {
|
||||||
s.Commands[i] = (C.uchar)(commands[i])
|
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) {
|
func TestBridge(t *testing.T) {
|
||||||
patch := []go4k.Instrument{
|
patch := []go4k.Instrument{
|
||||||
go4k.Instrument{1, []go4k.Unit{
|
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": 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}},
|
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}},
|
go4k.Unit{"out", true, map[string]int{"gain": 128}, []int{}},
|
||||||
}}}
|
}}}
|
||||||
synth, err := bridge.Synth(patch)
|
synth, err := bridge.Synth(patch)
|
||||||
if err != nil {
|
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
|
Type string
|
||||||
Stereo bool
|
Stereo bool
|
||||||
Parameters map[string]int
|
Parameters map[string]int
|
||||||
|
DelayTimes []int
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -2,12 +2,13 @@ package go4k_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/vsariola/sointu/go4k"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"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{
|
var testSong = go4k.Song{
|
||||||
BPM: 100,
|
BPM: 100,
|
||||||
@ -18,13 +19,13 @@ var testSong = go4k.Song{
|
|||||||
SongLength: 0,
|
SongLength: 0,
|
||||||
Patch: go4k.Patch{
|
Patch: go4k.Patch{
|
||||||
go4k.Instrument{NumVoices: 1, Units: []go4k.Unit{
|
go4k.Instrument{NumVoices: 1, Units: []go4k.Unit{
|
||||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "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}},
|
{"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{}},
|
{"mulp", false, map[string]int{}, []int{}},
|
||||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
{"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}},
|
{"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{}},
|
{"mulp", false, map[string]int{}, []int{}},
|
||||||
{"out", true, map[string]int{"gain": 128}},
|
{"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
|
// const bufsize = su_max_samples * 2
|
||||||
|
|
||||||
func TestPlayer(t *testing.T) {
|
func TestPlayer(t *testing.T) {
|
||||||
patch := go4k.Patch{go4k.Instrument{1, []go4k.Unit{
|
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}},
|
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}},
|
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{}},
|
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}},
|
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}},
|
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{}},
|
go4k.Unit{"mulp", false, map[string]int{}, []int{}},
|
||||||
go4k.Unit{"out", true, map[string]int{"gain": 128}},
|
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}}
|
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}}}
|
tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
|
||||||
|
@ -15,13 +15,13 @@ var defaultSong = go4k.Song{
|
|||||||
SongLength: 0,
|
SongLength: 0,
|
||||||
Patch: go4k.Patch{
|
Patch: go4k.Patch{
|
||||||
go4k.Instrument{NumVoices: 2, Units: []go4k.Unit{
|
go4k.Instrument{NumVoices: 2, Units: []go4k.Unit{
|
||||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "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}},
|
{"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{}},
|
{"mulp", false, map[string]int{}, []int{}},
|
||||||
{"envelope", false, map[string]int{"attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
{"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}},
|
{"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{}},
|
{"mulp", false, map[string]int{}, []int{}},
|
||||||
{"out", true, map[string]int{"gain": 128}},
|
{"out", true, map[string]int{"gain": 128}, []int{}},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef _SOINTU_H
|
#ifndef _SOINTU_H
|
||||||
#define _SOINTU_H
|
#define _SOINTU_H
|
||||||
|
|
||||||
#pragma pack(push,4) // this should be fine for both Go and assembly
|
#pragma pack(push,1) // this should be fine for both Go and assembly
|
||||||
typedef struct Unit {
|
typedef struct Unit {
|
||||||
float State[8];
|
float State[8];
|
||||||
float Ports[8];
|
float Ports[8];
|
||||||
@ -30,9 +30,17 @@ typedef struct SynthWorkspace {
|
|||||||
struct Voice Voices[32];
|
struct Voice Voices[32];
|
||||||
} SynthWorkspace;
|
} SynthWorkspace;
|
||||||
|
|
||||||
|
typedef struct SampleOffset {
|
||||||
|
unsigned int Start;
|
||||||
|
unsigned short LoopStart;
|
||||||
|
unsigned short LoopLength;
|
||||||
|
} SampleOffset;
|
||||||
|
|
||||||
typedef struct Synth {
|
typedef struct Synth {
|
||||||
struct SynthWorkspace SynthWrk;
|
struct SynthWorkspace SynthWrk;
|
||||||
struct DelayWorkspace DelayWrks[64]; // let's keep this as 64 for now, so the delays take 16 meg. If that's too little or too much, we can change this in future.
|
struct DelayWorkspace DelayWrks[64]; // let's keep this as 64 for now, so the delays take 16 meg. If that's too little or too much, we can change this in future.
|
||||||
|
unsigned short DelayTimes[768];
|
||||||
|
struct SampleOffset SampleOffsets[256];
|
||||||
unsigned int RandSeed;
|
unsigned int RandSeed;
|
||||||
unsigned int GlobalTick;
|
unsigned int GlobalTick;
|
||||||
unsigned char Commands[32 * 64];
|
unsigned char Commands[32 * 64];
|
||||||
@ -52,9 +60,7 @@ typedef struct Synth {
|
|||||||
#define CALLCONV // the asm will use honor honor correct x64 ABI on all 64-bit platforms
|
#define CALLCONV // the asm will use honor honor correct x64 ABI on all 64-bit platforms
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef INCLUDE_GMDLS
|
void CALLCONV su_load_gmdls(void);
|
||||||
extern void CALLCONV su_load_gmdls(void);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// int su_render(Synth* synth, float* buffer, int* samples, int* time):
|
// int su_render(Synth* synth, float* buffer, int* samples, int* time):
|
||||||
// Renders samples until 'samples' number of samples are reached or 'time' number of
|
// Renders samples until 'samples' number of samples are reached or 'time' number of
|
||||||
|
35
render.asm
35
render.asm
@ -70,6 +70,7 @@ USE_IN
|
|||||||
%define INCLUDE_SINE
|
%define INCLUDE_SINE
|
||||||
%define INCLUDE_PULSE
|
%define INCLUDE_PULSE
|
||||||
%define INCLUDE_GATE
|
%define INCLUDE_GATE
|
||||||
|
%define INCLUDE_SAMPLES
|
||||||
%define INCLUDE_UNISONS
|
%define INCLUDE_UNISONS
|
||||||
%define INCLUDE_POLYPHONY
|
%define INCLUDE_POLYPHONY
|
||||||
%define INCLUDE_MULTIVOICE_TRACKS
|
%define INCLUDE_MULTIVOICE_TRACKS
|
||||||
@ -80,14 +81,26 @@ USE_IN
|
|||||||
%define INCLUDE_NEGBANDPASS
|
%define INCLUDE_NEGBANDPASS
|
||||||
%define INCLUDE_NEGHIGHPASS
|
%define INCLUDE_NEGHIGHPASS
|
||||||
%define INCLUDE_GLOBAL_SEND
|
%define INCLUDE_GLOBAL_SEND
|
||||||
|
%define INCLUDE_DELAY_NOTETRACKING
|
||||||
|
%define INCLUDE_DELAY_FLOAT_TIME
|
||||||
|
%define INCLUDE_GMDLS
|
||||||
|
|
||||||
%include "sointu/footer.inc"
|
%include "sointu/footer.inc"
|
||||||
|
|
||||||
section .text
|
section .text
|
||||||
|
|
||||||
|
struc su_sampleoff
|
||||||
|
.start resd 1
|
||||||
|
.loopstart resw 1
|
||||||
|
.looplength resw 1
|
||||||
|
.size:
|
||||||
|
endstruc
|
||||||
|
|
||||||
struc su_synth
|
struc su_synth
|
||||||
.synthwrk resb su_synthworkspace.size
|
.synthwrk resb su_synthworkspace.size
|
||||||
.delaywrks resb su_delayline_wrk.size * 64
|
.delaywrks resb su_delayline_wrk.size * 64
|
||||||
|
.delaytimes resw 768
|
||||||
|
.sampleoffs resb su_sampleoff.size * 256
|
||||||
.randseed resd 1
|
.randseed resd 1
|
||||||
.globaltime resd 1
|
.globaltime resd 1
|
||||||
.commands resb 32 * 64
|
.commands resb 32 * 64
|
||||||
@ -125,6 +138,10 @@ EXPORT MANGLE_FUNC(su_render,16)
|
|||||||
push _SI ; push bufsize
|
push _SI ; push bufsize
|
||||||
push _DX ; push bufptr
|
push _DX ; push bufptr
|
||||||
push _CX ; this takes place of the voicetrack
|
push _CX ; this takes place of the voicetrack
|
||||||
|
lea _AX, [_CX + su_synth.sampleoffs]
|
||||||
|
push _AX
|
||||||
|
lea _AX, [_CX + su_synth.delaytimes]
|
||||||
|
push _AX
|
||||||
mov eax, [_CX + su_synth.randseed]
|
mov eax, [_CX + su_synth.randseed]
|
||||||
push _AX ; randseed
|
push _AX ; randseed
|
||||||
mov eax, [_CX + su_synth.globaltime]
|
mov eax, [_CX + su_synth.globaltime]
|
||||||
@ -135,12 +152,12 @@ EXPORT MANGLE_FUNC(su_render,16)
|
|||||||
su_render_samples_loop:
|
su_render_samples_loop:
|
||||||
cmp eax, [_SP] ; if rowtick >= maxtime
|
cmp eax, [_SP] ; if rowtick >= maxtime
|
||||||
jge su_render_samples_time_finish ; goto finish
|
jge su_render_samples_time_finish ; goto finish
|
||||||
mov ecx, [_SP + PTRSIZE*5] ; ecx = buffer length in samples
|
mov ecx, [_SP + PTRSIZE*7] ; ecx = buffer length in samples
|
||||||
cmp [_SP + PTRSIZE*6], ecx ; if samples >= maxsamples
|
cmp [_SP + PTRSIZE*8], ecx ; if samples >= maxsamples
|
||||||
jge su_render_samples_time_finish ; goto finish
|
jge su_render_samples_time_finish ; goto finish
|
||||||
inc eax ; time++
|
inc eax ; time++
|
||||||
inc dword [_SP + PTRSIZE*6] ; samples++
|
inc dword [_SP + PTRSIZE*8] ; samples++
|
||||||
mov _CX, [_SP + PTRSIZE*3]
|
mov _CX, [_SP + PTRSIZE*5]
|
||||||
push _AX ; push rowtick
|
push _AX ; push rowtick
|
||||||
mov eax, [_CX + su_synth.polyphony]
|
mov eax, [_CX + su_synth.polyphony]
|
||||||
push _AX ;polyphony
|
push _AX ;polyphony
|
||||||
@ -154,12 +171,12 @@ su_render_samples_loop:
|
|||||||
call MANGLE_FUNC(su_run_vm,0)
|
call MANGLE_FUNC(su_run_vm,0)
|
||||||
pop _AX
|
pop _AX
|
||||||
pop _AX
|
pop _AX
|
||||||
mov _DI, [_SP + PTRSIZE*5] ; edi containts buffer ptr
|
mov _DI, [_SP + PTRSIZE*7] ; edi containts buffer ptr
|
||||||
mov _CX, [_SP + PTRSIZE*4]
|
mov _CX, [_SP + PTRSIZE*6]
|
||||||
lea _SI, [_CX + su_synth.synthwrk + su_synthworkspace.left]
|
lea _SI, [_CX + su_synth.synthwrk + su_synthworkspace.left]
|
||||||
movsd ; copy left channel to output buffer
|
movsd ; copy left channel to output buffer
|
||||||
movsd ; copy right channel to output buffer
|
movsd ; copy right channel to output buffer
|
||||||
mov [_SP + PTRSIZE*5], _DI ; save back the updated ptr
|
mov [_SP + PTRSIZE*7], _DI ; save back the updated ptr
|
||||||
lea _DI, [_SI-8]
|
lea _DI, [_SI-8]
|
||||||
xor eax, eax
|
xor eax, eax
|
||||||
stosd ; clear left channel so the VM is ready to write them again
|
stosd ; clear left channel so the VM is ready to write them again
|
||||||
@ -170,7 +187,9 @@ su_render_samples_loop:
|
|||||||
su_render_samples_time_finish:
|
su_render_samples_time_finish:
|
||||||
pop _CX
|
pop _CX
|
||||||
pop _BX
|
pop _BX
|
||||||
pop _DX
|
pop _DX
|
||||||
|
pop _CX ; discard delaytimes ptr
|
||||||
|
pop _CX ; discard samplesoffs ptr
|
||||||
pop _CX
|
pop _CX
|
||||||
mov [_CX + su_synth.randseed], edx
|
mov [_CX + su_synth.randseed], edx
|
||||||
mov [_CX + su_synth.globaltime], ebx
|
mov [_CX + su_synth.globaltime], ebx
|
||||||
|
Reference in New Issue
Block a user