mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-27 19:00:25 -04:00
766 lines
20 KiB
Go
766 lines
20 KiB
Go
package tracker
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/vsariola/sointu"
|
|
"github.com/vsariola/sointu/vm"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
type (
|
|
List struct {
|
|
ListData
|
|
}
|
|
|
|
ListData interface {
|
|
Selected() int
|
|
Selected2() int
|
|
SetSelected(int)
|
|
SetSelected2(int)
|
|
Count() int
|
|
}
|
|
|
|
MutableListData interface {
|
|
change(kind string, severity ChangeSeverity) func()
|
|
cancel()
|
|
swap(i, j int) (ok bool)
|
|
delete(i int) (ok bool)
|
|
marshal(from, to int) ([]byte, error)
|
|
unmarshal([]byte) (from, to int, err error)
|
|
}
|
|
|
|
UnitListItem struct {
|
|
Type string
|
|
StackNeed, StackBefore, StackAfter int
|
|
}
|
|
|
|
UnitYieldFunc func(item UnitListItem) (ok bool)
|
|
UnitSearchYieldFunc func(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
|
|
Presets Model // Presets is a unmutable list of all the presets, implementing ListData interface
|
|
)
|
|
|
|
// Model methods
|
|
|
|
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) }
|
|
|
|
// MoveElements moves the selected elements in a list by delta. If delta is
|
|
// negative, the elements move up, otherwise down. The list must implement the
|
|
// MutableListData interface.
|
|
func (v List) MoveElements(delta int) (ok bool) {
|
|
if delta == 0 {
|
|
return false
|
|
}
|
|
s, ok := v.ListData.(MutableListData)
|
|
if !ok {
|
|
return
|
|
}
|
|
defer s.change("MoveElements", MajorChange)()
|
|
a, b := v.listRange()
|
|
if a+delta < 0 {
|
|
delta = -a
|
|
}
|
|
if b+delta >= v.Count() {
|
|
delta = v.Count() - 1 - b
|
|
}
|
|
if delta < 0 {
|
|
for i := a; i <= b; i++ {
|
|
if !s.swap(i, i+delta) {
|
|
s.cancel()
|
|
return false
|
|
}
|
|
}
|
|
} else {
|
|
for i := b; i >= a; i-- {
|
|
if !s.swap(i, i+delta) {
|
|
s.cancel()
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
v.SetSelected(v.Selected() + delta)
|
|
v.SetSelected2(v.Selected2() + delta)
|
|
return true
|
|
}
|
|
|
|
// DeleteElements deletes the selected elements in a list. The list must
|
|
// implement the MutableListData interface.
|
|
func (v List) DeleteElements(backwards bool) (ok bool) {
|
|
d, ok := v.ListData.(MutableListData)
|
|
if !ok {
|
|
return
|
|
}
|
|
defer d.change("DeleteElements", MajorChange)()
|
|
a, b := v.listRange()
|
|
for i := b; i >= a; i-- {
|
|
if !d.delete(i) {
|
|
d.cancel()
|
|
return false
|
|
}
|
|
}
|
|
if backwards && a > 0 {
|
|
a--
|
|
}
|
|
v.SetSelected(a)
|
|
v.SetSelected2(a)
|
|
return true
|
|
}
|
|
|
|
// CopyElements copies the selected elements in a list. The list must implement
|
|
// the MutableListData interface. Returns the copied data, marshaled into byte
|
|
// slice, and true if successful.
|
|
func (v List) CopyElements() ([]byte, bool) {
|
|
a, b := v.listRange()
|
|
m, ok := v.ListData.(MutableListData)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
ret, err := m.marshal(a, b)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
return ret, true
|
|
}
|
|
|
|
// PasteElements pastes the data into the list. The data is unmarshaled from the
|
|
// 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)
|
|
if !ok {
|
|
return false
|
|
}
|
|
defer m.change("PasteElements", MajorChange)()
|
|
from, to, err := m.unmarshal(data)
|
|
if err != nil {
|
|
m.cancel()
|
|
return false
|
|
}
|
|
v.SetSelected(from)
|
|
v.SetSelected2(to)
|
|
return true
|
|
}
|
|
|
|
func (v *List) listRange() (lower, higher int) {
|
|
lower = intMin(v.Selected(), v.Selected2())
|
|
higher = intMax(v.Selected(), v.Selected2())
|
|
return
|
|
}
|
|
|
|
// Instruments methods
|
|
|
|
func (v *Instruments) List() List {
|
|
return List{v}
|
|
}
|
|
|
|
func (v *Instruments) Item(i int) (name string, maxLevel float32, ok bool) {
|
|
if i < 0 || i >= len(v.d.Song.Patch) {
|
|
return "", 0, false
|
|
}
|
|
name = v.d.Song.Patch[i].Name
|
|
start := v.d.Song.Patch.FirstVoiceForInstrument(i)
|
|
end := start + v.d.Song.Patch[i].NumVoices
|
|
if end >= vm.MAX_VOICES {
|
|
end = vm.MAX_VOICES
|
|
}
|
|
if start < end {
|
|
for _, level := range v.voiceLevels[start:end] {
|
|
if maxLevel < level {
|
|
maxLevel = level
|
|
}
|
|
}
|
|
}
|
|
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 intMax(intMin(v.d.InstrIndex, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Instruments) Selected2() int {
|
|
return intMax(intMin(v.d.InstrIndex2, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Instruments) SetSelected(value int) {
|
|
v.d.InstrIndex = intMax(intMin(value, v.Count()-1), 0)
|
|
v.d.UnitIndex = 0
|
|
v.d.UnitIndex2 = 0
|
|
v.d.UnitSearching = false
|
|
v.d.UnitSearchString = ""
|
|
}
|
|
|
|
func (v *Instruments) SetSelected2(value int) {
|
|
v.d.InstrIndex2 = intMax(intMin(value, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Instruments) swap(i, j int) (ok bool) {
|
|
if i < 0 || j < 0 || i >= len(v.d.Song.Patch) || j >= len(v.d.Song.Patch) || i == j {
|
|
return false
|
|
}
|
|
instr := v.d.Song.Patch
|
|
instr[i], instr[j] = instr[j], instr[i]
|
|
return true
|
|
}
|
|
|
|
func (v *Instruments) delete(i int) (ok bool) {
|
|
if i < 0 || i >= len(v.d.Song.Patch) {
|
|
return false
|
|
}
|
|
v.d.Song.Patch = append(v.d.Song.Patch[:i], v.d.Song.Patch[i+1:]...)
|
|
return true
|
|
}
|
|
|
|
func (v *Instruments) change(n string, severity ChangeSeverity) func() {
|
|
return (*Model)(v).change("InstrumentListView."+n, PatchChange, severity)
|
|
}
|
|
|
|
func (v *Instruments) cancel() {
|
|
v.changeCancel = true
|
|
}
|
|
|
|
func (v *Instruments) Count() int {
|
|
return len(v.d.Song.Patch)
|
|
}
|
|
|
|
func (v *Instruments) marshal(from, to int) ([]byte, error) {
|
|
if from < 0 || to >= len(v.d.Song.Patch) || from > to {
|
|
return nil, fmt.Errorf("InstrumentListView.marshal: index out of range: %d, %d", from, to)
|
|
}
|
|
ret, err := yaml.Marshal(struct{ Patch sointu.Patch }{v.d.Song.Patch[from : to+1]})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("InstrumentListView.marshal: %v", err)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (v *Instruments) unmarshal(data []byte) (from, to int, err error) {
|
|
var newInstr struct{ Patch sointu.Patch }
|
|
if err := yaml.Unmarshal(data, &newInstr); err != nil {
|
|
return 0, 0, fmt.Errorf("InstrumentListView.unmarshal: %v", err)
|
|
}
|
|
if len(newInstr.Patch) == 0 {
|
|
return 0, 0, errors.New("InstrumentListView.unmarshal: no instruments")
|
|
}
|
|
if v.d.Song.Patch.NumVoices()+newInstr.Patch.NumVoices() > vm.MAX_VOICES {
|
|
return 0, 0, fmt.Errorf("InstrumentListView.unmarshal: too many voices: %d", v.d.Song.Patch.NumVoices()+newInstr.Patch.NumVoices())
|
|
}
|
|
patch := append(v.d.Song.Patch, make([]sointu.Instrument, len(newInstr.Patch))...)
|
|
sel := v.Selected()
|
|
copy(patch[sel+len(newInstr.Patch):], patch[sel:])
|
|
copy(patch[sel:sel+len(newInstr.Patch)], newInstr.Patch)
|
|
v.d.Song.Patch = patch
|
|
from = sel
|
|
to = sel + len(newInstr.Patch) - 1
|
|
return
|
|
}
|
|
|
|
// Units methods
|
|
|
|
func (v *Units) List() List {
|
|
return List{v}
|
|
}
|
|
|
|
func (m *Units) SelectedType() string {
|
|
if m.d.InstrIndex < 0 ||
|
|
m.d.InstrIndex >= len(m.d.Song.Patch) ||
|
|
m.d.UnitIndex < 0 ||
|
|
m.d.UnitIndex >= len(m.d.Song.Patch[m.d.InstrIndex].Units) {
|
|
return ""
|
|
}
|
|
return m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex].Type
|
|
}
|
|
|
|
func (m *Units) SetSelectedType(t string) {
|
|
if m.d.InstrIndex < 0 ||
|
|
m.d.InstrIndex >= len(m.d.Song.Patch) ||
|
|
m.d.UnitIndex < 0 ||
|
|
m.d.UnitIndex >= len(m.d.Song.Patch[m.d.InstrIndex].Units) {
|
|
return
|
|
}
|
|
unit, ok := defaultUnits[t]
|
|
if !ok { // if the type is invalid, we just set it to empty unit
|
|
unit = sointu.Unit{Parameters: make(map[string]int)}
|
|
} else {
|
|
unit = unit.Copy()
|
|
}
|
|
oldUnit := m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex]
|
|
if oldUnit.Type == unit.Type {
|
|
return
|
|
}
|
|
defer 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) Iterate(yield UnitYieldFunc) {
|
|
if v.d.InstrIndex < 0 || v.d.InstrIndex >= len(v.d.Song.Patch) {
|
|
return
|
|
}
|
|
stackBefore := 0
|
|
for _, unit := range v.d.Song.Patch[v.d.InstrIndex].Units {
|
|
stackAfter := stackBefore + unit.StackChange()
|
|
if !yield(UnitListItem{unit.Type, unit.StackNeed(), stackBefore, stackAfter}) {
|
|
break
|
|
}
|
|
stackBefore = stackAfter
|
|
}
|
|
}
|
|
|
|
func (v *Units) Selected() int {
|
|
return intMax(intMin(v.d.UnitIndex, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Units) Selected2() int {
|
|
return intMax(intMin(v.d.UnitIndex2, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Units) SetSelected(value int) {
|
|
m := (*Model)(v)
|
|
m.d.UnitIndex = intMax(intMin(value, v.Count()-1), 0)
|
|
m.d.ParamIndex = 0
|
|
m.d.UnitSearching = false
|
|
m.d.UnitSearchString = ""
|
|
}
|
|
|
|
func (v *Units) SetSelected2(value int) {
|
|
(*Model)(v).d.UnitIndex2 = intMax(intMin(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) {
|
|
return 0
|
|
}
|
|
return len(m.d.Song.Patch[(*Model)(v).d.InstrIndex].Units)
|
|
}
|
|
|
|
func (v *Units) swap(i, j int) (ok bool) {
|
|
m := (*Model)(v)
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return false
|
|
}
|
|
units := m.d.Song.Patch[m.d.InstrIndex].Units
|
|
if i < 0 || j < 0 || i >= len(units) || j >= len(units) || i == j {
|
|
return false
|
|
}
|
|
units[i], units[j] = units[j], units[i]
|
|
return true
|
|
}
|
|
|
|
func (v *Units) delete(i int) (ok bool) {
|
|
m := (*Model)(v)
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return false
|
|
}
|
|
units := m.d.Song.Patch[m.d.InstrIndex].Units
|
|
if i < 0 || i >= len(units) {
|
|
return false
|
|
}
|
|
units = append(units[:i], units[i+1:]...)
|
|
m.d.Song.Patch[m.d.InstrIndex].Units = units
|
|
return true
|
|
}
|
|
|
|
func (v *Units) change(n string, severity ChangeSeverity) func() {
|
|
return (*Model)(v).change("UnitListView."+n, PatchChange, severity)
|
|
}
|
|
|
|
func (v *Units) cancel() {
|
|
(*Model)(v).changeCancel = true
|
|
}
|
|
|
|
func (v *Units) marshal(from, to int) ([]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")
|
|
}
|
|
if from < 0 || to >= len(m.d.Song.Patch[m.d.InstrIndex].Units) || from > to {
|
|
return nil, fmt.Errorf("UnitListView.marshal: index out of range: %d, %d", from, to)
|
|
}
|
|
ret, err := yaml.Marshal(struct{ Units []sointu.Unit }{m.d.Song.Patch[m.d.InstrIndex].Units[from : to+1]})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("UnitListView.marshal: %v", err)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (v *Units) unmarshal(data []byte) (from, to int, err error) {
|
|
m := (*Model)(v)
|
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
|
return 0, 0, errors.New("UnitListView.unmarshal: no instruments")
|
|
}
|
|
var pastedUnits struct{ Units []sointu.Unit }
|
|
if err := yaml.Unmarshal(data, &pastedUnits); err != nil {
|
|
return 0, 0, fmt.Errorf("UnitListView.unmarshal: %v", err)
|
|
}
|
|
if len(pastedUnits.Units) == 0 {
|
|
return 0, 0, errors.New("UnitListView.unmarshal: no units")
|
|
}
|
|
m.assignUnitIDs(pastedUnits.Units)
|
|
sel := v.Selected()
|
|
units := append(m.d.Song.Patch[m.d.InstrIndex].Units, make([]sointu.Unit, len(pastedUnits.Units))...)
|
|
copy(units[sel+len(pastedUnits.Units):], units[sel:])
|
|
copy(units[sel:], pastedUnits.Units)
|
|
m.d.Song.Patch[m.d.InstrIndex].Units = units
|
|
from = sel
|
|
to = sel + len(pastedUnits.Units) - 1
|
|
return
|
|
}
|
|
|
|
// Tracks methods
|
|
|
|
func (v *Tracks) List() List {
|
|
return List{v}
|
|
}
|
|
|
|
func (v *Tracks) Selected() int {
|
|
return intMax(intMin(v.d.Cursor.Track, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Tracks) Selected2() int {
|
|
return intMax(intMin(v.d.Cursor2.Track, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Tracks) SetSelected(value int) {
|
|
v.d.Cursor.Track = intMax(intMin(value, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Tracks) SetSelected2(value int) {
|
|
v.d.Cursor2.Track = intMax(intMin(value, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *Tracks) swap(i, j int) (ok bool) {
|
|
m := (*Model)(v)
|
|
if i < 0 || j < 0 || i >= len(m.d.Song.Score.Tracks) || j >= len(m.d.Song.Score.Tracks) || i == j {
|
|
return false
|
|
}
|
|
tracks := m.d.Song.Score.Tracks
|
|
tracks[i], tracks[j] = tracks[j], tracks[i]
|
|
return true
|
|
}
|
|
|
|
func (v *Tracks) delete(i int) (ok bool) {
|
|
m := (*Model)(v)
|
|
if i < 0 || i >= len(m.d.Song.Score.Tracks) {
|
|
return false
|
|
}
|
|
m.d.Song.Score.Tracks = append(m.d.Song.Score.Tracks[:i], m.d.Song.Score.Tracks[i+1:]...)
|
|
return true
|
|
}
|
|
|
|
func (v *Tracks) change(n string, severity ChangeSeverity) func() {
|
|
return (*Model)(v).change("TrackList."+n, ScoreChange, severity)
|
|
}
|
|
|
|
func (v *Tracks) cancel() {
|
|
v.changeCancel = true
|
|
}
|
|
|
|
func (v *Tracks) Count() int {
|
|
return len((*Model)(v).d.Song.Score.Tracks)
|
|
}
|
|
|
|
func (v *Tracks) marshal(from, to int) ([]byte, error) {
|
|
m := (*Model)(v)
|
|
if from < 0 || to >= len(m.d.Song.Score.Tracks) || from > to {
|
|
return nil, fmt.Errorf("TrackListView.marshal: index out of range: %d, %d", from, to)
|
|
}
|
|
ret, err := yaml.Marshal(struct{ Score sointu.Score }{sointu.Score{Tracks: m.d.Song.Score.Tracks[from : to+1]}})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("TrackListView.marshal: %v", err)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (v *Tracks) unmarshal(data []byte) (from, to int, err error) {
|
|
m := (*Model)(v)
|
|
var newTracks struct{ Score sointu.Score }
|
|
if err := yaml.Unmarshal(data, &newTracks); err != nil {
|
|
return 0, 0, fmt.Errorf("TrackListView.unmarshal: %v", err)
|
|
}
|
|
if len(newTracks.Score.Tracks) == 0 {
|
|
return 0, 0, errors.New("TrackListView.unmarshal: no tracks")
|
|
}
|
|
if v.d.Song.Score.NumVoices()+newTracks.Score.NumVoices() > vm.MAX_VOICES {
|
|
return 0, 0, fmt.Errorf("InstrumentListView.unmarshal: too many voices: %d", v.d.Song.Patch.NumVoices()+newTracks.Score.NumVoices())
|
|
}
|
|
from = m.d.Cursor.Track
|
|
to = m.d.Cursor.Track + len(newTracks.Score.Tracks) - 1
|
|
tracks := m.d.Song.Score.Tracks
|
|
newTracks.Score.Tracks = append(newTracks.Score.Tracks, tracks[m.d.Cursor.Track:]...)
|
|
tracks = append(tracks[:m.d.Cursor.Track], newTracks.Score.Tracks...)
|
|
m.d.Song.Score.Tracks = tracks
|
|
return
|
|
}
|
|
|
|
// OrderRows methods
|
|
|
|
func (v *OrderRows) List() List {
|
|
return List{v}
|
|
}
|
|
|
|
func (v *OrderRows) Selected() int {
|
|
p := v.d.Cursor.OrderRow
|
|
p = intMax(intMin(p, v.Count()-1), 0)
|
|
return p
|
|
}
|
|
|
|
func (v *OrderRows) Selected2() int {
|
|
p := v.d.Cursor2.OrderRow
|
|
p = intMax(intMin(p, v.Count()-1), 0)
|
|
return p
|
|
}
|
|
|
|
func (v *OrderRows) SetSelected(value int) {
|
|
y := intMax(intMin(value, v.Count()-1), 0)
|
|
if y != v.d.Cursor.OrderRow {
|
|
v.noteTracking = false
|
|
}
|
|
v.d.Cursor.OrderRow = y
|
|
}
|
|
|
|
func (v *OrderRows) SetSelected2(value int) {
|
|
v.d.Cursor2.OrderRow = intMax(intMin(value, v.Count()-1), 0)
|
|
}
|
|
|
|
func (v *OrderRows) swap(x, y int) (ok bool) {
|
|
for i := range v.d.Song.Score.Tracks {
|
|
track := &v.d.Song.Score.Tracks[i]
|
|
a, b := track.Order.Get(x), track.Order.Get(y)
|
|
track.Order.Set(x, b)
|
|
track.Order.Set(y, a)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (v *OrderRows) delete(i int) (ok bool) {
|
|
for _, track := range v.d.Song.Score.Tracks {
|
|
if i < len(track.Order) {
|
|
track.Order = append(track.Order[:i], track.Order[i+1:]...)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (v *OrderRows) change(n string, severity ChangeSeverity) func() {
|
|
return (*Model)(v).change("OrderRowList."+n, ScoreChange, severity)
|
|
}
|
|
|
|
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(from, to int) ([]byte, error) {
|
|
var table marshalOrderRows
|
|
for i := range v.d.Song.Score.Tracks {
|
|
table.Columns = append(table.Columns, make([]int, to-from+1))
|
|
for j := 0; j < to-from+1; j++ {
|
|
table.Columns[i][j] = v.d.Song.Score.Tracks[i].Order.Get(from + j)
|
|
}
|
|
}
|
|
return yaml.Marshal(table)
|
|
}
|
|
|
|
func (v *OrderRows) unmarshal(data []byte) (from, to int, err error) {
|
|
var table marshalOrderRows
|
|
err = yaml.Unmarshal(data, &table)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(table.Columns) == 0 {
|
|
err = errors.New("OrderRowList.unmarshal: no rows")
|
|
return
|
|
}
|
|
from = v.d.Cursor.OrderRow
|
|
to = v.d.Cursor.OrderRow + len(table.Columns[0]) - 1
|
|
for i := range v.d.Song.Score.Tracks {
|
|
if i >= len(table.Columns) {
|
|
break
|
|
}
|
|
order := &v.d.Song.Score.Tracks[i].Order
|
|
for j := 0; j < from-len(*order); j++ {
|
|
*order = append(*order, -1)
|
|
}
|
|
if len(*order) > from {
|
|
table.Columns[i] = append(table.Columns[i], (*order)[from:]...)
|
|
*order = (*order)[:from]
|
|
}
|
|
*order = append(*order, table.Columns[i]...)
|
|
}
|
|
return
|
|
}
|
|
|
|
// NoteRows methods
|
|
|
|
func (v *NoteRows) List() List {
|
|
return List{v}
|
|
}
|
|
|
|
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.noteTracking = false
|
|
}
|
|
v.d.Cursor.SongPos = v.d.Song.Score.Clamp(v.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) swap(i, j int) (ok bool) {
|
|
ipos := v.d.Song.Score.SongPos(i)
|
|
jpos := v.d.Song.Score.SongPos(j)
|
|
for _, track := range v.d.Song.Score.Tracks {
|
|
n1 := track.Note(ipos)
|
|
n2 := track.Note(jpos)
|
|
track.SetNote(ipos, n2)
|
|
track.SetNote(jpos, n1)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (v *NoteRows) delete(i int) (ok bool) {
|
|
if i < 0 || i >= v.Count() {
|
|
return
|
|
}
|
|
pos := v.d.Song.Score.SongPos(i)
|
|
for _, track := range v.d.Song.Score.Tracks {
|
|
track.SetNote(pos, 1)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (v *NoteRows) change(n string, severity ChangeSeverity) func() {
|
|
return (*Model)(v).change("NoteRowList."+n, ScoreChange, severity)
|
|
}
|
|
|
|
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(from, to int) ([]byte, error) {
|
|
var table marshalNoteRows
|
|
for i, track := range v.d.Song.Score.Tracks {
|
|
table.NoteRows = append(table.NoteRows, make([]byte, to-from+1))
|
|
for j := 0; j < to-from+1; j++ {
|
|
row := from + j
|
|
pos := v.d.Song.Score.SongPos(row)
|
|
table.NoteRows[i][j] = track.Note(pos)
|
|
}
|
|
}
|
|
return yaml.Marshal(table)
|
|
}
|
|
|
|
func (v *NoteRows) unmarshal(data []byte) (from, to int, err error) {
|
|
var table marshalNoteRows
|
|
if err := yaml.Unmarshal(data, &table); err != nil {
|
|
return 0, 0, fmt.Errorf("NoteRowList.unmarshal: %v", err)
|
|
}
|
|
if len(table.NoteRows) < 1 {
|
|
return 0, 0, errors.New("NoteRowList.unmarshal: no tracks")
|
|
}
|
|
from = v.d.Song.Score.SongRow(v.d.Cursor.SongPos)
|
|
for i, arr := range table.NoteRows {
|
|
if i >= len(v.d.Song.Score.Tracks) {
|
|
continue
|
|
}
|
|
to = from + len(arr) - 1
|
|
for j, note := range arr {
|
|
y := j + from
|
|
pos := v.d.Song.Score.SongPos(y)
|
|
v.d.Song.Score.Tracks[i].SetNote(pos, note)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// SearchResults
|
|
|
|
func (v *SearchResults) List() List {
|
|
return List{v}
|
|
}
|
|
|
|
func (l *SearchResults) Iterate(yield UnitSearchYieldFunc) {
|
|
for _, name := range sointu.UnitNames {
|
|
if !strings.HasPrefix(name, l.d.UnitSearchString) {
|
|
continue
|
|
}
|
|
if !yield(name) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *SearchResults) Selected() int {
|
|
return intMax(intMin(l.d.UnitSearchIndex, l.Count()-1), 0)
|
|
}
|
|
|
|
func (l *SearchResults) Selected2() int {
|
|
return intMax(intMin(l.d.UnitSearchIndex, l.Count()-1), 0)
|
|
}
|
|
|
|
func (l *SearchResults) SetSelected(value int) {
|
|
l.d.UnitSearchIndex = intMax(intMin(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
|
|
}
|