-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce "cache" for derived model information
- Loading branch information
Showing
7 changed files
with
271 additions
and
155 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
package tracker | ||
|
||
import ( | ||
"github.com/vsariola/sointu" | ||
"iter" | ||
"slices" | ||
) | ||
|
||
/* | ||
from modelData we can derive useful information that can be cached for performance | ||
or easy access, because of nested iterations over the score or patch data. | ||
i.e. this needs to update when the model changes, and only then. | ||
*/ | ||
|
||
type ( | ||
derivedForUnit struct { | ||
unit *sointu.Unit | ||
instrument *sointu.Instrument | ||
sends []*sointu.Unit | ||
} | ||
|
||
derivedForTrack struct { | ||
instrumentRange []int | ||
tracksWithSameInstrument []int | ||
title string | ||
} | ||
|
||
derivedModelData struct { | ||
// map unit by ID | ||
forUnit map[int]derivedForUnit | ||
// map track by index | ||
forTrack map[int]derivedForTrack | ||
} | ||
) | ||
|
||
// public access functions | ||
|
||
func (m *Model) forUnitById(id int) *derivedForUnit { | ||
forUnit, ok := m.derived.forUnit[id] | ||
if !ok { | ||
return nil | ||
} | ||
return &forUnit | ||
} | ||
|
||
func (m *Model) InstrumentForUnit(id int) *sointu.Instrument { | ||
fu := m.forUnitById(id) | ||
if fu == nil { | ||
return nil | ||
} | ||
return fu.instrument | ||
} | ||
|
||
func (m *Model) UnitById(id int) *sointu.Unit { | ||
fu := m.forUnitById(id) | ||
if fu == nil { | ||
return nil | ||
} | ||
return fu.unit | ||
} | ||
|
||
func (m *Model) SendTargetsForUnit(id int) []*sointu.Unit { | ||
fu := m.forUnitById(id) | ||
if fu == nil { | ||
return nil | ||
} | ||
return fu.sends | ||
} | ||
|
||
func (m *Model) forTrackByIndex(index int) *derivedForTrack { | ||
forTrack, ok := m.derived.forTrack[index] | ||
if !ok { | ||
return nil | ||
} | ||
return &forTrack | ||
} | ||
|
||
func (m *Model) TrackTitle(index int) string { | ||
ft := m.forTrackByIndex(index) | ||
if ft == nil { | ||
return "" | ||
} | ||
return ft.title | ||
} | ||
|
||
// public getters with further model information | ||
|
||
func (m *Model) TracksWithSameInstrumentAsCurrent() []int { | ||
currentTrack := m.d.Cursor.Track | ||
return m.derived.forTrack[currentTrack].tracksWithSameInstrument | ||
} | ||
|
||
func (m *Model) CountNextTracksForCurrentInstrument() int { | ||
currentTrack := m.d.Cursor.Track | ||
count := 0 | ||
for t := range m.TracksWithSameInstrumentAsCurrent() { | ||
if t > currentTrack { | ||
count++ | ||
} | ||
} | ||
return count | ||
} | ||
|
||
// init / update methods | ||
|
||
func (m *Model) initDerivedData() { | ||
m.derived = derivedModelData{ | ||
forUnit: make(map[int]derivedForUnit), | ||
forTrack: make(map[int]derivedForTrack), | ||
} | ||
m.updateDerivedScoreData() | ||
m.updateDerivedPatchData() | ||
} | ||
|
||
func (m *Model) updateDerivedScoreData() { | ||
for index, _ := range m.d.Song.Score.Tracks { | ||
firstInstr, lastInstr, _ := m.instrumentRangeFor(index) | ||
m.derived.forTrack[index] = derivedForTrack{ | ||
instrumentRange: []int{firstInstr, lastInstr}, | ||
tracksWithSameInstrument: slices.Collect(m.tracksWithSameInstrument(index)), | ||
title: m.buildTrackTitle(index), | ||
} | ||
} | ||
} | ||
|
||
func (m *Model) updateDerivedPatchData() { | ||
for _, instr := range m.d.Song.Patch { | ||
for _, unit := range instr.Units { | ||
m.derived.forUnit[unit.ID] = derivedForUnit{ | ||
unit: &unit, | ||
instrument: &instr, | ||
sends: slices.Collect(m.collectSendsTo(unit)), | ||
} | ||
} | ||
} | ||
} | ||
|
||
// internals... | ||
|
||
func (m *Model) collectSendsTo(unit sointu.Unit) iter.Seq[*sointu.Unit] { | ||
return func(yield func(*sointu.Unit) bool) { | ||
for _, instr := range m.d.Song.Patch { | ||
for _, u := range instr.Units { | ||
if u.Type != "send" { | ||
continue | ||
} | ||
targetId, ok := u.Parameters["target"] | ||
if !ok || targetId != unit.ID { | ||
continue | ||
} | ||
if !yield(&u) { | ||
return | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
func (m *Model) instrumentRangeFor(trackIndex int) (int, int, error) { | ||
track := m.d.Song.Score.Tracks[trackIndex] | ||
firstVoice := m.d.Song.Score.FirstVoiceForTrack(trackIndex) | ||
lastVoice := firstVoice + track.NumVoices - 1 | ||
firstIndex, err1 := m.d.Song.Patch.InstrumentForVoice(firstVoice) | ||
if err1 != nil { | ||
return trackIndex, trackIndex, err1 | ||
} | ||
lastIndex, err2 := m.d.Song.Patch.InstrumentForVoice(lastVoice) | ||
if err2 != nil { | ||
return trackIndex, trackIndex, err2 | ||
} | ||
return firstIndex, lastIndex, nil | ||
} | ||
|
||
func (m *Model) buildTrackTitle(x int) (title string) { | ||
title = "?" | ||
if x < 0 || x >= len(m.d.Song.Score.Tracks) { | ||
return | ||
} | ||
firstIndex, lastIndex, err := m.instrumentRangeFor(x) | ||
if err != nil { | ||
return | ||
} | ||
switch diff := lastIndex - firstIndex; diff { | ||
case 0: | ||
title = m.d.Song.Patch[firstIndex].Name | ||
default: | ||
n1 := m.d.Song.Patch[firstIndex].Name | ||
n2 := m.d.Song.Patch[firstIndex+1].Name | ||
if len(n1) > 0 { | ||
n1 = string(n1[0]) | ||
} else { | ||
n1 = "?" | ||
} | ||
if len(n2) > 0 { | ||
n2 = string(n2[0]) | ||
} else { | ||
n2 = "?" | ||
} | ||
if diff > 1 { | ||
title = n1 + "/" + n2 + "..." | ||
} else { | ||
title = n1 + "/" + n2 | ||
} | ||
} | ||
return | ||
} | ||
|
||
func (m *Model) instrumentForTrack(trackIndex int) (int, bool) { | ||
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(trackIndex) | ||
instrument, err := m.d.Song.Patch.InstrumentForVoice(voiceIndex) | ||
return instrument, err == nil | ||
} | ||
|
||
func (m *Model) tracksWithSameInstrument(trackIndex int) iter.Seq[int] { | ||
return func(yield func(int) bool) { | ||
|
||
currentInstrument, currentExists := m.instrumentForTrack(trackIndex) | ||
if !currentExists { | ||
return | ||
} | ||
|
||
for i := 0; i < len(m.d.Song.Score.Tracks); i++ { | ||
instrument, exists := m.instrumentForTrack(i) | ||
if !exists { | ||
return | ||
} | ||
if instrument != currentInstrument { | ||
continue | ||
} | ||
if !yield(i) { | ||
return | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.