mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-14 11:04:23 -04:00
feat(tracker): adding, setting, unsetting and deleting units
This commit is contained in:
@ -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")
|
return nil, errors.New("Each instrument must have at least 1 voice")
|
||||||
}
|
}
|
||||||
for _, unit := range instr.Units {
|
for _, unit := range instr.Units {
|
||||||
|
if unit.Type == "" { // empty units are just ignored & skipped
|
||||||
|
continue
|
||||||
|
}
|
||||||
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
|
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"])}
|
s := SampleOffset{Start: uint32(unit.Parameters["start"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
|
||||||
index, ok := sampleOffsetMap[s]
|
index, ok := sampleOffsetMap[s]
|
||||||
|
@ -1,14 +1,65 @@
|
|||||||
package tracker
|
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{
|
var defaultInstrument = sointu.Instrument{
|
||||||
NumVoices: 1,
|
NumVoices: 1,
|
||||||
Units: []sointu.Unit{
|
Units: []sointu.Unit{
|
||||||
{Type: "envelope", Parameters: map[string]int{"stereo": 1, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 64}},
|
defaultUnits["envelope"],
|
||||||
{Type: "oscillator", Parameters: map[string]int{"stereo": 1, "transpose": 64, "detune": 64, "phase": 0, "color": 128, "shape": 64, "gain": 64, "type": sointu.Sine}},
|
defaultUnits["oscillator"],
|
||||||
{Type: "mulp", Parameters: map[string]int{"stereo": 1}},
|
defaultUnits["mulp"],
|
||||||
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}},
|
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{{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}}},
|
{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{
|
Patch: sointu.Patch{Instruments: []sointu.Instrument{{NumVoices: 4, Units: defaultInstrument.Units}}},
|
||||||
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}},
|
|
||||||
}}}},
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ package tracker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
@ -52,7 +53,7 @@ func (t *Tracker) layoutInstruments() layout.Widget {
|
|||||||
|
|
||||||
func (t *Tracker) layoutInstrumentHeader() layout.Widget {
|
func (t *Tracker) layoutInstrumentHeader() layout.Widget {
|
||||||
headerBg := func(gtx C) D {
|
headerBg := func(gtx C) D {
|
||||||
paint.FillShape(gtx.Ops, trackMenuSurfaceColor, clip.Rect{
|
paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{
|
||||||
Max: gtx.Constraints.Min,
|
Max: gtx.Constraints.Min,
|
||||||
}.Op())
|
}.Op())
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
||||||
@ -129,82 +130,65 @@ func (t *Tracker) layoutInstrumentNames() layout.Widget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (t *Tracker) layoutInstrumentEditor() 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 {
|
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,
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
layout.Rigid(t.layoutUnitList()),
|
layout.Rigid(func(gtx C) D {
|
||||||
layout.Rigid(t.layoutUnitControls()))
|
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 {
|
func (t *Tracker) layoutUnitList() layout.Widget {
|
||||||
return func(gtx C) D {
|
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
|
units := t.song.Patch.Instruments[t.CurrentInstrument].Units
|
||||||
count := len(units)
|
count := len(units)
|
||||||
if len(t.UnitBtns) < count {
|
for len(t.UnitBtns) < count {
|
||||||
tail := make([]*widget.Clickable, count-len(t.UnitBtns))
|
t.UnitBtns = append(t.UnitBtns, new(widget.Clickable))
|
||||||
for t := range tail {
|
|
||||||
tail[t] = new(widget.Clickable)
|
|
||||||
}
|
}
|
||||||
t.UnitBtns = append(t.UnitBtns, tail...)
|
|
||||||
}
|
listElem := func(gtx C, i int) D {
|
||||||
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() {
|
for t.UnitBtns[i].Clicked() {
|
||||||
t.CurrentUnit = i
|
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)}
|
labelStyle := LabelStyle{Text: u.Type, ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)}
|
||||||
children[i] = layout.Rigid(func(gtx C) D {
|
if labelStyle.Text == "" {
|
||||||
dims := labelStyle.Layout(gtx)
|
labelStyle.Text = "---"
|
||||||
gtx.Constraints = layout.Exact(dims.Size)
|
labelStyle.Alignment = layout.Center
|
||||||
t.UnitBtns[i2].Layout(gtx)
|
|
||||||
return dims
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
|
bg := func(gtx C) D {
|
||||||
}
|
gtx.Constraints = layout.Exact(image.Pt(120, 20))
|
||||||
}
|
var color color.NRGBA
|
||||||
|
if t.CurrentUnit == i {
|
||||||
func (t *Tracker) layoutUnitControls() layout.Widget {
|
color = unitListSelectedColor
|
||||||
return func(gtx C) D {
|
} else if t.UnitBtns[i].Hovered() {
|
||||||
params := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters
|
color = unitListHighlightColor
|
||||||
count := len(params)
|
}
|
||||||
children := make([]layout.FlexChild, 0, count)
|
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
||||||
if len(t.ParameterSliders) < count {
|
return D{Size: gtx.Constraints.Min}
|
||||||
tail := make([]*widget.Float, count-len(t.ParameterSliders))
|
}
|
||||||
for t := range tail {
|
return layout.Stack{Alignment: layout.W}.Layout(gtx,
|
||||||
tail[t] = new(widget.Float)
|
layout.Stacked(bg),
|
||||||
}
|
layout.Expanded(labelStyle.Layout),
|
||||||
t.ParameterSliders = append(t.ParameterSliders, tail...)
|
layout.Expanded(t.UnitBtns[i].Layout))
|
||||||
}
|
}
|
||||||
keys := make([]string, 0, len(params))
|
return t.UnitList.Layout(gtx, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), listElem)
|
||||||
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...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,13 @@ type LabelStyle struct {
|
|||||||
Text string
|
Text string
|
||||||
Color color.NRGBA
|
Color color.NRGBA
|
||||||
ShadeColor color.NRGBA
|
ShadeColor color.NRGBA
|
||||||
|
Alignment layout.Direction
|
||||||
Font text.Font
|
Font text.Font
|
||||||
FontSize unit.Value
|
FontSize unit.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
|
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 {
|
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
||||||
defer op.Save(gtx.Ops).Load()
|
defer op.Save(gtx.Ops).Load()
|
||||||
paint.ColorOp{Color: l.ShadeColor}.Add(gtx.Ops)
|
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 {
|
func Label(str string, color color.NRGBA) layout.Widget {
|
||||||
return LabelStyle{Text: text, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize}.Layout
|
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 {
|
bg := func(gtx C) D {
|
||||||
|
pointer.PassOp{Pass: true}.Add(gtx.Ops)
|
||||||
pointer.InputOp{Tag: s.Visible,
|
pointer.InputOp{Tag: s.Visible,
|
||||||
Types: pointer.Press,
|
Types: pointer.Press,
|
||||||
}.Add(gtx.Ops)
|
}.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 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 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 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
|
CursorColumn int
|
||||||
CurrentInstrument int
|
CurrentInstrument int
|
||||||
CurrentUnit int
|
CurrentUnit int
|
||||||
|
UnitGroupMenuVisible bool
|
||||||
|
UnitGroupMenuIndex int
|
||||||
|
UnitSubMenuIndex int
|
||||||
NoteTracking bool
|
NoteTracking bool
|
||||||
Theme *material.Theme
|
Theme *material.Theme
|
||||||
Octave *NumberInput
|
Octave *NumberInput
|
||||||
@ -47,7 +50,13 @@ type Tracker struct {
|
|||||||
FileMenuVisible bool
|
FileMenuVisible bool
|
||||||
ParameterSliders []*widget.Float
|
ParameterSliders []*widget.Float
|
||||||
UnitBtns []*widget.Clickable
|
UnitBtns []*widget.Clickable
|
||||||
|
UnitList *layout.List
|
||||||
|
DeleteUnitBtn *widget.Clickable
|
||||||
|
ClearUnitBtn *widget.Clickable
|
||||||
|
ChooseUnitTypeList *layout.List
|
||||||
|
ChooseUnitTypeBtns []*widget.Clickable
|
||||||
InstrumentBtns []*widget.Clickable
|
InstrumentBtns []*widget.Clickable
|
||||||
|
AddUnitBtn *widget.Clickable
|
||||||
InstrumentList *layout.List
|
InstrumentList *layout.List
|
||||||
TrackHexCheckBoxes []*widget.Bool
|
TrackHexCheckBoxes []*widget.Bool
|
||||||
TrackShowHex []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() {
|
func (t *Tracker) ClampPositions() {
|
||||||
t.PlayPosition.Clamp(t.song)
|
t.PlayPosition.Clamp(t.song)
|
||||||
t.Cursor.Clamp(t.song)
|
t.Cursor.Clamp(t.song)
|
||||||
@ -416,6 +470,10 @@ func New(audioContext sointu.AudioContext) *Tracker {
|
|||||||
SubtractSemitoneBtn: new(widget.Clickable),
|
SubtractSemitoneBtn: new(widget.Clickable),
|
||||||
AddOctaveBtn: new(widget.Clickable),
|
AddOctaveBtn: new(widget.Clickable),
|
||||||
SubtractOctaveBtn: 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),
|
setPlaying: make(chan bool),
|
||||||
rowJump: make(chan int),
|
rowJump: make(chan int),
|
||||||
patternJump: make(chan int),
|
patternJump: make(chan int),
|
||||||
@ -427,12 +485,16 @@ func New(audioContext sointu.AudioContext) *Tracker {
|
|||||||
TopHorizontalSplit: new(Split),
|
TopHorizontalSplit: new(Split),
|
||||||
BottomHorizontalSplit: new(Split),
|
BottomHorizontalSplit: new(Split),
|
||||||
VerticalSplit: new(Split),
|
VerticalSplit: new(Split),
|
||||||
|
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
|
||||||
}
|
}
|
||||||
t.Octave.Value = 4
|
t.Octave.Value = 4
|
||||||
t.VerticalSplit.Axis = layout.Vertical
|
t.VerticalSplit.Axis = layout.Vertical
|
||||||
t.BottomHorizontalSplit.Ratio = -.5
|
t.BottomHorizontalSplit.Ratio = -.5
|
||||||
t.Theme.Palette.Fg = primaryColor
|
t.Theme.Palette.Fg = primaryColor
|
||||||
t.Theme.Palette.ContrastFg = black
|
t.Theme.Palette.ContrastFg = black
|
||||||
|
for range allUnits {
|
||||||
|
t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable))
|
||||||
|
}
|
||||||
go t.sequencerLoop(t.closer)
|
go t.sequencerLoop(t.closer)
|
||||||
if err := t.LoadSong(defaultSong.Copy()); err != nil {
|
if err := t.LoadSong(defaultSong.Copy()); err != nil {
|
||||||
panic(fmt.Errorf("cannot load default song: %w", err))
|
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)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user