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.
This commit is contained in:
Veikko Sariola
2020-10-28 13:44:34 +02:00
parent 64afa9fb48
commit 8183c698da
13 changed files with 196 additions and 156 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<<MAX_UNITS_SHIFT units + small header
do inc dword [,su_synth_obj+su_synth.voices+su_voice.release,_DI,] ; set the voice currently active to release; notice that it could increment any number of times
do inc dword [,su_synth_obj+su_synthworkspace.voices+su_voice.release,_DI,] ; set the voice currently active to release; notice that it could increment any number of times
cmp al, HLD ; if cl < HLD (no new note triggered)
jl su_update_voices_nexttrack ; goto nexttrack
inc ecx ; curvoice++
@ -435,7 +435,7 @@ su_update_voices_skipreset:
mov byte [_BP],cl
add ecx, ebx
shl ecx, MAX_UNITS_SHIFT + 6 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
do{lea _DI,[},su_synth_obj+su_synth.voices,_CX,]
do{lea _DI,[},su_synth_obj+su_synthworkspace.voices,_CX,]
stosd ; save note
mov ecx, (su_voice.size - su_voice.release)/4
xor eax, eax
@ -457,7 +457,7 @@ su_update_voices: ; Stack: retaddr row
mov bl, PATTERN_SIZE
div ebx ; eax = current pattern, edx = current row in pattern
do{lea _SI, [},MANGLE_DATA(su_tracks),_AX,]; esi points to the pattern data for current track
mov _DI, PTRWORD su_synth_obj+su_synth.voices
mov _DI, PTRWORD su_synth_obj+su_synthworkspace.voices
mov bl, MAX_TRACKS ; MAX_TRACKS is always <= 32 so this is ok
su_update_voices_trackloop:
movzx eax, byte [_SI] ; eax = current pattern

View File

@ -251,9 +251,9 @@ struc su_voice
endstruc
;-------------------------------------------------------------------------------
; synth struct
; synthworkspace struct
;-------------------------------------------------------------------------------
struc su_synth
struc su_synthworkspace
.curvoices resb 32 ; these are used by the multitrack player to store which voice is playing on which track
.left resd 1
.right resd 1

View File

@ -20,6 +20,7 @@
const int su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS;
void CALLCONV su_render_song(float* buffer) {
Synth* synth;
SynthState* synthState;
const unsigned char commands[] = { su_envelope_id, // MONO
su_envelope_id, // MONO
@ -29,17 +30,23 @@ void CALLCONV su_render_song(float* buffer) {
95, 64, 64, 80, 128, // envelope 2
128};
int retval;
synthState = malloc(sizeof(SynthState));
// 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));
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;
}

View File

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