From 7d6daba3d25ea77dd5d7f3dc5acb3a8fcdbf24d7 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:53:22 +0300 Subject: [PATCH] fix(vm/compiler/bridge): empty patch should not crash native synth Fixes #148. --- CHANGELOG.md | 4 ++++ vm/compiler/bridge/native_synth.go | 14 +++++++++++++ vm/compiler/bridge/native_synth_test.go | 28 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f559514..2248e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/vm/compiler/bridge/native_synth.go b/vm/compiler/bridge/native_synth.go index ac13a39..d4f75c3 100644 --- a/vm/compiler/bridge/native_synth.go +++ b/vm/compiler/bridge/native_synth.go @@ -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 { diff --git a/vm/compiler/bridge/native_synth_test.go b/vm/compiler/bridge/native_synth_test.go index 834b447..1410bca 100644 --- a/vm/compiler/bridge/native_synth_test.go +++ b/vm/compiler/bridge/native_synth_test.go @@ -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}},