feat: keeping instruments and tracks linked & splitting them

Also includes a refactoring of the List: all methods that accepted
or returned a [from, to] range now return a Range, which is
non-inclusive i.e. [start,end).

Also the assignUnitIds was slightly refactored & a new function
called assignUnitIdsForPatch was added, to assign all unit IDs for
an patch at once.

Closes #157, #163.
This commit is contained in:
5684185+vsariola@users.noreply.github.com
2024-10-20 11:30:52 +03:00
parent 025f8832d9
commit 216cde2365
14 changed files with 675 additions and 284 deletions

View File

@ -3,6 +3,9 @@ package tracker
import (
"errors"
"fmt"
"iter"
"math"
"math/bits"
"strings"
"github.com/vsariola/sointu"
@ -26,10 +29,10 @@ type (
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)
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 {
@ -38,6 +41,11 @@ type (
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)
@ -59,39 +67,21 @@ 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
}
// 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
return false
}
r := v.listRange()
if delta == 0 || r.Start+delta < 0 || r.End+delta > v.Count() {
return false
}
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
}
}
if !s.move(r, delta) {
s.cancel()
return false
}
v.SetSelected(v.Selected() + delta)
v.SetSelected2(v.Selected2() + delta)
@ -100,24 +90,25 @@ func (v List) MoveElements(delta int) (ok bool) {
// DeleteElements deletes the selected elements in a list. The list must
// implement the MutableListData interface.
func (v List) DeleteElements(backwards bool) (ok bool) {
func (v List) DeleteElements(backwards bool) bool {
d, ok := v.ListData.(MutableListData)
if !ok {
return
return false
}
r := v.listRange()
if r.Len() == 0 {
return false
}
defer d.change("DeleteElements", MajorChange)()
a, b := v.listRange()
for i := b; i >= a; i-- {
if !d.delete(i) {
d.cancel()
return false
}
if !d.delete(r) {
d.cancel()
return false
}
if backwards && a > 0 {
a--
if backwards && r.Start > 0 {
r.Start--
}
v.SetSelected(a)
v.SetSelected2(a)
v.SetSelected(r.Start)
v.SetSelected2(r.Start)
return true
}
@ -125,12 +116,15 @@ func (v List) DeleteElements(backwards bool) (ok bool) {
// 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)
r := v.listRange()
if r.Len() == 0 {
return nil, false
}
ret, err := m.marshal(r)
if err != nil {
return nil, false
}
@ -146,19 +140,19 @@ func (v List) PasteElements(data []byte) (ok bool) {
return false
}
defer m.change("PasteElements", MajorChange)()
from, to, err := m.unmarshal(data)
r, err := m.unmarshal(data)
if err != nil {
m.cancel()
return false
}
v.SetSelected(from)
v.SetSelected2(to)
v.SetSelected(r.Start)
v.SetSelected2(r.End - 1)
return true
}
func (v *List) listRange() (lower, higher int) {
lower = min(v.Selected(), v.Selected2())
higher = max(v.Selected(), v.Selected2())
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
}
@ -219,25 +213,27 @@ func (v *Instruments) SetSelected2(value int) {
v.d.InstrIndex2 = max(min(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 {
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
}
instr := v.d.Song.Patch
instr[i], instr[j] = instr[j], instr[i]
return true
ranges := MakeMoveRanges(VoiceRange(v.d.Song.Patch, r), voiceDelta)
return (*Model)(v).sliceInstrumentsTracks(true, v.linkInstrTrack, ranges[:]...)
}
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) 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("InstrumentListView."+n, PatchChange, severity)
return (*Model)(v).change("Instruments."+n, SongChange, severity)
}
func (v *Instruments) cancel() {
@ -248,38 +244,17 @@ 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) marshal(r Range) ([]byte, error) {
return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Patch, r))
}
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)
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")
}
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())
}
v.d.Song.Patch = append(v.d.Song.Patch, make([]sointu.Instrument, len(newInstr.Patch))...)
sel := v.Selected()
copy(v.d.Song.Patch[sel+len(newInstr.Patch):], v.d.Song.Patch[sel:])
for i := 0; i < len(newInstr.Patch); i++ {
(*Model)(v).assignUnitIDs(newInstr.Patch[i].Units)
v.d.Song.Patch[sel+i] = newInstr.Patch[i]
}
from = sel
to = sel + len(newInstr.Patch) - 1
return
return r, nil
}
// Units methods
@ -373,30 +348,25 @@ func (v *Units) Count() int {
return len(m.d.Song.Patch[(*Model)(v).d.InstrIndex].Units)
}
func (v *Units) swap(i, j 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
}
units := m.d.Song.Patch[m.d.InstrIndex].Units
if i < 0 || j < 0 || i >= len(units) || j >= len(units) || i == j {
return false
for i, j := range r.Swaps(delta) {
units[i], units[j] = units[j], units[i]
}
units[i], units[j] = units[j], units[i]
return true
}
func (v *Units) delete(i int) (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
}
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
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
}
@ -408,42 +378,39 @@ func (v *Units) cancel() {
(*Model)(v).changeCancel = true
}
func (v *Units) marshal(from, to int) ([]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")
}
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]})
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) (from, to int, 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 0, 0, errors.New("UnitListView.unmarshal: no instruments")
return Range{}, 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)
return Range{}, fmt.Errorf("UnitListView.unmarshal: %v", err)
}
if len(pastedUnits.Units) == 0 {
return 0, 0, errors.New("UnitListView.unmarshal: no units")
return Range{}, 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
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
@ -468,27 +435,27 @@ func (v *Tracks) SetSelected2(value int) {
v.d.Cursor2.Track = max(min(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 {
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
}
tracks := m.d.Song.Score.Tracks
tracks[i], tracks[j] = tracks[j], tracks[i]
return true
ranges := MakeMoveRanges(VoiceRange(v.d.Song.Score.Tracks, r), voiceDelta)
return (*Model)(v).sliceInstrumentsTracks(v.linkInstrTrack, true, ranges[:]...)
}
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) 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, ScoreChange, severity)
return (*Model)(v).change("TrackList."+n, SongChange, severity)
}
func (v *Tracks) cancel() {
@ -499,37 +466,17 @@ 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) marshal(r Range) ([]byte, error) {
return (*Model)(v).marshalVoices(VoiceRange(v.d.Song.Score.Tracks, r))
}
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)
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")
}
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
return r, nil
}
// OrderRows methods
@ -562,21 +509,22 @@ func (v *OrderRows) SetSelected2(value int) {
v.d.Cursor2.OrderRow = max(min(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)
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(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:]...)
}
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
}
@ -597,18 +545,18 @@ type marshalOrderRows struct {
Columns [][]int `yaml:",flow"`
}
func (v *OrderRows) marshal(from, to int) ([]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, 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)
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) (from, to int, err error) {
func (v *OrderRows) unmarshal(data []byte) (r Range, err error) {
var table marshalOrderRows
err = yaml.Unmarshal(data, &table)
if err != nil {
@ -618,19 +566,19 @@ func (v *OrderRows) unmarshal(data []byte) (from, to int, err error) {
err = errors.New("OrderRowList.unmarshal: no rows")
return
}
from = v.d.Cursor.OrderRow
to = v.d.Cursor.OrderRow + len(table.Columns[0]) - 1
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 < from-len(*order); j++ {
for j := 0; j < r.Start-len(*order); j++ {
*order = append(*order, -1)
}
if len(*order) > from {
table.Columns[i] = append(table.Columns[i], (*order)[from:]...)
*order = (*order)[:from]
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]...)
}
@ -663,25 +611,26 @@ func (v *NoteRows) SetSelected2(value int) {
}
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, v.uniquePatterns)
track.SetNote(jpos, n1, v.uniquePatterns)
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(i int) (ok bool) {
if i < 0 || i >= v.Count() {
return
}
pos := v.d.Song.Score.SongPos(i)
func (v *NoteRows) delete(r Range) (ok bool) {
for _, track := range v.d.Song.Score.Tracks {
track.SetNote(pos, 1, v.uniquePatterns)
for i := r.Start; i < r.End; i++ {
pos := v.d.Song.Score.SongPos(i)
track.SetNote(pos, 1, v.uniquePatterns)
}
}
return true
}
@ -702,12 +651,12 @@ type marshalNoteRows struct {
NoteRows [][]byte `yaml:",flow"`
}
func (v *NoteRows) marshal(from, to int) ([]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, to-from+1))
for j := 0; j < to-from+1; j++ {
row := from + j
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)
}
@ -715,22 +664,22 @@ func (v *NoteRows) marshal(from, to int) ([]byte, error) {
return yaml.Marshal(table)
}
func (v *NoteRows) unmarshal(data []byte) (from, to int, err error) {
func (v *NoteRows) unmarshal(data []byte) (r Range, err error) {
var table marshalNoteRows
if err := yaml.Unmarshal(data, &table); err != nil {
return 0, 0, fmt.Errorf("NoteRowList.unmarshal: %v", err)
return Range{}, fmt.Errorf("NoteRowList.unmarshal: %v", err)
}
if len(table.NoteRows) < 1 {
return 0, 0, errors.New("NoteRowList.unmarshal: no tracks")
return Range{}, errors.New("NoteRowList.unmarshal: no tracks")
}
from = v.d.Song.Score.SongRow(v.d.Cursor.SongPos)
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
}
to = from + len(arr) - 1
r.End = r.Start + len(arr)
for j, note := range arr {
y := j + from
y := j + r.Start
pos := v.d.Song.Score.SongPos(y)
v.d.Song.Score.Tracks[i].SetNote(pos, note, v.uniquePatterns)
}
@ -780,3 +729,274 @@ func (l *SearchResults) Count() (count int) {
}
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 {
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
}