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.
This commit is contained in:
Veikko Sariola 2020-11-29 22:12:29 +02:00
parent e2c6d4b70c
commit a1e7e82d6d
8 changed files with 27 additions and 21 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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}},

View File

@ -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)

View File

@ -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}},

View File

@ -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)

View File

@ -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})

Binary file not shown.