mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-14 02:54:37 -04:00
feat(tracker): Add a matrix showing track sequences
This commit is contained in:
@ -6,11 +6,13 @@ var defaultSong = sointu.Song{
|
|||||||
BPM: 100,
|
BPM: 100,
|
||||||
Patterns: [][]byte{
|
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, 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},
|
{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{
|
Tracks: []sointu.Track{
|
||||||
{NumVoices: 2, Sequence: []byte{0}},
|
{NumVoices: 2, Sequence: []byte{0, 0, 0, 1}},
|
||||||
{NumVoices: 2, Sequence: []byte{1}},
|
{NumVoices: 2, Sequence: []byte{2, 2, 2, 3}},
|
||||||
},
|
},
|
||||||
Patch: sointu.Patch{
|
Patch: sointu.Patch{
|
||||||
Instruments: []sointu.Instrument{{NumVoices: 4, Units: []sointu.Unit{
|
Instruments: []sointu.Instrument{{NumVoices: 4, Units: []sointu.Unit{
|
||||||
|
@ -80,9 +80,11 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
}
|
}
|
||||||
case key.NameUpArrow:
|
case key.NameUpArrow:
|
||||||
t.CursorRow = (t.CursorRow + t.song.PatternRows() - 1) % t.song.PatternRows()
|
t.CursorRow = (t.CursorRow + t.song.PatternRows() - 1) % t.song.PatternRows()
|
||||||
|
t.NoteTracking = false
|
||||||
return true
|
return true
|
||||||
case key.NameDownArrow:
|
case key.NameDownArrow:
|
||||||
t.CursorRow = (t.CursorRow + 1) % t.song.PatternRows()
|
t.CursorRow = (t.CursorRow + 1) % t.song.PatternRows()
|
||||||
|
t.NoteTracking = false
|
||||||
return true
|
return true
|
||||||
case key.NameLeftArrow:
|
case key.NameLeftArrow:
|
||||||
if t.CursorColumn == 0 {
|
if t.CursorColumn == 0 {
|
||||||
|
@ -9,6 +9,13 @@ import (
|
|||||||
func (t *Tracker) Layout(gtx layout.Context) {
|
func (t *Tracker) Layout(gtx layout.Context) {
|
||||||
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(t.layoutControls),
|
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)),
|
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))
|
flexTracks := make([]layout.FlexChild, len(t.song.Tracks))
|
||||||
t.playRowPatMutex.RLock()
|
t.playRowPatMutex.RLock()
|
||||||
defer t.playRowPatMutex.RUnlock()
|
defer t.playRowPatMutex.RUnlock()
|
||||||
|
playRow := int(t.PlayRow)
|
||||||
|
if t.DisplayPattern != t.PlayPattern {
|
||||||
|
playRow = -1
|
||||||
|
}
|
||||||
for i, trk := range t.song.Tracks {
|
for i, trk := range t.song.Tracks {
|
||||||
flexTracks[i] = layout.Rigid(Lowered(t.layoutTrack(
|
flexTracks[i] = layout.Rigid(Lowered(t.layoutTrack(
|
||||||
t.song.Patterns[trk.Sequence[t.DisplayPattern]],
|
t.song.Patterns[trk.Sequence[t.DisplayPattern]],
|
||||||
t.ActiveTrack == i,
|
t.ActiveTrack == i,
|
||||||
t.CursorRow,
|
t.CursorRow,
|
||||||
t.CursorColumn,
|
t.CursorColumn,
|
||||||
int(t.PlayRow),
|
playRow,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
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 {
|
func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions {
|
||||||
gtx.Constraints.Min.Y = 400
|
gtx.Constraints.Min.Y = 200
|
||||||
gtx.Constraints.Max.Y = 400
|
gtx.Constraints.Max.Y = 200
|
||||||
return layout.Stack{Alignment: layout.NW}.Layout(gtx,
|
return layout.Stack{Alignment: layout.NW}.Layout(gtx,
|
||||||
layout.Expanded(t.QuitButton.Layout),
|
layout.Expanded(t.QuitButton.Layout),
|
||||||
layout.Stacked(Raised(Label(fmt.Sprintf("Current octave: %v", t.CurrentOctave), white))),
|
layout.Stacked(Raised(Label(fmt.Sprintf("Current octave: %v", t.CurrentOctave), white))),
|
||||||
|
57
tracker/patterns.go
Normal file
57
tracker/patterns.go
Normal 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}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
package tracker
|
package tracker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
"image/color"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var fontCollection []text.FontFace = gofont.Collection()
|
var fontCollection []text.FontFace = gofont.Collection()
|
||||||
@ -33,3 +34,10 @@ var trackerFontSize = unit.Px(16)
|
|||||||
var trackerTextColor = white
|
var trackerTextColor = white
|
||||||
var trackerActiveTextColor = yellow
|
var trackerActiveTextColor = yellow
|
||||||
var trackerPlayColor = red
|
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)
|
||||||
|
@ -23,6 +23,7 @@ type Tracker struct {
|
|||||||
DisplayPattern int
|
DisplayPattern int
|
||||||
ActiveTrack int
|
ActiveTrack int
|
||||||
CurrentOctave byte
|
CurrentOctave byte
|
||||||
|
NoteTracking bool
|
||||||
|
|
||||||
ticked chan struct{}
|
ticked chan struct{}
|
||||||
setPlaying chan bool
|
setPlaying chan bool
|
||||||
@ -59,6 +60,9 @@ func (t *Tracker) TogglePlay() {
|
|||||||
t.songPlayMutex.Lock()
|
t.songPlayMutex.Lock()
|
||||||
defer t.songPlayMutex.Unlock()
|
defer t.songPlayMutex.Unlock()
|
||||||
t.Playing = !t.Playing
|
t.Playing = !t.Playing
|
||||||
|
if t.Playing {
|
||||||
|
t.NoteTracking = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) sequencerLoop(closer <-chan struct{}) {
|
func (t *Tracker) sequencerLoop(closer <-chan struct{}) {
|
||||||
@ -85,6 +89,10 @@ func (t *Tracker) sequencerLoop(closer <-chan struct{}) {
|
|||||||
if t.PlayPattern >= t.song.SequenceLength() {
|
if t.PlayPattern >= t.song.SequenceLength() {
|
||||||
t.PlayPattern = 0
|
t.PlayPattern = 0
|
||||||
}
|
}
|
||||||
|
if t.NoteTracking {
|
||||||
|
t.DisplayPattern = t.PlayPattern
|
||||||
|
t.CursorRow = t.PlayRow
|
||||||
|
}
|
||||||
notes := make([]Note, 0, 32)
|
notes := make([]Note, 0, 32)
|
||||||
for track := range t.song.Tracks {
|
for track := range t.song.Tracks {
|
||||||
patternIndex := t.song.Tracks[track].Sequence[t.PlayPattern]
|
patternIndex := t.song.Tracks[track].Sequence[t.PlayPattern]
|
||||||
|
Reference in New Issue
Block a user