From 8183c698da7ad68e307c84ff9aba47033b546b58 Mon Sep 17 00:00:00 2001 From: Veikko Sariola Date: Wed, 28 Oct 2020 13:44:34 +0200 Subject: [PATCH] Separate Synth and SynthState: SynthState is the part that Render changes. This should make testing easier, as Synth can be assumed to stay the same during each call. Synth is also the part that we can parse from .asm/.json file and a Patch can be compiled into a synth. Synth can be eventually made quite opaque to the user. The user should not need to worry about opcodes etc. --- bridge/bridge.go | 40 +++++----- bridge/bridge_test.go | 19 +++-- include/sointu.h | 43 ++++++----- song/song.go | 10 +-- song/song_test.go | 7 +- src/opcodes/sinks_footer.inc | 16 ++-- src/opcodes/sinks_header.inc | 2 +- src/opcodes/sources_footer.inc | 8 +- src/sointu.asm | 130 +++++++++++++++++--------------- src/sointu_footer.inc | 20 ++--- src/sointu_header.inc | 4 +- tests/test_render_samples.c | 23 ++++-- tests/test_render_samples_api.c | 30 +++++--- 13 files changed, 196 insertions(+), 156 deletions(-) diff --git a/bridge/bridge.go b/bridge/bridge.go index 4be0067..a162039 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -10,6 +10,8 @@ import ( import "C" // SynthState contains the entire state of sointu sound engine +type Synth C.Synth // hide C.Synth, explicit cast is still possible if needed + 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 @@ -87,12 +89,12 @@ func (o Opcode) Mono() Opcode { // buffer float32 slice to fill with rendered samples. Stereo signal, so // should have even length. // Returns an error if something went wrong. -func (s *SynthState) Render(buffer []float32) error { +func (synth *Synth) Render(state *SynthState, buffer []float32) error { if len(buffer)%1 == 1 { return errors.New("Render writes stereo signals, so buffer should have even length") } maxSamples := len(buffer) / 2 - errcode := C.su_render((*C.SynthState)(s), (*C.float)(&buffer[0]), C.int(maxSamples)) + errcode := C.su_render((*C.Synth)(synth), (*C.SynthState)(state), (*C.float)(&buffer[0]), C.int(maxSamples)) if errcode > 0 { return errors.New("Render failed") } @@ -116,30 +118,30 @@ func (s *SynthState) Render(buffer []float32) error { // time > maxtime, as it is modulated and the time could advance by 2 or more, so the loop // exit condition would fire when the time is already past maxtime. // Under no conditions, nsamples >= len(buffer)/2 i.e. guaranteed to never overwrite the buffer. -func (s *SynthState) RenderTime(buffer []float32, maxtime int) (int, int, error) { +func (synth *Synth) RenderTime(state *SynthState, buffer []float32, maxtime int) (int, int, error) { if len(buffer)%1 == 1 { return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length") } samples := C.int(len(buffer) / 2) time := C.int(maxtime) - errcode := int(C.su_render_time((*C.SynthState)(s), (*C.float)(&buffer[0]), &samples, &time)) + errcode := int(C.su_render_time((*C.Synth)(synth), (*C.SynthState)(state), (*C.float)(&buffer[0]), &samples, &time)) if errcode > 0 { return -1, -1, errors.New("RenderTime failed") } return int(samples), int(time), nil } -func (s *SynthState) SetPatch(patch Patch) error { +func Compile(patch Patch) (*Synth, error) { totalVoices := 0 commands := make([]Opcode, 0) values := make([]byte, 0) polyphonyBitmask := 0 for _, instr := range patch { if len(instr.Units) > 63 { - return errors.New("An instrument can have a maximum of 63 units") + return nil, 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") + return nil, errors.New("Each instrument must have at least 1 voice") } for _, unit := range instr.Units { commands = append(commands, unit.Command) @@ -153,35 +155,35 @@ func (s *SynthState) SetPatch(patch Patch) error { polyphonyBitmask <<= 1 } if totalVoices > 32 { - return errors.New("Sointu does not support more than 32 concurrent voices") + return nil, 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") + return nil, 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") + return nil, errors.New("The patch would result in more than 16384 values") } - cs := (*C.SynthState)(s) + s := new(Synth) for i := range commands { - cs.Commands[i] = (C.uchar)(commands[i]) + s.Commands[i] = (C.uchar)(commands[i]) } for i := range values { - cs.Values[i] = (C.uchar)(values[i]) + s.Values[i] = (C.uchar)(values[i]) } - cs.NumVoices = C.uint(totalVoices) - cs.Polyphony = C.uint(polyphonyBitmask) - return nil + s.NumVoices = C.uint(totalVoices) + s.Polyphony = C.uint(polyphonyBitmask) + return s, nil } func (s *SynthState) Trigger(voice int, note byte) { cs := (*C.SynthState)(s) - cs.Synth.Voices[voice] = C.Voice{} - cs.Synth.Voices[voice].Note = C.int(note) + cs.SynthWrk.Voices[voice] = C.Voice{} + cs.SynthWrk.Voices[voice].Note = C.int(note) } func (s *SynthState) Release(voice int) { cs := (*C.SynthState)(s) - cs.Synth.Voices[voice].Release = 1 + cs.SynthWrk.Voices[voice].Release = 1 } func NewSynthState() *SynthState { diff --git a/bridge/bridge_test.go b/bridge/bridge_test.go index 216b1da..0da25b0 100644 --- a/bridge/bridge_test.go +++ b/bridge/bridge_test.go @@ -21,22 +21,25 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS // const bufsize = su_max_samples * 2 func TestBridge(t *testing.T) { - s := bridge.NewSynthState() - s.SetPatch([]bridge.Instrument{ + patch := []bridge.Instrument{ bridge.Instrument{1, []bridge.Unit{ bridge.Unit{bridge.Envelope, []byte{64, 64, 64, 80, 128}}, bridge.Unit{bridge.Envelope, []byte{95, 64, 64, 80, 128}}, bridge.Unit{bridge.Out.Stereo(), []byte{128}}, - }}, - }) - s.Trigger(0, 64) + }}} + synth, err := bridge.Compile(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + state := bridge.NewSynthState() + state.Trigger(0, 64) buffer := make([]float32, 2*su_max_samples) - err := s.Render(buffer[:len(buffer)/2]) + err = synth.Render(state, buffer[:len(buffer)/2]) if err != nil { t.Fatalf("first render gave an error") } - s.Release(0) - err = s.Render(buffer[len(buffer)/2:]) + state.Release(0) + err = synth.Render(state, buffer[len(buffer)/2:]) if err != nil { t.Fatalf("first render gave an error") } diff --git a/include/sointu.h b/include/sointu.h index 74ca9b0..ae7840f 100644 --- a/include/sointu.h +++ b/include/sointu.h @@ -15,14 +15,6 @@ typedef struct Voice { struct Unit Units[63]; } Voice; -typedef struct Synth { - unsigned char Curvoices[32]; - float Left; - float Right; - float Aux[6]; - struct Voice Voices[32]; -} Synth; - typedef struct DelayWorkspace { float Buffer[65536]; float Dcin; @@ -30,16 +22,27 @@ typedef struct DelayWorkspace { float Filtstate; } DelayWorkspace; +typedef struct SynthWorkspace { + unsigned char Curvoices[32]; + float Left; + float Right; + float Aux[6]; + struct Voice Voices[32]; +} SynthWorkspace; + typedef struct SynthState { - struct Synth Synth; - 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 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 int RandSeed; + unsigned int GlobalTick; +} SynthState; + +typedef struct Synth { unsigned char Commands[32 * 64]; unsigned char Values[32 * 64 * 8]; unsigned int Polyphony; unsigned int NumVoices; - unsigned int RandSeed; - unsigned int GlobalTick; -} SynthState; +} Synth; #pragma pack(pop) #if UINTPTR_MAX == 0xffffffff // are we 32-bit? @@ -56,11 +59,12 @@ typedef struct SynthState { extern void CALLCONV su_load_gmdls(void); #endif -// int su_render(SynthState* synthState, float* buffer, int samples): -// Renders 'samples' number of samples to the buffer, using and modifying -// the synthesizer state in synthState. +// int su_render(Synth* synth,SynthState* synthState, float* buffer, int samples): +// Renders 'samples' number of 'samples' to the 'buffer', using 'synth'. +// Modifies 'synthState' and fills the 'buffer'. // // Parameters: +// synth pointer to the synthesizer used. Won't get modified by the call. // synthState pointer to current synthState. RandSeed should be > 0 e.g. 1 // buffer audio sample buffer, L R L R ... // samples maximum number of samples to be rendered. WARNING: buffer @@ -69,15 +73,16 @@ extern void CALLCONV su_load_gmdls(void); // Returns error code: // 0 everything ok // (returns always 0 as no errors are implemented yet) -int CALLCONV su_render(SynthState* synthState, float* buffer, int samples); +int CALLCONV su_render(Synth* synth,SynthState* synthState, float* buffer, int samples); -// int su_render_time(SynthState* synthState, float* buffer, int* samples, int* time): +// int su_render_time(Synth* synth,SynthState* synthState, float* buffer, int* samples, int* time): // Renders samples until 'samples' number of samples are reached or 'time' number of // modulated time ticks are reached, whichever happens first. 'samples' and 'time' are // are passed by reference as the function modifies to tell how many samples were // actually rendered and how many time ticks were actually advanced. // // Parameters: +// synth pointer to the synthesizer used. Won't get modified by the call. // synthState pointer to current synthState. RandSeed should be > 0 e.g. 1 // Also synthState->SamplesPerRow cannot be 0 or nothing will be // rendered; either set it to INT32_MAX to always render full @@ -99,7 +104,7 @@ int CALLCONV su_render(SynthState* synthState, float* buffer, int samples); // Returns error code: // 0 everything ok // (no actual errors implemented yet) -int CALLCONV su_render_time(SynthState* synthState, float* buffer, int* samples, int* time); +int CALLCONV su_render_time(Synth* synth,SynthState* synthState, float* buffer, int* samples, int* time); // Arithmetic opcode ids extern const int su_add_id; diff --git a/song/song.go b/song/song.go index 67ffb1a..b24cc9d 100644 --- a/song/song.go +++ b/song/song.go @@ -86,13 +86,11 @@ func (s *Song) FirstTrackVoice(track int) int { return ret } -func (s *Song) Render() ([]float32, error) { +func (s *Song) Render(synth *bridge.Synth, state *bridge.SynthState) ([]float32, error) { err := s.Validate() if err != nil { return nil, err } - synth := bridge.NewSynthState() - synth.SetPatch(s.Patch) curVoices := make([]int, len(s.Tracks)) for i := range curVoices { curVoices[i] = s.FirstTrackVoice(i) @@ -113,17 +111,17 @@ func (s *Song) Render() ([]float32, error) { if note == 1 { // anything but hold causes an action. continue // TODO: can hold be actually something else than 1? } - synth.Release(curVoices[t]) + state.Release(curVoices[t]) if note > 1 { curVoices[t]++ first := s.FirstTrackVoice(t) if curVoices[t] >= first+s.Tracks[t].NumVoices { curVoices[t] = first } - synth.Trigger(curVoices[t], note) + state.Trigger(curVoices[t], note) } } - samples, _, _ := synth.RenderTime(buffer[2*totaln:], rowtime) + samples, _, _ := synth.RenderTime(state, buffer[2*totaln:], rowtime) totaln += samples } return buffer, nil diff --git a/song/song_test.go b/song/song_test.go index d803f7e..b837d5c 100644 --- a/song/song_test.go +++ b/song/song_test.go @@ -38,7 +38,12 @@ func TestSongRender(t *testing.T) { if err != nil { t.Fatalf("NewSong failed: %v", err) } - buffer, err := song.Render() + synth, err := bridge.Compile(patch) + if err != nil { + t.Fatalf("Compiling patch failed: %v", err) + } + state := bridge.NewSynthState() + buffer, err := song.Render(synth, state) if err != nil { t.Fatalf("Render failed: %v", err) } diff --git a/src/opcodes/sinks_footer.inc b/src/opcodes/sinks_footer.inc index 37c578a..7f28217 100644 --- a/src/opcodes/sinks_footer.inc +++ b/src/opcodes/sinks_footer.inc @@ -17,8 +17,8 @@ EXPORT MANGLE_FUNC(su_op_out,0) ; l r su_op_out_mono: %endif fmul dword [INP + su_out_ports.gain] ; g*l - fadd dword [_AX + su_synth.left] ; g*l+o - fstp dword [_AX + su_synth.left] ; o'=g*l+o + fadd dword [_AX + su_synthworkspace.left] ; g*l+o + fstp dword [_AX + su_synthworkspace.left] ; o'=g*l+o ret %endif ; SU_OUT_ID > -1 @@ -43,11 +43,11 @@ EXPORT MANGLE_FUNC(su_op_outaux,0) ; l r %endif fld st0 ; l l fmul dword [INP + su_outaux_ports.outgain] ; g*l - fadd dword [_AX + su_synth.left] ; g*l+o - fstp dword [_AX + su_synth.left] ; o'=g*l+o + fadd dword [_AX + su_synthworkspace.left] ; g*l+o + fstp dword [_AX + su_synthworkspace.left] ; o'=g*l+o fmul dword [INP + su_outaux_ports.auxgain] ; h*l - fadd dword [_AX + su_synth.aux] ; h*l+a - fstp dword [_AX + su_synth.aux] ; a'=h*l+a + fadd dword [_AX + su_synthworkspace.aux] ; h*l+a + fstp dword [_AX + su_synthworkspace.aux] ; a'=h*l+a ret %endif ; SU_OUTAUX_ID > -1 @@ -72,8 +72,8 @@ EXPORT MANGLE_FUNC(su_op_aux,0) ; l r su_op_aux_mono: %endif fmul dword [INP + su_aux_ports.gain] ; g*l - fadd dword [_DI + su_synth.left + _AX*4] ; g*l+o - fstp dword [_DI + su_synth.left + _AX*4] ; o'=g*l+o + fadd dword [_DI + su_synthworkspace.left + _AX*4] ; g*l+o + fstp dword [_DI + su_synthworkspace.left + _AX*4] ; o'=g*l+o ret %endif ; SU_AUX_ID > -1 diff --git a/src/opcodes/sinks_header.inc b/src/opcodes/sinks_header.inc index 2e10b7c..c0c0b01 100644 --- a/src/opcodes/sinks_header.inc +++ b/src/opcodes/sinks_header.inc @@ -114,7 +114,7 @@ endstruc %define AMOUNT(val) val %define LOCALPORT(unit,port) ((unit+1)*su_unit.size + su_unit.ports)/4 + port -%define GLOBALPORT(voice,unit,port) SEND_GLOBAL + (su_synth.voices+voice*su_voice.size+su_voice.workspace+unit*su_unit.size + su_unit.ports)/4 + port +%define GLOBALPORT(voice,unit,port) SEND_GLOBAL + (su_synthworkspace.voices+voice*su_voice.size+su_voice.workspace+unit*su_unit.size + su_unit.ports)/4 + port %define OUTPORT 0 %define SEND_POP 0x8000 %define SEND_GLOBAL 0x4000 diff --git a/src/opcodes/sources_footer.inc b/src/opcodes/sources_footer.inc index c645d0f..8430aac 100644 --- a/src/opcodes/sources_footer.inc +++ b/src/opcodes/sources_footer.inc @@ -406,13 +406,13 @@ EXPORT MANGLE_FUNC(su_op_in,0) sub _DI, 4 su_op_in_right: xor ecx, ecx - fld dword [_DI + su_synth.right + _AX*4] - mov dword [_DI + su_synth.right + _AX*4], ecx + fld dword [_DI + su_synthworkspace.right + _AX*4] + mov dword [_DI + su_synthworkspace.right + _AX*4], ecx %else xor ecx, ecx mov _DI, [_SP + su_stack.synth] - fld dword [_DI + su_synth.left + _AX*4] - mov dword [_DI + su_synth.left + _AX*4], ecx + fld dword [_DI + su_synthworkspace.left + _AX*4] + mov dword [_DI + su_synthworkspace.left + _AX*4], ecx %endif ret diff --git a/src/sointu.asm b/src/sointu.asm index 790ba9d..dd877d9 100644 --- a/src/sointu.asm +++ b/src/sointu.asm @@ -26,37 +26,50 @@ USE_OUT section .text struc su_synth_state - .synth resb su_synth.size - .delaywrks resb su_delayline_wrk.size * 64 - .commands resb 32 * 64 - .values resb 32 * 64 * 8 - .polyphony resd 1 - .numvoices resd 1 + .synthwrk resb su_synthworkspace.size + .delaywrks resb su_delayline_wrk.size * 64 .randseed resd 1 .globaltime resd 1 endstruc +struc su_synth + .commands resb 32 * 64 + .values resb 32 * 64 * 8 + .polyphony resd 1 + .numvoices resd 1 +endstruc + SECT_TEXT(sursampl) -EXPORT MANGLE_FUNC(su_render_time,16) +EXPORT MANGLE_FUNC(su_render_time,20) %if BITS == 32 ; stdcall - pushad ; push registers - mov ecx, [esp + 4 + 32] ; ecx = &synthState - mov edx, [esp + 8 + 32] ; edx = &buffer - mov esi, [esp + 12 + 32] ; esi = &samples - mov ebx, [esp + 16 + 32] ; ebx = &time + pushad ; push registers + mov eax, [esp + 4 + 32] ; eax = &synth + mov ecx, [esp + 8 + 32] ; ecx = &synthState + mov edx, [esp + 12 + 32] ; edx = &buffer + mov esi, [esp + 16 + 32] ; esi = &samples + mov ebx, [esp + 20 + 32] ; ebx = &time %else - %ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rcx = &synthstate, rdx = &buffer, r8 = &bufsize, r9 = &time + %ifidn __OUTPUT_FORMAT__,win64 + ; win64 ABI parameter order: RCX, RDX, R8, R9; rest in stack (right-to-left, note shadow space!) + ; here: rcx = &synth, rdx = &synthstate, r8 = &buffer, r9 = &bufsize, stack1 = &time push_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile - mov rsi, r8 ; rsi = &samples - mov rbx, r9 ; rbx = &time - %else ; System V ABI: rdi = &synthstate, rsi = &buffer, rdx = &samples, rcx = &time + mov rbx, [rsp + 0x28 + PUSH_REG_SIZE(4)] ; rbx = &time, note the shadow space so &time is at rsp + 0x28 + mov rax, rcx + mov rcx, rdx + mov rdx, r8 + mov rsi, r9 + %else + ; System V ABI parameter order: RDI, RSI, RDX, RCX, R8, R9; rest in stack (right-to-left) + ; here: rdi = &synth, rsi = &synthstate, rdx = &buffer, rcx = &samples, r8 = &time push_registers rbx, rbp ; System V ABI: these registers are non-volatile - mov rbx, rcx ; rbx points to time - xchg rsi, rdx ; rdx points to buffer, rsi points to samples - mov rcx, rdi ; rcx = &Synthstate + mov rax, rdi + mov rbx, r8 + xchg rcx, rsi %endif %endif + ; _AX = &synth, _BX == &time, _CX == &synthstate, _DX == &buf, _SI == &samples, + push _AX ; push the pointer to synth to stack push _SI ; push the pointer to samples push _BX ; push the pointer to time xor eax, eax ; samplenumber starts at 0 @@ -80,23 +93,24 @@ su_render_samples_loop: jge su_render_samples_time_finish ; goto finish inc eax ; time++ inc dword [_SP + PTRSIZE*6] ; samples++ - mov _CX, [_SP + PTRSIZE*3] + mov _CX, [_SP + PTRSIZE*3] ; _CX = &synthstate + mov _BP, [_SP + PTRSIZE*9] ; _BP = &synth push _AX ; push rowtick - mov eax, [_CX + su_synth_state.polyphony] + mov eax, [_BP + su_synth.polyphony] push _AX ;polyphony - mov eax, [_CX + su_synth_state.numvoices] + mov eax, [_BP + su_synth.numvoices] push _AX ;numvoices - lea _DX, [_CX+ su_synth_state.synth] - lea COM, [_CX+ su_synth_state.commands] - lea VAL, [_CX+ su_synth_state.values] - lea WRK, [_DX + su_synth.voices] - lea _CX, [_CX+ su_synth_state.delaywrks - su_delayline_wrk.filtstate] + lea _DX, [_CX + su_synth_state.synthwrk] + lea COM, [_BP + su_synth.commands] + lea VAL, [_BP + su_synth.values] + lea WRK, [_DX + su_synthworkspace.voices] ; _BP and WRK are actually the same thing so here _BP gets overwritten + lea _CX, [_CX + su_synth_state.delaywrks - su_delayline_wrk.filtstate] 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] - lea _SI, [_CX + su_synth_state.synth + su_synth.left] + lea _SI, [_CX + su_synth_state.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 @@ -121,11 +135,12 @@ su_render_samples_time_finish: pop _SI ; pop the pointer to samples mov dword [_SI], edx ; *samples = samples rendered mov dword [_BX], eax ; *time = time ticks rendered + pop _AX ; pop the synth pointer xor eax, eax ; TODO: set eax to possible error code, now just 0 %if BITS == 32 ; stdcall mov [_SP + 28],eax ; we want to return eax, but popad pops everything, so put eax to stack for popad to pop popad - ret 16 + ret 20 %else %ifidn __OUTPUT_FORMAT__,win64 pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile @@ -135,47 +150,44 @@ su_render_samples_time_finish: ret %endif -EXPORT MANGLE_FUNC(su_render,12) +EXPORT MANGLE_FUNC(su_render,16) %if BITS == 32 ; stdcall mov eax, 0x7FFFFFFF ; don't care about time, just try to fill the buffer push eax - mov eax, [esp + 8] ; eax = &synthState - mov ecx, [esp + 12] ; ecx = &buffer - mov edx, [esp + 16] ; edx = samples - push edx - lea edx, [esp + 4] - push edx - lea edx, [esp + 4] - push edx - push ecx - push eax + push esp + lea eax, [esp + 24] + push eax + push dword [esp + 24] + push dword [esp + 24] + push dword [esp + 24] %else - %ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rdx = bufsize, r8 = &buffer, rcx = &synthstate - push r8 - mov r8, _SP - mov r9, 0x7FFFFFFF ; don't care about time, just try to fill the buffer - push r9 - mov r9, _SP ; still, we have to pass a pointer to time, so pointer to stack - %else ; System V ABI: rdi = &synthstate, rsi = &buffer, rdx = samples - push rdx - mov rdx, _SP - mov rcx, 0x7FFFFFFF ; don't care about time, just try to fill the buffer + %ifidn __OUTPUT_FORMAT__,win64 + ; win64 ABI parameter order: RCX, RDX, R8, R9; rest in stack (right-to-left, note shadow space!) + ; here: rcx = &synth, rdx = &synthstate, r8 = &buffer, r9 = samples + ; put the values in shadow space and get pointers to them + mov [_SP+0x8],r9 + lea r9, [_SP+0x8] + mov [_SP+0x10], dword 0x7FFFFFFF ; don't care about time, just try to fill the buffer + lea r10, [_SP+0x10] + mov [_SP+0x20], r10 + %else + ; System V ABI parameter order: RDI, RSI, RDX, RCX, R8, R9; rest in stack (right-to-left) + ; here: rdi = &synth, rsi = &synthstate, rdx = &buffer, rcx = samples push rcx - mov rcx, _SP ; still, we have to pass a pointer to time, so pointer to stack + mov rcx, _SP ; pass a pointer to samples instead of direct value + mov r8, 0x7FFFFFFF ; don't care about time, just try to fill the buffer + push r8 + mov r8, _SP ; still, we have to pass a pointer to time, so pointer to stack %endif %endif - call MANGLE_FUNC(su_render_time,16) + call MANGLE_FUNC(su_render_time,20) %if BITS == 32 ; stdcall pop ecx - pop ecx - ret 12 + ret 16 %else - %ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rdx = bufsize, r8 = &buffer, rcx = &synthstate - pop r9 - pop r8 - %else + %ifnidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rdx = bufsize, r8 = &buffer, rcx = &synthstate pop rcx - pop rdx + pop r8 %endif ret %endif diff --git a/src/sointu_footer.inc b/src/sointu_footer.inc index aeb9d07..4daba8a 100644 --- a/src/sointu_footer.inc +++ b/src/sointu_footer.inc @@ -168,7 +168,7 @@ endstruc ;=============================================================================== SECT_BSS(susynth) -su_synth_obj resb su_synth.size +su_synth_obj resb su_synthworkspace.size %if DELAY_ID > -1 ; if we use delay, then the synth obj should be immediately followed by the delay workspaces resb NUM_DELAY_LINES*su_delayline_wrk.size @@ -287,7 +287,7 @@ EXPORT MANGLE_FUNC(su_power,0) %ifndef SU_USE_16BIT_OUTPUT %ifndef SU_CLIP_OUTPUT ; The modern way. No need to clip; OS can do it. mov _DI, [_SP+su_stack.bufferptr - su_stack.output_sound] ; edi containts ptr - mov _SI, PTRWORD su_synth_obj + su_synth.left + mov _SI, PTRWORD su_synth_obj + su_synthworkspace.left movsd ; copy left channel to output buffer movsd ; copy right channel to output buffer mov [_SP+su_stack.bufferptr - su_stack.output_sound], _DI ; save back the updated ptr @@ -300,10 +300,10 @@ EXPORT MANGLE_FUNC(su_power,0) xor _CX,_CX xor eax,eax %%loop: ; loop over two channels, left & right - do fld dword [,su_synth_obj+su_synth.left,_CX*4,] + do fld dword [,su_synth_obj+su_synthworkspace.left,_CX*4,] call su_clip fstp dword [_SI] - do mov dword [,su_synth_obj+su_synth.left,_CX*4,{],eax} ; clear the sample so the VM is ready to write it + do mov dword [,su_synth_obj+su_synthworkspace.left,_CX*4,{],eax} ; clear the sample so the VM is ready to write it add _SI,4 cmp ecx,2 jl %%loop @@ -311,7 +311,7 @@ EXPORT MANGLE_FUNC(su_power,0) %endif %else ; 16-bit output, always clipped. This is a bit legacy method. mov _SI, [_SP+su_stack.bufferptr - su_stack.output_sound] ; esi points to the output buffer - mov _DI, PTRWORD su_synth_obj+su_synth.left + mov _DI, PTRWORD su_synth_obj+su_synthworkspace.left mov ecx, 2 %%loop: ; loop over two channels, left & right fld dword [_DI] @@ -361,9 +361,9 @@ su_render_sampleloop: ; loop through every sample in the row mov COM, PTRWORD MANGLE_DATA(su_commands) ; COM points to vm code mov VAL, PTRWORD MANGLE_DATA(su_params) ; VAL points to unit params %if DELAY_ID > -1 - lea _CX, [_DX + su_synth.size - su_delayline_wrk.filtstate] + lea _CX, [_DX + su_synthworkspace.size - su_delayline_wrk.filtstate] %endif - lea WRK, [_DX + su_synth.voices] ; WRK points to the first voice + lea WRK, [_DX + su_synthworkspace.voices] ; WRK points to the first voice call MANGLE_FUNC(su_run_vm,0) ; run through the VM code pop _AX %ifdef INCLUDE_POLYPHONY @@ -424,7 +424,7 @@ su_calculate_voices_loop: ; do { mov edi, ecx add edi, ebx shl edi, MAX_UNITS_SHIFT + 6 ; each unit = 64 bytes and there are 1<Commands, commands, sizeof(commands)); + memcpy(synth->Values, values, sizeof(values)); + synth->NumVoices = 1; + synth->Polyphony = 0; + // initialize SynthState + synthState = (SynthState*)malloc(sizeof(SynthState)); memset(synthState, 0, sizeof(SynthState)); - memcpy(synthState->Commands, commands, sizeof(commands)); - memcpy(synthState->Values, values, sizeof(values)); synthState->RandSeed = 1; - synthState->NumVoices = 1; - synthState->Synth.Voices[0].Note = 64; - retval = su_render(synthState, buffer, su_max_samples / 2); - synthState->Synth.Voices[0].Release++; + // triger first voice + synthState->SynthWrk.Voices[0].Note = 64; + retval = su_render(synth, synthState, buffer, su_max_samples / 2); + synthState->SynthWrk.Voices[0].Release++; buffer = buffer + su_max_samples; - retval = su_render(synthState, buffer, su_max_samples / 2); + retval = su_render(synth, synthState, buffer, su_max_samples / 2); + free(synth); free(synthState); return; } diff --git a/tests/test_render_samples_api.c b/tests/test_render_samples_api.c index 3755b14..1003a16 100644 --- a/tests/test_render_samples_api.c +++ b/tests/test_render_samples_api.c @@ -11,6 +11,7 @@ const int su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS; int main(int argc, char* argv[]) { + Synth* synth; SynthState* synthState; float* buffer; const unsigned char commands[] = { su_envelope_id, // MONO @@ -24,21 +25,27 @@ int main(int argc, char* argv[]) { int time; int samples; int totalrendered; - int retval; + int retval; + // initialize Synth + synth = (Synth*)malloc(sizeof(Synth)); + memcpy(synth->Commands, commands, sizeof(commands)); + memcpy(synth->Values, values, sizeof(values)); + synth->NumVoices = 1; + synth->Polyphony = 0; + // initialize SynthState synthState = (SynthState*)malloc(sizeof(SynthState)); - buffer = (float*)malloc(2 * sizeof(float) * su_max_samples); memset(synthState, 0, sizeof(SynthState)); - memcpy(synthState->Commands, commands, sizeof(commands)); - memcpy(synthState->Values, values, sizeof(values)); synthState->RandSeed = 1; - synthState->NumVoices = 1; - synthState->Synth.Voices[0].Note = 64; + // initialize Buffer + buffer = (float*)malloc(2 * sizeof(float) * su_max_samples); + // triger first voice + synthState->SynthWrk.Voices[0].Note = 64; totalrendered = 0; // First check that when we render using su_render_time with 0 time // we get nothing done samples = su_max_samples; time = 0; - errcode = su_render_time(synthState, buffer, &samples, &time); + errcode = su_render_time(synth, synthState, buffer, &samples, &time); if (errcode != 0) { printf("su_render_time returned error"); @@ -58,7 +65,7 @@ int main(int argc, char* argv[]) { // we get nothing done samples = 0; time = INT32_MAX; - errcode = su_render_time(synthState, buffer, &samples, &time); + errcode = su_render_time(synth, synthState, buffer, &samples, &time); if (errcode != 0) { printf("su_render_time returned error"); @@ -81,7 +88,7 @@ int main(int argc, char* argv[]) { // check that buffer full samples = 1; time = INT32_MAX; - su_render_time(synthState, &buffer[totalrendered*2], &samples, &time); + su_render_time(synth, synthState, &buffer[totalrendered*2], &samples, &time); totalrendered += samples; if (samples != 1) { @@ -95,7 +102,7 @@ int main(int argc, char* argv[]) { } samples = SAMPLES_PER_ROW - 1; time = INT32_MAX; - su_render_time(synthState, &buffer[totalrendered * 2], &samples, &time); + su_render_time(synth, synthState, &buffer[totalrendered * 2], &samples, &time); totalrendered += samples; if (samples != SAMPLES_PER_ROW - 1) { @@ -108,7 +115,7 @@ int main(int argc, char* argv[]) { goto fail; } if (i == 8) - synthState->Synth.Voices[0].Release++; + synthState->SynthWrk.Voices[0].Release++; } if (totalrendered != su_max_samples) { @@ -117,6 +124,7 @@ int main(int argc, char* argv[]) { } retval = 0; finish: + free(synth); free(synthState); free(buffer); return retval;