mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
feat: introduce "cache" for derived model information
This commit is contained in:
parent
c266db17d9
commit
6f1cb5e7ea
30
song.go
30
song.go
@ -2,7 +2,6 @@ package sointu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"iter"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -336,32 +335,3 @@ func TotalVoices[T any, S ~[]T, P NumVoicerPointer[T]](slice S) (ret int) {
|
|||||||
}
|
}
|
||||||
return
|
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
tracker/derived.go
Normal file
235
tracker/derived.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -220,12 +220,11 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
|||||||
pxRowMarkWidth := gtx.Dp(trackRowMarkWidth)
|
pxRowMarkWidth := gtx.Dp(trackRowMarkWidth)
|
||||||
|
|
||||||
colTitle := func(gtx C, i int) D {
|
colTitle := func(gtx C, i int) D {
|
||||||
h := gtx.Dp(unit.Dp(trackColTitleHeight))
|
h := gtx.Dp(trackColTitleHeight)
|
||||||
title := ((*tracker.Order)(t.Model)).Title(i)
|
|
||||||
gtx.Constraints = layout.Exact(image.Pt(pxWidth, h))
|
gtx.Constraints = layout.Exact(image.Pt(pxWidth, h))
|
||||||
LabelStyle{
|
LabelStyle{
|
||||||
Alignment: layout.N,
|
Alignment: layout.N,
|
||||||
Text: title,
|
Text: t.Model.TrackTitle(i),
|
||||||
FontSize: unit.Sp(12),
|
FontSize: unit.Sp(12),
|
||||||
Color: mediumEmphasisTextColor,
|
Color: mediumEmphasisTextColor,
|
||||||
Shaper: t.Theme.Shaper,
|
Shaper: t.Theme.Shaper,
|
||||||
@ -294,7 +293,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
|||||||
}
|
}
|
||||||
// draw the corresponding "fake cursors" for instrument-track-groups (for polyphony)
|
// draw the corresponding "fake cursors" for instrument-track-groups (for polyphony)
|
||||||
if hasTrackMidiIn {
|
if hasTrackMidiIn {
|
||||||
for trackIndex := range ((*tracker.Order)(t.Model)).TrackIndicesForCurrentInstrument() {
|
for trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() {
|
||||||
if x == trackIndex && y == cursor.Y {
|
if x == trackIndex && y == cursor.Y {
|
||||||
te.paintColumnCell(gtx, x, t, cursorNeighborForTrackMidiInColor)
|
te.paintColumnCell(gtx, x, t, cursorNeighborForTrackMidiInColor)
|
||||||
}
|
}
|
||||||
@ -414,7 +413,7 @@ func (te *NoteEditor) HandleMidiInput(t *Tracker) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
te.scrollTable.Table.SetCursor2(te.scrollTable.Table.Cursor())
|
te.scrollTable.Table.SetCursor2(te.scrollTable.Table.Cursor())
|
||||||
remaining := (*tracker.Order)(t.Model).CountNextTracksForCurrentInstrument()
|
remaining := t.Model.CountNextTracksForCurrentInstrument()
|
||||||
for i, note := range t.MidiNotePlaying {
|
for i, note := range t.MidiNotePlaying {
|
||||||
t.Model.Notes().Table().Set(note)
|
t.Model.Notes().Table().Set(note)
|
||||||
te.scrollTable.Table.MoveCursor(1, 0)
|
te.scrollTable.Table.MoveCursor(1, 0)
|
||||||
|
@ -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.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()
|
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))
|
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
|
||||||
title := t.Model.Order().Title(i)
|
LabelStyle{
|
||||||
LabelStyle{Alignment: layout.NW, Text: title, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor, Shaper: t.Theme.Shaper}.Layout(gtx)
|
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)}
|
return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,20 +247,26 @@ type ParameterWidget struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ParameterStyle struct {
|
type ParameterStyle struct {
|
||||||
tracker *Tracker
|
tracker *Tracker
|
||||||
w *ParameterWidget
|
w *ParameterWidget
|
||||||
Theme *material.Theme
|
Theme *material.Theme
|
||||||
Focus bool
|
SendTargetTheme *material.Theme
|
||||||
sends []sointu.Unit
|
Focus bool
|
||||||
|
sends []sointu.Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) ParamStyle(th *material.Theme, paramWidget *ParameterWidget) ParameterStyle {
|
func (t *Tracker) ParamStyle(th *material.Theme, paramWidget *ParameterWidget) ParameterStyle {
|
||||||
sends := slices.Collect(t.Model.CollectSendsTo(paramWidget.Parameter))
|
sendTargetTheme := th.WithPalette(material.Palette{
|
||||||
|
Bg: th.Bg,
|
||||||
|
Fg: paramIsSendTargetColor,
|
||||||
|
ContrastBg: th.ContrastBg,
|
||||||
|
ContrastFg: th.ContrastFg,
|
||||||
|
})
|
||||||
return ParameterStyle{
|
return ParameterStyle{
|
||||||
tracker: t, // TODO: we need this to pull the instrument names for ID style parameters, find out another way
|
tracker: t, // TODO: we need this to pull the instrument names for ID style parameters, find out another way
|
||||||
Theme: th,
|
Theme: th,
|
||||||
w: paramWidget,
|
SendTargetTheme: &sendTargetTheme,
|
||||||
sends: sends,
|
w: paramWidget,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +302,6 @@ func (p ParameterStyle) Layout(gtx C) D {
|
|||||||
}
|
}
|
||||||
sliderStyle := material.Slider(p.Theme, &p.w.floatWidget)
|
sliderStyle := material.Slider(p.Theme, &p.w.floatWidget)
|
||||||
sliderStyle.Color = p.Theme.Fg
|
sliderStyle.Color = p.Theme.Fg
|
||||||
|
|
||||||
if len(sends) > 0 {
|
if len(sends) > 0 {
|
||||||
sliderStyle.Color = paramIsSendTargetColor
|
sliderStyle.Color = paramIsSendTargetColor
|
||||||
}
|
}
|
||||||
@ -374,11 +379,11 @@ func (p ParameterStyle) Layout(gtx C) D {
|
|||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
if p.w.Parameter.Type() != tracker.IDParameter {
|
if p.w.Parameter.Type() != tracker.IDParameter {
|
||||||
label := Label(p.w.Parameter.Hint(), white, p.tracker.Theme.Shaper)
|
label := Label(p.w.Parameter.Hint(), white, p.tracker.Theme.Shaper)
|
||||||
info := p.buildTooltip(sends)
|
info := p.buildSendTargetTooltip(sends)
|
||||||
if info == "" {
|
if info == "" {
|
||||||
return label(gtx)
|
return label(gtx)
|
||||||
}
|
}
|
||||||
tooltip := component.PlatformTooltip(p.Theme, info)
|
tooltip := component.PlatformTooltip(p.SendTargetTheme, info)
|
||||||
return p.w.tipArea.Layout(gtx, tooltip, label)
|
return p.w.tipArea.Layout(gtx, tooltip, label)
|
||||||
}
|
}
|
||||||
return D{}
|
return D{}
|
||||||
@ -413,7 +418,7 @@ func (p ParameterStyle) findSends() iter.Seq[sointu.Unit] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ParameterStyle) buildTooltip(sends []sointu.Unit) string {
|
func (p ParameterStyle) buildSendTargetTooltip(sends []sointu.Unit) string {
|
||||||
if len(sends) == 0 {
|
if len(sends) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -424,7 +429,7 @@ func (p ParameterStyle) buildTooltip(sends []sointu.Unit) string {
|
|||||||
sourceInstr := p.tracker.Model.InstrumentForUnit(sends[0].ID)
|
sourceInstr := p.tracker.Model.InstrumentForUnit(sends[0].ID)
|
||||||
sourceInfo := ""
|
sourceInfo := ""
|
||||||
if sourceInstr != targetInstr {
|
if sourceInstr != targetInstr {
|
||||||
sourceInfo = fmt.Sprintf(" (%s)", sourceInstr.Name)
|
sourceInfo = fmt.Sprintf(" from \"%s\"", sourceInstr.Name)
|
||||||
}
|
}
|
||||||
if amounts == "" {
|
if amounts == "" {
|
||||||
amounts = fmt.Sprintf("x %d%s", sends[i].Parameters["amount"], sourceInfo)
|
amounts = fmt.Sprintf("x %d%s", sends[i].Parameters["amount"], sourceInfo)
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -40,7 +39,8 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
Model struct {
|
Model struct {
|
||||||
d modelData
|
d modelData
|
||||||
|
derived derivedModelData
|
||||||
|
|
||||||
instrEnlarged bool
|
instrEnlarged bool
|
||||||
commentExpanded bool
|
commentExpanded bool
|
||||||
@ -202,6 +202,7 @@ func NewModel(broker *Broker, synther sointu.Synther, midiContext MIDIContext, r
|
|||||||
}
|
}
|
||||||
trySend(broker.ToPlayer, any(m.d.Song.Copy())) // we should be non-blocking in the constructor
|
trySend(broker.ToPlayer, any(m.d.Song.Copy())) // we should be non-blocking in the constructor
|
||||||
m.signalAnalyzer = NewScopeModel(broker, m.d.Song.BPM)
|
m.signalAnalyzer = NewScopeModel(broker, m.d.Song.BPM)
|
||||||
|
m.initDerivedData()
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +237,7 @@ func (m *Model) change(kind string, t ChangeType, severity ChangeSeverity) func(
|
|||||||
m.updatePatternUseCount()
|
m.updatePatternUseCount()
|
||||||
m.d.Cursor.SongPos = m.d.Song.Score.Clamp(m.d.Cursor.SongPos)
|
m.d.Cursor.SongPos = m.d.Song.Score.Clamp(m.d.Cursor.SongPos)
|
||||||
m.d.Cursor2.SongPos = m.d.Song.Score.Clamp(m.d.Cursor2.SongPos)
|
m.d.Cursor2.SongPos = m.d.Song.Score.Clamp(m.d.Cursor2.SongPos)
|
||||||
|
m.updateDerivedScoreData()
|
||||||
trySend(m.broker.ToPlayer, any(m.d.Song.Score.Copy()))
|
trySend(m.broker.ToPlayer, any(m.d.Song.Score.Copy()))
|
||||||
}
|
}
|
||||||
if m.changeType&PatchChange != 0 {
|
if m.changeType&PatchChange != 0 {
|
||||||
@ -251,6 +253,7 @@ func (m *Model) change(kind string, t ChangeType, severity ChangeSeverity) func(
|
|||||||
m.d.UnitIndex2 = clamp(m.d.UnitIndex2, 0, unitCount-1)
|
m.d.UnitIndex2 = clamp(m.d.UnitIndex2, 0, unitCount-1)
|
||||||
m.d.UnitSearching = false // if we change anything in the patch, reset the unit searching
|
m.d.UnitSearching = false // if we change anything in the patch, reset the unit searching
|
||||||
m.d.UnitSearchString = ""
|
m.d.UnitSearchString = ""
|
||||||
|
m.updateDerivedPatchData()
|
||||||
trySend(m.broker.ToPlayer, any(m.d.Song.Patch.Copy()))
|
trySend(m.broker.ToPlayer, any(m.d.Song.Patch.Copy()))
|
||||||
}
|
}
|
||||||
if m.changeType&BPMChange != 0 {
|
if m.changeType&BPMChange != 0 {
|
||||||
@ -598,38 +601,3 @@ func clamp(a, min, max int) int {
|
|||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) CollectSendsTo(param Parameter) iter.Seq[sointu.Unit] {
|
|
||||||
return func(yield func(sointu.Unit) bool) {
|
|
||||||
p, ok := param.(NamedParameter)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, instr := range m.d.Song.Patch {
|
|
||||||
for _, unit := range instr.Units {
|
|
||||||
if unit.Type != "send" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
targetId, ok := unit.Parameters["target"]
|
|
||||||
if !ok || targetId != p.Unit().ID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !yield(unit) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) InstrumentForUnit(id int) *sointu.Instrument {
|
|
||||||
for _, instr := range m.d.Song.Patch {
|
|
||||||
for _, unit := range instr.Units {
|
|
||||||
if unit.ID == id {
|
|
||||||
return &instr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ID does not exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package tracker
|
package tracker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"iter"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
@ -367,71 +366,6 @@ func (m *Order) SetValue(p Point, val int) {
|
|||||||
m.d.Song.Score.Tracks[p.X].Order.Set(p.Y, val)
|
m.d.Song.Score.Tracks[p.X].Order.Set(p.Y, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Order) Title(x int) (title string) {
|
|
||||||
title = "?"
|
|
||||||
if x < 0 || x >= len(e.d.Song.Score.Tracks) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
firstIndex, lastIndex, err := e.instrumentListFor(x)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch diff := lastIndex - firstIndex; diff {
|
|
||||||
case 0:
|
|
||||||
title = e.d.Song.Patch[firstIndex].Name
|
|
||||||
default:
|
|
||||||
n1 := e.d.Song.Patch[firstIndex].Name
|
|
||||||
n2 := e.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 (e *Order) instrumentListFor(trackIndex int) (int, int, error) {
|
|
||||||
track := e.d.Song.Score.Tracks[trackIndex]
|
|
||||||
firstVoice := e.d.Song.Score.FirstVoiceForTrack(trackIndex)
|
|
||||||
lastVoice := firstVoice + track.NumVoices - 1
|
|
||||||
firstIndex, err1 := e.d.Song.Patch.InstrumentForVoice(firstVoice)
|
|
||||||
if err1 != nil {
|
|
||||||
return trackIndex, trackIndex, err1
|
|
||||||
}
|
|
||||||
lastIndex, err2 := e.d.Song.Patch.InstrumentForVoice(lastVoice)
|
|
||||||
if err2 != nil {
|
|
||||||
return trackIndex, trackIndex, err2
|
|
||||||
}
|
|
||||||
return firstIndex, lastIndex, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Order) TrackIndicesForCurrentInstrument() iter.Seq[int] {
|
|
||||||
currentTrack := e.d.Cursor.Track
|
|
||||||
return e.d.Song.AllTracksWithSameInstrument(currentTrack)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Order) CountNextTracksForCurrentInstrument() int {
|
|
||||||
currentTrack := e.d.Cursor.Track
|
|
||||||
count := 0
|
|
||||||
for t := range e.TrackIndicesForCurrentInstrument() {
|
|
||||||
if t > currentTrack {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoteTable
|
// NoteTable
|
||||||
|
|
||||||
func (v *Notes) Table() Table {
|
func (v *Notes) Table() Table {
|
||||||
|
Loading…
Reference in New Issue
Block a user