From a1e7e82d6d2a2345ad53e8614633c02f41a44928 Mon Sep 17 00:00:00 2001 From: Veikko Sariola Date: Sun, 29 Nov 2020 22:12:29 +0200 Subject: [PATCH] refactor(song): Remove song length from Song and assume the user knows MAX_SAMPLES Trying to force a specific song length other than the default never quite worked, so we'll only support the default MAX_SAMPLES & will calculate it for the user in the user in the exported .h header file. --- go4k/asmformat.go | 2 +- go4k/song.go | 35 +++++++++++++++------------ go4k/song_json_test.go | 3 +-- go4k/song_test.go | 2 +- go4k/tracker/defaultsong.go | 1 - include/sointu/header.inc | 4 ++- tests/CMakeLists.txt | 1 + tests/expected_output/test_speed.raw | Bin 1693440 -> 1692736 bytes 8 files changed, 27 insertions(+), 21 deletions(-) diff --git a/go4k/asmformat.go b/go4k/asmformat.go index ddb30ce..b91f750 100644 --- a/go4k/asmformat.go +++ b/go4k/asmformat.go @@ -158,7 +158,7 @@ func DeserializeAsm(asmcode string) (*Song, error) { } } } - s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch, SongLength: -1} + s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch} return &s, nil } diff --git a/go4k/song.go b/go4k/song.go index 82e6dda..a7fedff 100644 --- a/go4k/song.go +++ b/go4k/song.go @@ -1,13 +1,15 @@ package go4k -import "errors" +import ( + "errors" + "fmt" +) type Song struct { - BPM int - Patterns [][]byte - Tracks []Track - SongLength int // in samples, 0 means calculate automatically from BPM and Track lengths, but can also set manually - Patch Patch + BPM int + Patterns [][]byte + Tracks []Track + Patch Patch } func (s *Song) PatternRows() int { @@ -74,13 +76,9 @@ func Play(synth Synth, song Song) ([]float32, error) { for i := range curVoices { curVoices[i] = song.FirstTrackVoice(i) } - samples := song.SongLength - if samples <= 0 { - samples = song.TotalRows() * song.SamplesPerRow() - } - buffer := make([]float32, samples*2) - totaln := 0 - rowtime := song.SamplesPerRow() + initialCapacity := song.TotalRows() * song.SamplesPerRow() * 2 + buffer := make([]float32, 0, initialCapacity) + rowbuffer := make([]float32, song.SamplesPerRow()*2) for row := 0; row < song.TotalRows(); row++ { patternRow := row % song.PatternRows() pattern := row / song.PatternRows() @@ -100,8 +98,15 @@ func Play(synth Synth, song Song) ([]float32, error) { synth.Trigger(curVoices[t], note) } } - samples, _, _ := synth.Render(buffer[2*totaln:], rowtime) - totaln += samples + tries := 0 + for rowtime := 0; rowtime < song.SamplesPerRow(); { + samples, time, _ := synth.Render(rowbuffer, song.SamplesPerRow()-rowtime) + rowtime += time + buffer = append(buffer, rowbuffer[:samples*2]...) + if tries > 100 { + return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow) + } + } } return buffer, nil } diff --git a/go4k/song_json_test.go b/go4k/song_json_test.go index 415b893..6e87b45 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=="}],"SongLength":0,"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":[]}}` var testSong = go4k.Song{ BPM: 100, @@ -16,7 +16,6 @@ var testSong = go4k.Song{ Tracks: []go4k.Track{ {NumVoices: 1, Sequence: []byte{0}}, }, - SongLength: 0, Patch: go4k.Patch{ Instruments: []go4k.Instrument{{NumVoices: 1, Units: []go4k.Unit{ {"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, diff --git a/go4k/song_test.go b/go4k/song_test.go index 4e90b69..b265f7e 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, 0, patch} + song := go4k.Song{100, patterns, tracks, patch} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("Compiling patch failed: %v", err) diff --git a/go4k/tracker/defaultsong.go b/go4k/tracker/defaultsong.go index 1dab304..19f9be6 100644 --- a/go4k/tracker/defaultsong.go +++ b/go4k/tracker/defaultsong.go @@ -12,7 +12,6 @@ var defaultSong = go4k.Song{ {NumVoices: 1, Sequence: []byte{0}}, {NumVoices: 1, Sequence: []byte{1}}, }, - SongLength: 0, Patch: go4k.Patch{ Instruments: []go4k.Instrument{{NumVoices: 2, Units: []go4k.Unit{ {"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, diff --git a/include/sointu/header.inc b/include/sointu/header.inc index aac9066..6851da6 100644 --- a/include/sointu/header.inc +++ b/include/sointu/header.inc @@ -115,7 +115,9 @@ section .text ; yasm throws section redeclaration warnings if strucs are defined %define TOTAL_ROWS (MAX_PATTERNS*PATTERN_SIZE) %define SAMPLES_PER_ROW (SAMPLE_RATE*4*60/(BPM*16)) -%define MAX_SAMPLES (SAMPLES_PER_ROW*TOTAL_ROWS) +%ifndef MAX_SAMPLES + %define MAX_SAMPLES (SAMPLES_PER_ROW*TOTAL_ROWS) +%endif %macro BEGIN_PATCH 0 SECT_DATA(params) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0026469..8a113eb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -159,6 +159,7 @@ 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") +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}) diff --git a/tests/expected_output/test_speed.raw b/tests/expected_output/test_speed.raw index a6be26446da65124097da45b9978fc2c5759cddd..e2068ee0d9c6cecd06978b66403efbefa2bff702 100644 GIT binary patch delta 59 zcmV~$$q_>U006*2;)+{b-RMRe9$So;mjV=_AiMjy-N&{Igd!hbu|z79`ze$vwMMJc M`x}fV^R=wc3tg!bQUCw| delta 64 zcmV~$%^5%-0D$4A{D-6n+t{WxI()-)a?-sa4CH-F3h@aMfuy3Qp{1i|Kru2gqggPl RSR5NWo`dtf?k^ir