mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-17 20:44:29 -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"
|
import "C"
|
||||||
|
|
||||||
// SynthState contains the entire state of sointu sound engine
|
// 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
|
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
|
// 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
|
// buffer float32 slice to fill with rendered samples. Stereo signal, so
|
||||||
// should have even length.
|
// should have even length.
|
||||||
// Returns an error if something went wrong.
|
// 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 {
|
if len(buffer)%1 == 1 {
|
||||||
return errors.New("Render writes stereo signals, so buffer should have even length")
|
return errors.New("Render writes stereo signals, so buffer should have even length")
|
||||||
}
|
}
|
||||||
maxSamples := len(buffer) / 2
|
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 {
|
if errcode > 0 {
|
||||||
return errors.New("Render failed")
|
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
|
// 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.
|
// 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.
|
// 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 {
|
if len(buffer)%1 == 1 {
|
||||||
return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length")
|
return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length")
|
||||||
}
|
}
|
||||||
samples := C.int(len(buffer) / 2)
|
samples := C.int(len(buffer) / 2)
|
||||||
time := C.int(maxtime)
|
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 {
|
if errcode > 0 {
|
||||||
return -1, -1, errors.New("RenderTime failed")
|
return -1, -1, errors.New("RenderTime failed")
|
||||||
}
|
}
|
||||||
return int(samples), int(time), nil
|
return int(samples), int(time), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SynthState) SetPatch(patch Patch) error {
|
func Compile(patch Patch) (*Synth, error) {
|
||||||
totalVoices := 0
|
totalVoices := 0
|
||||||
commands := make([]Opcode, 0)
|
commands := make([]Opcode, 0)
|
||||||
values := make([]byte, 0)
|
values := make([]byte, 0)
|
||||||
polyphonyBitmask := 0
|
polyphonyBitmask := 0
|
||||||
for _, instr := range patch {
|
for _, instr := range patch {
|
||||||
if len(instr.Units) > 63 {
|
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 {
|
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 {
|
for _, unit := range instr.Units {
|
||||||
commands = append(commands, unit.Command)
|
commands = append(commands, unit.Command)
|
||||||
@ -153,35 +155,35 @@ func (s *SynthState) SetPatch(patch Patch) error {
|
|||||||
polyphonyBitmask <<= 1
|
polyphonyBitmask <<= 1
|
||||||
}
|
}
|
||||||
if totalVoices > 32 {
|
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
|
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
|
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 {
|
for i := range commands {
|
||||||
cs.Commands[i] = (C.uchar)(commands[i])
|
s.Commands[i] = (C.uchar)(commands[i])
|
||||||
}
|
}
|
||||||
for i := range values {
|
for i := range values {
|
||||||
cs.Values[i] = (C.uchar)(values[i])
|
s.Values[i] = (C.uchar)(values[i])
|
||||||
}
|
}
|
||||||
cs.NumVoices = C.uint(totalVoices)
|
s.NumVoices = C.uint(totalVoices)
|
||||||
cs.Polyphony = C.uint(polyphonyBitmask)
|
s.Polyphony = C.uint(polyphonyBitmask)
|
||||||
return nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SynthState) Trigger(voice int, note byte) {
|
func (s *SynthState) Trigger(voice int, note byte) {
|
||||||
cs := (*C.SynthState)(s)
|
cs := (*C.SynthState)(s)
|
||||||
cs.Synth.Voices[voice] = C.Voice{}
|
cs.SynthWrk.Voices[voice] = C.Voice{}
|
||||||
cs.Synth.Voices[voice].Note = C.int(note)
|
cs.SynthWrk.Voices[voice].Note = C.int(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SynthState) Release(voice int) {
|
func (s *SynthState) Release(voice int) {
|
||||||
cs := (*C.SynthState)(s)
|
cs := (*C.SynthState)(s)
|
||||||
cs.Synth.Voices[voice].Release = 1
|
cs.SynthWrk.Voices[voice].Release = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSynthState() *SynthState {
|
func NewSynthState() *SynthState {
|
||||||
|
@ -21,22 +21,25 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
|||||||
// const bufsize = su_max_samples * 2
|
// const bufsize = su_max_samples * 2
|
||||||
|
|
||||||
func TestBridge(t *testing.T) {
|
func TestBridge(t *testing.T) {
|
||||||
s := bridge.NewSynthState()
|
patch := []bridge.Instrument{
|
||||||
s.SetPatch([]bridge.Instrument{
|
|
||||||
bridge.Instrument{1, []bridge.Unit{
|
bridge.Instrument{1, []bridge.Unit{
|
||||||
bridge.Unit{bridge.Envelope, []byte{64, 64, 64, 80, 128}},
|
bridge.Unit{bridge.Envelope, []byte{64, 64, 64, 80, 128}},
|
||||||
bridge.Unit{bridge.Envelope, []byte{95, 64, 64, 80, 128}},
|
bridge.Unit{bridge.Envelope, []byte{95, 64, 64, 80, 128}},
|
||||||
bridge.Unit{bridge.Out.Stereo(), []byte{128}},
|
bridge.Unit{bridge.Out.Stereo(), []byte{128}},
|
||||||
}},
|
}}}
|
||||||
})
|
synth, err := bridge.Compile(patch)
|
||||||
s.Trigger(0, 64)
|
if err != nil {
|
||||||
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
|
}
|
||||||
|
state := bridge.NewSynthState()
|
||||||
|
state.Trigger(0, 64)
|
||||||
buffer := make([]float32, 2*su_max_samples)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("first render gave an error")
|
t.Fatalf("first render gave an error")
|
||||||
}
|
}
|
||||||
s.Release(0)
|
state.Release(0)
|
||||||
err = s.Render(buffer[len(buffer)/2:])
|
err = synth.Render(state, buffer[len(buffer)/2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("first render gave an error")
|
t.Fatalf("first render gave an error")
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,6 @@ typedef struct Voice {
|
|||||||
struct Unit Units[63];
|
struct Unit Units[63];
|
||||||
} Voice;
|
} Voice;
|
||||||
|
|
||||||
typedef struct Synth {
|
|
||||||
unsigned char Curvoices[32];
|
|
||||||
float Left;
|
|
||||||
float Right;
|
|
||||||
float Aux[6];
|
|
||||||
struct Voice Voices[32];
|
|
||||||
} Synth;
|
|
||||||
|
|
||||||
typedef struct DelayWorkspace {
|
typedef struct DelayWorkspace {
|
||||||
float Buffer[65536];
|
float Buffer[65536];
|
||||||
float Dcin;
|
float Dcin;
|
||||||
@ -30,16 +22,27 @@ typedef struct DelayWorkspace {
|
|||||||
float Filtstate;
|
float Filtstate;
|
||||||
} DelayWorkspace;
|
} DelayWorkspace;
|
||||||
|
|
||||||
|
typedef struct SynthWorkspace {
|
||||||
|
unsigned char Curvoices[32];
|
||||||
|
float Left;
|
||||||
|
float Right;
|
||||||
|
float Aux[6];
|
||||||
|
struct Voice Voices[32];
|
||||||
|
} SynthWorkspace;
|
||||||
|
|
||||||
typedef struct SynthState {
|
typedef struct SynthState {
|
||||||
struct Synth 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.
|
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 Commands[32 * 64];
|
||||||
unsigned char Values[32 * 64 * 8];
|
unsigned char Values[32 * 64 * 8];
|
||||||
unsigned int Polyphony;
|
unsigned int Polyphony;
|
||||||
unsigned int NumVoices;
|
unsigned int NumVoices;
|
||||||
unsigned int RandSeed;
|
} Synth;
|
||||||
unsigned int GlobalTick;
|
|
||||||
} SynthState;
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
#if UINTPTR_MAX == 0xffffffff // are we 32-bit?
|
#if UINTPTR_MAX == 0xffffffff // are we 32-bit?
|
||||||
@ -56,11 +59,12 @@ typedef struct SynthState {
|
|||||||
extern void CALLCONV su_load_gmdls(void);
|
extern void CALLCONV su_load_gmdls(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// int su_render(SynthState* synthState, float* buffer, int samples):
|
// int su_render(Synth* synth,SynthState* synthState, float* buffer, int samples):
|
||||||
// Renders 'samples' number of samples to the buffer, using and modifying
|
// Renders 'samples' number of 'samples' to the 'buffer', using 'synth'.
|
||||||
// the synthesizer state in synthState.
|
// Modifies 'synthState' and fills the 'buffer'.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// 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
|
// synthState pointer to current synthState. RandSeed should be > 0 e.g. 1
|
||||||
// buffer audio sample buffer, L R L R ...
|
// buffer audio sample buffer, L R L R ...
|
||||||
// samples maximum number of samples to be rendered. WARNING: buffer
|
// samples maximum number of samples to be rendered. WARNING: buffer
|
||||||
@ -69,15 +73,16 @@ extern void CALLCONV su_load_gmdls(void);
|
|||||||
// Returns error code:
|
// Returns error code:
|
||||||
// 0 everything ok
|
// 0 everything ok
|
||||||
// (returns always 0 as no errors are implemented yet)
|
// (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
|
// 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
|
// 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
|
// are passed by reference as the function modifies to tell how many samples were
|
||||||
// actually rendered and how many time ticks were actually advanced.
|
// actually rendered and how many time ticks were actually advanced.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// 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
|
// synthState pointer to current synthState. RandSeed should be > 0 e.g. 1
|
||||||
// Also synthState->SamplesPerRow cannot be 0 or nothing will be
|
// Also synthState->SamplesPerRow cannot be 0 or nothing will be
|
||||||
// rendered; either set it to INT32_MAX to always render full
|
// 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:
|
// Returns error code:
|
||||||
// 0 everything ok
|
// 0 everything ok
|
||||||
// (no actual errors implemented yet)
|
// (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
|
// Arithmetic opcode ids
|
||||||
extern const int su_add_id;
|
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
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Song) Render() ([]float32, error) {
|
func (s *Song) Render(synth *bridge.Synth, state *bridge.SynthState) ([]float32, error) {
|
||||||
err := s.Validate()
|
err := s.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
synth := bridge.NewSynthState()
|
|
||||||
synth.SetPatch(s.Patch)
|
|
||||||
curVoices := make([]int, len(s.Tracks))
|
curVoices := make([]int, len(s.Tracks))
|
||||||
for i := range curVoices {
|
for i := range curVoices {
|
||||||
curVoices[i] = s.FirstTrackVoice(i)
|
curVoices[i] = s.FirstTrackVoice(i)
|
||||||
@ -113,17 +111,17 @@ func (s *Song) Render() ([]float32, error) {
|
|||||||
if note == 1 { // anything but hold causes an action.
|
if note == 1 { // anything but hold causes an action.
|
||||||
continue // TODO: can hold be actually something else than 1?
|
continue // TODO: can hold be actually something else than 1?
|
||||||
}
|
}
|
||||||
synth.Release(curVoices[t])
|
state.Release(curVoices[t])
|
||||||
if note > 1 {
|
if note > 1 {
|
||||||
curVoices[t]++
|
curVoices[t]++
|
||||||
first := s.FirstTrackVoice(t)
|
first := s.FirstTrackVoice(t)
|
||||||
if curVoices[t] >= first+s.Tracks[t].NumVoices {
|
if curVoices[t] >= first+s.Tracks[t].NumVoices {
|
||||||
curVoices[t] = first
|
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
|
totaln += samples
|
||||||
}
|
}
|
||||||
return buffer, nil
|
return buffer, nil
|
||||||
|
@ -38,7 +38,12 @@ func TestSongRender(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewSong failed: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("Render failed: %v", err)
|
t.Fatalf("Render failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ EXPORT MANGLE_FUNC(su_op_out,0) ; l r
|
|||||||
su_op_out_mono:
|
su_op_out_mono:
|
||||||
%endif
|
%endif
|
||||||
fmul dword [INP + su_out_ports.gain] ; g*l
|
fmul dword [INP + su_out_ports.gain] ; g*l
|
||||||
fadd dword [_AX + su_synth.left] ; g*l+o
|
fadd dword [_AX + su_synthworkspace.left] ; g*l+o
|
||||||
fstp dword [_AX + su_synth.left] ; o'=g*l+o
|
fstp dword [_AX + su_synthworkspace.left] ; o'=g*l+o
|
||||||
ret
|
ret
|
||||||
|
|
||||||
%endif ; SU_OUT_ID > -1
|
%endif ; SU_OUT_ID > -1
|
||||||
@ -43,11 +43,11 @@ EXPORT MANGLE_FUNC(su_op_outaux,0) ; l r
|
|||||||
%endif
|
%endif
|
||||||
fld st0 ; l l
|
fld st0 ; l l
|
||||||
fmul dword [INP + su_outaux_ports.outgain] ; g*l
|
fmul dword [INP + su_outaux_ports.outgain] ; g*l
|
||||||
fadd dword [_AX + su_synth.left] ; g*l+o
|
fadd dword [_AX + su_synthworkspace.left] ; g*l+o
|
||||||
fstp dword [_AX + su_synth.left] ; o'=g*l+o
|
fstp dword [_AX + su_synthworkspace.left] ; o'=g*l+o
|
||||||
fmul dword [INP + su_outaux_ports.auxgain] ; h*l
|
fmul dword [INP + su_outaux_ports.auxgain] ; h*l
|
||||||
fadd dword [_AX + su_synth.aux] ; h*l+a
|
fadd dword [_AX + su_synthworkspace.aux] ; h*l+a
|
||||||
fstp dword [_AX + su_synth.aux] ; a'=h*l+a
|
fstp dword [_AX + su_synthworkspace.aux] ; a'=h*l+a
|
||||||
ret
|
ret
|
||||||
|
|
||||||
%endif ; SU_OUTAUX_ID > -1
|
%endif ; SU_OUTAUX_ID > -1
|
||||||
@ -72,8 +72,8 @@ EXPORT MANGLE_FUNC(su_op_aux,0) ; l r
|
|||||||
su_op_aux_mono:
|
su_op_aux_mono:
|
||||||
%endif
|
%endif
|
||||||
fmul dword [INP + su_aux_ports.gain] ; g*l
|
fmul dword [INP + su_aux_ports.gain] ; g*l
|
||||||
fadd dword [_DI + su_synth.left + _AX*4] ; g*l+o
|
fadd dword [_DI + su_synthworkspace.left + _AX*4] ; g*l+o
|
||||||
fstp dword [_DI + su_synth.left + _AX*4] ; o'=g*l+o
|
fstp dword [_DI + su_synthworkspace.left + _AX*4] ; o'=g*l+o
|
||||||
ret
|
ret
|
||||||
|
|
||||||
%endif ; SU_AUX_ID > -1
|
%endif ; SU_AUX_ID > -1
|
||||||
|
@ -114,7 +114,7 @@ endstruc
|
|||||||
|
|
||||||
%define AMOUNT(val) val
|
%define AMOUNT(val) val
|
||||||
%define LOCALPORT(unit,port) ((unit+1)*su_unit.size + su_unit.ports)/4 + port
|
%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 OUTPORT 0
|
||||||
%define SEND_POP 0x8000
|
%define SEND_POP 0x8000
|
||||||
%define SEND_GLOBAL 0x4000
|
%define SEND_GLOBAL 0x4000
|
||||||
|
@ -406,13 +406,13 @@ EXPORT MANGLE_FUNC(su_op_in,0)
|
|||||||
sub _DI, 4
|
sub _DI, 4
|
||||||
su_op_in_right:
|
su_op_in_right:
|
||||||
xor ecx, ecx
|
xor ecx, ecx
|
||||||
fld dword [_DI + su_synth.right + _AX*4]
|
fld dword [_DI + su_synthworkspace.right + _AX*4]
|
||||||
mov dword [_DI + su_synth.right + _AX*4], ecx
|
mov dword [_DI + su_synthworkspace.right + _AX*4], ecx
|
||||||
%else
|
%else
|
||||||
xor ecx, ecx
|
xor ecx, ecx
|
||||||
mov _DI, [_SP + su_stack.synth]
|
mov _DI, [_SP + su_stack.synth]
|
||||||
fld dword [_DI + su_synth.left + _AX*4]
|
fld dword [_DI + su_synthworkspace.left + _AX*4]
|
||||||
mov dword [_DI + su_synth.left + _AX*4], ecx
|
mov dword [_DI + su_synthworkspace.left + _AX*4], ecx
|
||||||
%endif
|
%endif
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
130
src/sointu.asm
130
src/sointu.asm
@ -26,37 +26,50 @@ USE_OUT
|
|||||||
section .text
|
section .text
|
||||||
|
|
||||||
struc su_synth_state
|
struc su_synth_state
|
||||||
.synth resb su_synth.size
|
.synthwrk resb su_synthworkspace.size
|
||||||
.delaywrks resb su_delayline_wrk.size * 64
|
.delaywrks resb su_delayline_wrk.size * 64
|
||||||
.commands resb 32 * 64
|
|
||||||
.values resb 32 * 64 * 8
|
|
||||||
.polyphony resd 1
|
|
||||||
.numvoices resd 1
|
|
||||||
.randseed resd 1
|
.randseed resd 1
|
||||||
.globaltime resd 1
|
.globaltime resd 1
|
||||||
endstruc
|
endstruc
|
||||||
|
|
||||||
|
struc su_synth
|
||||||
|
.commands resb 32 * 64
|
||||||
|
.values resb 32 * 64 * 8
|
||||||
|
.polyphony resd 1
|
||||||
|
.numvoices resd 1
|
||||||
|
endstruc
|
||||||
|
|
||||||
SECT_TEXT(sursampl)
|
SECT_TEXT(sursampl)
|
||||||
|
|
||||||
EXPORT MANGLE_FUNC(su_render_time,16)
|
EXPORT MANGLE_FUNC(su_render_time,20)
|
||||||
%if BITS == 32 ; stdcall
|
%if BITS == 32 ; stdcall
|
||||||
pushad ; push registers
|
pushad ; push registers
|
||||||
mov ecx, [esp + 4 + 32] ; ecx = &synthState
|
mov eax, [esp + 4 + 32] ; eax = &synth
|
||||||
mov edx, [esp + 8 + 32] ; edx = &buffer
|
mov ecx, [esp + 8 + 32] ; ecx = &synthState
|
||||||
mov esi, [esp + 12 + 32] ; esi = &samples
|
mov edx, [esp + 12 + 32] ; edx = &buffer
|
||||||
mov ebx, [esp + 16 + 32] ; ebx = &time
|
mov esi, [esp + 16 + 32] ; esi = &samples
|
||||||
|
mov ebx, [esp + 20 + 32] ; ebx = &time
|
||||||
%else
|
%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
|
push_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||||
mov rsi, r8 ; rsi = &samples
|
mov rbx, [rsp + 0x28 + PUSH_REG_SIZE(4)] ; rbx = &time, note the shadow space so &time is at rsp + 0x28
|
||||||
mov rbx, r9 ; rbx = &time
|
mov rax, rcx
|
||||||
%else ; System V ABI: rdi = &synthstate, rsi = &buffer, rdx = &samples, rcx = &time
|
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
|
push_registers rbx, rbp ; System V ABI: these registers are non-volatile
|
||||||
mov rbx, rcx ; rbx points to time
|
mov rax, rdi
|
||||||
xchg rsi, rdx ; rdx points to buffer, rsi points to samples
|
mov rbx, r8
|
||||||
mov rcx, rdi ; rcx = &Synthstate
|
xchg rcx, rsi
|
||||||
%endif
|
%endif
|
||||||
%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 _SI ; push the pointer to samples
|
||||||
push _BX ; push the pointer to time
|
push _BX ; push the pointer to time
|
||||||
xor eax, eax ; samplenumber starts at 0
|
xor eax, eax ; samplenumber starts at 0
|
||||||
@ -80,23 +93,24 @@ su_render_samples_loop:
|
|||||||
jge su_render_samples_time_finish ; goto finish
|
jge su_render_samples_time_finish ; goto finish
|
||||||
inc eax ; time++
|
inc eax ; time++
|
||||||
inc dword [_SP + PTRSIZE*6] ; samples++
|
inc dword [_SP + PTRSIZE*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
|
push _AX ; push rowtick
|
||||||
mov eax, [_CX + su_synth_state.polyphony]
|
mov eax, [_BP + su_synth.polyphony]
|
||||||
push _AX ;polyphony
|
push _AX ;polyphony
|
||||||
mov eax, [_CX + su_synth_state.numvoices]
|
mov eax, [_BP + su_synth.numvoices]
|
||||||
push _AX ;numvoices
|
push _AX ;numvoices
|
||||||
lea _DX, [_CX+ su_synth_state.synth]
|
lea _DX, [_CX + su_synth_state.synthwrk]
|
||||||
lea COM, [_CX+ su_synth_state.commands]
|
lea COM, [_BP + su_synth.commands]
|
||||||
lea VAL, [_CX+ su_synth_state.values]
|
lea VAL, [_BP + su_synth.values]
|
||||||
lea WRK, [_DX + su_synth.voices]
|
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]
|
lea _CX, [_CX + su_synth_state.delaywrks - su_delayline_wrk.filtstate]
|
||||||
call MANGLE_FUNC(su_run_vm,0)
|
call MANGLE_FUNC(su_run_vm,0)
|
||||||
pop _AX
|
pop _AX
|
||||||
pop _AX
|
pop _AX
|
||||||
mov _DI, [_SP + PTRSIZE*5] ; edi containts buffer ptr
|
mov _DI, [_SP + PTRSIZE*5] ; edi containts buffer ptr
|
||||||
mov _CX, [_SP + PTRSIZE*4]
|
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 left channel to output buffer
|
||||||
movsd ; copy right channel to output buffer
|
movsd ; copy right channel to output buffer
|
||||||
mov [_SP + PTRSIZE*5], _DI ; save back the updated ptr
|
mov [_SP + PTRSIZE*5], _DI ; save back the updated ptr
|
||||||
@ -121,11 +135,12 @@ su_render_samples_time_finish:
|
|||||||
pop _SI ; pop the pointer to samples
|
pop _SI ; pop the pointer to samples
|
||||||
mov dword [_SI], edx ; *samples = samples rendered
|
mov dword [_SI], edx ; *samples = samples rendered
|
||||||
mov dword [_BX], eax ; *time = time ticks 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
|
xor eax, eax ; TODO: set eax to possible error code, now just 0
|
||||||
%if BITS == 32 ; stdcall
|
%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
|
mov [_SP + 28],eax ; we want to return eax, but popad pops everything, so put eax to stack for popad to pop
|
||||||
popad
|
popad
|
||||||
ret 16
|
ret 20
|
||||||
%else
|
%else
|
||||||
%ifidn __OUTPUT_FORMAT__,win64
|
%ifidn __OUTPUT_FORMAT__,win64
|
||||||
pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||||
@ -135,47 +150,44 @@ su_render_samples_time_finish:
|
|||||||
ret
|
ret
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
EXPORT MANGLE_FUNC(su_render,12)
|
EXPORT MANGLE_FUNC(su_render,16)
|
||||||
%if BITS == 32 ; stdcall
|
%if BITS == 32 ; stdcall
|
||||||
mov eax, 0x7FFFFFFF ; don't care about time, just try to fill the buffer
|
mov eax, 0x7FFFFFFF ; don't care about time, just try to fill the buffer
|
||||||
push eax
|
push eax
|
||||||
mov eax, [esp + 8] ; eax = &synthState
|
push esp
|
||||||
mov ecx, [esp + 12] ; ecx = &buffer
|
lea eax, [esp + 24]
|
||||||
mov edx, [esp + 16] ; edx = samples
|
push eax
|
||||||
push edx
|
push dword [esp + 24]
|
||||||
lea edx, [esp + 4]
|
push dword [esp + 24]
|
||||||
push edx
|
push dword [esp + 24]
|
||||||
lea edx, [esp + 4]
|
|
||||||
push edx
|
|
||||||
push ecx
|
|
||||||
push eax
|
|
||||||
%else
|
%else
|
||||||
%ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rdx = bufsize, r8 = &buffer, rcx = &synthstate
|
%ifidn __OUTPUT_FORMAT__,win64
|
||||||
push r8
|
; win64 ABI parameter order: RCX, RDX, R8, R9; rest in stack (right-to-left, note shadow space!)
|
||||||
mov r8, _SP
|
; here: rcx = &synth, rdx = &synthstate, r8 = &buffer, r9 = samples
|
||||||
mov r9, 0x7FFFFFFF ; don't care about time, just try to fill the buffer
|
; put the values in shadow space and get pointers to them
|
||||||
push r9
|
mov [_SP+0x8],r9
|
||||||
mov r9, _SP ; still, we have to pass a pointer to time, so pointer to stack
|
lea r9, [_SP+0x8]
|
||||||
%else ; System V ABI: rdi = &synthstate, rsi = &buffer, rdx = samples
|
mov [_SP+0x10], dword 0x7FFFFFFF ; don't care about time, just try to fill the buffer
|
||||||
push rdx
|
lea r10, [_SP+0x10]
|
||||||
mov rdx, _SP
|
mov [_SP+0x20], r10
|
||||||
mov rcx, 0x7FFFFFFF ; don't care about time, just try to fill the buffer
|
%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
|
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
|
||||||
%endif
|
%endif
|
||||||
call MANGLE_FUNC(su_render_time,16)
|
call MANGLE_FUNC(su_render_time,20)
|
||||||
%if BITS == 32 ; stdcall
|
%if BITS == 32 ; stdcall
|
||||||
pop ecx
|
pop ecx
|
||||||
pop ecx
|
ret 16
|
||||||
ret 12
|
|
||||||
%else
|
%else
|
||||||
%ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rdx = bufsize, r8 = &buffer, rcx = &synthstate
|
%ifnidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rdx = bufsize, r8 = &buffer, rcx = &synthstate
|
||||||
pop r9
|
|
||||||
pop r8
|
|
||||||
%else
|
|
||||||
pop rcx
|
pop rcx
|
||||||
pop rdx
|
pop r8
|
||||||
%endif
|
%endif
|
||||||
ret
|
ret
|
||||||
%endif
|
%endif
|
||||||
|
@ -168,7 +168,7 @@ endstruc
|
|||||||
;===============================================================================
|
;===============================================================================
|
||||||
SECT_BSS(susynth)
|
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
|
%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
|
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_USE_16BIT_OUTPUT
|
||||||
%ifndef SU_CLIP_OUTPUT ; The modern way. No need to clip; OS can do it.
|
%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 _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 left channel to output buffer
|
||||||
movsd ; copy right 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
|
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 _CX,_CX
|
||||||
xor eax,eax
|
xor eax,eax
|
||||||
%%loop: ; loop over two channels, left & right
|
%%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
|
call su_clip
|
||||||
fstp dword [_SI]
|
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
|
add _SI,4
|
||||||
cmp ecx,2
|
cmp ecx,2
|
||||||
jl %%loop
|
jl %%loop
|
||||||
@ -311,7 +311,7 @@ EXPORT MANGLE_FUNC(su_power,0)
|
|||||||
%endif
|
%endif
|
||||||
%else ; 16-bit output, always clipped. This is a bit legacy method.
|
%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 _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
|
mov ecx, 2
|
||||||
%%loop: ; loop over two channels, left & right
|
%%loop: ; loop over two channels, left & right
|
||||||
fld dword [_DI]
|
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 COM, PTRWORD MANGLE_DATA(su_commands) ; COM points to vm code
|
||||||
mov VAL, PTRWORD MANGLE_DATA(su_params) ; VAL points to unit params
|
mov VAL, PTRWORD MANGLE_DATA(su_params) ; VAL points to unit params
|
||||||
%if DELAY_ID > -1
|
%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
|
%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
|
call MANGLE_FUNC(su_run_vm,0) ; run through the VM code
|
||||||
pop _AX
|
pop _AX
|
||||||
%ifdef INCLUDE_POLYPHONY
|
%ifdef INCLUDE_POLYPHONY
|
||||||
@ -424,7 +424,7 @@ su_calculate_voices_loop: ; do {
|
|||||||
mov edi, ecx
|
mov edi, ecx
|
||||||
add edi, ebx
|
add edi, ebx
|
||||||
shl edi, MAX_UNITS_SHIFT + 6 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
|
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)
|
cmp al, HLD ; if cl < HLD (no new note triggered)
|
||||||
jl su_update_voices_nexttrack ; goto nexttrack
|
jl su_update_voices_nexttrack ; goto nexttrack
|
||||||
inc ecx ; curvoice++
|
inc ecx ; curvoice++
|
||||||
@ -435,7 +435,7 @@ su_update_voices_skipreset:
|
|||||||
mov byte [_BP],cl
|
mov byte [_BP],cl
|
||||||
add ecx, ebx
|
add ecx, ebx
|
||||||
shl ecx, MAX_UNITS_SHIFT + 6 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
|
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
|
stosd ; save note
|
||||||
mov ecx, (su_voice.size - su_voice.release)/4
|
mov ecx, (su_voice.size - su_voice.release)/4
|
||||||
xor eax, eax
|
xor eax, eax
|
||||||
@ -457,7 +457,7 @@ su_update_voices: ; Stack: retaddr row
|
|||||||
mov bl, PATTERN_SIZE
|
mov bl, PATTERN_SIZE
|
||||||
div ebx ; eax = current pattern, edx = current row in pattern
|
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
|
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
|
mov bl, MAX_TRACKS ; MAX_TRACKS is always <= 32 so this is ok
|
||||||
su_update_voices_trackloop:
|
su_update_voices_trackloop:
|
||||||
movzx eax, byte [_SI] ; eax = current pattern
|
movzx eax, byte [_SI] ; eax = current pattern
|
||||||
|
@ -251,9 +251,9 @@ struc su_voice
|
|||||||
endstruc
|
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
|
.curvoices resb 32 ; these are used by the multitrack player to store which voice is playing on which track
|
||||||
.left resd 1
|
.left resd 1
|
||||||
.right resd 1
|
.right resd 1
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
const int su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS;
|
const int su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS;
|
||||||
|
|
||||||
void CALLCONV su_render_song(float* buffer) {
|
void CALLCONV su_render_song(float* buffer) {
|
||||||
|
Synth* synth;
|
||||||
SynthState* synthState;
|
SynthState* synthState;
|
||||||
const unsigned char commands[] = { su_envelope_id, // MONO
|
const unsigned char commands[] = { su_envelope_id, // MONO
|
||||||
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
|
95, 64, 64, 80, 128, // envelope 2
|
||||||
128};
|
128};
|
||||||
int retval;
|
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));
|
memset(synthState, 0, sizeof(SynthState));
|
||||||
memcpy(synthState->Commands, commands, sizeof(commands));
|
|
||||||
memcpy(synthState->Values, values, sizeof(values));
|
|
||||||
synthState->RandSeed = 1;
|
synthState->RandSeed = 1;
|
||||||
synthState->NumVoices = 1;
|
// triger first voice
|
||||||
synthState->Synth.Voices[0].Note = 64;
|
synthState->SynthWrk.Voices[0].Note = 64;
|
||||||
retval = su_render(synthState, buffer, su_max_samples / 2);
|
retval = su_render(synth, synthState, buffer, su_max_samples / 2);
|
||||||
synthState->Synth.Voices[0].Release++;
|
synthState->SynthWrk.Voices[0].Release++;
|
||||||
buffer = buffer + su_max_samples;
|
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);
|
free(synthState);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
const int su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS;
|
const int su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS;
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
Synth* synth;
|
||||||
SynthState* synthState;
|
SynthState* synthState;
|
||||||
float* buffer;
|
float* buffer;
|
||||||
const unsigned char commands[] = { su_envelope_id, // MONO
|
const unsigned char commands[] = { su_envelope_id, // MONO
|
||||||
@ -24,21 +25,27 @@ int main(int argc, char* argv[]) {
|
|||||||
int time;
|
int time;
|
||||||
int samples;
|
int samples;
|
||||||
int totalrendered;
|
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));
|
synthState = (SynthState*)malloc(sizeof(SynthState));
|
||||||
buffer = (float*)malloc(2 * sizeof(float) * su_max_samples);
|
|
||||||
memset(synthState, 0, sizeof(SynthState));
|
memset(synthState, 0, sizeof(SynthState));
|
||||||
memcpy(synthState->Commands, commands, sizeof(commands));
|
|
||||||
memcpy(synthState->Values, values, sizeof(values));
|
|
||||||
synthState->RandSeed = 1;
|
synthState->RandSeed = 1;
|
||||||
synthState->NumVoices = 1;
|
// initialize Buffer
|
||||||
synthState->Synth.Voices[0].Note = 64;
|
buffer = (float*)malloc(2 * sizeof(float) * su_max_samples);
|
||||||
|
// triger first voice
|
||||||
|
synthState->SynthWrk.Voices[0].Note = 64;
|
||||||
totalrendered = 0;
|
totalrendered = 0;
|
||||||
// First check that when we render using su_render_time with 0 time
|
// First check that when we render using su_render_time with 0 time
|
||||||
// we get nothing done
|
// we get nothing done
|
||||||
samples = su_max_samples;
|
samples = su_max_samples;
|
||||||
time = 0;
|
time = 0;
|
||||||
errcode = su_render_time(synthState, buffer, &samples, &time);
|
errcode = su_render_time(synth, synthState, buffer, &samples, &time);
|
||||||
if (errcode != 0)
|
if (errcode != 0)
|
||||||
{
|
{
|
||||||
printf("su_render_time returned error");
|
printf("su_render_time returned error");
|
||||||
@ -58,7 +65,7 @@ int main(int argc, char* argv[]) {
|
|||||||
// we get nothing done
|
// we get nothing done
|
||||||
samples = 0;
|
samples = 0;
|
||||||
time = INT32_MAX;
|
time = INT32_MAX;
|
||||||
errcode = su_render_time(synthState, buffer, &samples, &time);
|
errcode = su_render_time(synth, synthState, buffer, &samples, &time);
|
||||||
if (errcode != 0)
|
if (errcode != 0)
|
||||||
{
|
{
|
||||||
printf("su_render_time returned error");
|
printf("su_render_time returned error");
|
||||||
@ -81,7 +88,7 @@ int main(int argc, char* argv[]) {
|
|||||||
// check that buffer full
|
// check that buffer full
|
||||||
samples = 1;
|
samples = 1;
|
||||||
time = INT32_MAX;
|
time = INT32_MAX;
|
||||||
su_render_time(synthState, &buffer[totalrendered*2], &samples, &time);
|
su_render_time(synth, synthState, &buffer[totalrendered*2], &samples, &time);
|
||||||
totalrendered += samples;
|
totalrendered += samples;
|
||||||
if (samples != 1)
|
if (samples != 1)
|
||||||
{
|
{
|
||||||
@ -95,7 +102,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
samples = SAMPLES_PER_ROW - 1;
|
samples = SAMPLES_PER_ROW - 1;
|
||||||
time = INT32_MAX;
|
time = INT32_MAX;
|
||||||
su_render_time(synthState, &buffer[totalrendered * 2], &samples, &time);
|
su_render_time(synth, synthState, &buffer[totalrendered * 2], &samples, &time);
|
||||||
totalrendered += samples;
|
totalrendered += samples;
|
||||||
if (samples != SAMPLES_PER_ROW - 1)
|
if (samples != SAMPLES_PER_ROW - 1)
|
||||||
{
|
{
|
||||||
@ -108,7 +115,7 @@ int main(int argc, char* argv[]) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
if (i == 8)
|
if (i == 8)
|
||||||
synthState->Synth.Voices[0].Release++;
|
synthState->SynthWrk.Voices[0].Release++;
|
||||||
}
|
}
|
||||||
if (totalrendered != su_max_samples)
|
if (totalrendered != su_max_samples)
|
||||||
{
|
{
|
||||||
@ -117,6 +124,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
finish:
|
finish:
|
||||||
|
free(synth);
|
||||||
free(synthState);
|
free(synthState);
|
||||||
free(buffer);
|
free(buffer);
|
||||||
return retval;
|
return retval;
|
||||||
|
Reference in New Issue
Block a user