diff --git a/go4k/asmformat.go b/go4k/asmformat.go index ebbb886..4145e1a 100644 --- a/go4k/asmformat.go +++ b/go4k/asmformat.go @@ -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 } diff --git a/go4k/asmformat_test.go b/go4k/asmformat_test.go index 550b6bf..ce1dbaf 100644 --- a/go4k/asmformat_test.go +++ b/go4k/asmformat_test.go @@ -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) diff --git a/go4k/bridge/bridge.go b/go4k/bridge/bridge.go index 93b5bfc..74f85ca 100644 --- a/go4k/bridge/bridge.go +++ b/go4k/bridge/bridge.go @@ -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]) } diff --git a/go4k/bridge/bridge_test.go b/go4k/bridge/bridge_test.go index c5a8618..a2ae725 100644 --- a/go4k/bridge/bridge_test.go +++ b/go4k/bridge/bridge_test.go @@ -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 { diff --git a/go4k/bridge/init_nonwin.go b/go4k/bridge/init_nonwin.go new file mode 100644 index 0000000..f23fcd9 --- /dev/null +++ b/go4k/bridge/init_nonwin.go @@ -0,0 +1,6 @@ +// +build !windows + +package bridge + +func Init() { +} diff --git a/go4k/bridge/init_win.go b/go4k/bridge/init_win.go new file mode 100644 index 0000000..b49ee7e --- /dev/null +++ b/go4k/bridge/init_win.go @@ -0,0 +1,12 @@ +// +build windows + +package bridge + +// #cgo CFLAGS: -I"${SRCDIR}/../../include/sointu" +// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a" +// #include +import "C" + +func Init() { + C.su_load_gmdls() // GM.DLS is an windows specific sound bank so samples work currently only on windows +} diff --git a/go4k/go4k.go b/go4k/go4k.go index d8edf19..9e603b5 100644 --- a/go4k/go4k.go +++ b/go4k/go4k.go @@ -10,6 +10,7 @@ type Unit struct { Type string Stereo bool Parameters map[string]int + DelayTimes []int } const ( diff --git a/go4k/song_json_test.go b/go4k/song_json_test.go index 80217d3..3c7b7ee 100644 --- a/go4k/song_json_test.go +++ b/go4k/song_json_test.go @@ -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{}}, }}, }, } diff --git a/go4k/song_test.go b/go4k/song_test.go index b59c7f3..d174b7a 100644 --- a/go4k/song_test.go +++ b/go4k/song_test.go @@ -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}}} diff --git a/go4k/tracker/defaultsong.go b/go4k/tracker/defaultsong.go index 9fe338a..0622be4 100644 --- a/go4k/tracker/defaultsong.go +++ b/go4k/tracker/defaultsong.go @@ -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{}}, }}, }, } diff --git a/include/sointu/sointu.h b/include/sointu/sointu.h index 584d21a..4ef5867 100644 --- a/include/sointu/sointu.h +++ b/include/sointu/sointu.h @@ -1,7 +1,7 @@ #ifndef _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 { float State[8]; float Ports[8]; @@ -30,9 +30,17 @@ typedef struct SynthWorkspace { struct Voice Voices[32]; } SynthWorkspace; +typedef struct SampleOffset { + unsigned int Start; + unsigned short LoopStart; + unsigned short LoopLength; +} SampleOffset; + typedef struct Synth { 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. + unsigned short DelayTimes[768]; + struct SampleOffset SampleOffsets[256]; unsigned int RandSeed; unsigned int GlobalTick; 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 #endif -#ifdef INCLUDE_GMDLS -extern void CALLCONV su_load_gmdls(void); -#endif +void CALLCONV su_load_gmdls(void); // int su_render(Synth* synth, float* buffer, int* samples, int* time): // Renders samples until 'samples' number of samples are reached or 'time' number of diff --git a/render.asm b/render.asm index 87b422c..9bf53de 100644 --- a/render.asm +++ b/render.asm @@ -70,6 +70,7 @@ USE_IN %define INCLUDE_SINE %define INCLUDE_PULSE %define INCLUDE_GATE +%define INCLUDE_SAMPLES %define INCLUDE_UNISONS %define INCLUDE_POLYPHONY %define INCLUDE_MULTIVOICE_TRACKS @@ -80,14 +81,26 @@ USE_IN %define INCLUDE_NEGBANDPASS %define INCLUDE_NEGHIGHPASS %define INCLUDE_GLOBAL_SEND +%define INCLUDE_DELAY_NOTETRACKING +%define INCLUDE_DELAY_FLOAT_TIME +%define INCLUDE_GMDLS %include "sointu/footer.inc" section .text +struc su_sampleoff + .start resd 1 + .loopstart resw 1 + .looplength resw 1 + .size: +endstruc + struc su_synth .synthwrk resb su_synthworkspace.size .delaywrks resb su_delayline_wrk.size * 64 + .delaytimes resw 768 + .sampleoffs resb su_sampleoff.size * 256 .randseed resd 1 .globaltime resd 1 .commands resb 32 * 64 @@ -125,6 +138,10 @@ EXPORT MANGLE_FUNC(su_render,16) push _SI ; push bufsize push _DX ; push bufptr 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] push _AX ; randseed mov eax, [_CX + su_synth.globaltime] @@ -135,12 +152,12 @@ EXPORT MANGLE_FUNC(su_render,16) su_render_samples_loop: cmp eax, [_SP] ; if rowtick >= maxtime jge su_render_samples_time_finish ; goto finish - mov ecx, [_SP + PTRSIZE*5] ; ecx = buffer length in samples - cmp [_SP + PTRSIZE*6], ecx ; if samples >= maxsamples + mov ecx, [_SP + PTRSIZE*7] ; ecx = buffer length in samples + cmp [_SP + PTRSIZE*8], ecx ; if samples >= maxsamples jge su_render_samples_time_finish ; goto finish inc eax ; time++ - inc dword [_SP + PTRSIZE*6] ; samples++ - mov _CX, [_SP + PTRSIZE*3] + inc dword [_SP + PTRSIZE*8] ; samples++ + mov _CX, [_SP + PTRSIZE*5] push _AX ; push rowtick mov eax, [_CX + su_synth.polyphony] push _AX ;polyphony @@ -154,12 +171,12 @@ su_render_samples_loop: call MANGLE_FUNC(su_run_vm,0) pop _AX pop _AX - mov _DI, [_SP + PTRSIZE*5] ; edi containts buffer ptr - mov _CX, [_SP + PTRSIZE*4] + mov _DI, [_SP + PTRSIZE*7] ; edi containts buffer ptr + mov _CX, [_SP + PTRSIZE*6] lea _SI, [_CX + su_synth.synthwrk + su_synthworkspace.left] movsd ; copy left 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] xor eax, eax 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: pop _CX pop _BX - pop _DX + pop _DX + pop _CX ; discard delaytimes ptr + pop _CX ; discard samplesoffs ptr pop _CX mov [_CX + su_synth.randseed], edx mov [_CX + su_synth.globaltime], ebx