diff --git a/tracker/defaultsong.go b/tracker/defaultsong.go index 5ed243b..e658bcb 100644 --- a/tracker/defaultsong.go +++ b/tracker/defaultsong.go @@ -6,11 +6,13 @@ var defaultSong = sointu.Song{ BPM: 100, Patterns: [][]byte{ {64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}, + {64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 75, 0, 75, 0, 80, 0}, {0, 0, 64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0}, + {32, 0, 64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 68, 0, 68, 0}, }, Tracks: []sointu.Track{ - {NumVoices: 2, Sequence: []byte{0}}, - {NumVoices: 2, Sequence: []byte{1}}, + {NumVoices: 2, Sequence: []byte{0, 0, 0, 1}}, + {NumVoices: 2, Sequence: []byte{2, 2, 2, 3}}, }, Patch: sointu.Patch{ Instruments: []sointu.Instrument{{NumVoices: 4, Units: []sointu.Unit{ diff --git a/tracker/keyevent.go b/tracker/keyevent.go index 43727a1..1d8848e 100644 --- a/tracker/keyevent.go +++ b/tracker/keyevent.go @@ -80,9 +80,11 @@ func (t *Tracker) KeyEvent(e key.Event) bool { } case key.NameUpArrow: t.CursorRow = (t.CursorRow + t.song.PatternRows() - 1) % t.song.PatternRows() + t.NoteTracking = false return true case key.NameDownArrow: t.CursorRow = (t.CursorRow + 1) % t.song.PatternRows() + t.NoteTracking = false return true case key.NameLeftArrow: if t.CursorColumn == 0 { diff --git a/tracker/layout.go b/tracker/layout.go index 84f89fb..731e6c5 100644 --- a/tracker/layout.go +++ b/tracker/layout.go @@ -9,6 +9,13 @@ import ( func (t *Tracker) Layout(gtx layout.Context) { layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(t.layoutControls), + layout.Rigid(Lowered(t.layoutPatterns( + t.song.Tracks, + t.ActiveTrack, + t.DisplayPattern, + t.CursorColumn, + t.PlayPattern, + ))), layout.Flexed(1, Lowered(t.layoutTracker)), ) } @@ -17,13 +24,17 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { flexTracks := make([]layout.FlexChild, len(t.song.Tracks)) t.playRowPatMutex.RLock() defer t.playRowPatMutex.RUnlock() + playRow := int(t.PlayRow) + if t.DisplayPattern != t.PlayPattern { + playRow = -1 + } for i, trk := range t.song.Tracks { flexTracks[i] = layout.Rigid(Lowered(t.layoutTrack( t.song.Patterns[trk.Sequence[t.DisplayPattern]], t.ActiveTrack == i, t.CursorRow, t.CursorColumn, - int(t.PlayRow), + playRow, ))) } return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, @@ -32,8 +43,8 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { } func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions { - gtx.Constraints.Min.Y = 400 - gtx.Constraints.Max.Y = 400 + gtx.Constraints.Min.Y = 200 + gtx.Constraints.Max.Y = 200 return layout.Stack{Alignment: layout.NW}.Layout(gtx, layout.Expanded(t.QuitButton.Layout), layout.Stacked(Raised(Label(fmt.Sprintf("Current octave: %v", t.CurrentOctave), white))), diff --git a/tracker/patterns.go b/tracker/patterns.go new file mode 100644 index 0000000..4f23ad0 --- /dev/null +++ b/tracker/patterns.go @@ -0,0 +1,57 @@ +package tracker + +import ( + "fmt" + "image" + + "gioui.org/f32" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/widget" + "github.com/vsariola/sointu" +) + +const patternCellHeight = 12 +const patternCellWidth = 16 + +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 * len(tracks) + gtx.Constraints.Max.X = patternCellWidth * len(tracks) + gtx.Constraints.Max.Y = 50 + defer op.Push(gtx.Ops).Pop() + clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops) + paint.FillShape(gtx.Ops, panelColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, trackRowHeight)}.Op()) + for i, track := range tracks { + pop := op.Push(gtx.Ops) + gtx.Constraints.Max.X = patternCellWidth + clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops) + if activeTrack == i { + paint.FillShape(gtx.Ops, activeTrackColor, clip.Rect{ + Max: gtx.Constraints.Max, + }.Op()) + } else { + paint.FillShape(gtx.Ops, inactiveTrackColor, clip.Rect{ + Max: gtx.Constraints.Max, + }.Op()) + } + for j, p := range track.Sequence { + if j == playingPattern { + paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight)}.Op()) + } + if j == cursorPattern { + paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops) + } else { + paint.ColorOp{Color: trackerTextColor}.Add(gtx.Ops) + } + widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, fmt.Sprintf("%d", p)) + op.Offset(f32.Pt(0, patternCellHeight)).Add(gtx.Ops) + } + pop.Pop() + op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops) + } + return layout.Dimensions{Size: gtx.Constraints.Max} + } +} diff --git a/tracker/theme.go b/tracker/theme.go index 812abcd..1121d41 100644 --- a/tracker/theme.go +++ b/tracker/theme.go @@ -1,10 +1,11 @@ package tracker import ( + "image/color" + "gioui.org/font/gofont" "gioui.org/text" "gioui.org/unit" - "image/color" ) var fontCollection []text.FontFace = gofont.Collection() @@ -33,3 +34,10 @@ var trackerFontSize = unit.Px(16) var trackerTextColor = white var trackerActiveTextColor = yellow var trackerPlayColor = red + +var patternBgColor = black +var patternPlayColor = red +var patternTextColor = white +var patternActiveTextColor = yellow +var patternFont = fontCollection[6].Font +var patternFontSize = unit.Px(12) diff --git a/tracker/tracker.go b/tracker/tracker.go index a96fe62..ac21da8 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -23,6 +23,7 @@ type Tracker struct { DisplayPattern int ActiveTrack int CurrentOctave byte + NoteTracking bool ticked chan struct{} setPlaying chan bool @@ -59,6 +60,9 @@ func (t *Tracker) TogglePlay() { t.songPlayMutex.Lock() defer t.songPlayMutex.Unlock() t.Playing = !t.Playing + if t.Playing { + t.NoteTracking = true + } } func (t *Tracker) sequencerLoop(closer <-chan struct{}) { @@ -85,6 +89,10 @@ func (t *Tracker) sequencerLoop(closer <-chan struct{}) { if t.PlayPattern >= t.song.SequenceLength() { t.PlayPattern = 0 } + if t.NoteTracking { + t.DisplayPattern = t.PlayPattern + t.CursorRow = t.PlayRow + } notes := make([]Note, 0, 32) for track := range t.song.Tracks { patternIndex := t.song.Tracks[track].Sequence[t.PlayPattern]