mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
Change the sointu.h api to return -1, 0 or n>0 depending if buffer is full and/or row ended.
test_render_samples_api.c was added to test the api. bridge.go was modified to reflect that there is no need to check for row manually; su_render_samples already returns the information if a row has ended.
This commit is contained in:
parent
c9e8000c5f
commit
6e85ff674a
@ -2,6 +2,7 @@ package bridge
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
import "math"
|
||||||
|
|
||||||
// #cgo CFLAGS: -I"${SRCDIR}/../include"
|
// #cgo CFLAGS: -I"${SRCDIR}/../include"
|
||||||
// #cgo LDFLAGS: "${SRCDIR}/../build/src/libsointu.a"
|
// #cgo LDFLAGS: "${SRCDIR}/../build/src/libsointu.a"
|
||||||
@ -83,16 +84,12 @@ func (s *SynthState) Release(voice int) {
|
|||||||
fmt.Printf("Returning from Release...\n")
|
fmt.Printf("Returning from Release...\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SynthState) RowEnd() bool {
|
|
||||||
return s.RowTick == s.RowLen
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SynthState) ResetRow() bool {
|
|
||||||
return s.RowTick == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSynthState() *SynthState {
|
func NewSynthState() *SynthState {
|
||||||
s := new(SynthState)
|
s := new(SynthState)
|
||||||
s.RandSeed = 1
|
s.RandSeed = 1
|
||||||
|
// The default behaviour will be to have rows/beats disabled i.e.
|
||||||
|
// fill the whole buffer every call. This is a lot better default
|
||||||
|
// behaviour than leaving this 0 (Render would never render anything)
|
||||||
|
s.SamplesPerRow = math.MaxInt32
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/vsariola/sointu/bridge"
|
"github.com/vsariola/sointu/bridge"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@ -34,7 +33,6 @@ func TestBridge(t *testing.T) {
|
|||||||
// synthState->RandSeed = 1;
|
// synthState->RandSeed = 1;
|
||||||
// initialized in NewSynthState
|
// initialized in NewSynthState
|
||||||
// synthState->RowLen = INT32_MAX;
|
// synthState->RowLen = INT32_MAX;
|
||||||
s.RowLen = math.MaxInt32 // (why?)
|
|
||||||
// synthState->NumVoices = 1;
|
// synthState->NumVoices = 1;
|
||||||
s.NumVoices = 1
|
s.NumVoices = 1
|
||||||
// synthState->Synth.Voices[0].Note = 64;
|
// synthState->Synth.Voices[0].Note = 64;
|
||||||
@ -42,15 +40,15 @@ func TestBridge(t *testing.T) {
|
|||||||
// retval = su_render_samples(buffer, su_max_samples / 2, synthState);
|
// retval = su_render_samples(buffer, su_max_samples / 2, synthState);
|
||||||
buffer := make([]float32, su_max_samples)
|
buffer := make([]float32, su_max_samples)
|
||||||
remaining := s.Render(buffer)
|
remaining := s.Render(buffer)
|
||||||
if remaining != 0 {
|
if remaining > 0 {
|
||||||
t.Fatalf("could not render full buffer, %v bytes remaining, expected %v", remaining, len(buffer))
|
t.Fatalf("could not render full buffer, %v bytes remaining, expected <= 0", remaining)
|
||||||
}
|
}
|
||||||
// synthState->Synth.Voices[0].Release++;
|
// synthState->Synth.Voices[0].Release++;
|
||||||
s.Synth.Voices[0].Release++
|
s.Synth.Voices[0].Release++
|
||||||
sbuffer := make([]float32, su_max_samples)
|
sbuffer := make([]float32, su_max_samples)
|
||||||
remaining = s.Render(sbuffer)
|
remaining = s.Render(sbuffer)
|
||||||
if remaining != 0 {
|
if remaining > 0 {
|
||||||
t.Fatalf("could not render second full buffer, %v bytes remaining, expected %v", remaining, len(buffer))
|
t.Fatalf("could not render second full buffer, %v bytes remaining, expected <= 0", remaining)
|
||||||
}
|
}
|
||||||
buffer = append(buffer, sbuffer...)
|
buffer = append(buffer, sbuffer...)
|
||||||
_, filename, _, _ := runtime.Caller(0)
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
|
@ -38,9 +38,9 @@ typedef struct SynthState {
|
|||||||
unsigned int Polyphony;
|
unsigned int Polyphony;
|
||||||
unsigned int NumVoices;
|
unsigned int NumVoices;
|
||||||
unsigned int RandSeed;
|
unsigned int RandSeed;
|
||||||
unsigned int Globaltime;
|
unsigned int GlobalTick;
|
||||||
unsigned int RowTick;
|
unsigned int RowTick;
|
||||||
unsigned int RowLen;
|
unsigned int SamplesPerRow; // nominal value, actual rows could be more or less due to speed modulation
|
||||||
} SynthState;
|
} SynthState;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
@ -58,22 +58,49 @@ typedef struct SynthState {
|
|||||||
extern void CALLCONV su_load_gmdls(void);
|
extern void CALLCONV su_load_gmdls(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Returns the number of samples remaining in the buffer i.e. 0 if the buffer was
|
// su_render_samples(SynthState* synthState, int maxSamples, float* buffer):
|
||||||
// filled completely.
|
// Renders at most maxsamples to the buffer, using and modifying the
|
||||||
|
// synthesizer state in synthState.
|
||||||
//
|
//
|
||||||
// NOTE: The buffer should have a length of 2 * maxsamples, as the audio
|
// Parameters:
|
||||||
// is stereo.
|
// 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
|
||||||
|
// buffer, or something like SAMPLE_RATE * 60 / (BPM * 4) for
|
||||||
|
// having 4 rows per beat.
|
||||||
|
// maxSamples maximum number of samples to be rendered. buffer should
|
||||||
|
// have a length of 2 * maxsamples as the audio is stereo.
|
||||||
|
// buffer audio sample buffer, L R L R ...
|
||||||
//
|
//
|
||||||
// You should always check if rowtick >= rowlen after calling this. If so, most
|
// Returns:
|
||||||
// likely you didn't get full buffer filled but the end of row was hit before
|
// -1 end of row was not reached & buffer full
|
||||||
// filling the buffer. In that case, trigger/release new voices, set rowtick to 0.
|
// 0 end of row was reached & buffer full (there is space for zero
|
||||||
|
// samples in the buffer)
|
||||||
|
// n>0 end of row was reached & there is space for n samples in the buffer
|
||||||
//
|
//
|
||||||
// Beware of infinite loops: with a rowlen of 0; or without resetting rowtick
|
// Beware of infinite loops: with a rowlen of 0; or without resetting rowtick
|
||||||
// between rows; or with a problematic synth patch e.g. if the speed is
|
// between rows; or with a problematic synth patch e.g. if the speed is
|
||||||
// modulated to be become infinite, this function might return maxsamples i.e. not
|
// modulated to be become infinite, this function might return maxsamples i.e.
|
||||||
// render any samples. If you try to call this with your buffer until the whole
|
// not render any samples. If you try to call this with your buffer until the
|
||||||
// buffer is filled, you will be stuck in an infinite loop.
|
// whole buffer is filled, you will be stuck in an infinite loop.
|
||||||
extern int CALLCONV su_render_samples(SynthState* synthState, int maxsamples, float* buffer);
|
//
|
||||||
|
// So a reasonable track player would be something like:
|
||||||
|
//
|
||||||
|
// function render_buffer(maxsamples,buffer) {
|
||||||
|
// remaining = maxsamples
|
||||||
|
// for i = 0..MAX_TRIES // limit retries to prevent infinite loop
|
||||||
|
// remaining = su_render_samples(synthState,
|
||||||
|
// remaining,
|
||||||
|
// &buffer[(maxsamples-remaining)*2])
|
||||||
|
// if remaining >= 0 // end of row reached
|
||||||
|
// song_row++ // advance row
|
||||||
|
// retrigger/release voices based on the new row
|
||||||
|
// if remaining <= 0 // buffer full
|
||||||
|
// return
|
||||||
|
// return // could not fill buffer despite MAX_TRIES, something is wrong
|
||||||
|
// // audio will come to sudden end
|
||||||
|
// }
|
||||||
|
extern int CALLCONV su_render_samples(SynthState* synthState, int maxSamples, float* buffer);
|
||||||
|
|
||||||
// Arithmetic opcode ids
|
// Arithmetic opcode ids
|
||||||
extern const int su_add_id;
|
extern const int su_add_id;
|
||||||
@ -110,7 +137,6 @@ extern const int su_send_id;
|
|||||||
// Source opcode ids
|
// Source opcode ids
|
||||||
extern const int su_envelope_id;
|
extern const int su_envelope_id;
|
||||||
extern const int su_noise_id;
|
extern const int su_noise_id;
|
||||||
extern const int su_aux_id;
|
|
||||||
extern const int su_oscillat_id;
|
extern const int su_oscillat_id;
|
||||||
extern const int su_loadval_id;
|
extern const int su_loadval_id;
|
||||||
extern const int su_receive_id;
|
extern const int su_receive_id;
|
||||||
|
@ -66,7 +66,11 @@ EXPORT MANGLE_FUNC(su_render_samples,12)
|
|||||||
mov eax, [_CX + su_synth_state.rowlen]
|
mov eax, [_CX + su_synth_state.rowlen]
|
||||||
push _AX ; push the rowlength to stack so we can easily compare to it, normally this would be row
|
push _AX ; push the rowlength to stack so we can easily compare to it, normally this would be row
|
||||||
mov eax, [_CX + su_synth_state.rowtick]
|
mov eax, [_CX + su_synth_state.rowtick]
|
||||||
su_render_samples_loop:
|
su_render_samples_loop:
|
||||||
|
cmp eax, [_SP] ; compare current tick to rowlength
|
||||||
|
jge su_render_samples_row_advance
|
||||||
|
sub dword [_SP + PTRSIZE*5], 1 ; compare current tick to rowlength
|
||||||
|
jb su_render_samples_buffer_full
|
||||||
mov _CX, [_SP + PTRSIZE*3]
|
mov _CX, [_SP + PTRSIZE*3]
|
||||||
push _AX ; push rowtick
|
push _AX ; push rowtick
|
||||||
mov eax, [_CX + su_synth_state.polyphony]
|
mov eax, [_CX + su_synth_state.polyphony]
|
||||||
@ -93,12 +97,11 @@ su_render_samples_loop:
|
|||||||
stosd ; clear right channel so the VM is ready to write them again
|
stosd ; clear right channel so the VM is ready to write them again
|
||||||
pop _AX
|
pop _AX
|
||||||
inc dword [_SP + PTRSIZE] ; increment global time, used by delays
|
inc dword [_SP + PTRSIZE] ; increment global time, used by delays
|
||||||
inc eax
|
inc eax
|
||||||
dec dword [_SP + PTRSIZE*5]
|
jmp su_render_samples_loop
|
||||||
jz su_render_samples_finish
|
su_render_samples_row_advance:
|
||||||
cmp eax, [_SP] ; compare current tick to rowlength
|
xor eax, eax ; row has finished, so clear the rowtick for next round
|
||||||
jl su_render_samples_loop
|
su_render_samples_buffer_full:
|
||||||
su_render_samples_finish:
|
|
||||||
pop _CX
|
pop _CX
|
||||||
pop _BX
|
pop _BX
|
||||||
pop _DX
|
pop _DX
|
||||||
@ -107,7 +110,7 @@ su_render_samples_finish:
|
|||||||
mov [_CX + su_synth_state.globaltime], ebx
|
mov [_CX + su_synth_state.globaltime], ebx
|
||||||
mov [_CX + su_synth_state.rowtick], eax
|
mov [_CX + su_synth_state.rowtick], eax
|
||||||
pop _AX
|
pop _AX
|
||||||
pop _AX ; todo: return correct value based on this
|
pop _AX
|
||||||
%if BITS == 32 ; stdcall
|
%if BITS == 32 ; stdcall
|
||||||
popad
|
popad
|
||||||
ret 12
|
ret 12
|
||||||
|
@ -151,4 +151,8 @@ regression_test(test_chords "ENVELOPE;VCO_SINE")
|
|||||||
regression_test(test_speed "ENVELOPE;VCO_SINE")
|
regression_test(test_speed "ENVELOPE;VCO_SINE")
|
||||||
|
|
||||||
regression_test(test_render_samples ENVELOPE "" test_render_samples.c)
|
regression_test(test_render_samples ENVELOPE "" test_render_samples.c)
|
||||||
target_link_libraries(test_render_samples sointu)
|
target_link_libraries(test_render_samples sointu)
|
||||||
|
|
||||||
|
add_executable(test_render_samples_api test_render_samples_api.c)
|
||||||
|
target_link_libraries(test_render_samples_api sointu)
|
||||||
|
add_test(test_render_samples_api test_render_samples_api)
|
||||||
|
@ -34,7 +34,7 @@ void CALLCONV su_render(float* buffer) {
|
|||||||
memcpy(synthState->Commands, commands, sizeof(commands));
|
memcpy(synthState->Commands, commands, sizeof(commands));
|
||||||
memcpy(synthState->Values, values, sizeof(values));
|
memcpy(synthState->Values, values, sizeof(values));
|
||||||
synthState->RandSeed = 1;
|
synthState->RandSeed = 1;
|
||||||
synthState->RowLen = INT32_MAX;
|
synthState->SamplesPerRow = INT32_MAX;
|
||||||
synthState->NumVoices = 1;
|
synthState->NumVoices = 1;
|
||||||
synthState->Synth.Voices[0].Note = 64;
|
synthState->Synth.Voices[0].Note = 64;
|
||||||
retval = su_render_samples(synthState, su_max_samples / 2, buffer);
|
retval = su_render_samples(synthState, su_max_samples / 2, buffer);
|
||||||
|
92
tests/test_render_samples_api.c
Normal file
92
tests/test_render_samples_api.c
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "../include/sointu.h"
|
||||||
|
|
||||||
|
#define BPM 100
|
||||||
|
#define SAMPLE_RATE 44100
|
||||||
|
#define TOTAL_ROWS 16
|
||||||
|
#define SAMPLES_PER_ROW SAMPLE_RATE * 4 * 60 / (BPM * 16)
|
||||||
|
const int su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
SynthState* synthState;
|
||||||
|
float* buffer;
|
||||||
|
const unsigned char commands[] = { su_envelope_id, // MONO
|
||||||
|
su_envelope_id, // MONO
|
||||||
|
su_out_id + 1, // STEREO
|
||||||
|
su_advance_id };// MONO
|
||||||
|
const unsigned char values[] = { 64, 64, 64, 80, 128, // envelope 1
|
||||||
|
95, 64, 64, 80, 128, // envelope 2
|
||||||
|
128 };
|
||||||
|
int remaining, remainingOut;
|
||||||
|
int retval;
|
||||||
|
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;
|
||||||
|
remaining = su_max_samples;
|
||||||
|
// First check that when RowLen = 0, we render nothing and remaining does not change
|
||||||
|
synthState->SamplesPerRow = 0;
|
||||||
|
if (su_render_samples(synthState, remaining, buffer) != remaining)
|
||||||
|
{
|
||||||
|
printf("su_render_samples rendered samples despite number of samples per row being 0");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
// Then check that each time we call render, only SAMPLES_PER_ROW
|
||||||
|
// number of samples are rendered
|
||||||
|
synthState->SamplesPerRow = SAMPLES_PER_ROW;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
// Simulate "small buffers" i.e. render a buffer with 1 sample
|
||||||
|
// check that buffer full
|
||||||
|
remainingOut = su_render_samples(synthState, 1, &buffer[2 * (su_max_samples - remaining)]);
|
||||||
|
if (remainingOut != -1)
|
||||||
|
{
|
||||||
|
printf("su_render_samples should have return -1, as it should have believed buffer is full");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (synthState->RowTick != 1)
|
||||||
|
{
|
||||||
|
printf("su_render_samples RowTick should be at 1 after rendering 1 tick of a row");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
remaining--; // we rendered just one sample
|
||||||
|
remainingOut = su_render_samples(synthState, remaining, &buffer[2 * (su_max_samples - remaining)]);
|
||||||
|
if (remainingOut != remaining - SAMPLES_PER_ROW + 1)
|
||||||
|
{
|
||||||
|
printf("su_render_samples did not render SAMPLES_PER_ROW, despite rowLen being SAMPLES_PER_ROW");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (synthState->RowTick != 0)
|
||||||
|
{
|
||||||
|
printf("The row should be have been reseted");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
remaining = remainingOut;
|
||||||
|
if (i == 8)
|
||||||
|
synthState->Synth.Voices[0].Release++;
|
||||||
|
}
|
||||||
|
if (remaining != 0) {
|
||||||
|
printf("The buffer should be full and row finished");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
// Finally, now that there is no more buffer remaining, should return -1
|
||||||
|
if (su_render_samples(synthState, remaining, &buffer[2 * (su_max_samples - remaining)]) != -1)
|
||||||
|
{
|
||||||
|
printf("su_render_samples should have ran out of buffer and thus return -1");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
finish:
|
||||||
|
free(synthState);
|
||||||
|
free(buffer);
|
||||||
|
return retval;
|
||||||
|
fail:
|
||||||
|
retval = 1;
|
||||||
|
goto finish;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user