mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-03 17:18:20 -04:00
feat(go4k&sointu): Export .h C header files from the songs using go, also automatically during build for the tests.
The header files are automatically generated during build. No need to #define anything; everything is fixed by the .asm file. This adds go as a dependency to run the unit tests, but this is probably not a bad thing, as go is probably needed anyway if one wants to actually start developing Sointu.
This commit is contained in:
parent
a1e7e82d6d
commit
efbcf1454e
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@ -42,7 +42,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: lukka/get-cmake@v3.18.3
|
- uses: lukka/get-cmake@v3.18.3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
if: ${{ matrix.config.gotests == 'yes' }}
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: ilammy/setup-nasm@v1.2.0
|
- uses: ilammy/setup-nasm@v1.2.0
|
||||||
- name: Run ctest
|
- name: Run ctest
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
func DeserializeAsm(asmcode string) (*Song, error) {
|
func DeserializeAsm(asmcode string) (*Song, error) {
|
||||||
var bpm int
|
var bpm int
|
||||||
|
output16Bit := false
|
||||||
scanner := bufio.NewScanner(strings.NewReader(asmcode))
|
scanner := bufio.NewScanner(strings.NewReader(asmcode))
|
||||||
patterns := make([][]byte, 0)
|
patterns := make([][]byte, 0)
|
||||||
tracks := make([]Track, 0)
|
tracks := make([]Track, 0)
|
||||||
@ -86,6 +87,8 @@ func DeserializeAsm(asmcode string) (*Song, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
bpm = ints[0]
|
bpm = ints[0]
|
||||||
|
} else if defineName == "OUTPUT_16BIT" {
|
||||||
|
output16Bit = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "PATTERN":
|
case "PATTERN":
|
||||||
@ -158,7 +161,7 @@ func DeserializeAsm(asmcode string) (*Song, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch}
|
s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch, Output16Bit: output16Bit}
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,6 +255,9 @@ func SerializeAsm(song *Song) (string, error) {
|
|||||||
}
|
}
|
||||||
// The actual printing starts here
|
// The actual printing starts here
|
||||||
println("%%define BPM %d", song.BPM)
|
println("%%define BPM %d", song.BPM)
|
||||||
|
if song.Output16Bit {
|
||||||
|
println("%%define OUTPUT_16BIT")
|
||||||
|
}
|
||||||
// delay modulation is pretty much the only %define that the asm preprocessor cannot figure out
|
// delay modulation is pretty much the only %define that the asm preprocessor cannot figure out
|
||||||
// as the preprocessor has no clue if a SEND modulates a delay unit. So, unfortunately, for the
|
// as the preprocessor has no clue if a SEND modulates a delay unit. So, unfortunately, for the
|
||||||
// time being, we need to figure during export if INCLUDE_DELAY_MODULATION needs to be defined.
|
// time being, we need to figure during export if INCLUDE_DELAY_MODULATION needs to be defined.
|
||||||
@ -369,3 +375,68 @@ func SerializeAsm(song *Song) (string, error) {
|
|||||||
ret := b.String()
|
ret := b.String()
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExportCHeader(song *Song, maxSamples int) string {
|
||||||
|
template :=
|
||||||
|
`// auto-generated by Sointu, editing not recommended
|
||||||
|
#ifndef SU_RENDER_H
|
||||||
|
#define SU_RENDER_H
|
||||||
|
|
||||||
|
#define SU_MAX_SAMPLES %v
|
||||||
|
#define SU_BUFFER_LENGTH (SU_MAX_SAMPLES*2)
|
||||||
|
|
||||||
|
#define SU_SAMPLE_RATE 44100
|
||||||
|
#define SU_BPM %v
|
||||||
|
#define SU_PATTERN_SIZE %v
|
||||||
|
#define SU_MAX_PATTERNS %v
|
||||||
|
#define SU_TOTAL_ROWS (SU_MAX_PATTERNS*SU_PATTERN_SIZE)
|
||||||
|
#define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*4*60/(SU_BPM*16))
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#if UINTPTR_MAX == 0xffffffff
|
||||||
|
#if defined(__clang__) || defined(__GNUC__)
|
||||||
|
#define SU_CALLCONV __attribute__ ((stdcall))
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#define SU_CALLCONV __stdcall
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define SU_CALLCONV
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef %v SUsample;
|
||||||
|
#define SU_SAMPLE_RANGE %v
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void SU_CALLCONV su_render_song(SUsample *buffer);
|
||||||
|
%v
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
`
|
||||||
|
maxSamplesText := "SU_TOTAL_ROWS*SU_SAMPLES_PER_ROW"
|
||||||
|
if maxSamples > 0 {
|
||||||
|
maxSamplesText = fmt.Sprintf("%v", maxSamples)
|
||||||
|
}
|
||||||
|
sampleType := "float"
|
||||||
|
sampleRange := "1.0f"
|
||||||
|
if song.Output16Bit {
|
||||||
|
sampleType = "short"
|
||||||
|
sampleRange = "32768"
|
||||||
|
}
|
||||||
|
defineGmdls := ""
|
||||||
|
for _, instr := range song.Patch.Instruments {
|
||||||
|
for _, unit := range instr.Units {
|
||||||
|
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample {
|
||||||
|
defineGmdls = "\n#define SU_LOAD_GMDLS\nvoid SU_CALLCONV su_load_gmdls(void);"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header := fmt.Sprintf(template, maxSamplesText, song.BPM, song.PatternRows(), song.SequenceLength(), sampleType, sampleRange, defineGmdls)
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
@ -46,6 +46,7 @@ func TestAllAsmFiles(t *testing.T) {
|
|||||||
t.Fatalf("Compiling patch failed: %v", err)
|
t.Fatalf("Compiling patch failed: %v", err)
|
||||||
}
|
}
|
||||||
buffer, err := go4k.Play(synth, *song)
|
buffer, err := go4k.Play(synth, *song)
|
||||||
|
buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Play failed: %v", err)
|
t.Fatalf("Play failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -70,7 +71,12 @@ func TestAllAsmFiles(t *testing.T) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compareToRaw(t, buffer, testname+".raw")
|
if song.Output16Bit {
|
||||||
|
int16Buffer := convertToInt16Buffer(buffer)
|
||||||
|
compareToRawInt16(t, int16Buffer, testname+".raw")
|
||||||
|
} else {
|
||||||
|
compareToRawFloat32(t, buffer, testname+".raw")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,6 +120,7 @@ func TestSerializingAllAsmFiles(t *testing.T) {
|
|||||||
t.Fatalf("Compiling patch failed: %v", err)
|
t.Fatalf("Compiling patch failed: %v", err)
|
||||||
}
|
}
|
||||||
buffer, err := go4k.Play(synth, *song2)
|
buffer, err := go4k.Play(synth, *song2)
|
||||||
|
buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Play failed: %v", err)
|
t.Fatalf("Play failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -138,12 +145,17 @@ func TestSerializingAllAsmFiles(t *testing.T) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compareToRaw(t, buffer, testname+".raw")
|
if song.Output16Bit {
|
||||||
|
int16Buffer := convertToInt16Buffer(buffer)
|
||||||
|
compareToRawInt16(t, int16Buffer, testname+".raw")
|
||||||
|
} else {
|
||||||
|
compareToRawFloat32(t, buffer, testname+".raw")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareToRaw(t *testing.T, buffer []float32, rawname string) {
|
func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
||||||
_, filename, _, _ := runtime.Caller(0)
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,3 +176,33 @@ func compareToRaw(t *testing.T, buffer []float32, rawname string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compareToRawInt16(t *testing.T, buffer []int16, rawname string) {
|
||||||
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
|
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot read expected: %v", err)
|
||||||
|
}
|
||||||
|
expected := make([]int16, len(expectedb)/2)
|
||||||
|
buf := bytes.NewReader(expectedb)
|
||||||
|
err = binary.Read(buf, binary.LittleEndian, &expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error converting expected buffer: %v", err)
|
||||||
|
}
|
||||||
|
if len(expected) != len(buffer) {
|
||||||
|
t.Fatalf("buffer length mismatch, got %v, expected %v", len(buffer), len(expected))
|
||||||
|
}
|
||||||
|
for i, v := range expected {
|
||||||
|
if math.IsNaN(float64(buffer[i])) || v != buffer[i] {
|
||||||
|
t.Fatalf("error at sample position %v", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToInt16Buffer(buffer []float32) []int16 {
|
||||||
|
int16Buffer := make([]int16, len(buffer))
|
||||||
|
for i, v := range buffer {
|
||||||
|
int16Buffer[i] = int16(math.Round(math.Min(math.Max(float64(v), -1.0), 1.0) * 32767))
|
||||||
|
}
|
||||||
|
return int16Buffer
|
||||||
|
}
|
||||||
|
@ -7,14 +7,20 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/vsariola/sointu/go4k"
|
"github.com/vsariola/sointu/go4k"
|
||||||
|
"github.com/vsariola/sointu/go4k/bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
write := flag.Bool("w", false, "Do not print reformatted asm songs to standard output. If a file's formatting is different from asmfmt's, overwrite it with asmfmt's version.")
|
write := flag.Bool("w", false, "Do not print reformatted asm songs to standard output. If a file's formatting is different from asmfmt's, overwrite it with asmfmt's version.")
|
||||||
list := flag.Bool("l", false, "Do not print reformatted asm songs to standard output, just list the filenames that reformatting changes.")
|
list := flag.Bool("l", false, "Do not print reformatted asm songs to standard output, just list the filenames that reformatting changes.")
|
||||||
help := flag.Bool("h", false, "show help")
|
help := flag.Bool("h", false, "Show help.")
|
||||||
|
exactLength := flag.Bool("e", false, "Calculate the exact length of song by rendering it once. Only useful when using SPEED opcodes.")
|
||||||
|
noformat := flag.Bool("d", false, "Disable formatting completely.")
|
||||||
|
header := flag.Bool("c", false, "Generate the .h C-header files.")
|
||||||
|
headeroutdir := flag.String("o", "", "Output directory for C-header files. By default, the headers are put in the same directory as the .asm file.")
|
||||||
flag.Usage = printUsage
|
flag.Usage = printUsage
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if flag.NArg() == 0 || *help {
|
if flag.NArg() == 0 || *help {
|
||||||
@ -31,6 +37,44 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse the file (%v)", err)
|
return fmt.Errorf("could not parse the file (%v)", err)
|
||||||
}
|
}
|
||||||
|
if *header {
|
||||||
|
folder, name := filepath.Split(filename)
|
||||||
|
if *headeroutdir != "" {
|
||||||
|
folder = *headeroutdir
|
||||||
|
}
|
||||||
|
name = strings.TrimSuffix(name, filepath.Ext(name)) + ".h"
|
||||||
|
headerfile := filepath.Join(folder, name)
|
||||||
|
maxSamples := 0 // 0 means it is calculated automatically
|
||||||
|
if *exactLength {
|
||||||
|
synth, err := bridge.Synth(song.Patch)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create synth based on the patch (%v)", err)
|
||||||
|
}
|
||||||
|
buffer, err := go4k.Play(synth, *song) // render the song to calculate its length
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error when rendering the song for calculating its length (%v)", err)
|
||||||
|
}
|
||||||
|
maxSamples = len(buffer) / 2
|
||||||
|
}
|
||||||
|
newheader := go4k.ExportCHeader(song, maxSamples)
|
||||||
|
origHeader, err := ioutil.ReadFile(headerfile)
|
||||||
|
if *list {
|
||||||
|
if err != nil || newheader != string(origHeader) {
|
||||||
|
fmt.Println(headerfile)
|
||||||
|
}
|
||||||
|
} else if !*write {
|
||||||
|
fmt.Print(newheader)
|
||||||
|
}
|
||||||
|
if *write {
|
||||||
|
if err != nil || newheader != string(origHeader) {
|
||||||
|
err := ioutil.WriteFile(headerfile, []byte(newheader), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could write to file (%v)", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !*noformat {
|
||||||
formattedCode, err := go4k.SerializeAsm(song)
|
formattedCode, err := go4k.SerializeAsm(song)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not reformat the file (%v)", err)
|
return fmt.Errorf("could not reformat the file (%v)", err)
|
||||||
@ -50,6 +94,7 @@ func main() {
|
|||||||
} else if !*write {
|
} else if !*write {
|
||||||
fmt.Print(formattedCode)
|
fmt.Print(formattedCode)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
retval := 0
|
retval := 0
|
||||||
|
@ -10,6 +10,7 @@ type Song struct {
|
|||||||
Patterns [][]byte
|
Patterns [][]byte
|
||||||
Tracks []Track
|
Tracks []Track
|
||||||
Patch Patch
|
Patch Patch
|
||||||
|
Output16Bit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Song) PatternRows() int {
|
func (s *Song) PatternRows() int {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/vsariola/sointu/go4k"
|
"github.com/vsariola/sointu/go4k"
|
||||||
)
|
)
|
||||||
|
|
||||||
const expectedMarshaled = `{"BPM":100,"Patterns":["QABEACAAAABLAE4AAAAAAA=="],"Tracks":[{"NumVoices":1,"Sequence":"AA=="}],"Patch":{"Instruments":[{"NumVoices":1,"Units":[{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":96,"detune":64,"flags":64,"gain":128,"phase":0,"shape":64,"stereo":0,"transpose":64}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":64,"detune":64,"flags":64,"gain":128,"phase":64,"shape":96,"stereo":0,"transpose":72}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"out","Parameters":{"gain":128,"stereo":1}}]}],"DelayTimes":[],"SampleOffsets":[]}}`
|
const expectedMarshaled = `{"BPM":100,"Patterns":["QABEACAAAABLAE4AAAAAAA=="],"Tracks":[{"NumVoices":1,"Sequence":"AA=="}],"Patch":{"Instruments":[{"NumVoices":1,"Units":[{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":96,"detune":64,"flags":64,"gain":128,"phase":0,"shape":64,"stereo":0,"transpose":64}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":64,"detune":64,"flags":64,"gain":128,"phase":64,"shape":96,"stereo":0,"transpose":72}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"out","Parameters":{"gain":128,"stereo":1}}]}],"DelayTimes":[],"SampleOffsets":[]},"Output16Bit":false}`
|
||||||
|
|
||||||
var testSong = go4k.Song{
|
var testSong = go4k.Song{
|
||||||
BPM: 100,
|
BPM: 100,
|
||||||
|
@ -37,7 +37,7 @@ func TestPlayer(t *testing.T) {
|
|||||||
SampleOffsets: []go4k.SampleOffset{}}
|
SampleOffsets: []go4k.SampleOffset{}}
|
||||||
patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}
|
patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}
|
||||||
tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
|
tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
|
||||||
song := go4k.Song{100, patterns, tracks, patch}
|
song := go4k.Song{100, patterns, tracks, patch, false}
|
||||||
synth, err := bridge.Synth(patch)
|
synth, err := bridge.Synth(patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Compiling patch failed: %v", err)
|
t.Fatalf("Compiling patch failed: %v", err)
|
||||||
|
@ -292,7 +292,7 @@ EXPORT MANGLE_FUNC(su_power,0)
|
|||||||
; Stack : sample row pushad output_ptr
|
; Stack : sample row pushad output_ptr
|
||||||
;-------------------------------------------------------------------------------
|
;-------------------------------------------------------------------------------
|
||||||
%macro output_sound 0
|
%macro output_sound 0
|
||||||
%ifndef SU_USE_16BIT_OUTPUT
|
%ifndef OUTPUT_16BIT
|
||||||
%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_synthworkspace.left
|
mov _SI, PTRWORD su_synth_obj + su_synthworkspace.left
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
%endif
|
%endif
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%ifdef SU_USE_16BIT_OUTPUT
|
%ifdef OUTPUT_16BIT
|
||||||
%define SU_INCLUDE_CLIP
|
%define SU_INCLUDE_CLIP
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
%ifdef INCLUDE_GMDLS
|
%ifdef INCLUDE_SAMPLES
|
||||||
|
|
||||||
%define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls
|
%define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
%ifdef INCLUDE_GMDLS
|
%ifdef INCLUDE_SAMPLES
|
||||||
|
|
||||||
%define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls
|
%define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls
|
||||||
|
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
function(regression_test testname)
|
function(regression_test testname)
|
||||||
if(${ARGC} LESS 4)
|
if(${ARGC} LESS 4)
|
||||||
set(source ${testname}.asm)
|
set(source ${testname}.asm)
|
||||||
|
set (headerfile ${CMAKE_CURRENT_BINARY_DIR}/${testname}.h)
|
||||||
|
add_custom_command(
|
||||||
|
PRE_BUILD
|
||||||
|
OUTPUT ${headerfile}
|
||||||
|
COMMAND go run ${PROJECT_SOURCE_DIR}/go4k/cmd/asmfmt/main.go -c -d -w -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source}
|
||||||
|
DEPENDS ${source}
|
||||||
|
)
|
||||||
|
add_executable(${testname} ${source} test_renderer.c ${headerfile})
|
||||||
|
target_compile_definitions(${testname} PUBLIC TEST_HEADER=<${testname}.h>)
|
||||||
else()
|
else()
|
||||||
set(source ${ARGV3})
|
set(source ${ARGV3})
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(${testname} ${source} test_renderer.c)
|
add_executable(${testname} ${source} test_renderer.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
# 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
|
||||||
@ -20,7 +28,6 @@ function(regression_test testname)
|
|||||||
set_source_files_properties(${FOURKLANG} PROPERTIES HEADER_FILE_ONLY TRUE)
|
set_source_files_properties(${FOURKLANG} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||||
|
|
||||||
add_test(${testname} ${testname})
|
add_test(${testname} ${testname})
|
||||||
target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}" SU_USE_INTROSPECTION SU_USE_PLAYER)
|
|
||||||
target_link_libraries(${testname} ${HEADERLIB})
|
target_link_libraries(${testname} ${HEADERLIB})
|
||||||
|
|
||||||
set (rawinput ${CMAKE_CURRENT_SOURCE_DIR}/expected_output/${testname}.raw)
|
set (rawinput ${CMAKE_CURRENT_SOURCE_DIR}/expected_output/${testname}.raw)
|
||||||
@ -30,6 +37,9 @@ function(regression_test testname)
|
|||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${rawinput} ${rawoutput}
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${rawinput} ${rawoutput}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(${testname} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}")
|
||||||
|
|
||||||
add_dependencies(${testname} ${testname}_rawcopy)
|
add_dependencies(${testname} ${testname}_rawcopy)
|
||||||
|
|
||||||
if(ARGC GREATER 1)
|
if(ARGC GREATER 1)
|
||||||
@ -94,13 +104,7 @@ regression_test(test_oscillat_gate ENVELOPE)
|
|||||||
regression_test(test_oscillat_stereo ENVELOPE)
|
regression_test(test_oscillat_stereo ENVELOPE)
|
||||||
if(WIN32) # The samples are currently only GMDLs based, and thus require Windows.
|
if(WIN32) # The samples are currently only GMDLs based, and thus require Windows.
|
||||||
regression_test(test_oscillat_sample ENVELOPE)
|
regression_test(test_oscillat_sample ENVELOPE)
|
||||||
target_compile_definitions(test_oscillat_sample PUBLIC INCLUDE_GMDLS)
|
|
||||||
regression_test(test_oscillat_sample_stereo ENVELOPE)
|
regression_test(test_oscillat_sample_stereo ENVELOPE)
|
||||||
target_compile_definitions(test_oscillat_sample_stereo PUBLIC INCLUDE_GMDLS)
|
|
||||||
regression_test(test_oscillat_sample_rtables ENVELOPE "" test_oscillat_sample.asm)
|
|
||||||
target_compile_definitions(test_oscillat_sample_rtables PUBLIC INCLUDE_GMDLS RUNTIME_TABLES)
|
|
||||||
regression_test(test_oscillat_sample_stereo_rtables ENVELOPE "" test_oscillat_sample_stereo.asm)
|
|
||||||
target_compile_definitions(test_oscillat_sample_stereo_rtables PUBLIC INCLUDE_GMDLS RUNTIME_TABLES)
|
|
||||||
endif()
|
endif()
|
||||||
regression_test(test_oscillat_unison ENVELOPE)
|
regression_test(test_oscillat_unison ENVELOPE)
|
||||||
regression_test(test_oscillat_unison_stereo ENVELOPE)
|
regression_test(test_oscillat_unison_stereo ENVELOPE)
|
||||||
@ -147,22 +151,16 @@ regression_test(test_delay_dampmod "ENVELOPE;FOP_MULP;PANNING;VCO_SINE;SEND")
|
|||||||
regression_test(test_delay_drymod "ENVELOPE;FOP_MULP;PANNING;VCO_SINE;SEND")
|
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_delay_rtables "ENVELOPE;FOP_MULP;PANNING;VCO_SINE" "" test_delay.asm)
|
|
||||||
target_compile_definitions(test_delay_rtables PUBLIC RUNTIME_TABLES)
|
|
||||||
regression_test(test_delay_stereo_rtables "ENVELOPE;FOP_MULP;PANNING;VCO_SINE" "" test_delay_stereo.asm)
|
|
||||||
target_compile_definitions(test_delay_stereo_rtables PUBLIC RUNTIME_TABLES)
|
|
||||||
|
|
||||||
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.asm)
|
regression_test(test_envelope_16bit ENVELOPE)
|
||||||
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")
|
||||||
target_compile_definitions(test_speed PUBLIC MAX_SAMPLES=211592)
|
|
||||||
|
|
||||||
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 ${STATICLIB})
|
target_link_libraries(test_render_samples ${STATICLIB})
|
||||||
|
target_compile_definitions(test_render_samples PUBLIC TEST_HEADER="test_render_samples.h")
|
||||||
|
|
||||||
add_executable(test_render_samples_api test_render_samples_api.c)
|
add_executable(test_render_samples_api test_render_samples_api.c)
|
||||||
target_link_libraries(test_render_samples_api ${STATICLIB})
|
target_link_libraries(test_render_samples_api ${STATICLIB})
|
||||||
|
Binary file not shown.
22
tests/test_envelope_16bit.asm
Normal file
22
tests/test_envelope_16bit.asm
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
%define BPM 100
|
||||||
|
%define OUTPUT_16BIT
|
||||||
|
|
||||||
|
%include "sointu/header.inc"
|
||||||
|
|
||||||
|
BEGIN_PATTERNS
|
||||||
|
PATTERN 64,HLD,HLD,HLD,HLD,HLD,HLD,HLD,0,0,0,0,0,0,0,0
|
||||||
|
END_PATTERNS
|
||||||
|
|
||||||
|
BEGIN_TRACKS
|
||||||
|
TRACK VOICES(1),0
|
||||||
|
END_TRACKS
|
||||||
|
|
||||||
|
BEGIN_PATCH
|
||||||
|
BEGIN_INSTRUMENT VOICES(1)
|
||||||
|
SU_ENVELOPE STEREO(0),ATTACK(64),DECAY(64),SUSTAIN(64),RELEASE(80),GAIN(128)
|
||||||
|
SU_ENVELOPE STEREO(0),ATTACK(95),DECAY(64),SUSTAIN(64),RELEASE(80),GAIN(128)
|
||||||
|
SU_OUT STEREO(1),GAIN(128)
|
||||||
|
END_INSTRUMENT
|
||||||
|
END_PATCH
|
||||||
|
|
||||||
|
%include "sointu/footer.inc"
|
@ -2,24 +2,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sointu/sointu.h>
|
#include <sointu/sointu.h>
|
||||||
|
#include "test_render_samples.h"
|
||||||
|
|
||||||
#if UINTPTR_MAX == 0xffffffff // are we 32-bit?
|
void SU_CALLCONV su_render_song(float* buffer) {
|
||||||
#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_song(float* buffer) {
|
|
||||||
Synth* synth;
|
Synth* synth;
|
||||||
const unsigned char commands[] = { su_envelope_id, // MONO
|
const unsigned char commands[] = { su_envelope_id, // MONO
|
||||||
su_envelope_id, // MONO
|
su_envelope_id, // MONO
|
||||||
@ -41,12 +26,12 @@ void CALLCONV su_render_song(float* buffer) {
|
|||||||
synth->RandSeed = 1;
|
synth->RandSeed = 1;
|
||||||
// triger first voice
|
// triger first voice
|
||||||
synth->SynthWrk.Voices[0].Note = 64;
|
synth->SynthWrk.Voices[0].Note = 64;
|
||||||
samples = su_max_samples / 2;
|
samples = SU_MAX_SAMPLES / 2;
|
||||||
time = INT32_MAX;
|
time = INT32_MAX;
|
||||||
retval = su_render(synth, buffer, &samples, &time);
|
retval = su_render(synth, buffer, &samples, &time);
|
||||||
synth->SynthWrk.Voices[0].Release++;
|
synth->SynthWrk.Voices[0].Release++;
|
||||||
buffer = buffer + su_max_samples;
|
buffer = buffer + SU_MAX_SAMPLES;
|
||||||
samples = su_max_samples / 2;
|
samples = SU_MAX_SAMPLES / 2;
|
||||||
time = INT32_MAX;
|
time = INT32_MAX;
|
||||||
retval = su_render(synth, buffer, &samples, &time);
|
retval = su_render(synth, buffer, &samples, &time);
|
||||||
free(synth);
|
free(synth);
|
||||||
|
38
tests/test_render_samples.h
Normal file
38
tests/test_render_samples.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#ifndef SU_RENDER_H
|
||||||
|
#define SU_RENDER_H
|
||||||
|
|
||||||
|
#define SU_MAX_SAMPLES 105840
|
||||||
|
#define SU_BUFFER_LENGTH (SU_MAX_SAMPLES*2)
|
||||||
|
|
||||||
|
#define SU_SAMPLE_RATE 44100
|
||||||
|
#define SU_BPM 100
|
||||||
|
#define SU_PATTERN_SIZE 16
|
||||||
|
#define SU_MAX_PATTERNS 1
|
||||||
|
#define SU_TOTAL_ROWS (SU_MAX_PATTERNS*SU_PATTERN_SIZE)
|
||||||
|
#define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*4*60/(BPM*16))
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#if UINTPTR_MAX == 0xffffffff
|
||||||
|
#if defined(__clang__) || defined(__GNUC__)
|
||||||
|
#define SU_CALLCONV __attribute__ ((stdcall))
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#define SU_CALLCONV __stdcall
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define SU_CALLCONV
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef float SUsample;
|
||||||
|
#define SU_SAMPLE_RANGE 1.0f
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void SU_CALLCONV su_render_song(SUsample *buffer);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -13,25 +13,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include TEST_HEADER
|
||||||
#include <stdint.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
|
|
||||||
extern void CALLCONV su_render_song(void *);
|
|
||||||
|
|
||||||
#ifdef INCLUDE_GMDLS
|
|
||||||
extern void CALLCONV su_load_gmdls(void);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern int su_max_samples;
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
FILE* f;
|
FILE* f;
|
||||||
@ -45,28 +27,19 @@ int main(int argc, char* argv[]) {
|
|||||||
long bufsize;
|
long bufsize;
|
||||||
float max_diff;
|
float max_diff;
|
||||||
float diff;
|
float diff;
|
||||||
#ifndef SU_USE_16BIT_OUTPUT
|
SUsample* buf = NULL;
|
||||||
float* buf = NULL;
|
SUsample* filebuf = NULL;
|
||||||
float* filebuf = NULL;
|
SUsample v;
|
||||||
float v;
|
bufsize = SU_BUFFER_LENGTH * sizeof(SUsample);
|
||||||
bufsize = su_max_samples * 2 * sizeof(float);
|
buf = (SUsample*)malloc(bufsize);
|
||||||
buf = (float*)malloc(bufsize);
|
|
||||||
memset(buf, 0, bufsize);
|
memset(buf, 0, bufsize);
|
||||||
#else
|
|
||||||
short* buf = NULL;
|
|
||||||
short* filebuf = NULL;
|
|
||||||
short v;
|
|
||||||
bufsize = su_max_samples * 2 * sizeof(short);
|
|
||||||
buf = (short*)malloc(bufsize);
|
|
||||||
memset(buf, 0, bufsize);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (buf == NULL) {
|
if (buf == NULL) {
|
||||||
printf("Could not allocate buffer for 4klang rendering\n");
|
printf("Could not allocate buffer for 4klang rendering\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef INCLUDE_GMDLS
|
#ifdef SU_LOAD_GMDLS
|
||||||
su_load_gmdls();
|
su_load_gmdls();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -95,27 +68,19 @@ int main(int argc, char* argv[]) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SU_USE_16BIT_OUTPUT
|
filebuf = (SUsample*)malloc(bufsize);
|
||||||
filebuf = (float*)malloc(bufsize);
|
|
||||||
#else
|
|
||||||
filebuf = (short*)malloc(bufsize);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (filebuf == NULL) {
|
if (filebuf == NULL) {
|
||||||
printf("Could not allocate buffer for file contents\n");
|
printf("Could not allocate buffer for file contents\n");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
fread((void*)filebuf, su_max_samples * 2, sizeof(*filebuf), f);
|
fread((void*)filebuf, fsize, 1, f);
|
||||||
|
|
||||||
max_diff = 0.0f;
|
max_diff = 0.0f;
|
||||||
|
|
||||||
for (n = 0; n < su_max_samples * 2; n++) {
|
for (n = 0; n < SU_BUFFER_LENGTH; n++) {
|
||||||
diff = buf[n] - filebuf[n];
|
diff = fabs((float)(buf[n] - filebuf[n])/SU_SAMPLE_RANGE);
|
||||||
#ifdef SU_USE_16BIT_OUTPUT
|
|
||||||
diff = diff / 32768.0f;
|
|
||||||
#endif
|
|
||||||
diff = fabs(diff);
|
|
||||||
if (diff > 1e-3f || isnan(diff)) {
|
if (diff > 1e-3f || isnan(diff)) {
|
||||||
printf("4klang rendered different wave than expected\n");
|
printf("4klang rendered different wave than expected\n");
|
||||||
goto fail;
|
goto fail;
|
||||||
@ -151,7 +116,7 @@ end:
|
|||||||
|
|
||||||
snprintf(filename, sizeof filename, "%s%s%s", actual_output_folder, test_name, ".raw");
|
snprintf(filename, sizeof filename, "%s%s%s", actual_output_folder, test_name, ".raw");
|
||||||
f = fopen(filename, "wb");
|
f = fopen(filename, "wb");
|
||||||
fwrite((void*)buf, sizeof(*buf), 2 * su_max_samples, f);
|
fwrite((void*)buf, 1, bufsize, f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
if (buf != 0) {
|
if (buf != 0) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user