From efbcf1454e5c273be07637419be868e8b34f21c3 Mon Sep 17 00:00:00 2001 From: Veikko Sariola Date: Thu, 3 Dec 2020 23:43:39 +0200 Subject: [PATCH] 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. --- .github/workflows/tests.yml | 1 - go4k/asmformat.go | 73 +++++++++++++++++++- go4k/asmformat_test.go | 48 ++++++++++++- go4k/cmd/asmfmt/main.go | 73 ++++++++++++++++---- go4k/song.go | 9 +-- go4k/song_json_test.go | 2 +- go4k/song_test.go | 2 +- include/sointu/footer.inc | 2 +- include/sointu/header.inc | 2 +- include/sointu/win32/gmdls_win32_footer.inc | 2 +- include/sointu/win64/gmdls_win64_footer.inc | 2 +- tests/CMakeLists.txt | 32 ++++----- tests/expected_output/test_speed.raw | Bin 1692736 -> 1693440 bytes tests/test_envelope_16bit.asm | 22 ++++++ tests/test_render_samples.c | 25 ++----- tests/test_render_samples.h | 38 ++++++++++ tests/test_renderer.c | 59 ++++------------ 17 files changed, 279 insertions(+), 113 deletions(-) create mode 100644 tests/test_envelope_16bit.asm create mode 100644 tests/test_render_samples.h diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a94952..dd8fe13 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,7 +42,6 @@ jobs: steps: - uses: lukka/get-cmake@v3.18.3 - uses: actions/setup-go@v2 - if: ${{ matrix.config.gotests == 'yes' }} - uses: actions/checkout@v2 - uses: ilammy/setup-nasm@v1.2.0 - name: Run ctest diff --git a/go4k/asmformat.go b/go4k/asmformat.go index b91f750..80c4f38 100644 --- a/go4k/asmformat.go +++ b/go4k/asmformat.go @@ -11,6 +11,7 @@ import ( func DeserializeAsm(asmcode string) (*Song, error) { var bpm int + output16Bit := false scanner := bufio.NewScanner(strings.NewReader(asmcode)) patterns := make([][]byte, 0) tracks := make([]Track, 0) @@ -86,6 +87,8 @@ func DeserializeAsm(asmcode string) (*Song, error) { return nil, err } bpm = ints[0] + } else if defineName == "OUTPUT_16BIT" { + output16Bit = true } } 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 } @@ -252,6 +255,9 @@ func SerializeAsm(song *Song) (string, error) { } // The actual printing starts here 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 // 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. @@ -369,3 +375,68 @@ func SerializeAsm(song *Song) (string, error) { ret := b.String() 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 +#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 +} diff --git a/go4k/asmformat_test.go b/go4k/asmformat_test.go index ce9a9b8..e4d1761 100644 --- a/go4k/asmformat_test.go +++ b/go4k/asmformat_test.go @@ -46,6 +46,7 @@ func TestAllAsmFiles(t *testing.T) { t.Fatalf("Compiling patch failed: %v", err) } buffer, err := go4k.Play(synth, *song) + buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always. if err != nil { t.Fatalf("Play failed: %v", err) } @@ -70,7 +71,12 @@ func TestAllAsmFiles(t *testing.T) { 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) } buffer, err := go4k.Play(synth, *song2) + buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always. if err != nil { t.Fatalf("Play failed: %v", err) } @@ -138,12 +145,17 @@ func TestSerializingAllAsmFiles(t *testing.T) { 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) expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname)) 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 +} diff --git a/go4k/cmd/asmfmt/main.go b/go4k/cmd/asmfmt/main.go index 858f8d8..9d8481e 100644 --- a/go4k/cmd/asmfmt/main.go +++ b/go4k/cmd/asmfmt/main.go @@ -7,14 +7,20 @@ import ( "os" "path" "path/filepath" + "strings" "github.com/vsariola/sointu/go4k" + "github.com/vsariola/sointu/go4k/bridge" ) 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.") 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.Parse() if flag.NArg() == 0 || *help { @@ -31,24 +37,63 @@ func main() { if err != nil { return fmt.Errorf("could not parse the file (%v)", err) } - formattedCode, err := go4k.SerializeAsm(song) - if err != nil { - return fmt.Errorf("could not reformat the file (%v)", err) - } - if *write { - if formattedCode != origCode { - err := ioutil.WriteFile(filename, []byte(formattedCode), 0644) + 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 write to file (%v)", err) + 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 *list { - if formattedCode != origCode { - fmt.Println(filename) + if !*noformat { + formattedCode, err := go4k.SerializeAsm(song) + if err != nil { + return fmt.Errorf("could not reformat the file (%v)", err) + } + if *write { + if formattedCode != origCode { + err := ioutil.WriteFile(filename, []byte(formattedCode), 0644) + if err != nil { + return fmt.Errorf("could write to file (%v)", err) + } + } + } + if *list { + if formattedCode != origCode { + fmt.Println(filename) + } + } else if !*write { + fmt.Print(formattedCode) } - } else if !*write { - fmt.Print(formattedCode) } return nil } diff --git a/go4k/song.go b/go4k/song.go index a7fedff..a56015e 100644 --- a/go4k/song.go +++ b/go4k/song.go @@ -6,10 +6,11 @@ import ( ) type Song struct { - BPM int - Patterns [][]byte - Tracks []Track - Patch Patch + BPM int + Patterns [][]byte + Tracks []Track + Patch Patch + Output16Bit bool } func (s *Song) PatternRows() int { diff --git a/go4k/song_json_test.go b/go4k/song_json_test.go index 6e87b45..4c46f57 100644 --- a/go4k/song_json_test.go +++ b/go4k/song_json_test.go @@ -8,7 +8,7 @@ import ( "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{ BPM: 100, diff --git a/go4k/song_test.go b/go4k/song_test.go index b265f7e..f31f022 100644 --- a/go4k/song_test.go +++ b/go4k/song_test.go @@ -37,7 +37,7 @@ func TestPlayer(t *testing.T) { SampleOffsets: []go4k.SampleOffset{}} 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}}} - song := go4k.Song{100, patterns, tracks, patch} + song := go4k.Song{100, patterns, tracks, patch, false} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("Compiling patch failed: %v", err) diff --git a/include/sointu/footer.inc b/include/sointu/footer.inc index 3ae66fe..3378a0f 100644 --- a/include/sointu/footer.inc +++ b/include/sointu/footer.inc @@ -292,7 +292,7 @@ EXPORT MANGLE_FUNC(su_power,0) ; Stack : sample row pushad output_ptr ;------------------------------------------------------------------------------- %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. mov _DI, [_SP+su_stack.bufferptr - su_stack.output_sound] ; edi containts ptr mov _SI, PTRWORD su_synth_obj + su_synthworkspace.left diff --git a/include/sointu/header.inc b/include/sointu/header.inc index 6851da6..5b5c929 100644 --- a/include/sointu/header.inc +++ b/include/sointu/header.inc @@ -77,7 +77,7 @@ %endif %endif -%ifdef SU_USE_16BIT_OUTPUT +%ifdef OUTPUT_16BIT %define SU_INCLUDE_CLIP %endif diff --git a/include/sointu/win32/gmdls_win32_footer.inc b/include/sointu/win32/gmdls_win32_footer.inc index f8098a6..e6159cb 100644 --- a/include/sointu/win32/gmdls_win32_footer.inc +++ b/include/sointu/win32/gmdls_win32_footer.inc @@ -1,4 +1,4 @@ -%ifdef INCLUDE_GMDLS +%ifdef INCLUDE_SAMPLES %define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls diff --git a/include/sointu/win64/gmdls_win64_footer.inc b/include/sointu/win64/gmdls_win64_footer.inc index 844fb38..69344b6 100644 --- a/include/sointu/win64/gmdls_win64_footer.inc +++ b/include/sointu/win64/gmdls_win64_footer.inc @@ -1,4 +1,4 @@ -%ifdef INCLUDE_GMDLS +%ifdef INCLUDE_SAMPLES %define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8a113eb..cb98271 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,12 +1,20 @@ function(regression_test testname) if(${ARGC} LESS 4) 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() set(source ${ARGV3}) + add_executable(${testname} ${source} test_renderer.c) endif() - 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 ${PROJECT_SOURCE_DIR}/src/*.asm @@ -20,7 +28,6 @@ function(regression_test testname) set_source_files_properties(${FOURKLANG} PROPERTIES HEADER_FILE_ONLY TRUE) add_test(${testname} ${testname}) - target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}" SU_USE_INTROSPECTION SU_USE_PLAYER) target_link_libraries(${testname} ${HEADERLIB}) 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} ) + target_include_directories(${testname} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}") + add_dependencies(${testname} ${testname}_rawcopy) if(ARGC GREATER 1) @@ -94,13 +104,7 @@ regression_test(test_oscillat_gate ENVELOPE) regression_test(test_oscillat_stereo ENVELOPE) if(WIN32) # The samples are currently only GMDLs based, and thus require Windows. regression_test(test_oscillat_sample ENVELOPE) - target_compile_definitions(test_oscillat_sample PUBLIC INCLUDE_GMDLS) 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() regression_test(test_oscillat_unison 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_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_16bit ENVELOPE "" test_envelope.asm) -target_compile_definitions(test_envelope_16bit PUBLIC SU_USE_16BIT_OUTPUT) +regression_test(test_envelope_16bit ENVELOPE) regression_test(test_polyphony "ENVELOPE;VCO_SINE") regression_test(test_chords "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) 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) target_link_libraries(test_render_samples_api ${STATICLIB}) diff --git a/tests/expected_output/test_speed.raw b/tests/expected_output/test_speed.raw index e2068ee0d9c6cecd06978b66403efbefa2bff702..a6be26446da65124097da45b9978fc2c5759cddd 100644 GIT binary patch delta 64 zcmV~$%^5%-0D$4A{D-6n+t{WxI()-)a?-sa4CH-F3h@aMfuy3Qp{1i|Kru2gqggPl RSR5NWo`dtf?k^irU006*2;)+{b-RMRe9$So;mjV=_AiMjy-N&{Igd!hbu|z79`ze$vwMMJc M`x}fV^R=wc3tg!bQUCw| diff --git a/tests/test_envelope_16bit.asm b/tests/test_envelope_16bit.asm new file mode 100644 index 0000000..fd82ac8 --- /dev/null +++ b/tests/test_envelope_16bit.asm @@ -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" diff --git a/tests/test_render_samples.c b/tests/test_render_samples.c index ed32ae9..fae83d4 100644 --- a/tests/test_render_samples.c +++ b/tests/test_render_samples.c @@ -2,24 +2,9 @@ #include #include #include +#include "test_render_samples.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_song(float* buffer) { +void SU_CALLCONV su_render_song(float* buffer) { Synth* synth; const unsigned char commands[] = { su_envelope_id, // MONO su_envelope_id, // MONO @@ -41,12 +26,12 @@ void CALLCONV su_render_song(float* buffer) { synth->RandSeed = 1; // triger first voice synth->SynthWrk.Voices[0].Note = 64; - samples = su_max_samples / 2; + samples = SU_MAX_SAMPLES / 2; time = INT32_MAX; retval = su_render(synth, buffer, &samples, &time); synth->SynthWrk.Voices[0].Release++; - buffer = buffer + su_max_samples; - samples = su_max_samples / 2; + buffer = buffer + SU_MAX_SAMPLES; + samples = SU_MAX_SAMPLES / 2; time = INT32_MAX; retval = su_render(synth, buffer, &samples, &time); free(synth); diff --git a/tests/test_render_samples.h b/tests/test_render_samples.h new file mode 100644 index 0000000..e9d2b07 --- /dev/null +++ b/tests/test_render_samples.h @@ -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 +#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 diff --git a/tests/test_renderer.c b/tests/test_renderer.c index 247f937..beaae79 100644 --- a/tests/test_renderer.c +++ b/tests/test_renderer.c @@ -13,25 +13,7 @@ #endif #include - -#include - -#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; +#include TEST_HEADER int main(int argc, char* argv[]) { FILE* f; @@ -45,28 +27,19 @@ int main(int argc, char* argv[]) { long bufsize; float max_diff; float diff; -#ifndef SU_USE_16BIT_OUTPUT - float* buf = NULL; - float* filebuf = NULL; - float v; - bufsize = su_max_samples * 2 * sizeof(float); - buf = (float*)malloc(bufsize); + SUsample* buf = NULL; + SUsample* filebuf = NULL; + SUsample v; + bufsize = SU_BUFFER_LENGTH * sizeof(SUsample); + buf = (SUsample*)malloc(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) { printf("Could not allocate buffer for 4klang rendering\n"); return 1; } - #ifdef INCLUDE_GMDLS + #ifdef SU_LOAD_GMDLS su_load_gmdls(); #endif @@ -95,27 +68,19 @@ int main(int argc, char* argv[]) { goto fail; } -#ifndef SU_USE_16BIT_OUTPUT - filebuf = (float*)malloc(bufsize); -#else - filebuf = (short*)malloc(bufsize); -#endif + filebuf = (SUsample*)malloc(bufsize); if (filebuf == NULL) { printf("Could not allocate buffer for file contents\n"); goto fail; } - fread((void*)filebuf, su_max_samples * 2, sizeof(*filebuf), f); + fread((void*)filebuf, fsize, 1, f); max_diff = 0.0f; - for (n = 0; n < su_max_samples * 2; n++) { - diff = buf[n] - filebuf[n]; -#ifdef SU_USE_16BIT_OUTPUT - diff = diff / 32768.0f; -#endif - diff = fabs(diff); + for (n = 0; n < SU_BUFFER_LENGTH; n++) { + diff = fabs((float)(buf[n] - filebuf[n])/SU_SAMPLE_RANGE); if (diff > 1e-3f || isnan(diff)) { printf("4klang rendered different wave than expected\n"); goto fail; @@ -151,7 +116,7 @@ end: snprintf(filename, sizeof filename, "%s%s%s", actual_output_folder, test_name, ".raw"); f = fopen(filename, "wb"); - fwrite((void*)buf, sizeof(*buf), 2 * su_max_samples, f); + fwrite((void*)buf, 1, bufsize, f); fclose(f); if (buf != 0) {