Skip to content

Commit

Permalink
feat: introduce "cache" for derived model information
Browse files Browse the repository at this point in the history
  • Loading branch information
qm210 authored and vsariola committed Nov 9, 2024
1 parent 4d7c998 commit d517576
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 155 deletions.
30 changes: 0 additions & 30 deletions song.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package sointu

import (
"errors"
"iter"
)

type (
Expand Down Expand Up @@ -336,32 +335,3 @@ func TotalVoices[T any, S ~[]T, P NumVoicerPointer[T]](slice S) (ret int) {
}
return
}

func (s *Song) InstrumentForTrack(trackIndex int) (int, bool) {
voiceIndex := s.Score.FirstVoiceForTrack(trackIndex)
instrument, err := s.Patch.InstrumentForVoice(voiceIndex)
return instrument, err == nil
}

func (s *Song) AllTracksWithSameInstrument(trackIndex int) iter.Seq[int] {
return func(yield func(int) bool) {

currentInstrument, currentExists := s.InstrumentForTrack(trackIndex)
if !currentExists {
return
}

for i := 0; i < len(s.Score.Tracks); i++ {
instrument, exists := s.InstrumentForTrack(i)
if !exists {
return
}
if instrument != currentInstrument {
continue
}
if !yield(i) {
return
}
}
}
}
235 changes: 235 additions & 0 deletions tracker/derived.go
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
}
}
}
}
9 changes: 4 additions & 5 deletions tracker/gioui/note_editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,11 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
pxRowMarkWidth := gtx.Dp(trackRowMarkWidth)

colTitle := func(gtx C, i int) D {
h := gtx.Dp(unit.Dp(trackColTitleHeight))
title := ((*tracker.Order)(t.Model)).Title(i)
h := gtx.Dp(trackColTitleHeight)
gtx.Constraints = layout.Exact(image.Pt(pxWidth, h))
LabelStyle{
Alignment: layout.N,
Text: title,
Text: t.Model.TrackTitle(i),
FontSize: unit.Sp(12),
Color: mediumEmphasisTextColor,
Shaper: t.Theme.Shaper,
Expand Down Expand Up @@ -294,7 +293,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
}
// draw the corresponding "fake cursors" for instrument-track-groups (for polyphony)
if hasTrackMidiIn {
for trackIndex := range ((*tracker.Order)(t.Model)).TrackIndicesForCurrentInstrument() {
for trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() {
if x == trackIndex && y == cursor.Y {
te.paintColumnCell(gtx, x, t, cursorNeighborForTrackMidiInColor)
}
Expand Down Expand Up @@ -414,7 +413,7 @@ func (te *NoteEditor) HandleMidiInput(t *Tracker) {
return
}
te.scrollTable.Table.SetCursor2(te.scrollTable.Table.Cursor())
remaining := (*tracker.Order)(t.Model).CountNextTracksForCurrentInstrument()
remaining := t.Model.CountNextTracksForCurrentInstrument()
for i, note := range t.MidiNotePlaying {
t.Model.Notes().Table().Set(note)
te.scrollTable.Table.MoveCursor(1, 0)
Expand Down
9 changes: 7 additions & 2 deletions tracker/gioui/order_editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,13 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop()
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
title := t.Model.Order().Title(i)
LabelStyle{Alignment: layout.NW, Text: title, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor, Shaper: t.Theme.Shaper}.Layout(gtx)
LabelStyle{
Alignment: layout.NW,
Text: t.Model.TrackTitle(i),
FontSize: unit.Sp(12),
Color: mediumEmphasisTextColor,
Shaper: t.Theme.Shaper,
}.Layout(gtx)
return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)}
}

Expand Down
Loading

0 comments on commit d517576

Please sign in to comment.