refactor(tracker): make Model methods return List, avoiding .List()

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-01-23 22:42:25 +02:00
parent 74beb6760c
commit 1693d7ed5e
13 changed files with 215 additions and 322 deletions

View File

@ -145,7 +145,7 @@ func (m *AddTrack) Do() {
func (m *Model) DeleteTrack() Action { return MakeAction((*DeleteTrack)(m)) }
func (m *DeleteTrack) Enabled() bool { return len(m.d.Song.Score.Tracks) > 0 }
func (m *DeleteTrack) Do() { (*Model)(m).Tracks().List().DeleteElements(false) }
func (m *DeleteTrack) Do() { (*Model)(m).Tracks().DeleteElements(false) }
// AddInstrument
@ -164,7 +164,7 @@ func (m *AddInstrument) Do() {
func (m *Model) DeleteInstrument() Action { return MakeAction((*DeleteInstrument)(m)) }
func (m *DeleteInstrument) Enabled() bool { return len((*Model)(m).d.Song.Patch) > 0 }
func (m *DeleteInstrument) Do() { (*Model)(m).Instruments().List().DeleteElements(false) }
func (m *DeleteInstrument) Do() { (*Model)(m).Instruments().DeleteElements(false) }
// SplitTrack
@ -259,7 +259,7 @@ func (m *DeleteUnit) Enabled() bool {
}
func (m *DeleteUnit) Do() {
defer (*Model)(m).change("DeleteUnitAction", PatchChange, MajorChange)()
(*Model)(m).Units().List().DeleteElements(true)
(*Model)(m).Units().DeleteElements(true)
}
// ClearUnit
@ -271,7 +271,7 @@ func (m *ClearUnit) Enabled() bool {
}
func (m *ClearUnit) Do() {
defer (*Model)(m).change("DeleteUnitAction", PatchChange, MajorChange)()
l := ((*Model)(m)).Units().List()
l := ((*Model)(m)).Units()
r := l.listRange()
for i := r.Start; i < r.End; i++ {
m.d.Song.Patch[m.d.InstrIndex].Units[i] = sointu.Unit{}
@ -426,7 +426,7 @@ func (m *PlaySelected) Enabled() bool { return !m.instrEnlarged }
func (m *PlaySelected) Do() {
(*Model)(m).setPanic(false)
m.playing = true
l := (*Model)(m).OrderRows().List()
l := (*Model)(m).OrderRows()
r := l.listRange()
newLoop := Loop{r.Start, r.End - r.Start}
(*Model)(m).setLoop(newLoop)

View File

@ -272,6 +272,7 @@ func (m *UnitSearching) SetValue(val bool) {
return
}
m.d.UnitSearchString = m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type
(*Model)(m).updateDerivedUnitSearch()
}
// UnitDisabled methods
@ -290,7 +291,7 @@ func (m *UnitDisabled) SetValue(val bool) {
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return
}
l := ((*Model)(m)).Units().List()
l := ((*Model)(m)).Units()
r := l.listRange()
defer (*Model)(m).change("UnitDisabledSet", PatchChange, MajorChange)()
for i := r.Start; i < r.End; i++ {
@ -315,7 +316,7 @@ func (t *LoopToggle) SetValue(val bool) {
m := (*Model)(t)
newLoop := Loop{}
if val {
l := m.OrderRows().List()
l := m.OrderRows()
r := l.listRange()
newLoop = Loop{r.Start, r.End - r.Start}
}

View File

@ -33,10 +33,11 @@ type (
// corresponding part of the model changes.
derivedModelData struct {
// map Unit by ID, other entities by their respective index
patch []derivedInstrument
tracks []derivedTrack
railError RailError
presetSearch derivedPresetSearch
patch []derivedInstrument
tracks []derivedTrack
railError RailError
presetSearch derivedPresetSearch
searchResults []string
}
derivedInstrument struct {

View File

@ -128,7 +128,7 @@ func (s FilledDragListStyle) Layout(gtx C, element, bg func(gtx C, i int) D) D {
gtx.Execute(op.InvalidateCmd{})
}
_, isMutable := s.dragList.TrackerList.ListData.(tracker.MutableListData)
isMutable := s.dragList.TrackerList.Mutable()
listElem := func(gtx C, index int) D {
for len(s.dragList.tags) <= index {

View File

@ -53,7 +53,7 @@ type (
func NewInstrumentEditor(m *tracker.Model) *InstrumentEditor {
ret := &InstrumentEditor{
dragList: NewDragList(m.Units().List(), layout.Vertical),
dragList: NewDragList(m.Units(), layout.Vertical),
addUnitBtn: new(Clickable),
searchEditor: NewEditor(true, true, text.Start),
DeleteUnitBtn: new(Clickable),
@ -62,8 +62,8 @@ func NewInstrumentEditor(m *tracker.Model) *InstrumentEditor {
CopyUnitBtn: new(Clickable),
SelectTypeBtn: new(Clickable),
commentEditor: NewEditor(true, true, text.Start),
paramTable: NewScrollTable(m.Params().Table(), m.ParamVertList().List(), m.Units().List()),
searchList: NewDragList(m.SearchResults().List(), layout.Vertical),
paramTable: NewScrollTable(m.Params().Table(), m.ParamVertList().List(), m.Units()),
searchList: NewDragList(m.SearchResults(), layout.Vertical),
searching: m.UnitSearching(),
}
ret.caser = cases.Title(language.English)
@ -95,7 +95,7 @@ func (ul *InstrumentEditor) layoutList(gtx C) D {
element := func(gtx C, i int) D {
gtx.Constraints.Max.Y = gtx.Dp(20)
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
u := t.Units().Item(i)
u := t.Unit(i)
editorStyle := t.Theme.InstrumentEditor.UnitList.Name
signalError := t.RailError()
switch {
@ -169,7 +169,7 @@ func (ul *InstrumentEditor) update(gtx C) {
case key.NameRightArrow:
t.PatchPanel.instrEditor.paramTable.RowTitleList.Focus()
case key.NameDeleteBackward:
t.Units().SetSelectedType("")
t.SetSelectedUnitType("")
t.UnitSearching().SetValue(true)
ul.searchEditor.Focus()
case key.NameEnter, key.NameReturn:
@ -185,12 +185,12 @@ func (ul *InstrumentEditor) update(gtx C) {
if str.Value() != "" {
for _, n := range sointu.UnitNames {
if strings.HasPrefix(n, str.Value()) {
t.Units().SetSelectedType(n)
t.SetSelectedUnitType(n)
break
}
}
} else {
t.Units().SetSelectedType("")
t.SetSelectedUnitType("")
}
}
ul.dragList.Focus()
@ -202,7 +202,7 @@ func (ul *InstrumentEditor) update(gtx C) {
ul.searchEditor.Focus()
}
for ul.CopyUnitBtn.Clicked(gtx) {
if contents, ok := t.Units().List().CopyElements(); ok {
if contents, ok := t.Units().CopyElements(); ok {
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
t.Alerts().Add("Unit(s) copied to clipboard", tracker.Info)
}
@ -288,8 +288,8 @@ func (pe *InstrumentEditor) layoutTable(gtx C) D {
}
func (pe *InstrumentEditor) ChooseUnitType(t *Tracker) {
if ut, ok := t.SearchResults().Item(pe.searchList.TrackerList.Selected()); ok {
t.Units().SetSelectedType(ut)
if ut, ok := t.SearchResult(pe.searchList.TrackerList.Selected()); ok {
t.SetSelectedUnitType(ut)
pe.paramTable.RowTitleList.Focus()
}
}
@ -321,7 +321,7 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
if y < 0 || y >= len(pe.Parameters) {
return D{}
}
item := t.Units().Item(y)
item := t.Unit(y)
sr := Rail(t.Theme, item.Signals)
label := Label(t.Theme, &t.Theme.UnitEditor.UnitList.Name, item.Type)
switch {
@ -360,7 +360,7 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
}
param := t.Model.Params().Item(point)
paramStyle := Param(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Units().Item(y).Disabled)
paramStyle := Param(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Unit(y).Disabled)
paramStyle.Layout(gtx)
if x == t.Model.Params().RowWidth(y) {
if y == cursor.Y {
@ -373,7 +373,7 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
return pe.commentEditor.Layout(gtx, t.UnitComment(), t.Theme, &t.Theme.InstrumentEditor.UnitComment, "---")
})
} else {
comment := t.Units().Item(y).Comment
comment := t.Unit(y).Comment
if comment != "" {
style := t.Theme.InstrumentEditor.UnitComment.AsLabelStyle()
label := Label(t.Theme, &style, comment)
@ -530,16 +530,9 @@ func (pe *InstrumentEditor) layoutFooter(gtx C) D {
func (pe *InstrumentEditor) layoutUnitTypeChooser(gtx C) D {
t := TrackerFromContext(gtx)
var namesArray [256]string
names := namesArray[:0]
for _, item := range t.Model.SearchResults().Iterate {
names = append(names, item)
}
element := func(gtx C, i int) D {
if i < 0 || i >= len(names) {
return D{}
}
w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, names[i])
name, _ := t.SearchResult(i)
w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, name)
if i == pe.searchList.TrackerList.Selected() {
return pe.SelectTypeBtn.Layout(gtx, w.Layout)
}

View File

@ -289,7 +289,7 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
if err != nil {
break
}
instr := t.Model.Instruments().List().Selected()
instr := t.Model.Instruments().Selected()
n := noteAsValue(t.Model.Octave().Value(), val-12)
t.KeyNoteMap.Press(e.Name, tracker.NoteEvent{Channel: instr, Note: n})
}

View File

@ -93,8 +93,8 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
TrackMidiInBtn: new(Clickable),
scrollTable: NewScrollTable(
model.Notes().Table(),
model.Tracks().List(),
model.NoteRows().List(),
model.Tracks(),
model.NoteRows(),
),
}
for k, a := range keyBindingMap {

View File

@ -42,8 +42,8 @@ func NewOrderEditor(m *tracker.Model) *OrderEditor {
return &OrderEditor{
scrollTable: NewScrollTable(
m.Order().Table(),
m.Tracks().List(),
m.OrderRows().List(),
m.Tracks(),
m.OrderRows(),
),
}
}

View File

@ -179,7 +179,7 @@ func (it *InstrumentTools) Layout(gtx C) D {
func (it *InstrumentTools) update(gtx C, tr *Tracker) {
for it.copyInstrumentBtn.Clicked(gtx) {
if contents, ok := tr.Instruments().List().CopyElements(); ok {
if contents, ok := tr.Instruments().CopyElements(); ok {
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
tr.Alerts().Add("Instrument copied to clipboard", tracker.Info)
}
@ -208,7 +208,7 @@ func (it *InstrumentTools) Tags(level int, yield TagYieldFunc) bool {
func MakeInstrList(model *tracker.Model) InstrumentList {
return InstrumentList{
instrumentDragList: NewDragList(model.Instruments().List(), layout.Horizontal),
instrumentDragList: NewDragList(model.Instruments(), layout.Horizontal),
nameEditor: NewEditor(true, true, text.Middle),
}
}
@ -221,7 +221,7 @@ func (il *InstrumentList) Layout(gtx C) D {
element := func(gtx C, i int) D {
grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1))
label := func(gtx C) D {
name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i)
name, level, mute, ok := t.Instrument(i)
if !ok {
labelStyle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, "")
return layout.Center.Layout(gtx, labelStyle.Layout)

View File

@ -6,7 +6,6 @@ import (
"iter"
"math"
"math/bits"
"strings"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/vm"
@ -15,7 +14,7 @@ import (
type (
List struct {
ListData
data ListData
}
ListData interface {
@ -27,49 +26,32 @@ type (
}
MutableListData interface {
change(kind string, severity ChangeSeverity) func()
cancel()
move(r Range, delta int) (ok bool)
delete(r Range) (ok bool)
marshal(r Range) ([]byte, error)
unmarshal([]byte) (r Range, err error)
}
UnitListItem struct {
Type, Comment string
Disabled bool
Signals Rail
Change(kind string, severity ChangeSeverity) func()
Cancel()
Move(r Range, delta int) (ok bool)
Delete(r Range) (ok bool)
Marshal(r Range) ([]byte, error)
Unmarshal([]byte) (r Range, err error)
}
// Range is used to represent a range [Start,End) of integers
Range struct {
Start, End int
}
UnitYieldFunc func(index int, item UnitListItem) (ok bool)
UnitSearchYieldFunc func(index int, item string) (ok bool)
Instruments Model // Instruments is a list of instruments, implementing ListData & MutableListData interfaces
Units Model // Units is a list of all the units in the selected instrument, implementing ListData & MutableListData interfaces
Tracks Model // Tracks is a list of all the tracks, implementing ListData & MutableListData interfaces
OrderRows Model // OrderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
NoteRows Model // NoteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
SearchResults Model // SearchResults is a unmutable list of all the search results, implementing ListData interface
)
// Model methods
func MakeList(data ListData) List { return List{data} }
func (m *Model) Instruments() *Instruments { return (*Instruments)(m) }
func (m *Model) Units() *Units { return (*Units)(m) }
func (m *Model) Tracks() *Tracks { return (*Tracks)(m) }
func (m *Model) OrderRows() *OrderRows { return (*OrderRows)(m) }
func (m *Model) NoteRows() *NoteRows { return (*NoteRows)(m) }
func (m *Model) SearchResults() *SearchResults { return (*SearchResults)(m) }
func (l List) Selected() int { return max(min(l.data.Selected(), l.data.Count()-1), 0) }
func (l List) Selected2() int { return max(min(l.data.Selected2(), l.data.Count()-1), 0) }
func (l List) SetSelected(value int) { l.data.SetSelected(max(min(value, l.data.Count()-1), 0)) }
func (l List) SetSelected2(value int) { l.data.SetSelected2(max(min(value, l.data.Count()-1), 0)) }
func (l List) Count() int { return l.data.Count() }
// MoveElements moves the selected elements in a list by delta. The list must
// implement the MutableListData interface.
func (v List) MoveElements(delta int) bool {
s, ok := v.ListData.(MutableListData)
s, ok := v.data.(MutableListData)
if !ok {
return false
}
@ -77,9 +59,9 @@ func (v List) MoveElements(delta int) bool {
if delta == 0 || r.Start+delta < 0 || r.End+delta > v.Count() {
return false
}
defer s.change("MoveElements", MajorChange)()
if !s.move(r, delta) {
s.cancel()
defer s.Change("MoveElements", MajorChange)()
if !s.Move(r, delta) {
s.Cancel()
return false
}
v.SetSelected(v.Selected() + delta)
@ -90,7 +72,7 @@ func (v List) MoveElements(delta int) bool {
// DeleteElements deletes the selected elements in a list. The list must
// implement the MutableListData interface.
func (v List) DeleteElements(backwards bool) bool {
d, ok := v.ListData.(MutableListData)
d, ok := v.data.(MutableListData)
if !ok {
return false
}
@ -98,9 +80,9 @@ func (v List) DeleteElements(backwards bool) bool {
if r.Len() == 0 {
return false
}
defer d.change("DeleteElements", MajorChange)()
if !d.delete(r) {
d.cancel()
defer d.Change("DeleteElements", MajorChange)()
if !d.Delete(r) {
d.Cancel()
return false
}
if backwards && r.Start > 0 {
@ -115,7 +97,7 @@ func (v List) DeleteElements(backwards bool) bool {
// the MutableListData interface. Returns the copied data, marshaled into byte
// slice, and true if successful.
func (v List) CopyElements() ([]byte, bool) {
m, ok := v.ListData.(MutableListData)
m, ok := v.data.(MutableListData)
if !ok {
return nil, false
}
@ -123,7 +105,7 @@ func (v List) CopyElements() ([]byte, bool) {
if r.Len() == 0 {
return nil, false
}
ret, err := m.marshal(r)
ret, err := m.Marshal(r)
if err != nil {
return nil, false
}
@ -134,14 +116,14 @@ func (v List) CopyElements() ([]byte, bool) {
// byte slice. The list must implement the MutableListData interface. Returns
// true if successful.
func (v List) PasteElements(data []byte) (ok bool) {
m, ok := v.ListData.(MutableListData)
m, ok := v.data.(MutableListData)
if !ok {
return false
}
defer m.change("PasteElements", MajorChange)()
r, err := m.unmarshal(data)
defer m.Change("PasteElements", MajorChange)()
r, err := m.Unmarshal(data)
if err != nil {
m.cancel()
m.Cancel()
return false
}
v.SetSelected(r.Start)
@ -149,19 +131,23 @@ func (v List) PasteElements(data []byte) (ok bool) {
return true
}
func (v List) Mutable() bool {
_, ok := v.data.(MutableListData)
return ok
}
func (v *List) listRange() (r Range) {
r.Start = max(min(v.Selected(), v.Selected2()), 0)
r.End = min(max(v.Selected(), v.Selected2())+1, v.Count())
return
}
// Instruments methods
// instruments is a list of instruments, implementing ListData & MutableListData interfaces
type instruments Model
func (v *Instruments) List() List {
return List{v}
}
func (m *Model) Instruments() List { return List{(*instruments)(m)} }
func (v *Instruments) Item(i int) (name string, maxLevel float32, mute bool, ok bool) {
func (v *Model) Instrument(i int) (name string, maxLevel float32, mute bool, ok bool) {
if i < 0 || i >= len(v.d.Song.Patch) {
return "", 0, false, false
}
@ -182,37 +168,20 @@ func (v *Instruments) Item(i int) (name string, maxLevel float32, mute bool, ok
ok = true
return
}
func (v *Instruments) FirstID(i int) (id int, ok bool) {
if i < 0 || i >= len(v.d.Song.Patch) {
return 0, false
}
if len(v.d.Song.Patch[i].Units) == 0 {
return 0, false
}
return v.d.Song.Patch[i].Units[0].ID, true
}
func (v *Instruments) Selected() int {
return max(min(v.d.InstrIndex, v.Count()-1), 0)
}
func (v *Instruments) Selected2() int {
return max(min(v.d.InstrIndex2, v.Count()-1), 0)
}
func (v *Instruments) SetSelected(value int) {
v.d.InstrIndex = max(min(value, v.Count()-1), 0)
func (v *instruments) Count() int { return len(v.d.Song.Patch) }
func (v *instruments) Selected() int { return v.d.InstrIndex }
func (v *instruments) Selected2() int { return v.d.InstrIndex2 }
func (v *instruments) SetSelected2(value int) { v.d.InstrIndex2 = value }
func (v *instruments) SetSelected(value int) {
v.d.InstrIndex = value
v.d.UnitIndex = 0
v.d.UnitIndex2 = 0
v.d.UnitSearching = false
v.d.UnitSearchString = ""
}
func (v *Instruments) SetSelected2(value int) {
v.d.InstrIndex2 = max(min(value, v.Count()-1), 0)
}
func (v *Instruments) move(r Range, delta int) (ok bool) {
func (v *instruments) Move(r Range, delta int) (ok bool) {
voiceDelta := 0
if delta < 0 {
voiceDelta = -VoiceRange(v.d.Song.Patch, Range{r.Start + delta, r.Start}).Len()
@ -226,28 +195,24 @@ func (v *Instruments) move(r Range, delta int) (ok bool) {
return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...)
}
func (v *Instruments) delete(r Range) (ok bool) {
func (v *instruments) Delete(r Range) (ok bool) {
ranges := Complement(VoiceRange(v.d.Song.Patch, r))
return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...)
}
func (v *Instruments) change(n string, severity ChangeSeverity) func() {
func (v *instruments) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("Instruments."+n, SongChange, severity)
}
func (v *Instruments) cancel() {
func (v *instruments) Cancel() {
v.changeCancel = true
}
func (v *Instruments) Count() int {
return len(v.d.Song.Patch)
}
func (v *Instruments) marshal(r Range) ([]byte, error) {
func (v *instruments) Marshal(r Range) ([]byte, error) {
return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Patch, r))
}
func (m *Instruments) unmarshal(data []byte) (r Range, err error) {
func (m *instruments) Unmarshal(data []byte) (r Range, err error) {
voiceIndex := m.d.Song.Patch.FirstVoiceForInstrument(m.d.InstrIndex)
r, _, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, true, m.linkInstrTrack)
if !ok {
@ -256,13 +221,37 @@ func (m *Instruments) unmarshal(data []byte) (r Range, err error) {
return r, nil
}
// Units methods
// units is a list of all the units in the selected instrument, implementing ListData & MutableListData interfaces
type (
units Model
UnitListItem struct {
Type, Comment string
Disabled bool
Signals Rail
}
)
func (v *Units) List() List {
return List{v}
func (m *Model) Units() List { return List{(*units)(m)} }
func (v *Model) Unit(index int) UnitListItem {
i := v.d.InstrIndex
if i < 0 || i >= len(v.d.Song.Patch) || index < 0 || index >= (*units)(v).Count() {
return UnitListItem{}
}
unit := v.d.Song.Patch[v.d.InstrIndex].Units[index]
signals := Rail{}
if i >= 0 && i < len(v.derived.patch) && index >= 0 && index < len(v.derived.patch[i].rails) {
signals = v.derived.patch[i].rails[index]
}
return UnitListItem{
Type: unit.Type,
Comment: unit.Comment,
Disabled: unit.Disabled,
Signals: signals,
}
}
func (m *Units) SelectedType() string {
func (m *Model) SelectedUnitType() string {
if m.d.InstrIndex < 0 ||
m.d.InstrIndex >= len(m.d.Song.Patch) ||
m.d.UnitIndex < 0 ||
@ -272,7 +261,7 @@ func (m *Units) SelectedType() string {
return m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type
}
func (m *Units) SetSelectedType(t string) {
func (m *Model) SetSelectedUnitType(t string) {
if m.d.InstrIndex < 0 ||
m.d.InstrIndex >= len(m.d.Song.Patch) {
return
@ -293,58 +282,28 @@ func (m *Units) SetSelectedType(t string) {
if oldUnit.Type == unit.Type {
return
}
defer m.change("SetSelectedType", MajorChange)()
defer (*units)(m).Change("SetSelectedType", MajorChange)()
m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex] = unit
m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].ID = oldUnit.ID // keep the ID of the replaced unit
}
func (v *Units) Item(index int) UnitListItem {
i := v.d.InstrIndex
if i < 0 || i >= len(v.d.Song.Patch) || index < 0 || index >= v.Count() {
return UnitListItem{}
}
unit := v.d.Song.Patch[v.d.InstrIndex].Units[index]
signals := Rail{}
if i >= 0 && i < len(v.derived.patch) && index >= 0 && index < len(v.derived.patch[i].rails) {
signals = v.derived.patch[i].rails[index]
}
return UnitListItem{
Type: unit.Type,
Comment: unit.Comment,
Disabled: unit.Disabled,
Signals: signals,
}
}
func (v *Units) Selected() int {
return max(min(v.d.UnitIndex, v.Count()-1), 0)
}
func (v *Units) Selected2() int {
return max(min(v.d.UnitIndex2, v.Count()-1), 0)
}
func (v *Units) SetSelected(value int) {
m := (*Model)(v)
m.d.UnitIndex = max(min(value, v.Count()-1), 0)
func (v *units) Selected() int { return v.d.UnitIndex }
func (v *units) Selected2() int { return v.d.UnitIndex2 }
func (v *units) SetSelected2(value int) { v.d.UnitIndex2 = value }
func (m *units) SetSelected(value int) {
m.d.UnitIndex = value
m.d.ParamIndex = 0
m.d.UnitSearching = false
m.d.UnitSearchString = ""
}
func (v *Units) SetSelected2(value int) {
(*Model)(v).d.UnitIndex2 = max(min(value, v.Count()-1), 0)
}
func (v *Units) Count() int {
m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
func (v *units) Count() int {
if v.d.InstrIndex < 0 || v.d.InstrIndex >= len(v.d.Song.Patch) {
return 0
}
return len(m.d.Song.Patch[(*Model)(v).d.InstrIndex].Units)
return len(v.d.Song.Patch[v.d.InstrIndex].Units)
}
func (v *Units) move(r Range, delta int) (ok bool) {
func (v *units) Move(r Range, delta int) (ok bool) {
m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return false
@ -356,7 +315,7 @@ func (v *Units) move(r Range, delta int) (ok bool) {
return true
}
func (v *Units) delete(r Range) (ok bool) {
func (v *units) Delete(r Range) (ok bool) {
m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return false
@ -366,15 +325,15 @@ func (v *Units) delete(r Range) (ok bool) {
return true
}
func (v *Units) change(n string, severity ChangeSeverity) func() {
func (v *units) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("UnitListView."+n, PatchChange, severity)
}
func (v *Units) cancel() {
func (v *units) Cancel() {
(*Model)(v).changeCancel = true
}
func (v *Units) marshal(r Range) ([]byte, error) {
func (v *units) Marshal(r Range) ([]byte, error) {
m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return nil, errors.New("UnitListView.marshal: no instruments")
@ -387,7 +346,7 @@ func (v *Units) marshal(r Range) ([]byte, error) {
return ret, nil
}
func (v *Units) unmarshal(data []byte) (r Range, err error) {
func (v *units) Unmarshal(data []byte) (r Range, err error) {
m := (*Model)(v)
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
return Range{}, errors.New("UnitListView.unmarshal: no instruments")
@ -409,29 +368,18 @@ func (v *Units) unmarshal(data []byte) (r Range, err error) {
return Range{sel, sel + len(pastedUnits.Units)}, nil
}
// Tracks methods
// tracks is a list of all the tracks, implementing ListData & MutableListData interfaces
type tracks Model
func (v *Tracks) List() List {
return List{v}
}
func (m *Model) Tracks() List { return List{(*tracks)(m)} }
func (v *Tracks) Selected() int {
return max(min(v.d.Cursor.Track, v.Count()-1), 0)
}
func (v *tracks) Selected() int { return v.d.Cursor.Track }
func (v *tracks) Selected2() int { return v.d.Cursor2.Track }
func (v *tracks) SetSelected(value int) { v.d.Cursor.Track = value }
func (v *tracks) SetSelected2(value int) { v.d.Cursor2.Track = value }
func (v *tracks) Count() int { return len((*Model)(v).d.Song.Score.Tracks) }
func (v *Tracks) Selected2() int {
return max(min(v.d.Cursor2.Track, v.Count()-1), 0)
}
func (v *Tracks) SetSelected(value int) {
v.d.Cursor.Track = max(min(value, v.Count()-1), 0)
}
func (v *Tracks) SetSelected2(value int) {
v.d.Cursor2.Track = max(min(value, v.Count()-1), 0)
}
func (v *Tracks) move(r Range, delta int) (ok bool) {
func (v *tracks) Move(r Range, delta int) (ok bool) {
voiceDelta := 0
if delta < 0 {
voiceDelta = -VoiceRange(v.d.Song.Score.Tracks, Range{r.Start + delta, r.Start}).Len()
@ -445,28 +393,24 @@ func (v *Tracks) move(r Range, delta int) (ok bool) {
return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...)
}
func (v *Tracks) delete(r Range) (ok bool) {
func (v *tracks) Delete(r Range) (ok bool) {
ranges := Complement(VoiceRange(v.d.Song.Score.Tracks, r))
return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...)
}
func (v *Tracks) change(n string, severity ChangeSeverity) func() {
func (v *tracks) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("TrackList."+n, SongChange, severity)
}
func (v *Tracks) cancel() {
func (v *tracks) Cancel() {
v.changeCancel = true
}
func (v *Tracks) Count() int {
return len((*Model)(v).d.Song.Score.Tracks)
}
func (v *Tracks) marshal(r Range) ([]byte, error) {
func (v *tracks) Marshal(r Range) ([]byte, error) {
return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Score.Tracks, r))
}
func (m *Tracks) unmarshal(data []byte) (r Range, err error) {
func (m *tracks) Unmarshal(data []byte) (r Range, err error) {
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track)
_, r, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, m.linkInstrTrack, true)
if !ok {
@ -475,37 +419,23 @@ func (m *Tracks) unmarshal(data []byte) (r Range, err error) {
return r, nil
}
// OrderRows methods
// orderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
type orderRows Model
func (v *OrderRows) List() List {
return List{v}
}
func (m *Model) OrderRows() List { return List{(*orderRows)(m)} }
func (v *OrderRows) Selected() int {
p := v.d.Cursor.OrderRow
p = max(min(p, v.Count()-1), 0)
return p
}
func (v *OrderRows) Selected2() int {
p := v.d.Cursor2.OrderRow
p = max(min(p, v.Count()-1), 0)
return p
}
func (v *OrderRows) SetSelected(value int) {
y := max(min(value, v.Count()-1), 0)
if y != v.d.Cursor.OrderRow {
func (v *orderRows) Count() int { return v.d.Song.Score.Length }
func (v *orderRows) Selected() int { return v.d.Cursor.OrderRow }
func (v *orderRows) Selected2() int { return v.d.Cursor2.OrderRow }
func (v *orderRows) SetSelected2(value int) { v.d.Cursor2.OrderRow = value }
func (v *orderRows) SetSelected(value int) {
if value != v.d.Cursor.OrderRow {
v.follow = false
}
v.d.Cursor.OrderRow = y
v.d.Cursor.OrderRow = value
}
func (v *OrderRows) SetSelected2(value int) {
v.d.Cursor2.OrderRow = max(min(value, v.Count()-1), 0)
}
func (v *OrderRows) move(r Range, delta int) (ok bool) {
func (v *orderRows) Move(r Range, delta int) (ok bool) {
swaps := r.Swaps(delta)
for i, t := range v.d.Song.Score.Tracks {
for a, b := range swaps {
@ -517,7 +447,7 @@ func (v *OrderRows) move(r Range, delta int) (ok bool) {
return true
}
func (v *OrderRows) delete(r Range) (ok bool) {
func (v *orderRows) Delete(r Range) (ok bool) {
for i, t := range v.d.Song.Score.Tracks {
r2 := r.Intersect(Range{0, len(t.Order)})
v.d.Song.Score.Tracks[i].Order = append(t.Order[:r2.Start], t.Order[r2.End:]...)
@ -525,23 +455,19 @@ func (v *OrderRows) delete(r Range) (ok bool) {
return true
}
func (v *OrderRows) change(n string, severity ChangeSeverity) func() {
func (v *orderRows) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("OrderRowList."+n, ScoreChange, severity)
}
func (v *OrderRows) cancel() {
func (v *orderRows) Cancel() {
v.changeCancel = true
}
func (v *OrderRows) Count() int {
return v.d.Song.Score.Length
}
type marshalOrderRows struct {
Columns [][]int `yaml:",flow"`
}
func (v *OrderRows) marshal(r Range) ([]byte, error) {
func (v *orderRows) Marshal(r Range) ([]byte, error) {
var table marshalOrderRows
for i := range v.d.Song.Score.Tracks {
table.Columns = append(table.Columns, make([]int, r.Len()))
@ -552,7 +478,7 @@ func (v *OrderRows) marshal(r Range) ([]byte, error) {
return yaml.Marshal(table)
}
func (v *OrderRows) unmarshal(data []byte) (r Range, err error) {
func (v *orderRows) Unmarshal(data []byte) (r Range, err error) {
var table marshalOrderRows
err = yaml.Unmarshal(data, &table)
if err != nil {
@ -581,33 +507,23 @@ func (v *OrderRows) unmarshal(data []byte) (r Range, err error) {
return
}
// NoteRows methods
// noteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
type noteRows Model
func (v *NoteRows) List() List {
return List{v}
}
func (m *Model) NoteRows() List { return List{(*noteRows)(m)} }
func (v *NoteRows) Selected() int {
return v.d.Song.Score.SongRow(v.d.Song.Score.Clamp(v.d.Cursor.SongPos))
}
func (v *NoteRows) Selected2() int {
return v.d.Song.Score.SongRow(v.d.Song.Score.Clamp(v.d.Cursor2.SongPos))
}
func (v *NoteRows) SetSelected(value int) {
if value != v.d.Song.Score.SongRow(v.d.Cursor.SongPos) {
v.follow = false
func (n *noteRows) Count() int { return n.d.Song.Score.Length * n.d.Song.Score.RowsPerPattern }
func (n *noteRows) Selected() int { return n.d.Song.Score.SongRow(n.d.Cursor.SongPos) }
func (n *noteRows) Selected2() int { return n.d.Song.Score.SongRow(n.d.Cursor2.SongPos) }
func (n *noteRows) SetSelected2(v int) { n.d.Cursor2.SongPos = n.d.Song.Score.SongPos(v) }
func (n *noteRows) SetSelected(value int) {
if value != n.d.Song.Score.SongRow(n.d.Cursor.SongPos) {
n.follow = false
}
v.d.Cursor.SongPos = v.d.Song.Score.Clamp(v.d.Song.Score.SongPos(value))
n.d.Cursor.SongPos = n.d.Song.Score.Clamp(n.d.Song.Score.SongPos(value))
}
func (v *NoteRows) SetSelected2(value int) {
v.d.Cursor2.SongPos = v.d.Song.Score.Clamp(v.d.Song.Score.SongPos(value))
}
func (v *NoteRows) move(r Range, delta int) (ok bool) {
func (v *noteRows) Move(r Range, delta int) (ok bool) {
for a, b := range r.Swaps(delta) {
apos := v.d.Song.Score.SongPos(a)
bpos := v.d.Song.Score.SongPos(b)
@ -621,7 +537,7 @@ func (v *NoteRows) move(r Range, delta int) (ok bool) {
return true
}
func (v *NoteRows) delete(r Range) (ok bool) {
func (v *noteRows) Delete(r Range) (ok bool) {
for _, track := range v.d.Song.Score.Tracks {
for i := r.Start; i < r.End; i++ {
pos := v.d.Song.Score.SongPos(i)
@ -631,23 +547,19 @@ func (v *NoteRows) delete(r Range) (ok bool) {
return true
}
func (v *NoteRows) change(n string, severity ChangeSeverity) func() {
func (v *noteRows) Change(n string, severity ChangeSeverity) func() {
return (*Model)(v).change("NoteRowList."+n, ScoreChange, severity)
}
func (v *NoteRows) cancel() {
func (v *noteRows) Cancel() {
(*Model)(v).changeCancel = true
}
func (v *NoteRows) Count() int {
return (*Model)(v).d.Song.Score.Length * v.d.Song.Score.RowsPerPattern
}
type marshalNoteRows struct {
NoteRows [][]byte `yaml:",flow"`
}
func (v *NoteRows) marshal(r Range) ([]byte, error) {
func (v *noteRows) Marshal(r Range) ([]byte, error) {
var table marshalNoteRows
for i, track := range v.d.Song.Score.Tracks {
table.NoteRows = append(table.NoteRows, make([]byte, r.Len()))
@ -660,7 +572,7 @@ func (v *NoteRows) marshal(r Range) ([]byte, error) {
return yaml.Marshal(table)
}
func (v *NoteRows) unmarshal(data []byte) (r Range, err error) {
func (v *noteRows) Unmarshal(data []byte) (r Range, err error) {
var table marshalNoteRows
if err := yaml.Unmarshal(data, &table); err != nil {
return Range{}, fmt.Errorf("NoteRowList.unmarshal: %v", err)
@ -683,57 +595,25 @@ func (v *NoteRows) unmarshal(data []byte) (r Range, err error) {
return
}
// SearchResults
// searchResults is a unmutable list of all the search results, implementing ListData interface
type (
searchResults Model
UnitSearchYieldFunc func(index int, item string) (ok bool)
)
func (v *SearchResults) List() List {
return List{v}
}
func (l *SearchResults) Iterate(yield UnitSearchYieldFunc) {
index := 0
for _, name := range sointu.UnitNames {
if !strings.HasPrefix(name, l.d.UnitSearchString) {
continue
}
if !yield(index, name) {
break
}
index++
func (m *Model) SearchResults() List { return List{(*searchResults)(m)} }
func (l *Model) SearchResult(i int) (name string, ok bool) {
if i < 0 || i >= len(l.derived.searchResults) {
return "", false
}
return l.derived.searchResults[i], true
}
func (l *SearchResults) Item(index int) (name string, ok bool) {
for i, n := range l.Iterate {
if i == index {
return n, true
}
}
return "", false
}
func (l *SearchResults) Selected() int {
return max(min(l.d.UnitSearchIndex, l.Count()-1), 0)
}
func (l *SearchResults) Selected2() int {
return max(min(l.d.UnitSearchIndex, l.Count()-1), 0)
}
func (l *SearchResults) SetSelected(value int) {
l.d.UnitSearchIndex = max(min(value, l.Count()-1), 0)
}
func (l *SearchResults) SetSelected2(value int) {
}
func (l *SearchResults) Count() (count int) {
for _, n := range sointu.UnitNames {
if strings.HasPrefix(n, l.d.UnitSearchString) {
count++
}
}
return
}
func (l *searchResults) Selected() int { return l.d.UnitSearchIndex }
func (l *searchResults) Selected2() int { return l.d.UnitSearchIndex }
func (l *searchResults) SetSelected(value int) { l.d.UnitSearchIndex = value }
func (l *searchResults) SetSelected2(value int) {}
func (l *searchResults) Count() (count int) { return len(l.derived.searchResults) }
func (r Range) Len() int { return r.End - r.Start }

View File

@ -216,6 +216,8 @@ func NewModel(broker *Broker, synthers []sointu.Synther, midiContext MIDIContext
m.updateDeriveData(SongChange)
m.presets.load()
m.updateDerivedPresetSearch()
m.derived.searchResults = make([]string, 0, len(sointu.UnitNames))
m.updateDerivedUnitSearch()
return m
}

View File

@ -44,12 +44,12 @@ func (s *modelFuzzState) Iterate(yield func(string, func(p string, t *testing.T)
s.IterateInt("Step", s.model.Step(), yield, seed)
s.IterateInt("Octave", s.model.Octave(), yield, seed)
// Lists
s.IterateList("Instruments", s.model.Instruments().List(), yield, seed)
s.IterateList("Units", s.model.Units().List(), yield, seed)
s.IterateList("Tracks", s.model.Tracks().List(), yield, seed)
s.IterateList("OrderRows", s.model.OrderRows().List(), yield, seed)
s.IterateList("NoteRows", s.model.NoteRows().List(), yield, seed)
s.IterateList("UnitSearchResults", s.model.SearchResults().List(), yield, seed)
s.IterateList("Instruments", s.model.Instruments(), yield, seed)
s.IterateList("Units", s.model.Units(), yield, seed)
s.IterateList("Tracks", s.model.Tracks(), yield, seed)
s.IterateList("OrderRows", s.model.OrderRows(), yield, seed)
s.IterateList("NoteRows", s.model.NoteRows(), yield, seed)
s.IterateList("UnitSearchResults", s.model.SearchResults(), yield, seed)
s.IterateList("PresetDirs", s.model.PresetDirList().List(), yield, seed)
s.IterateList("PresetResults", s.model.PresetResultList().List(), yield, seed)
// Bools

View File

@ -1,5 +1,11 @@
package tracker
import (
"strings"
"github.com/vsariola/sointu"
)
type (
String struct {
value StringValue
@ -67,8 +73,18 @@ func (v *UnitSearch) Value() string {
func (v *UnitSearch) SetValue(value string) bool {
v.d.UnitSearchString = value
v.d.UnitSearching = true
(*Model)(v).updateDerivedUnitSearch()
return true
}
func (v *Model) updateDerivedUnitSearch() {
// update search results based on current search string
v.derived.searchResults = v.derived.searchResults[:0]
for _, name := range sointu.UnitNames {
if strings.HasPrefix(name, v.UnitSearch().Value()) {
v.derived.searchResults = append(v.derived.searchResults, name)
}
}
}
// InstrumentNameString