mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -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
5
.gitignore
vendored
5
.gitignore
vendored
@ -19,3 +19,8 @@ old/
|
|||||||
|
|
||||||
# VS Code
|
# 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
|
; Various compile time definitions exported
|
||||||
SECT_DATA(introscn)
|
SECT_DATA(introscn)
|
||||||
|
|
||||||
|
|
||||||
%ifdef SU_USE_16BIT_OUTPUT
|
%ifdef SU_USE_16BIT_OUTPUT
|
||||||
EXPORT MANGLE_DATA(su_use_16bit_output) dd 1
|
EXPORT MANGLE_DATA(su_use_16bit_output)
|
||||||
|
dd 1
|
||||||
%else
|
%else
|
||||||
EXPORT MANGLE_DATA(su_use_16bit_output) dd 0
|
EXPORT MANGLE_DATA(su_use_16bit_output)
|
||||||
|
dd 0
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
%ifndef SU_DISABLE_PLAYER
|
||||||
%ifdef MAX_SAMPLES
|
%ifdef MAX_SAMPLES
|
||||||
EXPORT MANGLE_DATA(su_max_samples) dd MAX_SAMPLES
|
EXPORT MANGLE_DATA(su_max_samples)
|
||||||
|
dd MAX_SAMPLES
|
||||||
%endif
|
%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
|
%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
|
fstp st1 ; 2^x
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
%ifndef SU_DISABLE_PLAYER
|
||||||
|
|
||||||
;-------------------------------------------------------------------------------
|
;-------------------------------------------------------------------------------
|
||||||
; output_sound macro: used by the render function to write sound to buffer
|
; 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 ;INCLUDE_MULTIVOICE_TRACKS
|
||||||
|
|
||||||
|
%endif ; SU_DISABLE_PLAYER
|
||||||
|
|
||||||
;-------------------------------------------------------------------------------
|
;-------------------------------------------------------------------------------
|
||||||
; Include the rest of the code
|
; Include the rest of the code
|
||||||
;-------------------------------------------------------------------------------
|
;-------------------------------------------------------------------------------
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
function(regression_test testname)
|
function(regression_test testname)
|
||||||
if(${ARGC} LESS 4)
|
if(${ARGC} LESS 4)
|
||||||
set(source ${testname})
|
set(source ${testname}.asm)
|
||||||
else()
|
else()
|
||||||
set(source ${ARGV3})
|
set(source ${ARGV3})
|
||||||
endif()
|
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
|
# the tests include the entire ASM but we still want to rebuild when they change
|
||||||
file(GLOB SOINTU ${PROJECT_SOURCE_DIR}/src/*.inc
|
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_delay_flanger "ENVELOPE;FOP_MULP;PANNING;VCO_SINE;SEND")
|
||||||
|
|
||||||
regression_test(test_envelope_mod "VCO_SINE;ENVELOPE;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)
|
target_compile_definitions(test_envelope_16bit PUBLIC SU_USE_16BIT_OUTPUT)
|
||||||
|
|
||||||
regression_test(test_polyphony "ENVELOPE;VCO_SINE")
|
regression_test(test_polyphony "ENVELOPE;VCO_SINE")
|
||||||
regression_test(test_chords "ENVELOPE;VCO_SINE")
|
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)
|
||||||
|
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