mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-27 19:00:25 -04:00
Implement a bridge to call Sointu from Go language.
The main interface is render_samples function, which renders several samples in one call, to limit the number of calls from Go to C. This is compiled into a library, which is then linked and called from bridge.go.
This commit is contained in:
parent
af14cd310b
commit
7aac3917b7
7
.gitignore
vendored
7
.gitignore
vendored
@ -18,4 +18,9 @@ build/
|
||||
old/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
.vscode/
|
||||
|
||||
# project specific
|
||||
# this is autogenerated from bridge.go.in
|
||||
bridge/bridge.go
|
||||
|
||||
|
20
BindConfig.txt
Normal file
20
BindConfig.txt
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright 2012 Douglas Linder
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cmake_minimum_required (VERSION 2.8)
|
||||
SET(HERE "${CMAKE_ARGV3}")
|
||||
SET(INCLUDE_PATH "${CMAKE_ARGV4}")
|
||||
SET(LIBRARY_PATH "${CMAKE_ARGV5}")
|
||||
message("${LIBRARY_PATH} ${INCLUDE_PATH}")
|
||||
configure_file(${HERE}/bridge/bridge.go.in ${HERE}/bridge/bridge.go)
|
55
bridge/bridge.go.in
Normal file
55
bridge/bridge.go.in
Normal file
@ -0,0 +1,55 @@
|
||||
package bridge
|
||||
|
||||
import "fmt"
|
||||
import "unsafe"
|
||||
|
||||
// #cgo CFLAGS: -I${INCLUDE_PATH}
|
||||
// #cgo LDFLAGS: ${LIBRARY_PATH}
|
||||
// #include <sointu.h>
|
||||
import "C"
|
||||
|
||||
type SynthState = C.SynthState
|
||||
|
||||
func (s *SynthState) Render(buffer []float32) int {
|
||||
fmt.Printf("Calling Render...\n")
|
||||
var ret = C.su_render_samples(s, C.int(len(buffer))/2, (*C.float)(&buffer[0]))
|
||||
fmt.Printf("Returning from Render...\n")
|
||||
return int(ret)
|
||||
}
|
||||
|
||||
func (s *SynthState) SetCommands(c [2048]byte) {
|
||||
pk := *((*[2048]C.uchar)(unsafe.Pointer(&c)))
|
||||
s.Commands = pk
|
||||
}
|
||||
|
||||
func (s *SynthState) SetValues(c [16384]byte) {
|
||||
pk := *((*[16384]C.uchar)(unsafe.Pointer(&c)))
|
||||
s.Values = pk
|
||||
}
|
||||
|
||||
func (s *SynthState) Trigger(voice int,note int) {
|
||||
fmt.Printf("Calling Trigger...\n")
|
||||
s.Synth.Voices[voice] = C.Voice{}
|
||||
s.Synth.Voices[voice].Note = C.int(note)
|
||||
fmt.Printf("Returning from Trigger...\n")
|
||||
}
|
||||
|
||||
func (s *SynthState) Release(voice int) {
|
||||
fmt.Printf("Calling Release...\n")
|
||||
s.Synth.Voices[voice].Release = 1
|
||||
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 {
|
||||
s := new(SynthState)
|
||||
s.RandSeed = 1
|
||||
return s
|
||||
}
|
77
bridge/bridge_test.go
Normal file
77
bridge/bridge_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
package bridge_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"github.com/vsariola/sointu/bridge"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const BPM = 100
|
||||
const SAMPLE_RATE = 44100
|
||||
const TOTAL_ROWS = 16
|
||||
const SAMPLES_PER_ROW = SAMPLE_RATE * 4 * 60 / (BPM * 16)
|
||||
|
||||
const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
||||
|
||||
// const bufsize = su_max_samples * 2
|
||||
|
||||
func TestBridge(t *testing.T) {
|
||||
commands := [2048]byte{
|
||||
2, 2, 11, 0, // envelope mono, envelope mono, out stereo, advance
|
||||
// TODO: pull these somehow from the C-side
|
||||
}
|
||||
values := [16384]byte{64, 64, 64, 80, 128, // envelope 1
|
||||
95, 64, 64, 80, 128, // envelope 2
|
||||
128}
|
||||
s := bridge.NewSynthState()
|
||||
// memcpy(synthState->Commands, commands, sizeof(commands));
|
||||
s.SetCommands(commands)
|
||||
// memcpy(synthState->Values, values, sizeof(values));
|
||||
s.SetValues(values)
|
||||
// synthState->RandSeed = 1;
|
||||
// initialized in NewSynthState
|
||||
// synthState->RowLen = INT32_MAX;
|
||||
s.RowLen = math.MaxInt32 // (why?)
|
||||
// synthState->NumVoices = 1;
|
||||
s.NumVoices = 1
|
||||
// synthState->Synth.Voices[0].Note = 64;
|
||||
s.Synth.Voices[0].Note = 64
|
||||
// retval = su_render_samples(buffer, su_max_samples / 2, synthState);
|
||||
buffer := make([]float32, su_max_samples)
|
||||
remaining := s.Render(buffer)
|
||||
if remaining != 0 {
|
||||
t.Fatalf("could not render full buffer, %v bytes remaining, expected %v", remaining, len(buffer))
|
||||
}
|
||||
// synthState->Synth.Voices[0].Release++;
|
||||
s.Synth.Voices[0].Release++
|
||||
sbuffer := make([]float32, su_max_samples)
|
||||
remaining = s.Render(sbuffer)
|
||||
if remaining != 0 {
|
||||
t.Fatalf("could not render second full buffer, %v bytes remaining, expected %v", remaining, len(buffer))
|
||||
}
|
||||
buffer = append(buffer, sbuffer...)
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", "test_render_samples.raw"))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
||||
var createdbuf bytes.Buffer
|
||||
err = binary.Write(&createdbuf, binary.LittleEndian, buffer)
|
||||
if err != nil {
|
||||
t.Fatalf("error converting buffer: %v", err)
|
||||
}
|
||||
createdb := createdbuf.Bytes()
|
||||
if len(createdb) != len(expectedb) {
|
||||
t.Fatalf("buffer length mismatch, got %v, expected %v", len(createdb), len(expectedb))
|
||||
}
|
||||
for i, v := range createdb {
|
||||
if expectedb[i] != v {
|
||||
t.Errorf("byte mismatch @ %v, got %v, expected %v", i, v, expectedb[i])
|
||||
}
|
||||
}
|
||||
}
|
119
include/sointu.h
Normal file
119
include/sointu.h
Normal file
@ -0,0 +1,119 @@
|
||||
#ifndef _SOINTU_H
|
||||
#define _SOINTU_H
|
||||
|
||||
#pragma pack(push,4) // this should be fine for both Go and assembly
|
||||
typedef struct Unit {
|
||||
float State[8];
|
||||
float Ports[8];
|
||||
} Unit;
|
||||
|
||||
typedef struct Voice {
|
||||
int Note;
|
||||
int Release;
|
||||
float Inputs[8];
|
||||
float Reserved[6];
|
||||
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;
|
||||
float Dcout;
|
||||
float Filtstate;
|
||||
} DelayWorkspace;
|
||||
|
||||
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.
|
||||
unsigned char Commands[32 * 64];
|
||||
unsigned char Values[32 * 64 * 8];
|
||||
unsigned int Polyphony;
|
||||
unsigned int NumVoices;
|
||||
unsigned int RandSeed;
|
||||
unsigned int Globaltime;
|
||||
unsigned int RowTick;
|
||||
unsigned int RowLen;
|
||||
} SynthState;
|
||||
#pragma pack(pop)
|
||||
|
||||
#if UINTPTR_MAX == 0xffffffff // are we 32-bit?
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
#define CALLCONV __attribute__ ((stdcall))
|
||||
#elif defined(_WIN32)
|
||||
#define CALLCONV __stdcall // on 32-bit platforms, we just use stdcall, as all know it
|
||||
#endif
|
||||
#else // 64-bit
|
||||
#define CALLCONV // the asm will use honor honor correct x64 ABI on all 64-bit platforms
|
||||
#endif
|
||||
|
||||
#ifdef INCLUDE_GMDLS
|
||||
extern void CALLCONV su_load_gmdls(void);
|
||||
#endif
|
||||
|
||||
// Returns the number of samples remaining in the buffer i.e. 0 if the buffer was
|
||||
// filled completely.
|
||||
//
|
||||
// NOTE: The buffer should have a length of 2 * maxsamples, as the audio
|
||||
// is stereo.
|
||||
//
|
||||
// You should always check if rowtick >= rowlen after calling this. If so, most
|
||||
// likely you didn't get full buffer filled but the end of row was hit before
|
||||
// filling the buffer. In that case, trigger/release new voices, set rowtick to 0.
|
||||
//
|
||||
// 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
|
||||
// modulated to be become infinite, this function might return maxsamples i.e. not
|
||||
// render any samples. If you try to call this with your buffer until the whole
|
||||
// buffer is filled, you will be stuck in an infinite loop.
|
||||
extern int CALLCONV su_render_samples(SynthState* synthState, int maxsamples, float* buffer);
|
||||
|
||||
// Arithmetic opcode ids
|
||||
extern const int su_add_id;
|
||||
extern const int su_addp_id;
|
||||
extern const int su_pop_id;
|
||||
extern const int su_loadnote_id;
|
||||
extern const int su_mul_id;
|
||||
extern const int su_mulp_id;
|
||||
extern const int su_push_id;
|
||||
extern const int su_xch_id;
|
||||
|
||||
// Effect opcode ids
|
||||
extern const int su_distort_id;
|
||||
extern const int su_hold_id;
|
||||
extern const int su_crush_id;
|
||||
extern const int su_gain_id;
|
||||
extern const int su_invgain_id;
|
||||
extern const int su_filter_id;
|
||||
extern const int su_clip_id;
|
||||
extern const int su_pan_id;
|
||||
extern const int su_delay_id;
|
||||
extern const int su_compres_id;
|
||||
|
||||
// Flowcontrol opcode ids
|
||||
extern const int su_advance_id;
|
||||
extern const int su_speed_id;
|
||||
|
||||
// Sink opcode ids
|
||||
extern const int su_out_id;
|
||||
extern const int su_outaux_id;
|
||||
extern const int su_aux_id;
|
||||
extern const int su_send_id;
|
||||
|
||||
// Source opcode ids
|
||||
extern const int su_envelope_id;
|
||||
extern const int su_noise_id;
|
||||
extern const int su_aux_id;
|
||||
extern const int su_oscillat_id;
|
||||
extern const int su_loadval_id;
|
||||
extern const int su_receive_id;
|
||||
extern const int su_in_id;
|
||||
|
||||
#endif // _SOINTU_H
|
@ -0,0 +1,18 @@
|
||||
set(LIB sointu)
|
||||
|
||||
set(SOURCES sointu.asm)
|
||||
|
||||
# Headers
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
# Library target
|
||||
add_library(${LIB} ${SOURCES})
|
||||
set_target_properties(${LIB} PROPERTIES LINKER_LANGUAGE C)
|
||||
target_link_libraries(${LIB})
|
||||
target_compile_definitions(${LIB} PUBLIC SU_USE_INTROSPECTION)
|
||||
|
||||
# Generate cgo wrapper
|
||||
add_custom_command(TARGET ${LIB}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -P ${PROJECT_SOURCE_DIR}/BindConfig.txt ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include $<TARGET_FILE:sointu>
|
||||
)
|
@ -3,14 +3,91 @@
|
||||
; Various compile time definitions exported
|
||||
SECT_DATA(introscn)
|
||||
|
||||
|
||||
%ifdef SU_USE_16BIT_OUTPUT
|
||||
EXPORT MANGLE_DATA(su_use_16bit_output) dd 1
|
||||
EXPORT MANGLE_DATA(su_use_16bit_output)
|
||||
dd 1
|
||||
%else
|
||||
EXPORT MANGLE_DATA(su_use_16bit_output) dd 0
|
||||
EXPORT MANGLE_DATA(su_use_16bit_output)
|
||||
dd 0
|
||||
%endif
|
||||
|
||||
%ifdef MAX_SAMPLES
|
||||
EXPORT MANGLE_DATA(su_max_samples) dd MAX_SAMPLES
|
||||
%ifndef SU_DISABLE_PLAYER
|
||||
%ifdef MAX_SAMPLES
|
||||
EXPORT MANGLE_DATA(su_max_samples)
|
||||
dd MAX_SAMPLES
|
||||
%endif
|
||||
%endif
|
||||
|
||||
; Arithmetic opcode ids
|
||||
EXPORT MANGLE_DATA(su_add_id)
|
||||
dd ADD_ID
|
||||
EXPORT MANGLE_DATA(su_addp_id)
|
||||
dd ADDP_ID
|
||||
EXPORT MANGLE_DATA(su_pop_id)
|
||||
dd POP_ID
|
||||
EXPORT MANGLE_DATA(su_loadnote_id)
|
||||
dd LOADNOTE_ID
|
||||
EXPORT MANGLE_DATA(su_mul_id)
|
||||
|
||||
dd MUL_ID
|
||||
EXPORT MANGLE_DATA(su_mulp_id)
|
||||
dd MULP_ID
|
||||
EXPORT MANGLE_DATA(su_push_id)
|
||||
dd PUSH_ID
|
||||
EXPORT MANGLE_DATA(su_xch_id)
|
||||
dd XCH_ID
|
||||
|
||||
; Effect opcode ids
|
||||
EXPORT MANGLE_DATA(su_distort_id)
|
||||
dd DISTORT_ID
|
||||
EXPORT MANGLE_DATA(su_hold_id)
|
||||
dd HOLD_ID
|
||||
EXPORT MANGLE_DATA(su_crush_id)
|
||||
dd CRUSH_ID
|
||||
EXPORT MANGLE_DATA(su_gain_id)
|
||||
dd GAIN_ID
|
||||
EXPORT MANGLE_DATA(su_invgain_id)
|
||||
dd INVGAIN_ID
|
||||
EXPORT MANGLE_DATA(su_filter_id)
|
||||
dd FILTER_ID
|
||||
EXPORT MANGLE_DATA(su_clip_id)
|
||||
dd CLIP_ID
|
||||
EXPORT MANGLE_DATA(su_pan_id)
|
||||
dd PAN_ID
|
||||
EXPORT MANGLE_DATA(su_delay_id)
|
||||
dd DELAY_ID
|
||||
EXPORT MANGLE_DATA(su_compres_id)
|
||||
dd COMPRES_ID
|
||||
|
||||
; Flowcontrol opcode ids
|
||||
EXPORT MANGLE_DATA(su_advance_id)
|
||||
dd SU_ADVANCE_ID
|
||||
EXPORT MANGLE_DATA(su_speed_id)
|
||||
dd SPEED_ID
|
||||
|
||||
; Sink opcode ids
|
||||
EXPORT MANGLE_DATA(su_out_id)
|
||||
dd OUT_ID
|
||||
EXPORT MANGLE_DATA(su_outaux_id)
|
||||
dd OUTAUX_ID
|
||||
EXPORT MANGLE_DATA(su_aux_id)
|
||||
dd AUX_ID
|
||||
EXPORT MANGLE_DATA(su_send_id)
|
||||
dd SEND_ID
|
||||
|
||||
; Source opcode ids
|
||||
EXPORT MANGLE_DATA(su_envelope_id)
|
||||
dd ENVELOPE_ID
|
||||
EXPORT MANGLE_DATA(su_noise_id)
|
||||
dd NOISE_ID
|
||||
EXPORT MANGLE_DATA(su_oscillat_id)
|
||||
dd OSCILLAT_ID
|
||||
EXPORT MANGLE_DATA(su_loadval_id)
|
||||
dd LOADVAL_ID
|
||||
EXPORT MANGLE_DATA(su_receive_id)
|
||||
dd RECEIVE_ID
|
||||
EXPORT MANGLE_DATA(su_in_id)
|
||||
dd IN_ID
|
||||
|
||||
%endif ; SU_USE_INTROSPECTION
|
||||
|
121
src/sointu.asm
Normal file
121
src/sointu.asm
Normal file
@ -0,0 +1,121 @@
|
||||
; source file for compiling sointu as a library
|
||||
%define SU_DISABLE_PLAYER
|
||||
|
||||
%include "sointu_header.inc"
|
||||
|
||||
; TODO: make sure compile everything in
|
||||
|
||||
USE_ENVELOPE
|
||||
USE_OSCILLAT
|
||||
USE_MULP
|
||||
USE_PAN
|
||||
USE_OUT
|
||||
|
||||
%define INCLUDE_TRISAW
|
||||
%define INCLUDE_SINE
|
||||
%define INCLUDE_PULSE
|
||||
%define INCLUDE_GATE
|
||||
%define INCLUDE_STEREO_OSCILLAT
|
||||
%define INCLUDE_STEREO_ENVELOPE
|
||||
%define INCLUDE_STEREO_OUT
|
||||
%define INCLUDE_POLYPHONY
|
||||
%define INCLUDE_MULTIVOICE_TRACKS
|
||||
|
||||
%include "sointu_footer.inc"
|
||||
|
||||
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
|
||||
.randseed resd 1
|
||||
.globaltime resd 1
|
||||
.rowtick resd 1
|
||||
.rowlen resd 1
|
||||
endstruc
|
||||
|
||||
SECT_TEXT(sursampl)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_render_samples,12)
|
||||
%if BITS == 32 ; stdcall
|
||||
pushad ; push registers
|
||||
mov ecx, [esp + 4 + 32] ; ecx = &synthState
|
||||
mov esi, [esp + 8 + 32] ; esi = bufsize
|
||||
mov edx, [esp + 12 + 32] ; edx = &buffer
|
||||
%else
|
||||
%ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rdx = bufsize, r8 = &buffer, rcx = &synthstate
|
||||
push_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||
mov rsi, rdx ; rsi = bufsize
|
||||
mov rdx, r8 ; rdx = &buffer
|
||||
%else ; System V ABI: rsi = bufsize, rdx = &buffer, rdi = &synthstate
|
||||
push_registers rbx, rbp ; System V ABI: these registers are non-volatile
|
||||
mov rcx, rdi ; rcx = &Synthstate
|
||||
%endif
|
||||
%endif
|
||||
push _SI ; push bufsize
|
||||
push _DX ; push bufptr
|
||||
push _CX ; this takes place of the voicetrack
|
||||
mov eax, [_CX + su_synth_state.randseed]
|
||||
push _AX ; randseed
|
||||
mov eax, [_CX + su_synth_state.globaltime]
|
||||
push _AX ; global tick time
|
||||
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
|
||||
mov eax, [_CX + su_synth_state.rowtick]
|
||||
su_render_samples_loop:
|
||||
mov _CX, [_SP + PTRSIZE*3]
|
||||
push _AX ; push rowtick
|
||||
mov eax, [_CX + su_synth_state.polyphony]
|
||||
push _AX ;polyphony
|
||||
mov eax, [_CX + su_synth_state.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]
|
||||
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]
|
||||
movsd ; copy left channel to output buffer
|
||||
movsd ; copy right channel to output buffer
|
||||
mov [_SP + PTRSIZE*5], _DI ; save back the updated ptr
|
||||
lea _DI, [_SI-8]
|
||||
xor eax, eax
|
||||
stosd ; clear left 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
|
||||
inc dword [_SP + PTRSIZE] ; increment global time, used by delays
|
||||
inc eax
|
||||
dec dword [_SP + PTRSIZE*5]
|
||||
jz su_render_samples_finish
|
||||
cmp eax, [_SP] ; compare current tick to rowlength
|
||||
jl su_render_samples_loop
|
||||
su_render_samples_finish:
|
||||
pop _CX
|
||||
pop _BX
|
||||
pop _DX
|
||||
pop _CX
|
||||
mov [_CX + su_synth_state.randseed], edx
|
||||
mov [_CX + su_synth_state.globaltime], ebx
|
||||
mov [_CX + su_synth_state.rowtick], eax
|
||||
pop _AX
|
||||
pop _AX ; todo: return correct value based on this
|
||||
%if BITS == 32 ; stdcall
|
||||
popad
|
||||
ret 12
|
||||
%else
|
||||
%ifidn __OUTPUT_FORMAT__,win64
|
||||
pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||
%else
|
||||
pop_registers rbx, rbp ; System V ABI: these registers are non-volatile
|
||||
%endif
|
||||
ret
|
||||
%endif
|
@ -274,6 +274,8 @@ EXPORT MANGLE_FUNC(su_power,0)
|
||||
fstp st1 ; 2^x
|
||||
ret
|
||||
|
||||
%ifndef SU_DISABLE_PLAYER
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; output_sound macro: used by the render function to write sound to buffer
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -482,6 +484,8 @@ su_update_voices_skipadd:
|
||||
|
||||
%endif ;INCLUDE_MULTIVOICE_TRACKS
|
||||
|
||||
%endif ; SU_DISABLE_PLAYER
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Include the rest of the code
|
||||
;-------------------------------------------------------------------------------
|
||||
|
@ -1,11 +1,11 @@
|
||||
function(regression_test testname)
|
||||
if(${ARGC} LESS 4)
|
||||
set(source ${testname})
|
||||
set(source ${testname}.asm)
|
||||
else()
|
||||
set(source ${ARGV3})
|
||||
endif()
|
||||
|
||||
add_executable(${testname} ${source}.asm test_renderer.c)
|
||||
add_executable(${testname} ${source} test_renderer.c)
|
||||
|
||||
# the tests include the entire ASM but we still want to rebuild when they change
|
||||
file(GLOB SOINTU ${PROJECT_SOURCE_DIR}/src/*.inc
|
||||
@ -143,9 +143,12 @@ regression_test(test_delay_drymod "ENVELOPE;FOP_MULP;PANNING;VCO_SINE;SEND")
|
||||
regression_test(test_delay_flanger "ENVELOPE;FOP_MULP;PANNING;VCO_SINE;SEND")
|
||||
|
||||
regression_test(test_envelope_mod "VCO_SINE;ENVELOPE;SEND")
|
||||
regression_test(test_envelope_16bit ENVELOPE "" test_envelope)
|
||||
regression_test(test_envelope_16bit ENVELOPE "" test_envelope.asm)
|
||||
target_compile_definitions(test_envelope_16bit PUBLIC SU_USE_16BIT_OUTPUT)
|
||||
|
||||
regression_test(test_polyphony "ENVELOPE;VCO_SINE")
|
||||
regression_test(test_chords "ENVELOPE;VCO_SINE")
|
||||
regression_test(test_speed "ENVELOPE;VCO_SINE")
|
||||
|
||||
regression_test(test_render_samples ENVELOPE "" test_render_samples.c)
|
||||
target_link_libraries(test_render_samples sointu)
|
BIN
tests/expected_output/test_render_samples.raw
Normal file
BIN
tests/expected_output/test_render_samples.raw
Normal file
Binary file not shown.
45
tests/test_render_samples.c
Normal file
45
tests/test_render_samples.c
Normal file
@ -0,0 +1,45 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "../include/sointu.h"
|
||||
|
||||
#if UINTPTR_MAX == 0xffffffff // are we 32-bit?
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
#define CALLCONV __attribute__ ((stdcall))
|
||||
#elif defined(_WIN32)
|
||||
#define CALLCONV __stdcall // on 32-bit platforms, we just use stdcall, as all know it
|
||||
#endif
|
||||
#else // 64-bit
|
||||
#define CALLCONV // the asm will use honor honor correct x64 ABI on all 64-bit platforms
|
||||
#endif
|
||||
|
||||
#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;
|
||||
|
||||
void CALLCONV su_render(float* buffer) {
|
||||
SynthState* synthState;
|
||||
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 retval;
|
||||
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->RowLen = INT32_MAX;
|
||||
synthState->NumVoices = 1;
|
||||
synthState->Synth.Voices[0].Note = 64;
|
||||
retval = su_render_samples(synthState, su_max_samples / 2, buffer);
|
||||
synthState->Synth.Voices[0].Release++;
|
||||
buffer = buffer + su_max_samples;
|
||||
retval = su_render_samples(synthState, su_max_samples / 2, buffer);
|
||||
free(synthState);
|
||||
return;
|
||||
}
|
Loading…
Reference in New Issue
Block a user