diff --git a/tracker/keyevent.go b/tracker/keyevent.go index 8e0db30..4978274 100644 --- a/tracker/keyevent.go +++ b/tracker/keyevent.go @@ -70,16 +70,9 @@ func (t *Tracker) KeyEvent(e key.Event) bool { return true case `\`: if e.Modifiers.Contain(key.ModShift) { - if t.CurrentOctave < 9 { - t.CurrentOctave++ - return true - } - } else { - if t.CurrentOctave > 0 { - t.CurrentOctave-- - return true - } + return t.ChangeBPM(1) } + return t.ChangeBPM(-1) case key.NameUpArrow: delta := -1 if e.Modifiers.Contain(key.ModCtrl) { diff --git a/tracker/layout.go b/tracker/layout.go index de5bccb..ae05126 100644 --- a/tracker/layout.go +++ b/tracker/layout.go @@ -3,14 +3,34 @@ package tracker import ( "fmt" "image" + "log" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" + + "golang.org/x/exp/shiny/materialdesign/icons" ) +var upIcon *widget.Icon +var downIcon *widget.Icon + +func init() { + var err error + upIcon, err = widget.NewIcon(icons.NavigationArrowUpward) + if err != nil { + log.Fatal(err) + } + downIcon, err = widget.NewIcon(icons.NavigationArrowDownward) + if err != nil { + log.Fatal(err) + } +} + func (t *Tracker) Layout(gtx layout.Context) { paint.FillShape(gtx.Ops, black, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx2 layout.Context) layout.Dimensions { @@ -56,6 +76,20 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions { if !t.Playing { playPat = -1 } + in := layout.UniformInset(unit.Dp(8)) + + for t.OctaveUpBtn.Clicked() { + t.ChangeOctave(1) + } + for t.OctaveDownBtn.Clicked() { + t.ChangeOctave(-1) + } + for t.BPMUpBtn.Clicked() { + t.ChangeBPM(1) + } + for t.BPMDownBtn.Clicked() { + t.ChangeBPM(-1) + } return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(Raised(t.layoutPatterns( @@ -66,7 +100,25 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions { playPat, ))), layout.Rigid(t.darkLine(false)), - layout.Flexed(1, Raised(Label(fmt.Sprintf("Current octave: %v", t.CurrentOctave), white))), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, material.IconButton(t.Theme, t.OctaveUpBtn, upIcon).Layout) + }), + layout.Rigid(t.darkLine(false)), + layout.Rigid(Raised(Label(fmt.Sprintf("OCT: %v", t.CurrentOctave), white))), + layout.Rigid(t.darkLine(false)), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, material.IconButton(t.Theme, t.OctaveDownBtn, downIcon).Layout) + }), + layout.Rigid(t.darkLine(false)), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, material.IconButton(t.Theme, t.BPMUpBtn, upIcon).Layout) + }), + layout.Rigid(t.darkLine(false)), + layout.Rigid(Raised(Label(fmt.Sprintf("BPM: %3v", t.song.BPM), white))), + layout.Rigid(t.darkLine(false)), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return in.Layout(gtx, material.IconButton(t.Theme, t.BPMDownBtn, downIcon).Layout) + }), ) } diff --git a/tracker/tracker.go b/tracker/tracker.go index 824d9c5..159dc22 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -4,7 +4,9 @@ import ( "fmt" "sync" + "gioui.org/font/gofont" "gioui.org/widget" + "gioui.org/widget/material" "github.com/vsariola/sointu" "github.com/vsariola/sointu/bridge" ) @@ -24,7 +26,13 @@ type Tracker struct { ActiveTrack int CurrentOctave byte NoteTracking bool + Theme *material.Theme + OctaveUpBtn *widget.Clickable + OctaveDownBtn *widget.Clickable + BPMUpBtn *widget.Clickable + BPMDownBtn *widget.Clickable + sequencer *Sequencer ticked chan struct{} setPlaying chan bool rowJump chan int @@ -75,9 +83,7 @@ func (t *Tracker) sequencerLoop(closer <-chan struct{}) { panic("cannot create a synth with the default patch") } curVoices := make([]int, 32) - sequencer := NewSequencer(synth, 44100*60/(4*t.song.BPM), func() ([]Note, bool) { - t.songPlayMutex.RLock() - defer t.songPlayMutex.RUnlock() + t.sequencer = NewSequencer(synth, 44100*60/(4*t.song.BPM), func() ([]Note, bool) { if !t.Playing { return nil, false } @@ -121,17 +127,53 @@ func (t *Tracker) sequencerLoop(closer <-chan struct{}) { case <-closer: return default: - sequencer.ReadAudio(buffer) + t.sequencer.ReadAudio(buffer) output.WriteAudio(buffer) } } } +func (t *Tracker) ChangeOctave(delta int) bool { + newOctave := int(t.CurrentOctave) + delta + if newOctave < 0 { + newOctave = 0 + } + if newOctave > 9 { + newOctave = 9 + } + if newOctave != int(t.CurrentOctave) { + t.CurrentOctave = byte(newOctave) + return true + } + return false +} + +func (t *Tracker) ChangeBPM(delta int) bool { + newBPM := t.song.BPM + delta + if newBPM < 1 { + newBPM = 1 + } + if newBPM > 999 { + newBPM = 999 + } + if newBPM != int(t.song.BPM) { + t.song.BPM = newBPM + t.sequencer.SetRowLength(44100 * 60 / (4 * t.song.BPM)) + return true + } + return false +} + func New(audioContext sointu.AudioContext) *Tracker { t := &Tracker{ + Theme: material.NewTheme(gofont.Collection()), QuitButton: new(widget.Clickable), CurrentOctave: 4, audioContext: audioContext, + OctaveUpBtn: new(widget.Clickable), + OctaveDownBtn: new(widget.Clickable), + BPMUpBtn: new(widget.Clickable), + BPMDownBtn: new(widget.Clickable), setPlaying: make(chan bool), rowJump: make(chan int), patternJump: make(chan int),