From 6c97b5e736fb07f2ce46a5131482bb3d47d66ea0 Mon Sep 17 00:00:00 2001 From: vsariola <5684185+vsariola@users.noreply.github.com> Date: Thu, 28 Jan 2021 22:35:27 +0200 Subject: [PATCH] feat(sointu): update synth instead of recompiling if no commands (units) change this avoids the nasty clicking resulting from complete reset / recompilation of the synth, which was the previous case --- bridge/bridge.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ sointu.go | 1 + tracker/sequencer.go | 9 +++++---- tracker/tracker.go | 15 ++------------- 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/bridge/bridge.go b/bridge/bridge.go index c8a6e29..e5a7f85 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -92,6 +92,50 @@ func (s *C.Synth) Release(voice int) { s.SynthWrk.Voices[voice].Release = 1 } +// Update +func (s *C.Synth) Update(patch sointu.Patch) error { + comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{}) + if err != nil { + return fmt.Errorf("error compiling patch: %v", err) + } + if len(comPatch.Commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo + return errors.New("bridge supports at most 2048 commands; the compiled patch has more") + } + if len(comPatch.Values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo + return errors.New("bridge supports at most 16384 values; the compiled patch has more") + } + needsRefresh := false + for i, v := range comPatch.Commands { + if cmdChar := (C.uchar)(v); s.Commands[i] != cmdChar { + s.Commands[i] = cmdChar + needsRefresh = true // if any of the commands change, we retrigger all units + } + } + for i, v := range comPatch.Values { + s.Values[i] = (C.uchar)(v) + } + for i, v := range comPatch.DelayTimes { + s.DelayTimes[i] = (C.ushort)(v) + } + for i, v := range comPatch.SampleOffsets { + s.SampleOffsets[i].Start = (C.uint)(v.Start) + s.SampleOffsets[i].LoopStart = (C.ushort)(v.LoopStart) + s.SampleOffsets[i].LoopLength = (C.ushort)(v.LoopLength) + } + s.NumVoices = C.uint(comPatch.NumVoices) + s.Polyphony = C.uint(comPatch.PolyphonyBitmask) + if needsRefresh { + for i := range s.SynthWrk.Voices { + // if any of the commands change, we retrigger all units + // note that we don't change the notes or release states, just the units + for j := range s.SynthWrk.Voices[i].Units { + s.SynthWrk.Voices[i].Units[j] = C.Unit{} + } + } + } + return nil +} + // Render error stores the exact errorcode, which is actually just the x87 FPU flags, // with only the critical failure flags masked. Useful if you are interested exactly // what went wrong with the patch. diff --git a/sointu.go b/sointu.go index 9f1094f..4bdbf8e 100644 --- a/sointu.go +++ b/sointu.go @@ -104,6 +104,7 @@ func (t *Track) Copy() Track { type Synth interface { Render(buffer []float32, maxtime int) (int, int, error) + Update(patch Patch) error Trigger(voice int, note byte) Release(voice int) } diff --git a/tracker/sequencer.go b/tracker/sequencer.go index fc3b257..e0d5124 100644 --- a/tracker/sequencer.go +++ b/tracker/sequencer.go @@ -88,11 +88,12 @@ func (s *Sequencer) ReadAudio(buffer []float32) (int, error) { return totalRendered * 2, fmt.Errorf("despite %v attempts, Sequencer.ReadAudio could not fill the buffer (rowLength was %v, should be >> 0)", SEQUENCER_MAX_READ_TRIES, s.rowLength) } -// Sets the synth used by the sequencer. This takes ownership of the synth: the -// synth should not be called by anyone else than the sequencer afterwards -func (s *Sequencer) SetSynth(synth sointu.Synth) { +// Updates the patch of the synth +func (s *Sequencer) SetPatch(patch sointu.Patch) { s.mutex.Lock() - s.synth = synth + if s.synth != nil { + s.synth.Update(patch) + } // TODO: what is s.synth is nil? s.mutex.Unlock() } diff --git a/tracker/tracker.go b/tracker/tracker.go index 0f43277..f03d5d9 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -69,17 +69,11 @@ func (t *Tracker) LoadSong(song sointu.Song) error { t.songPlayMutex.Lock() defer t.songPlayMutex.Unlock() t.song = song - if synth, err := bridge.Synth(song.Patch); err != nil { - fmt.Printf("error loading synth: %v\n", err) - t.synth = nil - } else { - t.synth = synth - } t.PlayPosition.Clamp(song) t.Cursor.Clamp(song) t.SelectionCorner.Clamp(song) if t.sequencer != nil { - t.sequencer.SetSynth(t.synth) + t.sequencer.SetPatch(song.Patch) } return nil } @@ -213,12 +207,7 @@ func (t *Tracker) AddInstrument() { Units: units, }) } - synth, err := bridge.Synth(t.song.Patch) - if err == nil { - t.sequencer.SetSynth(synth) - } else { - fmt.Printf("%v", err) - } + t.sequencer.SetPatch(t.song.Patch) } // SetCurrentNote sets the (note) value in current pattern under cursor to iv