fix(vm/compiler/bridge): empty patch should not crash native synth

Fixes #148.
This commit is contained in:
5684185+vsariola@users.noreply.github.com 2024-09-16 19:53:22 +03:00
parent 2b38e11643
commit 7d6daba3d2
3 changed files with 46 additions and 0 deletions

View File

@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
info is shown as a label in the tracker and can be checked with `-v` flag in
the command line tools.
### Fixed
- Empty patch should not crash the native synth ([#148][i148])
## [0.4.1]
### Added
- Clicking the parameter slider also selects that parameter ([#112][i112])
@ -209,3 +212,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[i144]: https://github.com/vsariola/sointu/issues/144
[i145]: https://github.com/vsariola/sointu/issues/145
[i146]: https://github.com/vsariola/sointu/issues/146
[i148]: https://github.com/vsariola/sointu/issues/148

View File

@ -38,6 +38,13 @@ func Synth(patch sointu.Patch, bpm int) (*NativeSynth, error) {
if len(comPatch.Operands) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
return nil, errors.New("bridge supports at most 16384 operands; the compiled patch has more")
}
// if the patch is empty, we still need to initialize the synth with a single opcode
if len(comPatch.Opcodes) == 0 {
s.Opcodes[0] = 0
s.NumVoices = 1
s.Polyphony = 0
return (*NativeSynth)(s), nil
}
for i, v := range comPatch.Opcodes {
s.Opcodes[i] = (C.uchar)(v)
}
@ -130,6 +137,13 @@ func (bridgesynth *NativeSynth) Update(patch sointu.Patch, bpm int) error {
if len(comPatch.Operands) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
return errors.New("bridge supports at most 16384 operands; the compiled patch has more")
}
// if the patch is empty, we still need to initialize the synth with a single opcode
if len(comPatch.Opcodes) == 0 {
s.Opcodes[0] = 0
s.NumVoices = 1
s.Polyphony = 0
return nil
}
needsRefresh := false
for i, v := range comPatch.Opcodes {
if cmdChar := (C.uchar)(v); s.Opcodes[i] != cmdChar {

View File

@ -28,6 +28,34 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
// const bufsize = su_max_samples * 2
func TestEmptyPatch(t *testing.T) {
patch := sointu.Patch{}
tracks := []sointu.Track{{NumVoices: 0, Order: []int{0}, Patterns: []sointu.Pattern{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}}}
song := sointu.Song{BPM: 100, RowsPerBeat: 4, Score: sointu.Score{RowsPerPattern: 16, Length: 1, Tracks: tracks}, Patch: patch}
// make sure that the empty patch does not crash the synth
sointu.Play(bridge.NativeSynther{}, song, nil)
}
func TestUpdatingEmptyPatch(t *testing.T) {
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
}}}
tracks := []sointu.Track{{NumVoices: 0, Order: []int{0}, Patterns: []sointu.Pattern{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}}}
song := sointu.Song{BPM: 100, RowsPerBeat: 4, Score: sointu.Score{RowsPerPattern: 16, Length: 1, Tracks: tracks}, Patch: patch}
synth, err := bridge.NativeSynther{}.Synth(patch, song.BPM)
if err != nil {
t.Fatalf("Synth creation failed: %v", err)
}
synth.Update(sointu.Patch{}, song.BPM)
buffer := make(sointu.AudioBuffer, su_max_samples)
err = buffer[:len(buffer)/2].Fill(synth)
if err != nil {
t.Fatalf("render gave an error: %v", err)
}
}
func TestOscillatSine(t *testing.T) {
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},