mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
feat(tracker): adding, setting, unsetting and deleting units
This commit is contained in:
parent
29b289d2fb
commit
6307dd51de
@ -33,6 +33,9 @@ func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
|
||||
return nil, errors.New("Each instrument must have at least 1 voice")
|
||||
}
|
||||
for _, unit := range instr.Units {
|
||||
if unit.Type == "" { // empty units are just ignored & skipped
|
||||
continue
|
||||
}
|
||||
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
|
||||
s := SampleOffset{Start: uint32(unit.Parameters["start"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
|
||||
index, ok := sampleOffsetMap[s]
|
||||
|
@ -1,14 +1,65 @@
|
||||
package tracker
|
||||
|
||||
import "github.com/vsariola/sointu"
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
)
|
||||
|
||||
var defaultUnits = map[string]sointu.Unit{
|
||||
"envelope": {Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 64, "gain": 64}},
|
||||
"oscillator": {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 64, "shape": 64, "gain": 64, "type": sointu.Sine}},
|
||||
"noise": {Type: "noise", Parameters: map[string]int{"stereo": 0, "shape": 64, "gain": 64}},
|
||||
"mulp": {Type: "mulp", Parameters: map[string]int{"stereo": 0}},
|
||||
"mul": {Type: "mul", Parameters: map[string]int{"stereo": 0}},
|
||||
"add": {Type: "add", Parameters: map[string]int{"stereo": 0}},
|
||||
"addp": {Type: "addp", Parameters: map[string]int{"stereo": 0}},
|
||||
"push": {Type: "push", Parameters: map[string]int{"stereo": 0}},
|
||||
"pop": {Type: "pop", Parameters: map[string]int{"stereo": 0}},
|
||||
"xch": {Type: "xch", Parameters: map[string]int{"stereo": 0}},
|
||||
"receive": {Type: "receive", Parameters: map[string]int{"stereo": 0}},
|
||||
"loadnote": {Type: "loadnote", Parameters: map[string]int{"stereo": 0}},
|
||||
"loadval": {Type: "loadval", Parameters: map[string]int{"stereo": 0, "value": 64}},
|
||||
"pan": {Type: "pan", Parameters: map[string]int{"stereo": 0, "panning": 64}},
|
||||
"gain": {Type: "gain", Parameters: map[string]int{"stereo": 0, "gain": 64}},
|
||||
"invgain": {Type: "invgain", Parameters: map[string]int{"stereo": 0, "invgain": 64}},
|
||||
"crush": {Type: "crush", Parameters: map[string]int{"stereo": 0, "resolution": 64}},
|
||||
"clip": {Type: "clip", Parameters: map[string]int{"stereo": 0}},
|
||||
"hold": {Type: "hold", Parameters: map[string]int{"stereo": 0, "holdfreq": 64}},
|
||||
"distort": {Type: "distort", Parameters: map[string]int{"stereo": 0, "drive": 64}},
|
||||
"filter": {Type: "filter", Parameters: map[string]int{"stereo": 0, "frequency": 64, "resonance": 64, "lowpass": 1, "bandpass": 0, "highpass": 0, "negbandpass": 0, "neghighpass": 0}},
|
||||
"out": {Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}},
|
||||
"outaux": {Type: "outaux", Parameters: map[string]int{"stereo": 1, "outgain": 64, "auxgain": 64}},
|
||||
"aux": {Type: "aux", Parameters: map[string]int{"stereo": 1, "gain": 64, "channel": 2}},
|
||||
"delay": {Type: "delay",
|
||||
Parameters: map[string]int{"damp": 0, "dry": 128, "feedback": 96, "notetracking": 0, "pregain": 40, "stereo": 0},
|
||||
VarArgs: []int{1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618,
|
||||
1140, 1212, 1300, 1380, 1446, 1516, 1580, 1642,
|
||||
}},
|
||||
"in": {Type: "in", Parameters: map[string]int{"stereo": 1, "channel": 2}},
|
||||
"speed": {Type: "speed", Parameters: map[string]int{}},
|
||||
"compressor": {Type: "compressor", Parameters: map[string]int{"stereo": 0, "attack": 64, "release": 64, "invgain": 64, "threshold": 64, "ratio": 64}},
|
||||
}
|
||||
|
||||
var allUnits []string
|
||||
|
||||
func init() {
|
||||
allUnits = make([]string, 0, len(sointu.UnitTypes))
|
||||
for k := range sointu.UnitTypes {
|
||||
allUnits = append(allUnits, k)
|
||||
}
|
||||
sort.Strings(allUnits)
|
||||
}
|
||||
|
||||
var defaultInstrument = sointu.Instrument{
|
||||
NumVoices: 1,
|
||||
Units: []sointu.Unit{
|
||||
{Type: "envelope", Parameters: map[string]int{"stereo": 1, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 64}},
|
||||
{Type: "oscillator", Parameters: map[string]int{"stereo": 1, "transpose": 64, "detune": 64, "phase": 0, "color": 128, "shape": 64, "gain": 64, "type": sointu.Sine}},
|
||||
{Type: "mulp", Parameters: map[string]int{"stereo": 1}},
|
||||
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}},
|
||||
defaultUnits["envelope"],
|
||||
defaultUnits["oscillator"],
|
||||
defaultUnits["mulp"],
|
||||
defaultUnits["delay"],
|
||||
defaultUnits["pan"],
|
||||
defaultUnits["out"],
|
||||
},
|
||||
}
|
||||
|
||||
@ -20,16 +71,5 @@ var defaultSong = sointu.Song{
|
||||
{NumVoices: 2, Sequence: []byte{0, 0, 0, 1}, 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}}},
|
||||
{NumVoices: 2, Sequence: []byte{0, 0, 0, 1}, Patterns: [][]byte{{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}}},
|
||||
},
|
||||
Patch: sointu.Patch{
|
||||
Instruments: []sointu.Instrument{{NumVoices: 4, Units: []sointu.Unit{
|
||||
{Type: "envelope", Parameters: map[string]int{"stereo": 1, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 64}},
|
||||
{Type: "oscillator", Parameters: map[string]int{"stereo": 1, "transpose": 64, "detune": 64, "phase": 0, "color": 128, "shape": 64, "gain": 64, "type": sointu.Sine}},
|
||||
{Type: "mulp", Parameters: map[string]int{"stereo": 1}},
|
||||
{Type: "delay",
|
||||
Parameters: map[string]int{"damp": 0, "dry": 128, "feedback": 96, "notetracking": 0, "pregain": 40, "stereo": 1},
|
||||
VarArgs: []int{1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618,
|
||||
1140, 1212, 1300, 1380, 1446, 1516, 1580, 1642,
|
||||
}},
|
||||
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}},
|
||||
}}}},
|
||||
Patch: sointu.Patch{Instruments: []sointu.Instrument{{NumVoices: 4, Units: defaultInstrument.Units}}},
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ package tracker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
@ -52,7 +53,7 @@ func (t *Tracker) layoutInstruments() layout.Widget {
|
||||
|
||||
func (t *Tracker) layoutInstrumentHeader() layout.Widget {
|
||||
headerBg := func(gtx C) D {
|
||||
paint.FillShape(gtx.Ops, trackMenuSurfaceColor, clip.Rect{
|
||||
paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{
|
||||
Max: gtx.Constraints.Min,
|
||||
}.Op())
|
||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||
@ -129,82 +130,65 @@ func (t *Tracker) layoutInstrumentNames() layout.Widget {
|
||||
}
|
||||
}
|
||||
func (t *Tracker) layoutInstrumentEditor() layout.Widget {
|
||||
for t.AddUnitBtn.Clicked() {
|
||||
t.AddUnit()
|
||||
}
|
||||
addUnitBtnStyle := material.IconButton(t.Theme, t.AddUnitBtn, widgetForIcon(icons.ContentAdd))
|
||||
addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4))
|
||||
margin := layout.UniformInset(unit.Dp(2))
|
||||
|
||||
return func(gtx C) D {
|
||||
paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{
|
||||
Max: gtx.Constraints.Max,
|
||||
}.Op())
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(t.layoutUnitList()),
|
||||
layout.Rigid(t.layoutUnitControls()))
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
|
||||
layout.Expanded(t.layoutUnitList()),
|
||||
layout.Stacked(func(gtx C) D {
|
||||
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
||||
}))
|
||||
}),
|
||||
layout.Rigid(t.layoutUnitEditor()))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) layoutUnitList() layout.Widget {
|
||||
return func(gtx C) D {
|
||||
paint.FillShape(gtx.Ops, unitListSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
|
||||
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
||||
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...)
|
||||
for len(t.UnitBtns) < count {
|
||||
t.UnitBtns = append(t.UnitBtns, new(widget.Clickable))
|
||||
}
|
||||
children := make([]layout.FlexChild, len(t.song.Patch.Instruments[t.CurrentInstrument].Units))
|
||||
for i, u := range t.song.Patch.Instruments[t.CurrentInstrument].Units {
|
||||
|
||||
listElem := func(gtx C, i int) D {
|
||||
for t.UnitBtns[i].Clicked() {
|
||||
t.CurrentUnit = i
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
}
|
||||
i2 := i
|
||||
u := t.song.Patch.Instruments[t.CurrentInstrument].Units[i]
|
||||
labelStyle := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
||||
children[i] = layout.Rigid(func(gtx C) D {
|
||||
dims := labelStyle.Layout(gtx)
|
||||
gtx.Constraints = layout.Exact(dims.Size)
|
||||
t.UnitBtns[i2].Layout(gtx)
|
||||
return dims
|
||||
})
|
||||
if labelStyle.Text == "" {
|
||||
labelStyle.Text = "---"
|
||||
labelStyle.Alignment = layout.Center
|
||||
}
|
||||
bg := func(gtx C) D {
|
||||
gtx.Constraints = layout.Exact(image.Pt(120, 20))
|
||||
var color color.NRGBA
|
||||
if t.CurrentUnit == i {
|
||||
color = unitListSelectedColor
|
||||
} else if t.UnitBtns[i].Hovered() {
|
||||
color = unitListHighlightColor
|
||||
}
|
||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
||||
return D{Size: gtx.Constraints.Min}
|
||||
}
|
||||
return layout.Stack{Alignment: layout.W}.Layout(gtx,
|
||||
layout.Stacked(bg),
|
||||
layout.Expanded(labelStyle.Layout),
|
||||
layout.Expanded(t.UnitBtns[i].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)
|
||||
sliderStyle.Color = t.Theme.Fg
|
||||
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...)
|
||||
return t.UnitList.Layout(gtx, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), listElem)
|
||||
}
|
||||
}
|
||||
|
@ -17,12 +17,13 @@ type LabelStyle struct {
|
||||
Text string
|
||||
Color color.NRGBA
|
||||
ShadeColor color.NRGBA
|
||||
Alignment layout.Direction
|
||||
Font text.Font
|
||||
FontSize unit.Value
|
||||
}
|
||||
|
||||
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
||||
return layout.Stack{Alignment: l.Alignment}.Layout(gtx,
|
||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
paint.ColorOp{Color: l.ShadeColor}.Add(gtx.Ops)
|
||||
@ -46,6 +47,6 @@ func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
)
|
||||
}
|
||||
|
||||
func Label(text string, color color.NRGBA) layout.Widget {
|
||||
return LabelStyle{Text: text, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize}.Layout
|
||||
func Label(str string, color color.NRGBA) layout.Widget {
|
||||
return LabelStyle{Text: str, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize, Alignment: layout.W}.Layout
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ func (s PopupStyle) Layout(gtx C, contents layout.Widget) D {
|
||||
}
|
||||
|
||||
bg := func(gtx C) D {
|
||||
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
||||
pointer.InputOp{Tag: s.Visible,
|
||||
Types: pointer.Press,
|
||||
}.Add(gtx.Ops)
|
||||
|
@ -85,9 +85,16 @@ var patternSelectionColor = color.NRGBA{R: 19, G: 40, B: 60, A: 128}
|
||||
|
||||
var inactiveBtnColor = color.NRGBA{R: 61, G: 55, B: 55, A: 255}
|
||||
|
||||
var instrumentSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
|
||||
var instrumentSurfaceColor = color.NRGBA{R: 45, G: 45, B: 45, A: 255}
|
||||
|
||||
var songSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
|
||||
|
||||
var popupSurfaceColor = color.NRGBA{R: 45, G: 45, B: 46, A: 255}
|
||||
var popupSurfaceColor = color.NRGBA{R: 50, G: 50, B: 51, A: 255}
|
||||
var popupShadowColor = color.NRGBA{R: 0, G: 0, B: 0, A: 192}
|
||||
|
||||
var unitListSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
|
||||
var unitListSelectedColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255}
|
||||
var unitListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
|
||||
|
||||
var unitSurfaceColor = color.NRGBA{R: 30, G: 30, B: 30, A: 255}
|
||||
var unitTypeListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
|
||||
|
@ -25,6 +25,9 @@ type Tracker struct {
|
||||
CursorColumn int
|
||||
CurrentInstrument int
|
||||
CurrentUnit int
|
||||
UnitGroupMenuVisible bool
|
||||
UnitGroupMenuIndex int
|
||||
UnitSubMenuIndex int
|
||||
NoteTracking bool
|
||||
Theme *material.Theme
|
||||
Octave *NumberInput
|
||||
@ -47,7 +50,13 @@ type Tracker struct {
|
||||
FileMenuVisible bool
|
||||
ParameterSliders []*widget.Float
|
||||
UnitBtns []*widget.Clickable
|
||||
UnitList *layout.List
|
||||
DeleteUnitBtn *widget.Clickable
|
||||
ClearUnitBtn *widget.Clickable
|
||||
ChooseUnitTypeList *layout.List
|
||||
ChooseUnitTypeBtns []*widget.Clickable
|
||||
InstrumentBtns []*widget.Clickable
|
||||
AddUnitBtn *widget.Clickable
|
||||
InstrumentList *layout.List
|
||||
TrackHexCheckBoxes []*widget.Bool
|
||||
TrackShowHex []bool
|
||||
@ -329,6 +338,51 @@ func (t *Tracker) SetRowsPerPattern(value int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) SetUnit(typ string) {
|
||||
unit, ok := defaultUnits[typ]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if unit.Type == t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type {
|
||||
return
|
||||
}
|
||||
t.SaveUndo()
|
||||
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] = unit.Copy()
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
func (t *Tracker) AddUnit() {
|
||||
t.SaveUndo()
|
||||
units := make([]sointu.Unit, len(t.song.Patch.Instruments[t.CurrentInstrument].Units)+1)
|
||||
copy(units, t.song.Patch.Instruments[t.CurrentInstrument].Units[:t.CurrentUnit+1])
|
||||
copy(units[t.CurrentUnit+2:], t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit+1:])
|
||||
t.song.Patch.Instruments[t.CurrentInstrument].Units = units
|
||||
t.CurrentUnit++
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
func (t *Tracker) ClearUnit() {
|
||||
t.SaveUndo()
|
||||
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type = ""
|
||||
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters = make(map[string]int)
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
func (t *Tracker) DeleteUnit() {
|
||||
if len(t.song.Patch.Instruments[t.CurrentInstrument].Units) <= 1 {
|
||||
return
|
||||
}
|
||||
t.SaveUndo()
|
||||
units := make([]sointu.Unit, len(t.song.Patch.Instruments[t.CurrentInstrument].Units)-1)
|
||||
copy(units, t.song.Patch.Instruments[t.CurrentInstrument].Units[:t.CurrentUnit])
|
||||
copy(units[t.CurrentUnit:], t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit+1:])
|
||||
t.song.Patch.Instruments[t.CurrentInstrument].Units = units
|
||||
if t.CurrentUnit > 0 {
|
||||
t.CurrentUnit--
|
||||
}
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
func (t *Tracker) ClampPositions() {
|
||||
t.PlayPosition.Clamp(t.song)
|
||||
t.Cursor.Clamp(t.song)
|
||||
@ -416,6 +470,10 @@ func New(audioContext sointu.AudioContext) *Tracker {
|
||||
SubtractSemitoneBtn: new(widget.Clickable),
|
||||
AddOctaveBtn: new(widget.Clickable),
|
||||
SubtractOctaveBtn: new(widget.Clickable),
|
||||
AddUnitBtn: new(widget.Clickable),
|
||||
DeleteUnitBtn: new(widget.Clickable),
|
||||
ClearUnitBtn: new(widget.Clickable),
|
||||
UnitList: &layout.List{Axis: layout.Vertical},
|
||||
setPlaying: make(chan bool),
|
||||
rowJump: make(chan int),
|
||||
patternJump: make(chan int),
|
||||
@ -427,12 +485,16 @@ func New(audioContext sointu.AudioContext) *Tracker {
|
||||
TopHorizontalSplit: new(Split),
|
||||
BottomHorizontalSplit: new(Split),
|
||||
VerticalSplit: new(Split),
|
||||
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
|
||||
}
|
||||
t.Octave.Value = 4
|
||||
t.VerticalSplit.Axis = layout.Vertical
|
||||
t.BottomHorizontalSplit.Ratio = -.5
|
||||
t.Theme.Palette.Fg = primaryColor
|
||||
t.Theme.Palette.ContrastFg = black
|
||||
for range allUnits {
|
||||
t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable))
|
||||
}
|
||||
go t.sequencerLoop(t.closer)
|
||||
if err := t.LoadSong(defaultSong.Copy()); err != nil {
|
||||
panic(fmt.Errorf("cannot load default song: %w", err))
|
||||
|
132
tracker/uniteditor.go
Normal file
132
tracker/uniteditor.go
Normal file
@ -0,0 +1,132 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"sort"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func (t *Tracker) layoutUnitEditor() layout.Widget {
|
||||
editorFunc := t.layoutUnitSliders
|
||||
if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" {
|
||||
editorFunc = t.layoutUnitTypeChooser
|
||||
}
|
||||
return func(gtx C) D {
|
||||
paint.FillShape(gtx.Ops, unitSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Flexed(1, editorFunc()),
|
||||
layout.Rigid(t.layoutUnitFooter()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) layoutUnitSliders() 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)
|
||||
sliderStyle.Color = t.Theme.Fg
|
||||
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...)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) layoutUnitFooter() layout.Widget {
|
||||
return func(gtx C) D {
|
||||
for t.ClearUnitBtn.Clicked() {
|
||||
t.ClearUnit()
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
}
|
||||
for t.DeleteUnitBtn.Clicked() {
|
||||
t.DeleteUnit()
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
}
|
||||
deleteUnitBtnStyle := material.IconButton(t.Theme, t.DeleteUnitBtn, widgetForIcon(icons.ActionDelete))
|
||||
deleteUnitBtnStyle.Background = transparent
|
||||
deleteUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||
if len(t.song.Patch.Instruments[t.CurrentInstrument].Units) > 1 {
|
||||
deleteUnitBtnStyle.Color = primaryColor
|
||||
} else {
|
||||
deleteUnitBtnStyle.Color = disabledTextColor
|
||||
}
|
||||
if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||
layout.Rigid(deleteUnitBtnStyle.Layout))
|
||||
}
|
||||
clearUnitBtnStyle := material.IconButton(t.Theme, t.ClearUnitBtn, widgetForIcon(icons.ContentClear))
|
||||
clearUnitBtnStyle.Color = primaryColor
|
||||
clearUnitBtnStyle.Background = transparent
|
||||
clearUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||
layout.Rigid(clearUnitBtnStyle.Layout),
|
||||
layout.Rigid(deleteUnitBtnStyle.Layout))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) layoutUnitTypeChooser() layout.Widget {
|
||||
return func(gtx C) D {
|
||||
paint.FillShape(gtx.Ops, unitSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
||||
listElem := func(gtx C, i int) D {
|
||||
for t.ChooseUnitTypeBtns[i].Clicked() {
|
||||
u := defaultUnits[allUnits[i]]
|
||||
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] = (&u).Copy()
|
||||
}
|
||||
labelStyle := LabelStyle{Text: allUnits[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
||||
bg := func(gtx C) D {
|
||||
gtx.Constraints = layout.Exact(image.Pt(120, 20))
|
||||
var color color.NRGBA
|
||||
if t.ChooseUnitTypeBtns[i].Hovered() {
|
||||
color = unitTypeListHighlightColor
|
||||
}
|
||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
||||
return D{Size: gtx.Constraints.Min}
|
||||
}
|
||||
return layout.Stack{Alignment: layout.W}.Layout(gtx,
|
||||
layout.Stacked(bg),
|
||||
layout.Expanded(labelStyle.Layout),
|
||||
layout.Expanded(t.ChooseUnitTypeBtns[i].Layout))
|
||||
}
|
||||
return t.ChooseUnitTypeList.Layout(gtx, len(allUnits), listElem)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user