mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
feat(tracker): implement selecting a range in the tracker
also refactored a location in song to SongPoint, and a particular row in song into SongRow
This commit is contained in:
parent
8f9bf75613
commit
eb36a96e29
@ -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
|
||||
|
@ -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),
|
||||
)
|
||||
|
@ -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 {
|
||||
|
85
tracker/songpoint.go
Normal file
85
tracker/songpoint.go
Normal file
@ -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
|
||||
}
|
@ -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}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()),
|
||||
|
Loading…
x
Reference in New Issue
Block a user