package tracker import ( "errors" "fmt" "iter" "math" "math/bits" "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() 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 StackNeed, StackBefore, StackAfter int } // 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 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. The list must // implement the MutableListData interface. func (v List) MoveElements(delta int) bool { s, ok := v.ListData.(MutableListData) if !ok { return false } r := v.listRange() 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() 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) bool { d, ok := v.ListData.(MutableListData) if !ok { return false } r := v.listRange() if r.Len() == 0 { return false } defer d.change("DeleteElements", MajorChange)() if !d.delete(r) { d.cancel() return false } if backwards && r.Start > 0 { r.Start-- } v.SetSelected(r.Start) v.SetSelected2(r.Start) 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) { m, ok := v.ListData.(MutableListData) if !ok { return nil, false } r := v.listRange() if r.Len() == 0 { return nil, false } ret, err := m.marshal(r) 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)() r, err := m.unmarshal(data) if err != nil { m.cancel() return false } v.SetSelected(r.Start) v.SetSelected2(r.End - 1) return true } 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 func (v *Instruments) List() List { return List{v} } func (v *Instruments) Item(i int) (name string, maxLevel float32, mute bool, ok bool) { if i < 0 || i >= len(v.d.Song.Patch) { return "", 0, false, false } name = v.d.Song.Patch[i].Name mute = v.d.Song.Patch[i].Mute 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 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) 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) { voiceDelta := 0 if delta < 0 { voiceDelta = -VoiceRange(v.d.Song.Patch, Range{r.Start + delta, r.Start}).Len() } else if delta > 0 { voiceDelta = VoiceRange(v.d.Song.Patch, Range{r.End, r.End + delta}).Len() } if voiceDelta == 0 { return false } ranges := MakeMoveRanges(VoiceRange(v.d.Song.Patch, r), voiceDelta) return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...) } 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() { return (*Model)(v).change("Instruments."+n, SongChange, severity) } 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) { return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Patch, r)) } 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 { return Range{}, fmt.Errorf("unmarshal: unmarshalVoices failed") } return r, nil } // 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) { return } if m.d.UnitIndex < 0 { m.d.UnitIndex = 0 } for len(m.d.Song.Patch[m.d.InstrIndex].Units) <= m.d.UnitIndex { m.d.Song.Patch[m.d.InstrIndex].Units = append(m.d.Song.Patch[m.d.InstrIndex].Units, sointu.Unit{}) } 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 i, unit := range v.d.Song.Patch[v.d.InstrIndex].Units { stackAfter := stackBefore + unit.StackChange() if !yield(i, UnitListItem{ Type: unit.Type, Comment: unit.Comment, Disabled: unit.Disabled, StackNeed: unit.StackNeed(), StackBefore: stackBefore, StackAfter: stackAfter, }) { break } stackBefore = stackAfter } } 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) 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) { return 0 } return len(m.d.Song.Patch[(*Model)(v).d.InstrIndex].Units) } 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 } units := m.d.Song.Patch[m.d.InstrIndex].Units for i, j := range r.Swaps(delta) { units[i], units[j] = units[j], units[i] } return true } 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 } u := m.d.Song.Patch[m.d.InstrIndex].Units m.d.Song.Patch[m.d.InstrIndex].Units = append(u[:r.Start], u[r.End:]...) 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(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") } units := m.d.Song.Patch[m.d.InstrIndex].Units[r.Start:r.End] ret, err := yaml.Marshal(struct{ Units []sointu.Unit }{units}) if err != nil { return nil, fmt.Errorf("UnitListView.marshal: %v", err) } return ret, nil } 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") } var pastedUnits struct{ Units []sointu.Unit } if err := yaml.Unmarshal(data, &pastedUnits); err != nil { return Range{}, fmt.Errorf("UnitListView.unmarshal: %v", err) } if len(pastedUnits.Units) == 0 { return Range{}, errors.New("UnitListView.unmarshal: no units") } m.assignUnitIDs(pastedUnits.Units) sel := v.Selected() var ok bool m.d.Song.Patch[m.d.InstrIndex].Units, ok = Insert(m.d.Song.Patch[m.d.InstrIndex].Units, sel, pastedUnits.Units...) if !ok { return Range{}, errors.New("UnitListView.unmarshal: insert failed") } return Range{sel, sel + len(pastedUnits.Units)}, nil } // Tracks methods func (v *Tracks) List() List { return List{v} } func (v *Tracks) Selected() int { return max(min(v.d.Cursor.Track, v.Count()-1), 0) } 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) { voiceDelta := 0 if delta < 0 { voiceDelta = -VoiceRange(v.d.Song.Score.Tracks, Range{r.Start + delta, r.Start}).Len() } else if delta > 0 { voiceDelta = VoiceRange(v.d.Song.Score.Tracks, Range{r.End, r.End + delta}).Len() } if voiceDelta == 0 { return false } ranges := MakeMoveRanges(VoiceRange(v.d.Song.Score.Tracks, r), voiceDelta) return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...) } 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() { return (*Model)(v).change("TrackList."+n, SongChange, 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(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) { voiceIndex := m.d.Song.Score.FirstVoiceForTrack(m.d.Cursor.Track) _, r, ok := (*Model)(m).unmarshalVoices(voiceIndex, data, m.linkInstrTrack, true) if !ok { return Range{}, fmt.Errorf("unmarshal: unmarshalVoices failed") } return r, nil } // OrderRows methods func (v *OrderRows) List() List { return List{v} } 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 { v.follow = false } v.d.Cursor.OrderRow = y } 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) { swaps := r.Swaps(delta) for i, t := range v.d.Song.Score.Tracks { for a, b := range swaps { ea, eb := t.Order.Get(a), t.Order.Get(b) v.d.Song.Score.Tracks[i].Order.Set(a, eb) v.d.Song.Score.Tracks[i].Order.Set(b, ea) } } return true } 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:]...) } 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(r Range) ([]byte, error) { var table marshalOrderRows for i := range v.d.Song.Score.Tracks { table.Columns = append(table.Columns, make([]int, r.Len())) for j := 0; j < r.Len(); j++ { table.Columns[i][j] = v.d.Song.Score.Tracks[i].Order.Get(r.Start + j) } } return yaml.Marshal(table) } func (v *OrderRows) unmarshal(data []byte) (r Range, 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 } r.Start = v.d.Cursor.OrderRow r.End = v.d.Cursor.OrderRow + len(table.Columns[0]) 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 < r.Start-len(*order); j++ { *order = append(*order, -1) } if len(*order) > r.Start { table.Columns[i] = append(table.Columns[i], (*order)[r.Start:]...) *order = (*order)[:r.Start] } *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.follow = 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) 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) for _, t := range v.d.Song.Score.Tracks { n1 := t.Note(apos) n2 := t.Note(bpos) t.SetNote(apos, n2, v.uniquePatterns) t.SetNote(bpos, n1, v.uniquePatterns) } } return true } 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) track.SetNote(pos, 1, v.uniquePatterns) } } 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(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())) for j := 0; j < r.Len(); j++ { row := r.Start + 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) (r Range, err error) { var table marshalNoteRows if err := yaml.Unmarshal(data, &table); err != nil { return Range{}, fmt.Errorf("NoteRowList.unmarshal: %v", err) } if len(table.NoteRows) < 1 { return Range{}, errors.New("NoteRowList.unmarshal: no tracks") } r.Start = v.d.Song.Score.SongRow(v.d.Cursor.SongPos) for i, arr := range table.NoteRows { if i >= len(v.d.Song.Score.Tracks) { continue } r.End = r.Start + len(arr) for j, note := range arr { y := j + r.Start pos := v.d.Song.Score.SongPos(y) v.d.Song.Score.Tracks[i].SetNote(pos, note, v.uniquePatterns) } } return } // SearchResults 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 (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 (r Range) Len() int { return r.End - r.Start } func (r Range) Swaps(delta int) iter.Seq2[int, int] { if delta > 0 { return func(yield func(int, int) bool) { for i := r.End - 1; i >= r.Start; i-- { if !yield(i, i+delta) { return } } } } return func(yield func(int, int) bool) { for i := r.Start; i < r.End; i++ { if !yield(i, i+delta) { return } } } } func (r Range) Intersect(s Range) (ret Range) { ret.Start = max(r.Start, s.Start) ret.End = max(min(r.End, s.End), ret.Start) if ret.Len() == 0 { return Range{} } return } func MakeMoveRanges(a Range, delta int) [4]Range { if delta < 0 { return [4]Range{ {math.MinInt, a.Start + delta}, {a.Start, a.End}, {a.Start + delta, a.Start}, {a.End, math.MaxInt}, } } return [4]Range{ {math.MinInt, a.Start}, {a.End, a.End + delta}, {a.Start, a.End}, {a.End + delta, math.MaxInt}, } } // MakeSetLength takes a range and a length, and returns a slice of ranges that // can be used with VoiceSlice to expand or shrink the range to the given // length, by either duplicating or removing elements. The function tries to // duplicate elements so all elements are equally spaced, and tries to remove // elements from the middle of the range. func MakeSetLength(a Range, length int) []Range { if length <= 0 || a.Len() <= 0 { return []Range{{a.Start, a.Start}} } ret := make([]Range, a.Len(), max(a.Len(), length)+2) for i := 0; i < a.Len(); i++ { ret[i] = Range{a.Start + i, a.Start + i + 1} } for x := len(ret); x < length; x++ { e := (x << 1) ^ (1 << bits.Len((uint)(x))) ret = append(ret[0:e+1], ret[e:]...) } for x := len(ret); x > length; x-- { e := (((x << 1) ^ (1 << bits.Len((uint)(x)))) + x - 1) % x ret = append(ret[0:e], ret[e+1:]...) } ret = append([]Range{{math.MinInt, a.Start}}, ret...) ret = append(ret, Range{a.End, math.MaxInt}) return ret } func Complement(a Range) [2]Range { return [2]Range{ {math.MinInt, a.Start}, {a.End, math.MaxInt}, } } // Insert inserts elements into a slice at the given index. If the index is out // of bounds, the function returns false. func Insert[T any, S ~[]T](slice S, index int, inserted ...T) (ret S, ok bool) { if index < 0 || index > len(slice) { return nil, false } ret = make(S, 0, len(slice)+len(inserted)) ret = append(ret, slice[:index]...) ret = append(ret, inserted...) ret = append(ret, slice[index:]...) return ret, true } // VoiceSlice works similar to the Slice function, but takes a slice of // NumVoicer:s and treats it as a "virtual slice", with element repeated by the // number of voices it has. NumVoicer interface is implemented at least by // sointu.Tracks and sointu.Instruments. For example, if parameter "slice" has // three elements, returning GetNumVoices 2, 1, and 3, the VoiceSlice thinks of // this as a virtual slice of 6 elements [0,0,1,2,2,2]. Then, the "ranges" // parameter are slicing ranges to this virtual slice. Continuing with the // example, if "ranges" was [2,5), the virtual slice would be [1,2,2], and the // function would return a slice with two elements: first with NumVoices 1 and // second with NumVoices 2. If multiple ranges are given, multiple virtual // slices are concatenated. However, when doing so, splitting an element is not // allowed. In the previous example, if the ranges were [1,3) and [0,1), the // resulting concatenated virtual slice would be [0,1,0], and here the 0 element // would be split. This is to avoid accidentally making shallow copies of // reference types. func VoiceSlice[T any, S ~[]T, P sointu.NumVoicerPointer[T]](slice S, ranges ...Range) (ret S, ok bool) { ret = make(S, 0, len(slice)) last := -1 used := make([]bool, len(slice)) outer: for _, r := range ranges { left := 0 for i, elem := range slice { right := left + (P)(&slice[i]).GetNumVoices() if left >= r.End { continue outer } if right <= r.Start { left = right continue } overlap := min(right, r.End) - max(left, r.Start) if last == i { (P)(&ret[len(ret)-1]).SetNumVoices( (P)(&ret[len(ret)-1]).GetNumVoices() + overlap) } else { if last == math.MaxInt || used[i] { return nil, false } ret = append(ret, elem) (P)(&ret[len(ret)-1]).SetNumVoices(overlap) used[i] = true } last = i left = right } if left >= r.End { continue outer } last = math.MaxInt // the list is closed, adding more elements causes it to fail } return ret, true } // VoiceInsert tries adding the elements "added" to the slice "orig" at the // voice index "index". Notice that index is the index into a virtual slice // where each element is repeated by the number of voices it has. If the index // is between elements, the new elements are added in between the old elements. // If the addition would cause splitting of an element, we rather increase the // number of voices the element has, but do not split it. func VoiceInsert[T any, S ~[]T, P sointu.NumVoicerPointer[T]](orig S, index, length int, added ...T) (ret S, retRange Range, ok bool) { ret = make(S, 0, len(orig)+length) left := 0 for i, elem := range orig { right := left + (P)(&orig[i]).GetNumVoices() if left == index { // we are between elements and it's safe to add there if sointu.TotalVoices[T, S, P](added) < length { return nil, Range{}, false // we are missing some elements } retRange = Range{len(ret), len(ret) + len(added)} ret = append(ret, added...) } else if left < index && index < right { // we are inside an element and would split it; just increase its voices instead of splitting (P)(&elem).SetNumVoices((P)(&orig[i]).GetNumVoices() + sointu.TotalVoices[T, S, P](added)) retRange = Range{len(ret), len(ret)} } ret = append(ret, elem) left = right } if left == index { // we are at the end and it's safe to add there, even if we are missing some elements retRange = Range{len(ret), len(ret) + len(added)} ret = append(ret, added...) } return ret, retRange, true } func VoiceRange[T any, S ~[]T, P sointu.NumVoicerPointer[T]](slice S, indexRange Range) (voiceRange Range) { indexRange.Start = max(0, indexRange.Start) indexRange.End = min(len(slice), indexRange.End) for _, e := range slice[:indexRange.Start] { voiceRange.Start += (P)(&e).GetNumVoices() } voiceRange.End = voiceRange.Start for i := indexRange.Start; i < indexRange.End; i++ { voiceRange.End += (P)(&slice[i]).GetNumVoices() } return } // helpers func (m *Model) sliceInstrumentsTracks(instruments, tracks bool, ranges ...Range) (ok bool) { defer m.change("sliceInstrumentsTracks", PatchChange, MajorChange)() if instruments { m.d.Song.Patch, ok = VoiceSlice(m.d.Song.Patch, ranges...) if !ok { goto fail } } if tracks { m.d.Song.Score.Tracks, ok = VoiceSlice(m.d.Song.Score.Tracks, ranges...) if !ok { goto fail } } return true fail: (*Model)(m).Alerts().AddNamed("slicesInstrumentsTracks", "Modify prevented by Instrument-Track linking", Warning) m.changeCancel = true return false } func (m *Model) marshalVoices(r Range) (data []byte, err error) { patch, ok := VoiceSlice(m.d.Song.Patch, r) if !ok { return nil, fmt.Errorf("marshalVoiceRange: slicing patch failed") } tracks, ok := VoiceSlice(m.d.Song.Score.Tracks, r) if !ok { return nil, fmt.Errorf("marshalVoiceRange: slicing tracks failed") } return yaml.Marshal(struct { Patch sointu.Patch Tracks []sointu.Track }{patch, tracks}) } func (m *Model) unmarshalVoices(voiceIndex int, data []byte, instruments, tracks bool) (instrRange, trackRange Range, ok bool) { var d struct { Patch sointu.Patch Tracks []sointu.Track } if err := yaml.Unmarshal(data, &d); err != nil { return Range{}, Range{}, false } return m.addVoices(voiceIndex, d.Patch, d.Tracks, instruments, tracks) } func (m *Model) addVoices(voiceIndex int, p sointu.Patch, t []sointu.Track, instruments, tracks bool) (instrRange Range, trackRange Range, ok bool) { defer m.change("addVoices", PatchChange, MajorChange)() addedLength := max(p.NumVoices(), sointu.TotalVoices(t)) if instruments { m.assignUnitIDsForPatch(p) m.d.Song.Patch, instrRange, ok = VoiceInsert(m.d.Song.Patch, voiceIndex, addedLength, p...) if !ok { goto fail } } if tracks { m.d.Song.Score.Tracks, trackRange, ok = VoiceInsert(m.d.Song.Score.Tracks, voiceIndex, addedLength, t...) if !ok { goto fail } } return instrRange, trackRange, true fail: (*Model)(m).Alerts().AddNamed("addVoices", "Adding voices prevented by Instrument-Track linking", Warning) m.changeCancel = true return Range{}, Range{}, false } func (m *Model) remainingVoices(instruments, tracks bool) (ret int) { ret = math.MaxInt if instruments { ret = min(ret, vm.MAX_VOICES-m.d.Song.Patch.NumVoices()) } if tracks { ret = min(ret, vm.MAX_VOICES-m.d.Song.Score.NumVoices()) } return }