mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-19 05:24:48 -04:00
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
This commit is contained in:
@ -92,6 +92,50 @@ func (s *C.Synth) Release(voice int) {
|
|||||||
s.SynthWrk.Voices[voice].Release = 1
|
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,
|
// 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
|
// with only the critical failure flags masked. Useful if you are interested exactly
|
||||||
// what went wrong with the patch.
|
// what went wrong with the patch.
|
||||||
|
@ -104,6 +104,7 @@ func (t *Track) Copy() Track {
|
|||||||
|
|
||||||
type Synth interface {
|
type Synth interface {
|
||||||
Render(buffer []float32, maxtime int) (int, int, error)
|
Render(buffer []float32, maxtime int) (int, int, error)
|
||||||
|
Update(patch Patch) error
|
||||||
Trigger(voice int, note byte)
|
Trigger(voice int, note byte)
|
||||||
Release(voice int)
|
Release(voice int)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
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
|
// Updates the patch of the synth
|
||||||
// synth should not be called by anyone else than the sequencer afterwards
|
func (s *Sequencer) SetPatch(patch sointu.Patch) {
|
||||||
func (s *Sequencer) SetSynth(synth sointu.Synth) {
|
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
s.synth = synth
|
if s.synth != nil {
|
||||||
|
s.synth.Update(patch)
|
||||||
|
} // TODO: what is s.synth is nil?
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,17 +69,11 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
|||||||
t.songPlayMutex.Lock()
|
t.songPlayMutex.Lock()
|
||||||
defer t.songPlayMutex.Unlock()
|
defer t.songPlayMutex.Unlock()
|
||||||
t.song = song
|
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.PlayPosition.Clamp(song)
|
||||||
t.Cursor.Clamp(song)
|
t.Cursor.Clamp(song)
|
||||||
t.SelectionCorner.Clamp(song)
|
t.SelectionCorner.Clamp(song)
|
||||||
if t.sequencer != nil {
|
if t.sequencer != nil {
|
||||||
t.sequencer.SetSynth(t.synth)
|
t.sequencer.SetPatch(song.Patch)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -213,12 +207,7 @@ func (t *Tracker) AddInstrument() {
|
|||||||
Units: units,
|
Units: units,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
synth, err := bridge.Synth(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
if err == nil {
|
|
||||||
t.sequencer.SetSynth(synth)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCurrentNote sets the (note) value in current pattern under cursor to iv
|
// SetCurrentNote sets the (note) value in current pattern under cursor to iv
|
||||||
|
Reference in New Issue
Block a user