feat(tracker): Add a matrix showing track sequences

This commit is contained in:
vsariola 2021-01-02 17:40:53 +02:00
parent c68d9d3bf5
commit 06c006086b
6 changed files with 94 additions and 6 deletions

View File

@ -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{

View File

@ -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 {

View File

@ -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))),

57
tracker/patterns.go Normal file
View File

@ -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}
}
}

View File

@ -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)

View File

@ -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]