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:
Veikko Sariola
2020-11-08 16:03:10 +02:00
parent e65b08d2b3
commit bcbb5aaf19
12 changed files with 155 additions and 50 deletions

View File

@ -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
} }

View File

@ -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)

View File

@ -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])
} }

View File

@ -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 {

View File

@ -0,0 +1,6 @@
// +build !windows
package bridge
func Init() {
}

12
go4k/bridge/init_win.go Normal file
View 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
}

View File

@ -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 (

View File

@ -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{}},
}}, }},
}, },
} }

View File

@ -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}}}

View File

@ -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{}},
}}, }},
}, },
} }

View File

@ -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

View File

@ -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