feat(tracker): add simple instrument editor

This commit is contained in:
vsariola 2021-01-10 01:51:16 +02:00
parent e62fe85867
commit fa893c94f1
4 changed files with 152 additions and 16 deletions

121
tracker/instruments.go Normal file
View File

@ -0,0 +1,121 @@
package tracker
import (
"fmt"
"sort"
"gioui.org/layout"
"gioui.org/widget"
"gioui.org/widget/material"
)
type C = layout.Context
type D = layout.Dimensions
func (t *Tracker) updateInstrumentScroll() {
if t.CurrentInstrument > 7 {
t.InstrumentList.Position.First = t.CurrentInstrument - 7
} else {
t.InstrumentList.Position.First = 0
}
}
func (t *Tracker) layoutInstruments() layout.Widget {
return func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(t.layoutInstrumentNames()),
layout.Flexed(1, t.layoutInstrumentEditor()))
}
}
func (t *Tracker) layoutInstrumentNames() layout.Widget {
return func(gtx C) D {
count := len(t.song.Patch.Instruments)
if len(t.InstrumentBtns) < count {
tail := make([]*widget.Clickable, count-len(t.InstrumentBtns))
for t := range tail {
tail[t] = new(widget.Clickable)
}
t.InstrumentBtns = append(t.InstrumentBtns, tail...)
}
return t.InstrumentList.Layout(gtx, count, func(gtx C, index int) D {
for t.InstrumentBtns[index].Clicked() {
t.CurrentInstrument = index
}
btnStyle := material.Button(t.Theme, t.InstrumentBtns[index], fmt.Sprintf("%v", index))
btnStyle.Background = transparent
return btnStyle.Layout(gtx)
})
}
}
func (t *Tracker) layoutInstrumentEditor() layout.Widget {
return func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(t.layoutUnitList()),
layout.Rigid(t.layoutUnitControls()))
}
}
func (t *Tracker) layoutUnitList() layout.Widget {
return func(gtx C) D {
units := t.song.Patch.Instruments[t.CurrentInstrument].Units
count := len(units)
if len(t.UnitBtns) < count {
tail := make([]*widget.Clickable, count-len(t.UnitBtns))
for t := range tail {
tail[t] = new(widget.Clickable)
}
t.UnitBtns = append(t.UnitBtns, tail...)
}
children := make([]layout.FlexChild, len(t.song.Patch.Instruments[t.CurrentInstrument].Units))
for i, u := range t.song.Patch.Instruments[t.CurrentInstrument].Units {
for t.UnitBtns[i].Clicked() {
t.CurrentUnit = i
}
btnStyle := material.Button(t.Theme, t.UnitBtns[i], u.Type)
btnStyle.Background = transparent
children[i] = layout.Rigid(btnStyle.Layout)
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
}
}
func (t *Tracker) layoutUnitControls() layout.Widget {
return func(gtx C) D {
params := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters
count := len(params)
children := make([]layout.FlexChild, 0, count)
if len(t.ParameterSliders) < count {
tail := make([]*widget.Float, count-len(t.ParameterSliders))
for t := range tail {
tail[t] = new(widget.Float)
}
t.ParameterSliders = append(t.ParameterSliders, tail...)
}
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
for t.ParameterSliders[i].Changed() {
params[k] = int(t.ParameterSliders[i].Value)
// TODO: tracker should have functions to update parameters and
// to do this efficiently i.e. not compile the whole patch again
t.LoadSong(t.song)
}
t.ParameterSliders[i].Value = float32(params[k])
sliderStyle := material.Slider(t.Theme, t.ParameterSliders[i], 0, 128)
k2 := k // avoid k changing in the closure
children = append(children, layout.Rigid(func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(Label(k2, white)),
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = 200
return sliderStyle.Layout(gtx)
}))
}))
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
}
}

View File

@ -59,6 +59,7 @@ func (t *Tracker) Layout(gtx layout.Context) {
layout.Rigid(t.line(true, separatorLineColor)), layout.Rigid(t.line(true, separatorLineColor)),
layout.Flexed(1, t.layoutTracker)) layout.Flexed(1, t.layoutTracker))
}) })
t.updateInstrumentScroll()
} }
func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
@ -184,6 +185,7 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions {
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.BPMDownBtn, downIcon)), t.song.BPM > 1).Layout) return in.Layout(gtx, enableButton(smallButton(material.IconButton(t.Theme, t.BPMDownBtn, downIcon)), t.song.BPM > 1).Layout)
}), }),
layout.Flexed(1, t.layoutInstruments()),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
iconBtn := enableButton(material.IconButton(t.Theme, t.NewInstrumentBtn, addIcon), t.song.Patch.TotalVoices() < 32) iconBtn := enableButton(material.IconButton(t.Theme, t.NewInstrumentBtn, addIcon), t.song.Patch.TotalVoices() < 32)
return in.Layout(gtx, iconBtn.Layout) return in.Layout(gtx, iconBtn.Layout)

View File

@ -22,6 +22,8 @@ var black = color.RGBA{R: 0, G: 0, B: 0, A: 255}
var yellow = color.RGBA{R: 255, G: 255, B: 130, A: 255} var yellow = color.RGBA{R: 255, G: 255, B: 130, A: 255}
var red = color.RGBA{R: 255, G: 0, B: 0, A: 255} var red = color.RGBA{R: 255, G: 0, B: 0, A: 255}
var transparent = color.RGBA{A: 0}
var primaryColorLight = color.RGBA{R: 243, G: 229, B: 245, A: 255} var primaryColorLight = color.RGBA{R: 243, G: 229, B: 245, A: 255}
var primaryColor = color.RGBA{R: 206, G: 147, B: 216, A: 255} var primaryColor = color.RGBA{R: 206, G: 147, B: 216, A: 255}
var primaryColorDark = color.RGBA{R: 123, G: 31, B: 162, A: 255} var primaryColorDark = color.RGBA{R: 123, G: 31, B: 162, A: 255}

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"gioui.org/font/gofont" "gioui.org/font/gofont"
"gioui.org/layout"
"gioui.org/widget" "gioui.org/widget"
"gioui.org/widget/material" "gioui.org/widget/material"
"github.com/vsariola/sointu" "github.com/vsariola/sointu"
@ -17,22 +18,28 @@ type Tracker struct {
song sointu.Song song sointu.Song
Playing bool Playing bool
// protects PlayPattern and PlayRow // protects PlayPattern and PlayRow
playRowPatMutex sync.RWMutex // protects song and playing playRowPatMutex sync.RWMutex // protects song and playing
PlayPattern int PlayPattern int
PlayRow int PlayRow int
CursorRow int CursorRow int
CursorColumn int CursorColumn int
DisplayPattern int DisplayPattern int
ActiveTrack int ActiveTrack int
CurrentOctave byte CurrentInstrument int
NoteTracking bool CurrentUnit int
Theme *material.Theme CurrentOctave byte
OctaveUpBtn *widget.Clickable NoteTracking bool
OctaveDownBtn *widget.Clickable Theme *material.Theme
BPMUpBtn *widget.Clickable OctaveUpBtn *widget.Clickable
BPMDownBtn *widget.Clickable OctaveDownBtn *widget.Clickable
NewTrackBtn *widget.Clickable BPMUpBtn *widget.Clickable
NewInstrumentBtn *widget.Clickable BPMDownBtn *widget.Clickable
NewTrackBtn *widget.Clickable
NewInstrumentBtn *widget.Clickable
ParameterSliders []*widget.Float
UnitBtns []*widget.Clickable
InstrumentBtns []*widget.Clickable
InstrumentList *layout.List
sequencer *Sequencer sequencer *Sequencer
ticked chan struct{} ticked chan struct{}
@ -75,6 +82,9 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
if t.ActiveTrack >= len(song.Tracks) { if t.ActiveTrack >= len(song.Tracks) {
t.ActiveTrack = len(song.Tracks) - 1 t.ActiveTrack = len(song.Tracks) - 1
} }
if t.sequencer != nil {
t.sequencer.SetSynth(t.synth)
}
return nil return nil
} }
@ -241,6 +251,7 @@ func New(audioContext sointu.AudioContext) *Tracker {
closer: make(chan struct{}), closer: make(chan struct{}),
undoStack: []sointu.Song{}, undoStack: []sointu.Song{},
redoStack: []sointu.Song{}, redoStack: []sointu.Song{},
InstrumentList: &layout.List{Axis: layout.Horizontal},
} }
t.Theme.Color.Primary = primaryColor t.Theme.Color.Primary = primaryColor
t.Theme.Color.InvText = black t.Theme.Color.InvText = black