package tracker import ( "errors" "github.com/vsariola/sointu" "gopkg.in/yaml.v3" ) // Order returns the Order view of the model, containing methods to manipulate // the pattern order list. func (m *Model) Order() *OrderModel { return (*OrderModel)(m) } type OrderModel Model // PatternUnique returns true if the given pattern in the given track is used // only once in the pattern order list. func (m *OrderModel) PatternUnique(track, pat int) bool { if track < 0 || track >= len(m.derived.tracks) { return false } if pat < 0 || pat >= len(m.derived.tracks[track].patternUseCounts) { return false } return m.derived.tracks[track].patternUseCounts[pat] <= 1 } // AddRow returns an Action that adds an order row before or after the current // cursor row. func (m *OrderModel) AddRow(before bool) Action { return MakeAction(addOrderRow{Before: before, Model: (*Model)(m)}) } type addOrderRow struct { Before bool *Model } func (a addOrderRow) Do() { m := a.Model defer m.change("AddOrderRowAction", ScoreChange, MinorChange)() if !a.Before { m.d.Cursor.OrderRow++ } m.d.Cursor2.OrderRow = m.d.Cursor.OrderRow from := m.d.Cursor.OrderRow m.d.Song.Score.Length++ for i := range m.d.Song.Score.Tracks { order := &m.d.Song.Score.Tracks[i].Order if len(*order) > from { *order = append(*order, -1) copy((*order)[from+1:], (*order)[from:]) (*order)[from] = -1 } } } // DeleteRow returns an Action to delete the current row of in the pattern order // list. func (m *OrderModel) DeleteRow(backwards bool) Action { return MakeAction(deleteOrderRow{Backwards: backwards, Model: (*Model)(m)}) } type deleteOrderRow struct { Backwards bool *Model } func (d deleteOrderRow) Do() { m := d.Model defer m.change("DeleteOrderRowAction", ScoreChange, MinorChange)() from := m.d.Cursor.OrderRow m.d.Song.Score.Length-- for i := range m.d.Song.Score.Tracks { order := &m.d.Song.Score.Tracks[i].Order if len(*order) > from { copy((*order)[from:], (*order)[from+1:]) *order = (*order)[:len(*order)-1] } } if d.Backwards { if m.d.Cursor.OrderRow > 0 { m.d.Cursor.OrderRow-- } } m.d.Cursor2.OrderRow = m.d.Cursor.OrderRow } // Table returns a Table of all the pattern order data. func (v *OrderModel) Table() Table { return Table{v} } func (m *OrderModel) Cursor() Point { t := max(min(m.d.Cursor.Track, len(m.d.Song.Score.Tracks)-1), 0) p := max(min(m.d.Cursor.OrderRow, m.d.Song.Score.Length-1), 0) return Point{t, p} } func (m *OrderModel) Cursor2() Point { t := max(min(m.d.Cursor2.Track, len(m.d.Song.Score.Tracks)-1), 0) p := max(min(m.d.Cursor2.OrderRow, m.d.Song.Score.Length-1), 0) return Point{t, p} } func (m *OrderModel) SetCursor(p Point) { m.d.Cursor.Track = max(min(p.X, len(m.d.Song.Score.Tracks)-1), 0) y := max(min(p.Y, m.d.Song.Score.Length-1), 0) if y != m.d.Cursor.OrderRow { m.follow = false } m.d.Cursor.OrderRow = y m.updateCursorRows() } func (m *OrderModel) SetCursor2(p Point) { m.d.Cursor2.Track = max(min(p.X, len(m.d.Song.Score.Tracks)-1), 0) m.d.Cursor2.OrderRow = max(min(p.Y, m.d.Song.Score.Length-1), 0) m.updateCursorRows() } func (v *OrderModel) updateCursorRows() { if v.Cursor() == v.Cursor2() { v.d.Cursor.PatternRow = 0 v.d.Cursor2.PatternRow = 0 return } if v.d.Cursor.OrderRow > v.d.Cursor2.OrderRow { v.d.Cursor.PatternRow = v.d.Song.Score.RowsPerPattern - 1 v.d.Cursor2.PatternRow = 0 } else { v.d.Cursor.PatternRow = 0 v.d.Cursor2.PatternRow = v.d.Song.Score.RowsPerPattern - 1 } } func (v *OrderModel) Width() int { return len((*Model)(v).d.Song.Score.Tracks) } func (v *OrderModel) Height() int { return (*Model)(v).d.Song.Score.Length } func (v *OrderModel) MoveCursor(dx, dy int) (ok bool) { p := v.Cursor() p.X += dx p.Y += dy v.SetCursor(p) return p == v.Cursor() } func (m *OrderModel) clear(p Point) { m.d.Song.Score.Tracks[p.X].Order.Set(p.Y, -1) } func (m *OrderModel) set(p Point, value int) { m.d.Song.Score.Tracks[p.X].Order.Set(p.Y, value) } func (v *OrderModel) add(rect Rect, delta int, largeStep bool) (ok bool) { if largeStep { delta *= 8 } for x := rect.TopLeft.X; x <= rect.BottomRight.X; x++ { for y := rect.TopLeft.Y; y <= rect.BottomRight.Y; y++ { if !v.add1(Point{x, y}, delta) { return false } } } return true } func (v *OrderModel) add1(p Point, delta int) (ok bool) { if p.X < 0 || p.X >= len(v.d.Song.Score.Tracks) { return true } val := v.d.Song.Score.Tracks[p.X].Order.Get(p.Y) if val < 0 { return true } val += delta if val < 0 || val > 36 { return false } v.d.Song.Score.Tracks[p.X].Order.Set(p.Y, val) return true } type marshalOrder struct { Order []int `yaml:",flow"` } type marshalTracks struct { Tracks []marshalOrder } func (m *OrderModel) marshal(rect Rect) (data []byte, ok bool) { width := rect.BottomRight.X - rect.TopLeft.X + 1 height := rect.BottomRight.Y - rect.TopLeft.Y + 1 var table = marshalTracks{Tracks: make([]marshalOrder, 0, width)} for x := 0; x < width; x++ { ax := x + rect.TopLeft.X if ax < 0 || ax >= len(m.d.Song.Score.Tracks) { continue } table.Tracks = append(table.Tracks, marshalOrder{Order: make([]int, 0, rect.BottomRight.Y-rect.TopLeft.Y+1)}) for y := 0; y < height; y++ { table.Tracks[x].Order = append(table.Tracks[x].Order, m.d.Song.Score.Tracks[ax].Order.Get(y+rect.TopLeft.Y)) } } ret, err := yaml.Marshal(table) if err != nil { return nil, false } return ret, true } func (m *OrderModel) unmarshal(data []byte) (marshalTracks, bool) { var table marshalTracks yaml.Unmarshal(data, &table) if len(table.Tracks) == 0 { return marshalTracks{}, false } for i := 0; i < len(table.Tracks); i++ { if len(table.Tracks[i].Order) > 0 { return table, true } } return marshalTracks{}, false } func (v *OrderModel) unmarshalAtCursor(data []byte) bool { table, ok := v.unmarshal(data) if !ok { return false } for i := 0; i < len(table.Tracks); i++ { for j, q := range table.Tracks[i].Order { if table.Tracks[i].Order[j] < -1 || table.Tracks[i].Order[j] > 36 { continue } x := i + v.Cursor().X y := j + v.Cursor().Y if x < 0 || x >= len(v.d.Song.Score.Tracks) || y < 0 || y >= v.d.Song.Score.Length { continue } v.d.Song.Score.Tracks[x].Order.Set(y, q) } } return true } func (v *OrderModel) unmarshalRange(rect Rect, data []byte) bool { table, ok := v.unmarshal(data) if !ok { return false } for i := 0; i < rect.Width(); i++ { for j := 0; j < rect.Height(); j++ { k := i % len(table.Tracks) l := j % len(table.Tracks[k].Order) a := table.Tracks[k].Order[l] if a < -1 || a > 36 { continue } x := i + rect.TopLeft.X y := j + rect.TopLeft.Y if x < 0 || x >= len(v.d.Song.Score.Tracks) || y < 0 || y >= v.d.Song.Score.Length { continue } v.d.Song.Score.Tracks[x].Order.Set(y, a) } } return true } func (v *OrderModel) change(kind string, severity ChangeSeverity) func() { return (*Model)(v).change("OrderTableView."+kind, ScoreChange, severity) } func (v *OrderModel) cancel() { v.changeCancel = true } func (m *OrderModel) Value(p Point) int { if p.X < 0 || p.X >= len(m.d.Song.Score.Tracks) { return -1 } return m.d.Song.Score.Tracks[p.X].Order.Get(p.Y) } func (m *OrderModel) SetValue(p Point, val int) { defer (*Model)(m).change("OrderElement.SetValue", ScoreChange, MinorChange)() m.d.Song.Score.Tracks[p.X].Order.Set(p.Y, val) } // RowList returns a List of all the rows of the pattern order table. func (m *OrderModel) RowList() List { return List{(*orderRows)(m)} } type orderRows OrderModel 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 = value } 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 } 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 } // RemoveUnused returns an Action that removes all unused patterns from all // tracks in the song, and updates the pattern orders accordingly. func (m *OrderModel) RemoveUnusedPatterns() Action { return MakeAction((*removeUnused)(m)) } type removeUnused OrderModel func (m *removeUnused) Do() { defer (*Model)(m).change("RemoveUnusedAction", ScoreChange, MajorChange)() for trkIndex, trk := range m.d.Song.Score.Tracks { // assign new indices to patterns newIndex := map[int]int{} runningIndex := 0 length := 0 if len(trk.Order) > m.d.Song.Score.Length { trk.Order = trk.Order[:m.d.Song.Score.Length] } for i, p := range trk.Order { // if the pattern hasn't been considered and is within limits if _, ok := newIndex[p]; !ok && p >= 0 && p < len(trk.Patterns) { pat := trk.Patterns[p] useful := false for _, n := range pat { // patterns that have anything else than all holds are useful and to be kept if n != 1 { useful = true break } } if useful { newIndex[p] = runningIndex runningIndex++ } else { newIndex[p] = -1 } } if ind, ok := newIndex[p]; ok && ind > -1 { length = i + 1 trk.Order[i] = ind } else { trk.Order[i] = -1 } } trk.Order = trk.Order[:length] newPatterns := make([]sointu.Pattern, runningIndex) for i, pat := range trk.Patterns { if ind, ok := newIndex[i]; ok && ind > -1 { patLength := 0 for j, note := range pat { // find last note that is something else that hold if note != 1 { patLength = j + 1 } } if patLength > m.d.Song.Score.RowsPerPattern { patLength = m.d.Song.Score.RowsPerPattern } newPatterns[ind] = pat[:patLength] // crop to either RowsPerPattern or last row having something else than hold } } trk.Patterns = newPatterns m.d.Song.Score.Tracks[trkIndex] = trk } }