diff --git a/tracker/keyevent.go b/tracker/keyevent.go index f1d2b85..bfd3cde 100644 --- a/tracker/keyevent.go +++ b/tracker/keyevent.go @@ -60,7 +60,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool { t.SetCurrentNote(0) return true case key.NameDeleteForward: - t.SetCurrentNote(1) + t.DeleteSelection() return true case key.NameEscape: os.Exit(0) @@ -77,7 +77,11 @@ func (t *Tracker) KeyEvent(e key.Event) bool { if e.Modifiers.Contain(key.ModCtrl) { delta = -t.song.PatternRows() } - t.moveCursor(delta) + t.Cursor.Row += delta + t.Cursor.Clamp(t.song) + if !e.Modifiers.Contain(key.ModShift) { + t.SelectionCorner = t.Cursor + } t.NoteTracking = false return true case key.NameDownArrow: @@ -85,37 +89,41 @@ func (t *Tracker) KeyEvent(e key.Event) bool { if e.Modifiers.Contain(key.ModCtrl) { delta = t.song.PatternRows() } - t.moveCursor(delta) + t.Cursor.Row += delta + t.Cursor.Clamp(t.song) + if !e.Modifiers.Contain(key.ModShift) { + t.SelectionCorner = t.Cursor + } t.NoteTracking = false return true case key.NameLeftArrow: - if t.CursorColumn == 0 || !t.TrackShowHex[t.ActiveTrack] || e.Modifiers.Contain(key.ModCtrl) { - t.ActiveTrack = (t.ActiveTrack + len(t.song.Tracks) - 1) % len(t.song.Tracks) - if t.TrackShowHex[t.ActiveTrack] { + if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) { + t.Cursor.Track-- + t.Cursor.Clamp(t.song) + if t.TrackShowHex[t.Cursor.Track] { t.CursorColumn = 1 } else { t.CursorColumn = 0 } + if !e.Modifiers.Contain(key.ModShift) { + t.SelectionCorner = t.Cursor + } } else { t.CursorColumn-- } return true case key.NameRightArrow: - if t.CursorColumn == 1 || !t.TrackShowHex[t.ActiveTrack] || e.Modifiers.Contain(key.ModCtrl) { - t.ActiveTrack = (t.ActiveTrack + 1) % len(t.song.Tracks) + if t.CursorColumn == 1 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) { + t.Cursor.Track++ + t.Cursor.Clamp(t.song) + if !e.Modifiers.Contain(key.ModShift) { + t.SelectionCorner = t.Cursor + } t.CursorColumn = 0 } else { t.CursorColumn++ } return true - case key.NameTab: - if e.Modifiers.Contain(key.ModShift) { - t.ActiveTrack = (t.ActiveTrack + len(t.song.Tracks) - 1) % len(t.song.Tracks) - } else { - t.ActiveTrack = (t.ActiveTrack + 1) % len(t.song.Tracks) - } - t.CursorColumn = 0 - return true } if e.Modifiers.Contain(key.ModCtrl) { if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil { @@ -123,7 +131,7 @@ func (t *Tracker) KeyEvent(e key.Event) bool { return true } } else { - if !t.TrackShowHex[t.ActiveTrack] { + if !t.TrackShowHex[t.Cursor.Track] { if val, ok := noteMap[e.Name]; ok { t.NotePressed(val) return true @@ -139,24 +147,9 @@ func (t *Tracker) KeyEvent(e key.Event) bool { return false } -func (t *Tracker) moveCursor(delta int) { - newRow := t.CursorRow + delta - remainder := (newRow + t.song.PatternRows()) % t.song.PatternRows() - t.DisplayPattern += (newRow - remainder) / t.song.PatternRows() - if t.DisplayPattern < 0 { - t.CursorRow = 0 - t.DisplayPattern = 0 - } else if t.DisplayPattern >= t.song.SequenceLength() { - t.CursorRow = t.song.PatternRows() - 1 - t.DisplayPattern = t.song.SequenceLength() - 1 - } else { - t.CursorRow = remainder - } -} - // getCurrent returns the current (note) value in current pattern under the cursor func (t *Tracker) getCurrent() byte { - return t.song.Tracks[t.ActiveTrack].Patterns[t.song.Tracks[t.ActiveTrack].Sequence[t.DisplayPattern]][t.CursorRow] + return t.song.Tracks[t.Cursor.Track].Patterns[t.song.Tracks[t.Cursor.Track].Sequence[t.Cursor.Pattern]][t.Cursor.Row] } // NotePressed handles incoming key presses while in the note column diff --git a/tracker/layout.go b/tracker/layout.go index 7afe781..e8ec6f7 100644 --- a/tracker/layout.go +++ b/tracker/layout.go @@ -83,18 +83,8 @@ func (t *Tracker) Layout(gtx layout.Context) { } func (t *Tracker) layoutTracksAndPatterns(gtx layout.Context) layout.Dimensions { - playPat := t.PlayPattern - if !t.Playing { - playPat = -1 - } return t.BottomHorizontalSplit.Layout(gtx, - t.layoutPatterns( - t.song.Tracks, - t.ActiveTrack, - t.DisplayPattern, - t.CursorColumn, - playPat, - ), + t.layoutPatterns, t.layoutTracks, ) } @@ -106,7 +96,7 @@ func (t *Tracker) layoutTracks(gtx layout.Context) layout.Dimensions { t.playRowPatMutex.RLock() defer t.playRowPatMutex.RUnlock() - playPat := t.PlayPattern + playPat := t.PlayPosition.Pattern if !t.Playing { playPat = -1 } @@ -114,16 +104,15 @@ func (t *Tracker) layoutTracks(gtx layout.Context) layout.Dimensions { rowMarkers := layout.Rigid(t.layoutRowMarkers( len(t.song.Tracks[0].Patterns[0]), len(t.song.Tracks[0].Sequence), - t.CursorRow, - t.DisplayPattern, + t.Cursor.Row, + t.Cursor.Pattern, t.CursorColumn, - t.PlayRow, + t.PlayPosition.Row, playPat, )) leftInset := layout.Inset{Left: unit.Dp(4)} - for i, trk := range t.song.Tracks { - i2 := i // avoids i being updated in the closure - trk2 := trk // avoids trk being updated in the closure + for i := range t.song.Tracks { + i2 := i // avoids i being updated in the closure if len(t.TrackHexCheckBoxes) <= i { t.TrackHexCheckBoxes = append(t.TrackHexCheckBoxes, new(widget.Bool)) } @@ -136,17 +125,7 @@ func (t *Tracker) layoutTracks(gtx layout.Context) layout.Dimensions { cbStyle.Color = white ret := layout.Stack{}.Layout(gtx, layout.Stacked(func(gtx layout.Context) D { - return leftInset.Layout(gtx, t.layoutTrack( - trk2.Patterns, - trk2.Sequence, - t.ActiveTrack == i2, - t.TrackShowHex[i2], - t.CursorRow, - t.DisplayPattern, - t.CursorColumn, - t.PlayRow, - playPat, - )) + return leftInset.Layout(gtx, t.layoutTrack(i2)) }), layout.Stacked(cbStyle.Layout), ) diff --git a/tracker/patterns.go b/tracker/patterns.go index 7374d72..4f14850 100644 --- a/tracker/patterns.go +++ b/tracker/patterns.go @@ -11,7 +11,6 @@ import ( "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/widget" - "github.com/vsariola/sointu" ) const patternCellHeight = 16 @@ -19,34 +18,41 @@ const patternCellWidth = 16 const patternVisibleTracks = 8 const patternRowMarkerWidth = 30 -func (t *Tracker) layoutPatterns(tracks []sointu.Track, activeTrack, cursorPattern, cursorCol, playingPattern int) layout.Widget { - return func(gtx layout.Context) layout.Dimensions { - gtx.Constraints.Min.X = patternCellWidth*patternVisibleTracks + patternRowMarkerWidth - gtx.Constraints.Max.X = patternCellWidth*patternVisibleTracks + patternRowMarkerWidth - defer op.Push(gtx.Ops).Pop() - clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops) - paint.FillShape(gtx.Ops, patternSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op()) - for j := range tracks[0].Sequence { - if j == playingPattern { - paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}.Op()) - } - paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j))) - stack := op.Push(gtx.Ops) - op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops) - for i, track := range tracks { - paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, fmt.Sprintf("%d", track.Sequence[j])) - if activeTrack == i && j == cursorPattern { - paint.FillShape(gtx.Ops, patternCursorColor, clip.Rect{Max: image.Pt(patternCellWidth, patternCellHeight)}.Op()) - } - op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops) - } - stack.Pop() - op.Offset(f32.Pt(0, patternCellHeight)).Add(gtx.Ops) - } - return layout.Dimensions{Size: gtx.Constraints.Max} +func (t *Tracker) layoutPatterns(gtx C) D { + gtx.Constraints.Min.X = patternCellWidth*patternVisibleTracks + patternRowMarkerWidth + gtx.Constraints.Max.X = patternCellWidth*patternVisibleTracks + patternRowMarkerWidth + defer op.Push(gtx.Ops).Pop() + clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops) + paint.FillShape(gtx.Ops, patternSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op()) + patternRect := SongRect{ + Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track}, + Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track}, } + for j := 0; j < t.song.SequenceLength(); j++ { + if j == t.PlayPosition.Pattern && t.Playing { + paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}.Op()) + } + paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops) + widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j))) + stack := op.Push(gtx.Ops) + op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops) + for i, track := range t.song.Tracks { + paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops) + widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, fmt.Sprintf("%d", track.Sequence[j])) + point := SongPoint{Track: i, SongRow: SongRow{Pattern: j}} + if patternRect.Contains(point) { + color := patternSelectionColor + if point.Pattern == t.Cursor.Pattern && point.Track == t.Cursor.Track { + color = patternCursorColor + } + paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(patternCellWidth, patternCellHeight)}.Op()) + } + op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops) + } + stack.Pop() + op.Offset(f32.Pt(0, patternCellHeight)).Add(gtx.Ops) + } + return layout.Dimensions{Size: gtx.Constraints.Max} } func patternIndexToString(index byte) string { diff --git a/tracker/songpoint.go b/tracker/songpoint.go new file mode 100644 index 0000000..52a52c1 --- /dev/null +++ b/tracker/songpoint.go @@ -0,0 +1,85 @@ +package tracker + +import "github.com/vsariola/sointu" + +type SongRow struct { + Pattern int + Row int +} + +type SongPoint struct { + Track int + SongRow +} + +type SongRect struct { + Corner1 SongPoint + Corner2 SongPoint +} + +func (r *SongRow) Wrap(song sointu.Song) { + totalRow := r.Pattern*song.PatternRows() + r.Row + r.Row = mod(totalRow, song.PatternRows()) + r.Pattern = mod((totalRow-r.Row)/song.PatternRows(), song.SequenceLength()) +} + +func (r *SongRow) Clamp(song sointu.Song) { + totalRow := r.Pattern*song.PatternRows() + r.Row + if totalRow < 0 { + totalRow = 0 + } + if totalRow >= song.TotalRows() { + totalRow = song.TotalRows() - 1 + } + r.Row = totalRow % song.PatternRows() + r.Pattern = ((totalRow - r.Row) / song.PatternRows()) % song.SequenceLength() +} + +func (p *SongPoint) Wrap(song sointu.Song) { + p.Track = mod(p.Track, len(song.Tracks)) + p.SongRow.Wrap(song) +} + +func (p *SongPoint) Clamp(song sointu.Song) { + if p.Track < 0 { + p.Track = 0 + } else if l := len(song.Tracks); p.Track >= l { + p.Track = l - 1 + } + p.SongRow.Clamp(song) +} + +func (r *SongRect) Contains(p SongPoint) bool { + track1, track2 := r.Corner1.Track, r.Corner2.Track + if track2 < track1 { + track1, track2 = track2, track1 + } + if p.Track < track1 || p.Track > track2 { + return false + } + pattern1, row1, pattern2, row2 := r.Corner1.Pattern, r.Corner1.Row, r.Corner2.Pattern, r.Corner2.Row + if pattern2 < pattern1 || (pattern1 == pattern2 && row2 < row1) { + pattern1, row1, pattern2, row2 = pattern2, row2, pattern1, row1 + } + if p.Pattern < pattern1 || p.Pattern > pattern2 { + return false + } + if p.Pattern == pattern1 && p.Row < row1 { + return false + } + if p.Pattern == pattern2 && p.Row > row2 { + return false + } + return true +} + +func mod(a, b int) int { + m := a % b + if a < 0 && b < 0 { + m -= b + } + if a < 0 && b > 0 { + m += b + } + return m +} diff --git a/tracker/theme.go b/tracker/theme.go index ca0af23..b8ed5bb 100644 --- a/tracker/theme.go +++ b/tracker/theme.go @@ -72,6 +72,7 @@ var trackerPatternRowTextColor = color.RGBA{R: 198, G: 198, B: 198, A: 255} var trackerPlayColor = color.RGBA{R: 55, G: 55, B: 61, A: 255} var trackerPatMarker = primaryColor var trackerCursorColor = color.RGBA{R: 38, G: 79, B: 120, A: 64} +var trackerSelectionColor = color.RGBA{R: 19, G: 40, B: 60, A: 128} var trackerSurfaceColor = color.RGBA{R: 18, G: 18, B: 18, A: 18} var patternPlayColor = color.RGBA{R: 55, G: 55, B: 61, A: 255} @@ -80,6 +81,7 @@ var patternActiveTextColor = yellow var patternFont = fontCollection[6].Font var patternFontSize = unit.Px(12) var patternCursorColor = color.RGBA{R: 38, G: 79, B: 120, A: 64} +var patternSelectionColor = color.RGBA{R: 19, G: 40, B: 60, A: 128} var inactiveBtnColor = color.RGBA{R: 61, G: 55, B: 55, A: 255} diff --git a/tracker/track.go b/tracker/track.go index bf9c5f9..e51b3dd 100644 --- a/tracker/track.go +++ b/tracker/track.go @@ -17,7 +17,7 @@ const trackRowHeight = 16 const trackWidth = 54 const patmarkWidth = 16 -func (t *Tracker) layoutTrack(patterns [][]byte, sequence []byte, active bool, hex bool, cursorRow, cursorPattern, cursorCol, playRow, playPattern int) layout.Widget { +func (t *Tracker) layoutTrack(trackNo int) layout.Widget { return func(gtx layout.Context) layout.Dimensions { gtx.Constraints.Min.X = trackWidth gtx.Constraints.Max.X = trackWidth @@ -26,29 +26,37 @@ func (t *Tracker) layoutTrack(patterns [][]byte, sequence []byte, active bool, h op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y/2)-trackRowHeight)).Add(gtx.Ops) // TODO: this is a time bomb; as soon as one of the patterns is not the same length as rest. Find a solution // to fix the pattern lengths to a constant value - cursorSongRow := cursorPattern*len(patterns[0]) + cursorRow - playSongRow := playPattern*len(patterns[0]) + playRow + cursorSongRow := t.Cursor.Pattern*t.song.PatternRows() + t.Cursor.Row op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops) - for i, s := range sequence { - if cursorPattern == i && active { - paint.FillShape(gtx.Ops, activeTrackColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight*len(patterns[0]))}.Op()) + patternRect := SongRect{ + Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track}, + Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track}, + } + pointRect := SongRect{ + Corner1: t.Cursor, + Corner2: t.SelectionCorner, + } + for i, s := range t.song.Tracks[trackNo].Sequence { + if patternRect.Contains(SongPoint{Track: trackNo, SongRow: SongRow{Pattern: i}}) { + paint.FillShape(gtx.Ops, activeTrackColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight*t.song.PatternRows())}.Op()) } - for j, c := range patterns[s] { - songRow := i*len(patterns[0]) + j - if songRow == playSongRow { + for j, c := range t.song.Tracks[trackNo].Patterns[s] { + songRow := SongRow{Pattern: i, Row: j} + songPoint := SongPoint{Track: trackNo, SongRow: songRow} + if songRow == t.PlayPosition && t.Playing { paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight)}.Op()) } if j == 0 { paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops) widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(s)) } - if songRow == cursorSongRow { + if songRow == t.Cursor.SongRow { paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops) } else { paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops) } op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops) - if hex { + if t.TrackShowHex[trackNo] { var text string switch c { case 0: @@ -59,13 +67,23 @@ func (t *Tracker) layoutTrack(patterns [][]byte, sequence []byte, active bool, h text = fmt.Sprintf("%02x", c) } widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(text)) - if active && songRow == cursorSongRow { - paint.FillShape(gtx.Ops, trackerCursorColor, clip.Rect{Min: image.Pt(cursorCol*10, 0), Max: image.Pt(cursorCol*10+10, trackRowHeight)}.Op()) + if pointRect.Contains(songPoint) { + for col := 0; col < 2; col++ { + color := trackerSelectionColor + if songPoint == t.Cursor && t.CursorColumn == col { + color = trackerCursorColor + } + paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(col*10, 0), Max: image.Pt(col*10+10, trackRowHeight)}.Op()) + } } } else { widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, valueAsNote(c)) - if active && cursorCol == 0 && songRow == cursorSongRow { - paint.FillShape(gtx.Ops, trackerCursorColor, clip.Rect{Max: image.Pt(30, trackRowHeight)}.Op()) + if pointRect.Contains(songPoint) { + color := trackerSelectionColor + if songPoint == t.Cursor { + color = trackerCursorColor + } + paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(30, trackRowHeight)}.Op()) } } op.Offset(f32.Pt(-patmarkWidth, trackRowHeight)).Add(gtx.Ops) diff --git a/tracker/tracker.go b/tracker/tracker.go index 6054f07..d62cd97 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -19,12 +19,10 @@ type Tracker struct { Playing bool // protects PlayPattern and PlayRow playRowPatMutex sync.RWMutex // protects song and playing - PlayPattern int - PlayRow int - CursorRow int + PlayPosition SongRow + SelectionCorner SongPoint + Cursor SongPoint CursorColumn int - DisplayPattern int - ActiveTrack int CurrentInstrument int CurrentUnit int NoteTracking bool @@ -73,21 +71,9 @@ func (t *Tracker) LoadSong(song sointu.Song) error { } else { t.synth = synth } - if t.DisplayPattern >= song.SequenceLength() { - t.DisplayPattern = song.SequenceLength() - 1 - } - if t.CursorRow >= song.PatternRows() { - t.CursorRow = song.PatternRows() - 1 - } - if t.PlayPattern >= song.SequenceLength() { - t.PlayPattern = song.SequenceLength() - 1 - } - if t.PlayRow >= song.PatternRows() { - t.PlayRow = song.PatternRows() - 1 - } - if t.ActiveTrack >= len(song.Tracks) { - t.ActiveTrack = len(song.Tracks) - 1 - } + t.PlayPosition.Clamp(song) + t.Cursor.Clamp(song) + t.SelectionCorner.Clamp(song) if t.sequencer != nil { t.sequencer.SetSynth(t.synth) } @@ -105,8 +91,8 @@ func (t *Tracker) TogglePlay() { t.Playing = !t.Playing if t.Playing { t.NoteTracking = true - t.PlayPattern = t.DisplayPattern - t.PlayRow = t.CursorRow - 1 + t.PlayPosition = t.Cursor.SongRow + t.PlayPosition.Row-- // TODO: we advance soon to make up for this -1, but this is not very elegant way to do it } } @@ -124,22 +110,16 @@ func (t *Tracker) sequencerLoop(closer <-chan struct{}) { t.playRowPatMutex.Unlock() return nil, false } - t.PlayRow++ - if t.PlayRow >= t.song.PatternRows() { - t.PlayRow = 0 - t.PlayPattern++ - } - if t.PlayPattern >= t.song.SequenceLength() { - t.PlayPattern = 0 - } + t.PlayPosition.Row++ + t.PlayPosition.Wrap(t.song) if t.NoteTracking { - t.DisplayPattern = t.PlayPattern - t.CursorRow = t.PlayRow + t.Cursor.SongRow = t.PlayPosition + t.SelectionCorner.SongRow = t.PlayPosition } notes := make([]Note, 0, 32) for track := range t.song.Tracks { - patternIndex := t.song.Tracks[track].Sequence[t.PlayPattern] - note := t.song.Tracks[track].Patterns[patternIndex][t.PlayRow] + patternIndex := t.song.Tracks[track].Sequence[t.PlayPosition.Pattern] + note := t.song.Tracks[track].Patterns[patternIndex][t.PlayPosition.Row] if note == 1 { // anything but hold causes an action. continue } @@ -240,20 +220,20 @@ func (t *Tracker) AddInstrument() { // SetCurrentNote sets the (note) value in current pattern under cursor to iv func (t *Tracker) SetCurrentNote(iv byte) { t.SaveUndo() - t.song.Tracks[t.ActiveTrack].Patterns[t.song.Tracks[t.ActiveTrack].Sequence[t.DisplayPattern]][t.CursorRow] = iv + t.song.Tracks[t.Cursor.Track].Patterns[t.song.Tracks[t.Cursor.Track].Sequence[t.Cursor.Pattern]][t.Cursor.Row] = iv } func (t *Tracker) SetCurrentPattern(pat byte) { t.SaveUndo() - length := len(t.song.Tracks[t.ActiveTrack].Patterns) + length := len(t.song.Tracks[t.Cursor.Track].Patterns) if int(pat) >= length { tail := make([][]byte, int(pat)-length+1) for i := range tail { tail[i] = make([]byte, t.song.PatternRows()) } - t.song.Tracks[t.ActiveTrack].Patterns = append(t.song.Tracks[t.ActiveTrack].Patterns, tail...) + t.song.Tracks[t.Cursor.Track].Patterns = append(t.song.Tracks[t.Cursor.Track].Patterns, tail...) } - t.song.Tracks[t.ActiveTrack].Sequence[t.DisplayPattern] = pat + t.song.Tracks[t.Cursor.Track].Sequence[t.Cursor.Pattern] = pat } func (t *Tracker) SetSongLength(value int) { @@ -276,6 +256,28 @@ func (t *Tracker) SetSongLength(value int) { } } +func (t *Tracker) DeleteSelection() { + t.SaveUndo() + r1 := t.Cursor.Pattern*t.song.PatternRows() + t.Cursor.Row + r2 := t.SelectionCorner.Pattern*t.song.PatternRows() + t.SelectionCorner.Row + if r2 < r1 { + r1, r2 = r2, r1 + } + t1 := t.Cursor.Track + t2 := t.SelectionCorner.Track + if t2 < t1 { + t1, t2 = t2, t1 + } + for r := r1; r <= r2; r++ { + for c := t1; c <= t2; c++ { + s := SongRow{Row: r} + s.Wrap(t.song) + p := t.song.Tracks[c].Sequence[s.Pattern] + t.song.Tracks[c].Patterns[p][s.Row] = 1 + } + } +} + func New(audioContext sointu.AudioContext) *Tracker { t := &Tracker{ Theme: material.NewTheme(gofont.Collection()),