mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-14 02:54:37 -04:00
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:
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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;
|
||||
|
10
song/song.go
10
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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
130
src/sointu.asm
130
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user