mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
feat(tracker): implement edit modes, resembling tab stops
This commit is contained in:
parent
7408956f77
commit
38008bdb87
@ -38,6 +38,10 @@ func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
|
|||||||
}
|
}
|
||||||
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
|
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
|
||||||
s := SampleOffset{Start: uint32(unit.Parameters["samplestart"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
|
s := SampleOffset{Start: uint32(unit.Parameters["samplestart"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
|
||||||
|
if s.LoopLength == 0 {
|
||||||
|
// hacky quick fix: looplength 0 causes div by zero so avoid crashing
|
||||||
|
s.LoopLength = 1
|
||||||
|
}
|
||||||
index, ok := sampleOffsetMap[s]
|
index, ok := sampleOffsetMap[s]
|
||||||
if !ok {
|
if !ok {
|
||||||
index = len(c.SampleOffsets)
|
index = len(c.SampleOffsets)
|
||||||
|
@ -255,7 +255,7 @@ var UnitTypes = map[string]([]UnitParameter){
|
|||||||
{Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false},
|
{Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false},
|
||||||
{Name: "samplestart", MinValue: 0, MaxValue: 1720329, CanSet: true, CanModulate: false},
|
{Name: "samplestart", MinValue: 0, MaxValue: 1720329, CanSet: true, CanModulate: false},
|
||||||
{Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false},
|
{Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false},
|
||||||
{Name: "looplength", MinValue: 1, MaxValue: 65535, CanSet: true, CanModulate: false}},
|
{Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}},
|
||||||
"loadval": []UnitParameter{
|
"loadval": []UnitParameter{
|
||||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||||
{Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
{Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||||
|
@ -24,7 +24,6 @@ type DragList struct {
|
|||||||
|
|
||||||
type FilledDragListStyle struct {
|
type FilledDragListStyle struct {
|
||||||
dragList *DragList
|
dragList *DragList
|
||||||
SurfaceColor color.NRGBA
|
|
||||||
HoverColor color.NRGBA
|
HoverColor color.NRGBA
|
||||||
SelectedColor color.NRGBA
|
SelectedColor color.NRGBA
|
||||||
Count int
|
Count int
|
||||||
@ -38,7 +37,6 @@ func FilledDragList(th *material.Theme, dragList *DragList, count int, element f
|
|||||||
element: element,
|
element: element,
|
||||||
swap: swap,
|
swap: swap,
|
||||||
Count: count,
|
Count: count,
|
||||||
SurfaceColor: dragListSurfaceColor,
|
|
||||||
HoverColor: dragListHoverColor,
|
HoverColor: dragListHoverColor,
|
||||||
SelectedColor: dragListSelectedColor,
|
SelectedColor: dragListSelectedColor,
|
||||||
}
|
}
|
||||||
@ -47,7 +45,6 @@ func FilledDragList(th *material.Theme, dragList *DragList, count int, element f
|
|||||||
func (s *FilledDragListStyle) Layout(gtx C) D {
|
func (s *FilledDragListStyle) Layout(gtx C) D {
|
||||||
swap := 0
|
swap := 0
|
||||||
|
|
||||||
paint.FillShape(gtx.Ops, s.SurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
|
||||||
defer op.Save(gtx.Ops).Load()
|
defer op.Save(gtx.Ops).Load()
|
||||||
|
|
||||||
if s.dragList.List.Axis == layout.Horizontal {
|
if s.dragList.List.Axis == layout.Horizontal {
|
||||||
|
@ -6,8 +6,6 @@ import (
|
|||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
|
||||||
"gioui.org/op/paint"
|
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
@ -50,12 +48,6 @@ func (t *Tracker) layoutInstruments(gtx C) D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutInstrumentHeader(gtx C) D {
|
func (t *Tracker) layoutInstrumentHeader(gtx C) D {
|
||||||
headerBg := func(gtx C) D {
|
|
||||||
paint.FillShape(gtx.Ops, instrumentSurfaceColor, clip.Rect{
|
|
||||||
Max: gtx.Constraints.Min,
|
|
||||||
}.Op())
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
|
||||||
}
|
|
||||||
header := func(gtx C) D {
|
header := func(gtx C) D {
|
||||||
deleteInstrumentBtnStyle := material.IconButton(t.Theme, t.DeleteInstrumentBtn, widgetForIcon(icons.ActionDelete))
|
deleteInstrumentBtnStyle := material.IconButton(t.Theme, t.DeleteInstrumentBtn, widgetForIcon(icons.ActionDelete))
|
||||||
deleteInstrumentBtnStyle.Background = transparent
|
deleteInstrumentBtnStyle.Background = transparent
|
||||||
@ -87,9 +79,7 @@ func (t *Tracker) layoutInstrumentHeader(gtx C) D {
|
|||||||
for t.DeleteInstrumentBtn.Clicked() {
|
for t.DeleteInstrumentBtn.Clicked() {
|
||||||
t.DeleteInstrument()
|
t.DeleteInstrument()
|
||||||
}
|
}
|
||||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
return Surface{Gray: 37, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, header)
|
||||||
layout.Expanded(headerBg),
|
|
||||||
layout.Stacked(header))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutInstrumentNames(gtx C) D {
|
func (t *Tracker) layoutInstrumentNames(gtx C) D {
|
||||||
@ -135,10 +125,13 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
color := inactiveLightSurfaceColor
|
||||||
|
if t.EditMode == EditUnits || t.EditMode == EditParameters {
|
||||||
|
color = activeLightSurfaceColor
|
||||||
|
}
|
||||||
instrumentList := FilledDragList(t.Theme, t.InstrumentDragList, len(t.song.Patch.Instruments), element, t.SwapInstruments)
|
instrumentList := FilledDragList(t.Theme, t.InstrumentDragList, len(t.song.Patch.Instruments), element, t.SwapInstruments)
|
||||||
instrumentList.SelectedColor = instrumentSurfaceColor
|
instrumentList.SelectedColor = color
|
||||||
instrumentList.HoverColor = instrumentHoverColor
|
instrumentList.HoverColor = instrumentHoverColor
|
||||||
instrumentList.SurfaceColor = transparent
|
|
||||||
|
|
||||||
t.InstrumentDragList.SelectedItem = t.CurrentInstrument
|
t.InstrumentDragList.SelectedItem = t.CurrentInstrument
|
||||||
defer op.Save(gtx.Ops).Load()
|
defer op.Save(gtx.Ops).Load()
|
||||||
@ -174,21 +167,28 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D {
|
|||||||
|
|
||||||
unitList := FilledDragList(t.Theme, t.UnitDragList, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), element, t.SwapUnits)
|
unitList := FilledDragList(t.Theme, t.UnitDragList, len(t.song.Patch.Instruments[t.CurrentInstrument].Units), element, t.SwapUnits)
|
||||||
|
|
||||||
|
if t.EditMode == EditUnits {
|
||||||
|
unitList.SelectedColor = cursorColor
|
||||||
|
}
|
||||||
|
|
||||||
t.UnitDragList.SelectedItem = t.CurrentUnit
|
t.UnitDragList.SelectedItem = t.CurrentUnit
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
return Surface{Gray: 30, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, func(gtx C) D {
|
||||||
layout.Rigid(func(gtx C) D {
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
|
layout.Rigid(func(gtx C) D {
|
||||||
layout.Expanded(func(gtx C) D {
|
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
|
||||||
dims := unitList.Layout(gtx)
|
layout.Expanded(func(gtx C) D {
|
||||||
if t.CurrentUnit != t.UnitDragList.SelectedItem {
|
dims := unitList.Layout(gtx)
|
||||||
t.CurrentUnit = t.UnitDragList.SelectedItem
|
if t.CurrentUnit != t.UnitDragList.SelectedItem {
|
||||||
op.InvalidateOp{}.Add(gtx.Ops)
|
t.CurrentUnit = t.UnitDragList.SelectedItem
|
||||||
}
|
t.EditMode = EditUnits
|
||||||
return dims
|
op.InvalidateOp{}.Add(gtx.Ops)
|
||||||
}),
|
}
|
||||||
layout.Stacked(func(gtx C) D {
|
return dims
|
||||||
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
}),
|
||||||
}))
|
layout.Stacked(func(gtx C) D {
|
||||||
}),
|
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
||||||
layout.Rigid(t.layoutUnitEditor))
|
}))
|
||||||
|
}),
|
||||||
|
layout.Rigid(t.layoutUnitEditor))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package tracker
|
package tracker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
)
|
)
|
||||||
@ -42,6 +42,38 @@ var noteMap = map[string]int{
|
|||||||
"P": 16,
|
"P": 16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var unitKeyMap = map[string]string{
|
||||||
|
"e": "envelope",
|
||||||
|
"o": "oscillator",
|
||||||
|
"m": "mulp",
|
||||||
|
"M": "mul",
|
||||||
|
"a": "addp",
|
||||||
|
"A": "add",
|
||||||
|
"p": "pan",
|
||||||
|
"S": "push",
|
||||||
|
"P": "pop",
|
||||||
|
"O": "out",
|
||||||
|
"l": "loadnote",
|
||||||
|
"L": "loadval",
|
||||||
|
"h": "xch",
|
||||||
|
"d": "delay",
|
||||||
|
"D": "distort",
|
||||||
|
"H": "hold",
|
||||||
|
"b": "crush",
|
||||||
|
"g": "gain",
|
||||||
|
"i": "invgain",
|
||||||
|
"f": "filter",
|
||||||
|
"I": "clip",
|
||||||
|
"E": "speed",
|
||||||
|
"r": "compressor",
|
||||||
|
"u": "outaux",
|
||||||
|
"U": "aux",
|
||||||
|
"s": "send",
|
||||||
|
"n": "noise",
|
||||||
|
"N": "in",
|
||||||
|
"R": "receive",
|
||||||
|
}
|
||||||
|
|
||||||
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
||||||
func (t *Tracker) KeyEvent(e key.Event) bool {
|
func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||||
if e.State == key.Press {
|
if e.State == key.Press {
|
||||||
@ -59,14 +91,15 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
t.Redo()
|
t.Redo()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case "A":
|
|
||||||
t.SetCurrentNote(0)
|
|
||||||
return true
|
|
||||||
case key.NameDeleteForward:
|
case key.NameDeleteForward:
|
||||||
t.DeleteSelection()
|
switch t.EditMode {
|
||||||
return true
|
case EditTracks:
|
||||||
case key.NameEscape:
|
t.DeleteSelection()
|
||||||
os.Exit(0)
|
return true
|
||||||
|
case EditUnits:
|
||||||
|
t.DeleteUnit()
|
||||||
|
return true
|
||||||
|
}
|
||||||
case "Space":
|
case "Space":
|
||||||
t.TogglePlay()
|
t.TogglePlay()
|
||||||
return true
|
return true
|
||||||
@ -75,75 +108,161 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
|||||||
return t.ChangeOctave(1)
|
return t.ChangeOctave(1)
|
||||||
}
|
}
|
||||||
return t.ChangeOctave(-1)
|
return t.ChangeOctave(-1)
|
||||||
|
case key.NameTab:
|
||||||
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.EditMode = (t.EditMode - 1 + 4) % 4
|
||||||
|
} else {
|
||||||
|
t.EditMode = (t.EditMode + 1) % 4
|
||||||
|
}
|
||||||
|
return true
|
||||||
case key.NameUpArrow:
|
case key.NameUpArrow:
|
||||||
delta := -1
|
switch t.EditMode {
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
case EditPatterns:
|
||||||
delta = -t.song.RowsPerPattern
|
if e.Modifiers.Contain(key.ModCtrl) {
|
||||||
|
t.Cursor.SongRow = SongRow{}
|
||||||
|
} else {
|
||||||
|
t.Cursor.Row -= t.song.RowsPerPattern
|
||||||
|
}
|
||||||
|
t.NoteTracking = false
|
||||||
|
case EditTracks:
|
||||||
|
if e.Modifiers.Contain(key.ModCtrl) {
|
||||||
|
t.Cursor.Row -= t.song.RowsPerPattern
|
||||||
|
} else {
|
||||||
|
t.Cursor.Row--
|
||||||
|
}
|
||||||
|
t.NoteTracking = false
|
||||||
|
case EditUnits:
|
||||||
|
t.CurrentUnit--
|
||||||
|
case EditParameters:
|
||||||
|
t.CurrentParam--
|
||||||
}
|
}
|
||||||
t.Cursor.Row += delta
|
t.ClampPositions()
|
||||||
t.Cursor.Clamp(t.song)
|
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
t.SelectionCorner = t.Cursor
|
t.Unselect()
|
||||||
}
|
}
|
||||||
t.NoteTracking = false
|
|
||||||
return true
|
return true
|
||||||
case key.NameDownArrow:
|
case key.NameDownArrow:
|
||||||
delta := 1
|
switch t.EditMode {
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
case EditPatterns:
|
||||||
delta = t.song.RowsPerPattern
|
if e.Modifiers.Contain(key.ModCtrl) {
|
||||||
|
t.Cursor.Row = t.song.TotalRows() - 1
|
||||||
|
} else {
|
||||||
|
t.Cursor.Row += t.song.RowsPerPattern
|
||||||
|
}
|
||||||
|
t.NoteTracking = false
|
||||||
|
case EditTracks:
|
||||||
|
if e.Modifiers.Contain(key.ModCtrl) {
|
||||||
|
t.Cursor.Row += t.song.RowsPerPattern
|
||||||
|
} else {
|
||||||
|
t.Cursor.Row++
|
||||||
|
}
|
||||||
|
t.NoteTracking = false
|
||||||
|
case EditUnits:
|
||||||
|
t.CurrentUnit++
|
||||||
|
case EditParameters:
|
||||||
|
t.CurrentParam++
|
||||||
}
|
}
|
||||||
t.Cursor.Row += delta
|
t.ClampPositions()
|
||||||
t.Cursor.Clamp(t.song)
|
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
t.SelectionCorner = t.Cursor
|
t.Unselect()
|
||||||
}
|
}
|
||||||
t.NoteTracking = false
|
|
||||||
return true
|
return true
|
||||||
case key.NameLeftArrow:
|
case key.NameLeftArrow:
|
||||||
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
switch t.EditMode {
|
||||||
t.Cursor.Track--
|
case EditPatterns:
|
||||||
t.Cursor.Clamp(t.song)
|
if e.Modifiers.Contain(key.ModCtrl) {
|
||||||
if t.TrackShowHex[t.Cursor.Track] {
|
t.Cursor.Track = 0
|
||||||
|
} else {
|
||||||
|
t.Cursor.Track--
|
||||||
|
}
|
||||||
|
case EditTracks:
|
||||||
|
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
||||||
|
t.Cursor.Track--
|
||||||
t.CursorColumn = 1
|
t.CursorColumn = 1
|
||||||
} else {
|
} else {
|
||||||
t.CursorColumn = 0
|
t.CursorColumn--
|
||||||
}
|
}
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
case EditUnits:
|
||||||
t.SelectionCorner = t.Cursor
|
t.CurrentInstrument--
|
||||||
|
case EditParameters:
|
||||||
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetUnitParam(t.GetUnitParam() - 16)
|
||||||
|
} else {
|
||||||
|
t.SetUnitParam(t.GetUnitParam() - 1)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
t.CursorColumn--
|
t.ClampPositions()
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.Unselect()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
case key.NameRightArrow:
|
case key.NameRightArrow:
|
||||||
if t.CursorColumn == 1 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
switch t.EditMode {
|
||||||
t.Cursor.Track++
|
case EditPatterns:
|
||||||
t.Cursor.Clamp(t.song)
|
if e.Modifiers.Contain(key.ModCtrl) {
|
||||||
if !e.Modifiers.Contain(key.ModShift) {
|
t.Cursor.Track = len(t.song.Tracks) - 1
|
||||||
t.SelectionCorner = t.Cursor
|
} else {
|
||||||
|
t.Cursor.Track++
|
||||||
}
|
}
|
||||||
t.CursorColumn = 0
|
case EditTracks:
|
||||||
} else {
|
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
||||||
t.CursorColumn++
|
t.Cursor.Track++
|
||||||
|
t.CursorColumn = 0
|
||||||
|
} else {
|
||||||
|
t.CursorColumn++
|
||||||
|
}
|
||||||
|
case EditUnits:
|
||||||
|
t.CurrentInstrument++
|
||||||
|
case EditParameters:
|
||||||
|
if e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.SetUnitParam(t.GetUnitParam() + 16)
|
||||||
|
} else {
|
||||||
|
t.SetUnitParam(t.GetUnitParam() + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.ClampPositions()
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
t.Unselect()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if e.Modifiers.Contain(key.ModCtrl) {
|
switch t.EditMode {
|
||||||
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
|
case EditPatterns:
|
||||||
|
if iv, err := strconv.Atoi(e.Name); err == nil {
|
||||||
t.SetCurrentPattern(byte(iv))
|
t.SetCurrentPattern(byte(iv))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
if b := byte(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 {
|
||||||
if !t.TrackShowHex[t.Cursor.Track] {
|
t.SetCurrentPattern(b + 10)
|
||||||
if val, ok := noteMap[e.Name]; ok {
|
return true
|
||||||
t.NotePressed(val)
|
}
|
||||||
return true
|
case EditTracks:
|
||||||
}
|
if t.TrackShowHex[t.Cursor.Track] {
|
||||||
} else {
|
|
||||||
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
|
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
|
||||||
t.NumberPressed(byte(iv))
|
t.NumberPressed(byte(iv))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if e.Name == "A" {
|
||||||
|
t.SetCurrentNote(0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if val, ok := noteMap[e.Name]; ok {
|
||||||
|
t.NotePressed(val)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case EditUnits:
|
||||||
|
name := e.Name
|
||||||
|
if !e.Modifiers.Contain(key.ModShift) {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
}
|
||||||
|
if val, ok := unitKeyMap[name]; ok {
|
||||||
|
if e.Modifiers.Contain(key.ModCtrl) {
|
||||||
|
t.AddUnit()
|
||||||
|
}
|
||||||
|
t.SetUnit(val)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,13 @@ package tracker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func smallButton(icStyle material.IconButtonStyle) material.IconButtonStyle {
|
func smallButton(icStyle material.IconButtonStyle) material.IconButtonStyle {
|
||||||
@ -40,156 +37,23 @@ func trackButton(t *material.Theme, w *widget.Clickable, text string, enabled bo
|
|||||||
func (t *Tracker) Layout(gtx layout.Context) {
|
func (t *Tracker) Layout(gtx layout.Context) {
|
||||||
paint.FillShape(gtx.Ops, backgroundColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op())
|
paint.FillShape(gtx.Ops, backgroundColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op())
|
||||||
t.VerticalSplit.Layout(gtx,
|
t.VerticalSplit.Layout(gtx,
|
||||||
t.layoutControls,
|
t.layoutTop,
|
||||||
t.layoutTracksAndPatterns)
|
t.layoutBottom)
|
||||||
t.updateInstrumentScroll()
|
t.updateInstrumentScroll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutTracksAndPatterns(gtx layout.Context) layout.Dimensions {
|
func (t *Tracker) layoutBottom(gtx layout.Context) layout.Dimensions {
|
||||||
return t.BottomHorizontalSplit.Layout(gtx,
|
return t.BottomHorizontalSplit.Layout(gtx,
|
||||||
t.layoutPatterns,
|
func(gtx C) D {
|
||||||
t.layoutTracks,
|
return Surface{Gray: 24, Focus: t.EditMode == 0}.Layout(gtx, t.layoutPatterns)
|
||||||
|
},
|
||||||
|
func(gtx C) D {
|
||||||
|
return Surface{Gray: 24, Focus: t.EditMode == 1}.Layout(gtx, t.layoutTracker)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutTracks(gtx layout.Context) layout.Dimensions {
|
func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions {
|
||||||
paint.FillShape(gtx.Ops, trackerSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
|
||||||
|
|
||||||
flexTracks := make([]layout.FlexChild, len(t.song.Tracks))
|
|
||||||
t.playRowPatMutex.RLock()
|
|
||||||
defer t.playRowPatMutex.RUnlock()
|
|
||||||
|
|
||||||
playPat := t.PlayPosition.Pattern
|
|
||||||
if !t.Playing {
|
|
||||||
playPat = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
rowMarkers := layout.Rigid(t.layoutRowMarkers(
|
|
||||||
t.song.RowsPerPattern,
|
|
||||||
len(t.song.Tracks[0].Sequence),
|
|
||||||
t.Cursor.Row,
|
|
||||||
t.Cursor.Pattern,
|
|
||||||
t.CursorColumn,
|
|
||||||
t.PlayPosition.Row,
|
|
||||||
playPat,
|
|
||||||
))
|
|
||||||
leftInset := layout.Inset{Left: unit.Dp(4)}
|
|
||||||
for i := range t.song.Tracks {
|
|
||||||
i2 := i // avoids i being updated in the closure
|
|
||||||
if len(t.TrackHexCheckBoxes) <= i {
|
|
||||||
t.TrackHexCheckBoxes = append(t.TrackHexCheckBoxes, new(widget.Bool))
|
|
||||||
}
|
|
||||||
if len(t.TrackShowHex) <= i {
|
|
||||||
t.TrackShowHex = append(t.TrackShowHex, false)
|
|
||||||
}
|
|
||||||
flexTracks[i] = layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
t.TrackHexCheckBoxes[i2].Value = t.TrackShowHex[i2]
|
|
||||||
cbStyle := material.CheckBox(t.Theme, t.TrackHexCheckBoxes[i2], "hex")
|
|
||||||
cbStyle.Color = white
|
|
||||||
cbStyle.IconColor = t.Theme.Fg
|
|
||||||
ret := layout.Stack{}.Layout(gtx,
|
|
||||||
layout.Stacked(func(gtx layout.Context) D {
|
|
||||||
return leftInset.Layout(gtx, t.layoutTrack(i2))
|
|
||||||
}),
|
|
||||||
layout.Stacked(cbStyle.Layout),
|
|
||||||
)
|
|
||||||
t.TrackShowHex[i2] = t.TrackHexCheckBoxes[i2].Value
|
|
||||||
return ret
|
|
||||||
})
|
|
||||||
}
|
|
||||||
menuBg := func(gtx C) D {
|
|
||||||
paint.FillShape(gtx.Ops, trackMenuSurfaceColor, clip.Rect{
|
|
||||||
Max: gtx.Constraints.Min,
|
|
||||||
}.Op())
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
|
||||||
}
|
|
||||||
|
|
||||||
for t.AddSemitoneBtn.Clicked() {
|
|
||||||
t.AdjustSelectionPitch(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for t.SubtractSemitoneBtn.Clicked() {
|
|
||||||
t.AdjustSelectionPitch(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for t.AddOctaveBtn.Clicked() {
|
|
||||||
t.AdjustSelectionPitch(12)
|
|
||||||
}
|
|
||||||
|
|
||||||
for t.SubtractOctaveBtn.Clicked() {
|
|
||||||
t.AdjustSelectionPitch(-12)
|
|
||||||
}
|
|
||||||
|
|
||||||
menu := func(gtx C) D {
|
|
||||||
addSemitoneBtnStyle := material.Button(t.Theme, t.AddSemitoneBtn, "+1")
|
|
||||||
addSemitoneBtnStyle.Color = primaryColor
|
|
||||||
addSemitoneBtnStyle.Background = transparent
|
|
||||||
addSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
|
||||||
subtractSemitoneBtnStyle := material.Button(t.Theme, t.SubtractSemitoneBtn, "-1")
|
|
||||||
subtractSemitoneBtnStyle.Color = primaryColor
|
|
||||||
subtractSemitoneBtnStyle.Background = transparent
|
|
||||||
subtractSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
|
||||||
addOctaveBtnStyle := material.Button(t.Theme, t.AddOctaveBtn, "+12")
|
|
||||||
addOctaveBtnStyle.Color = primaryColor
|
|
||||||
addOctaveBtnStyle.Background = transparent
|
|
||||||
addOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
|
||||||
subtractOctaveBtnStyle := material.Button(t.Theme, t.SubtractOctaveBtn, "-12")
|
|
||||||
subtractOctaveBtnStyle.Color = primaryColor
|
|
||||||
subtractOctaveBtnStyle.Background = transparent
|
|
||||||
subtractOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
|
||||||
newTrackBtnStyle := material.IconButton(t.Theme, t.NewTrackBtn, widgetForIcon(icons.ContentAdd))
|
|
||||||
newTrackBtnStyle.Background = transparent
|
|
||||||
newTrackBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
|
||||||
if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() {
|
|
||||||
newTrackBtnStyle.Color = primaryColor
|
|
||||||
} else {
|
|
||||||
newTrackBtnStyle.Color = disabledTextColor
|
|
||||||
}
|
|
||||||
in := layout.UniformInset(unit.Dp(1))
|
|
||||||
octave := func(gtx C) D {
|
|
||||||
numStyle := NumericUpDown(t.Theme, t.Octave, 0, 9)
|
|
||||||
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
|
|
||||||
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
|
|
||||||
return in.Layout(gtx, numStyle.Layout)
|
|
||||||
}
|
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
|
||||||
layout.Rigid(Label("OCT:", white)),
|
|
||||||
layout.Rigid(octave),
|
|
||||||
layout.Rigid(Label(" PITCH:", white)),
|
|
||||||
layout.Rigid(addSemitoneBtnStyle.Layout),
|
|
||||||
layout.Rigid(subtractSemitoneBtnStyle.Layout),
|
|
||||||
layout.Rigid(addOctaveBtnStyle.Layout),
|
|
||||||
layout.Rigid(subtractOctaveBtnStyle.Layout),
|
|
||||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
|
||||||
layout.Rigid(newTrackBtnStyle.Layout))
|
|
||||||
}
|
|
||||||
for t.NewTrackBtn.Clicked() {
|
|
||||||
t.AddTrack()
|
|
||||||
}
|
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
|
||||||
layout.Rigid(func(gtx C) D {
|
|
||||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
|
||||||
layout.Expanded(menuBg),
|
|
||||||
layout.Stacked(menu),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
layout.Flexed(1, func(gtx C) D {
|
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
|
||||||
rowMarkers,
|
|
||||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
defer op.Save(gtx.Ops).Load()
|
|
||||||
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
|
||||||
dims := layout.Flex{Axis: layout.Horizontal}.Layout(gtx, flexTracks...)
|
|
||||||
if dims.Size.X > gtx.Constraints.Max.X {
|
|
||||||
dims.Size.X = gtx.Constraints.Max.X
|
|
||||||
}
|
|
||||||
return dims
|
|
||||||
}))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions {
|
|
||||||
for t.NewInstrumentBtn.Clicked() {
|
for t.NewInstrumentBtn.Clicked() {
|
||||||
t.AddInstrument()
|
t.AddInstrument()
|
||||||
}
|
}
|
||||||
@ -200,19 +64,3 @@ func (t *Tracker) layoutControls(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) line(horizontal bool, color color.NRGBA) layout.Widget {
|
|
||||||
return func(gtx layout.Context) layout.Dimensions {
|
|
||||||
if horizontal {
|
|
||||||
gtx.Constraints.Min.Y = 1
|
|
||||||
gtx.Constraints.Max.Y = 1
|
|
||||||
} else {
|
|
||||||
gtx.Constraints.Min.X = 1
|
|
||||||
gtx.Constraints.Max.X = 1
|
|
||||||
}
|
|
||||||
defer op.Save(gtx.Ops).Load()
|
|
||||||
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
|
||||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -20,7 +20,6 @@ const patternRowMarkerWidth = 30
|
|||||||
func (t *Tracker) layoutPatterns(gtx C) D {
|
func (t *Tracker) layoutPatterns(gtx C) D {
|
||||||
defer op.Save(gtx.Ops).Load()
|
defer op.Save(gtx.Ops).Load()
|
||||||
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
||||||
paint.FillShape(gtx.Ops, patternSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
|
||||||
patternRect := SongRect{
|
patternRect := SongRect{
|
||||||
Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track},
|
Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track},
|
||||||
Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track},
|
Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track},
|
||||||
@ -35,14 +34,19 @@ func (t *Tracker) layoutPatterns(gtx C) D {
|
|||||||
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
|
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
|
||||||
for i, track := range t.song.Tracks {
|
for i, track := range t.song.Tracks {
|
||||||
paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
|
||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, fmt.Sprintf("%d", track.Sequence[j]))
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(track.Sequence[j]))
|
||||||
point := SongPoint{Track: i, SongRow: SongRow{Pattern: j}}
|
point := SongPoint{Track: i, SongRow: SongRow{Pattern: j}}
|
||||||
if patternRect.Contains(point) {
|
if t.EditMode == EditPatterns || t.EditMode == EditTracks {
|
||||||
color := patternSelectionColor
|
if patternRect.Contains(point) {
|
||||||
if point.Pattern == t.Cursor.Pattern && point.Track == t.Cursor.Track {
|
color := inactiveSelectionColor
|
||||||
color = patternCursorColor
|
if t.EditMode == EditPatterns {
|
||||||
|
color = selectionColor
|
||||||
|
if point.Pattern == t.Cursor.Pattern && point.Track == t.Cursor.Track {
|
||||||
|
color = cursorColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(patternCellWidth, patternCellHeight)}.Op())
|
||||||
}
|
}
|
||||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(patternCellWidth, patternCellHeight)}.Op())
|
|
||||||
}
|
}
|
||||||
op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops)
|
op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ func (t *Tracker) layoutRowMarkers(patternRows, sequenceLength, cursorRow, curso
|
|||||||
}.Op())
|
}.Op())
|
||||||
defer op.Save(gtx.Ops).Load()
|
defer op.Save(gtx.Ops).Load()
|
||||||
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
||||||
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y/2)-trackRowHeight)).Add(gtx.Ops)
|
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops)
|
||||||
cursorSongRow := cursorPattern*patternRows + cursorRow
|
cursorSongRow := cursorPattern*patternRows + cursorRow
|
||||||
playSongRow := playPattern*patternRows + playRow
|
playSongRow := playPattern*patternRows + playRow
|
||||||
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
|
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
|
||||||
@ -32,13 +32,13 @@ func (t *Tracker) layoutRowMarkers(patternRows, sequenceLength, cursorRow, curso
|
|||||||
for j := 0; j < patternRows; j++ {
|
for j := 0; j < patternRows; j++ {
|
||||||
songRow := i*patternRows + j
|
songRow := i*patternRows + j
|
||||||
if songRow == playSongRow {
|
if songRow == playSongRow {
|
||||||
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight)}.Op())
|
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(trackColWidth, trackRowHeight)}.Op())
|
||||||
}
|
}
|
||||||
if j == 0 {
|
if j == 0 {
|
||||||
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
|
||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", i)))
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", i)))
|
||||||
}
|
}
|
||||||
if songRow == cursorSongRow {
|
if t.EditMode == EditTracks && songRow == cursorSongRow {
|
||||||
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
||||||
} else {
|
} else {
|
||||||
paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops)
|
||||||
|
51
tracker/surface.go
Normal file
51
tracker/surface.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package tracker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/op/clip"
|
||||||
|
"gioui.org/op/paint"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Surface struct {
|
||||||
|
Gray int
|
||||||
|
Inset layout.Inset
|
||||||
|
FitSize bool
|
||||||
|
Focus bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Surface) Layout(gtx C, widget layout.Widget) D {
|
||||||
|
bg := func(gtx C) D {
|
||||||
|
grayInt := s.Gray
|
||||||
|
if s.Focus {
|
||||||
|
grayInt += 8
|
||||||
|
}
|
||||||
|
var grayUint8 uint8
|
||||||
|
if grayInt < 0 {
|
||||||
|
grayUint8 = 0
|
||||||
|
} else if grayInt > 255 {
|
||||||
|
grayUint8 = 255
|
||||||
|
} else {
|
||||||
|
grayUint8 = uint8(grayInt)
|
||||||
|
}
|
||||||
|
color := color.NRGBA{R: grayUint8, G: grayUint8, B: grayUint8, A: 255}
|
||||||
|
paint.FillShape(gtx.Ops, color, clip.Rect{
|
||||||
|
Max: gtx.Constraints.Min,
|
||||||
|
}.Op())
|
||||||
|
return D{Size: gtx.Constraints.Min}
|
||||||
|
}
|
||||||
|
fg := func(gtx C) D {
|
||||||
|
return s.Inset.Layout(gtx, widget)
|
||||||
|
}
|
||||||
|
if s.FitSize {
|
||||||
|
return layout.Stack{}.Layout(gtx,
|
||||||
|
layout.Expanded(bg),
|
||||||
|
layout.Stacked(fg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
gtxbg := gtx
|
||||||
|
gtxbg.Constraints.Min = gtxbg.Constraints.Max
|
||||||
|
bg(gtxbg)
|
||||||
|
return fg(gtx)
|
||||||
|
}
|
@ -56,6 +56,7 @@ var activeTrackColor = focusedContainerColor
|
|||||||
var trackSurfaceColor = color.NRGBA{R: 255, G: 255, B: 255, A: 31}
|
var trackSurfaceColor = color.NRGBA{R: 255, G: 255, B: 255, A: 31}
|
||||||
|
|
||||||
var patternSurfaceColor = color.NRGBA{R: 24, G: 24, B: 24, A: 255}
|
var patternSurfaceColor = color.NRGBA{R: 24, G: 24, B: 24, A: 255}
|
||||||
|
var patternSurfaceActiveColor = color.NRGBA{R: 30, G: 30, B: 30, A: 255}
|
||||||
|
|
||||||
var rowMarkerSurfaceColor = color.NRGBA{R: 0, G: 0, B: 0, A: 0}
|
var rowMarkerSurfaceColor = color.NRGBA{R: 0, G: 0, B: 0, A: 0}
|
||||||
var rowMarkerPatternTextColor = secondaryColor
|
var rowMarkerPatternTextColor = secondaryColor
|
||||||
@ -101,3 +102,10 @@ var dragListHoverColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
|
|||||||
|
|
||||||
var unitSurfaceColor = color.NRGBA{R: 30, G: 30, B: 30, 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}
|
var unitTypeListHighlightColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
|
||||||
|
|
||||||
|
var inactiveLightSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
|
||||||
|
var activeLightSurfaceColor = color.NRGBA{R: 45, G: 45, B: 45, A: 255}
|
||||||
|
|
||||||
|
var cursorColor = color.NRGBA{R: 100, G: 140, B: 255, A: 48}
|
||||||
|
var selectionColor = color.NRGBA{R: 100, G: 140, B: 255, A: 8}
|
||||||
|
var inactiveSelectionColor = color.NRGBA{R: 140, G: 140, B: 140, A: 16}
|
||||||
|
302
tracker/track.go
302
tracker/track.go
@ -10,86 +10,238 @@ import (
|
|||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
|
"gioui.org/unit"
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
|
"gioui.org/widget/material"
|
||||||
|
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||||
)
|
)
|
||||||
|
|
||||||
const trackRowHeight = 16
|
const trackRowHeight = 16
|
||||||
const trackWidth = 54
|
const trackColWidth = 54
|
||||||
const patmarkWidth = 16
|
const patmarkWidth = 16
|
||||||
|
|
||||||
func (t *Tracker) layoutTrack(trackNo int) layout.Widget {
|
func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
|
||||||
return func(gtx layout.Context) layout.Dimensions {
|
t.playRowPatMutex.RLock()
|
||||||
gtx.Constraints.Min.X = trackWidth
|
defer t.playRowPatMutex.RUnlock()
|
||||||
gtx.Constraints.Max.X = trackWidth
|
|
||||||
defer op.Save(gtx.Ops).Load()
|
playPat := t.PlayPosition.Pattern
|
||||||
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
if !t.Playing {
|
||||||
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y/2)-trackRowHeight)).Add(gtx.Ops)
|
playPat = -1
|
||||||
// TODO: this is a time bomb; as soon as one of the patterns is not the same length as rest. Find a solution
|
|
||||||
// to fix the pattern lengths to a constant value
|
|
||||||
cursorSongRow := t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row
|
|
||||||
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
|
|
||||||
patternRect := SongRect{
|
|
||||||
Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track},
|
|
||||||
Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track},
|
|
||||||
}
|
|
||||||
pointRect := SongRect{
|
|
||||||
Corner1: t.Cursor,
|
|
||||||
Corner2: t.SelectionCorner,
|
|
||||||
}
|
|
||||||
for i, s := range t.song.Tracks[trackNo].Sequence {
|
|
||||||
if patternRect.Contains(SongPoint{Track: trackNo, SongRow: SongRow{Pattern: i}}) {
|
|
||||||
paint.FillShape(gtx.Ops, activeTrackColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight*t.song.RowsPerPattern)}.Op())
|
|
||||||
}
|
|
||||||
for j := 0; j < t.song.RowsPerPattern; j++ {
|
|
||||||
c := t.song.Tracks[trackNo].Patterns[s][j]
|
|
||||||
songRow := SongRow{Pattern: i, Row: j}
|
|
||||||
songPoint := SongPoint{Track: trackNo, SongRow: songRow}
|
|
||||||
if songRow == t.PlayPosition && t.Playing {
|
|
||||||
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(trackWidth, trackRowHeight)}.Op())
|
|
||||||
}
|
|
||||||
if j == 0 {
|
|
||||||
paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops)
|
|
||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(s))
|
|
||||||
}
|
|
||||||
if songRow == t.Cursor.SongRow {
|
|
||||||
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
|
||||||
} else {
|
|
||||||
paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops)
|
|
||||||
if t.TrackShowHex[trackNo] {
|
|
||||||
var text string
|
|
||||||
switch c {
|
|
||||||
case 0:
|
|
||||||
text = "--"
|
|
||||||
case 1:
|
|
||||||
text = ".."
|
|
||||||
default:
|
|
||||||
text = fmt.Sprintf("%02x", c)
|
|
||||||
}
|
|
||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(text))
|
|
||||||
if pointRect.Contains(songPoint) {
|
|
||||||
for col := 0; col < 2; col++ {
|
|
||||||
color := trackerSelectionColor
|
|
||||||
if songPoint == t.Cursor && t.CursorColumn == col {
|
|
||||||
color = trackerCursorColor
|
|
||||||
}
|
|
||||||
paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(col*10, 0), Max: image.Pt(col*10+10, trackRowHeight)}.Op())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, valueAsNote(c))
|
|
||||||
if pointRect.Contains(songPoint) {
|
|
||||||
color := trackerSelectionColor
|
|
||||||
if songPoint == t.Cursor {
|
|
||||||
color = trackerCursorColor
|
|
||||||
}
|
|
||||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(30, trackRowHeight)}.Op())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
op.Offset(f32.Pt(-patmarkWidth, trackRowHeight)).Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rowMarkers := layout.Rigid(t.layoutRowMarkers(
|
||||||
|
t.song.RowsPerPattern,
|
||||||
|
len(t.song.Tracks[0].Sequence),
|
||||||
|
t.Cursor.Row,
|
||||||
|
t.Cursor.Pattern,
|
||||||
|
t.CursorColumn,
|
||||||
|
t.PlayPosition.Row,
|
||||||
|
playPat,
|
||||||
|
))
|
||||||
|
|
||||||
|
for t.NewTrackBtn.Clicked() {
|
||||||
|
t.AddTrack()
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(t.TrackHexCheckBoxes) < len(t.song.Tracks) {
|
||||||
|
t.TrackHexCheckBoxes = append(t.TrackHexCheckBoxes, new(widget.Bool))
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(t.TrackShowHex) < len(t.song.Tracks) {
|
||||||
|
t.TrackShowHex = append(t.TrackShowHex, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
//t.TrackHexCheckBoxes[i2].Value = t.TrackShowHex[i2]
|
||||||
|
//cbStyle := material.CheckBox(t.Theme, t.TrackHexCheckBoxes[i2], "hex")
|
||||||
|
//cbStyle.Color = white
|
||||||
|
//cbStyle.IconColor = t.Theme.Fg
|
||||||
|
|
||||||
|
for t.AddSemitoneBtn.Clicked() {
|
||||||
|
t.AdjustSelectionPitch(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for t.SubtractSemitoneBtn.Clicked() {
|
||||||
|
t.AdjustSelectionPitch(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for t.AddOctaveBtn.Clicked() {
|
||||||
|
t.AdjustSelectionPitch(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
for t.SubtractOctaveBtn.Clicked() {
|
||||||
|
t.AdjustSelectionPitch(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
menu := func(gtx C) D {
|
||||||
|
addSemitoneBtnStyle := material.Button(t.Theme, t.AddSemitoneBtn, "+1")
|
||||||
|
addSemitoneBtnStyle.Color = primaryColor
|
||||||
|
addSemitoneBtnStyle.Background = transparent
|
||||||
|
addSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||||
|
subtractSemitoneBtnStyle := material.Button(t.Theme, t.SubtractSemitoneBtn, "-1")
|
||||||
|
subtractSemitoneBtnStyle.Color = primaryColor
|
||||||
|
subtractSemitoneBtnStyle.Background = transparent
|
||||||
|
subtractSemitoneBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||||
|
addOctaveBtnStyle := material.Button(t.Theme, t.AddOctaveBtn, "+12")
|
||||||
|
addOctaveBtnStyle.Color = primaryColor
|
||||||
|
addOctaveBtnStyle.Background = transparent
|
||||||
|
addOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||||
|
subtractOctaveBtnStyle := material.Button(t.Theme, t.SubtractOctaveBtn, "-12")
|
||||||
|
subtractOctaveBtnStyle.Color = primaryColor
|
||||||
|
subtractOctaveBtnStyle.Background = transparent
|
||||||
|
subtractOctaveBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||||
|
newTrackBtnStyle := material.IconButton(t.Theme, t.NewTrackBtn, widgetForIcon(icons.ContentAdd))
|
||||||
|
newTrackBtnStyle.Background = transparent
|
||||||
|
newTrackBtnStyle.Inset = layout.UniformInset(unit.Dp(6))
|
||||||
|
if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() {
|
||||||
|
newTrackBtnStyle.Color = primaryColor
|
||||||
|
} else {
|
||||||
|
newTrackBtnStyle.Color = disabledTextColor
|
||||||
|
}
|
||||||
|
in := layout.UniformInset(unit.Dp(1))
|
||||||
|
octave := func(gtx C) D {
|
||||||
|
numStyle := NumericUpDown(t.Theme, t.Octave, 0, 9)
|
||||||
|
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20))
|
||||||
|
gtx.Constraints.Min.X = gtx.Px(unit.Dp(70))
|
||||||
|
return in.Layout(gtx, numStyle.Layout)
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(Label("OCT:", white)),
|
||||||
|
layout.Rigid(octave),
|
||||||
|
layout.Rigid(Label(" PITCH:", white)),
|
||||||
|
layout.Rigid(addSemitoneBtnStyle.Layout),
|
||||||
|
layout.Rigid(subtractSemitoneBtnStyle.Layout),
|
||||||
|
layout.Rigid(addOctaveBtnStyle.Layout),
|
||||||
|
layout.Rigid(subtractOctaveBtnStyle.Layout),
|
||||||
|
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||||
|
layout.Rigid(newTrackBtnStyle.Layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx C) D {
|
||||||
|
return Surface{Gray: 37, Focus: t.EditMode == 1, FitSize: true}.Layout(gtx, menu)
|
||||||
|
}),
|
||||||
|
layout.Flexed(1, func(gtx C) D {
|
||||||
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
|
rowMarkers,
|
||||||
|
layout.Flexed(1, func(gtx C) D {
|
||||||
|
return layout.Stack{Alignment: layout.NW}.Layout(gtx,
|
||||||
|
layout.Stacked(t.layoutTracks),
|
||||||
|
layout.Stacked(t.layoutTrackTitles),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracker) layoutTrackTitles(gtx C) D {
|
||||||
|
defer op.Save(gtx.Ops).Load()
|
||||||
|
hexFlexChildren := make([]layout.FlexChild, len(t.song.Tracks))
|
||||||
|
for trkIndex := range t.song.Tracks {
|
||||||
|
trkIndex2 := trkIndex
|
||||||
|
hexFlexChildren[trkIndex] = layout.Rigid(func(gtx C) D {
|
||||||
|
t.TrackHexCheckBoxes[trkIndex2].Value = t.TrackShowHex[trkIndex2]
|
||||||
|
cbStyle := material.CheckBox(t.Theme, t.TrackHexCheckBoxes[trkIndex2], "hex")
|
||||||
|
dims := cbStyle.Layout(gtx)
|
||||||
|
t.TrackShowHex[trkIndex2] = t.TrackHexCheckBoxes[trkIndex2].Value
|
||||||
|
return layout.Dimensions{Size: image.Pt(trackColWidth, dims.Size.Y)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, hexFlexChildren...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracker) layoutTracks(gtx C) D {
|
||||||
|
defer op.Save(gtx.Ops).Load()
|
||||||
|
clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops)
|
||||||
|
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops)
|
||||||
|
cursorSongRow := t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row
|
||||||
|
op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops)
|
||||||
|
if t.EditMode == EditPatterns || t.EditMode == EditTracks {
|
||||||
|
x1, y1 := t.Cursor.Track, t.Cursor.Pattern
|
||||||
|
x2, y2 := t.SelectionCorner.Track, t.SelectionCorner.Pattern
|
||||||
|
if x1 > x2 {
|
||||||
|
x1, x2 = x2, x1
|
||||||
|
}
|
||||||
|
if y1 > y2 {
|
||||||
|
y1, y2 = y2, y1
|
||||||
|
}
|
||||||
|
x2++
|
||||||
|
y2++
|
||||||
|
x1 *= trackColWidth
|
||||||
|
y1 *= trackRowHeight * t.song.RowsPerPattern
|
||||||
|
x2 *= trackColWidth
|
||||||
|
y2 *= trackRowHeight * t.song.RowsPerPattern
|
||||||
|
paint.FillShape(gtx.Ops, inactiveSelectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op())
|
||||||
|
}
|
||||||
|
if t.Playing {
|
||||||
|
py := trackRowHeight * (t.PlayPosition.Pattern*t.song.RowsPerPattern + t.PlayPosition.Row)
|
||||||
|
paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Min: image.Pt(0, py), Max: image.Pt(gtx.Constraints.Max.X, py+trackRowHeight)}.Op())
|
||||||
|
}
|
||||||
|
if t.EditMode == EditTracks {
|
||||||
|
x1, y1 := t.Cursor.Track, t.Cursor.Pattern*t.song.RowsPerPattern+t.Cursor.Row
|
||||||
|
x2, y2 := t.SelectionCorner.Track, t.SelectionCorner.Pattern*t.song.RowsPerPattern+t.SelectionCorner.Row
|
||||||
|
if x1 > x2 {
|
||||||
|
x1, x2 = x2, x1
|
||||||
|
}
|
||||||
|
if y1 > y2 {
|
||||||
|
y1, y2 = y2, y1
|
||||||
|
}
|
||||||
|
x2++
|
||||||
|
y2++
|
||||||
|
x1 *= trackColWidth
|
||||||
|
y1 *= trackRowHeight
|
||||||
|
x2 *= trackColWidth
|
||||||
|
y2 *= trackRowHeight
|
||||||
|
paint.FillShape(gtx.Ops, selectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op())
|
||||||
|
cx := t.Cursor.Track * trackColWidth
|
||||||
|
cy := (t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row) * trackRowHeight
|
||||||
|
paint.FillShape(gtx.Ops, cursorColor, clip.Rect{Min: image.Pt(cx, cy), Max: image.Pt(cx+trackColWidth, cy+trackRowHeight)}.Op())
|
||||||
|
}
|
||||||
|
delta := (gtx.Constraints.Max.Y/2 + trackRowHeight - 1) / trackRowHeight
|
||||||
|
firstRow := cursorSongRow - delta
|
||||||
|
lastRow := cursorSongRow + delta
|
||||||
|
if firstRow < 0 {
|
||||||
|
firstRow = 0
|
||||||
|
}
|
||||||
|
if l := t.song.TotalRows(); lastRow >= l {
|
||||||
|
lastRow = l - 1
|
||||||
|
}
|
||||||
|
op.Offset(f32.Pt(0, float32(trackRowHeight*firstRow))).Add(gtx.Ops)
|
||||||
|
for trkIndex, trk := range t.song.Tracks {
|
||||||
|
stack := op.Save(gtx.Ops)
|
||||||
|
for row := firstRow; row <= lastRow; row++ {
|
||||||
|
pat := row / t.song.RowsPerPattern
|
||||||
|
patRow := row % t.song.RowsPerPattern
|
||||||
|
s := trk.Sequence[pat]
|
||||||
|
if patRow == 0 {
|
||||||
|
paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops)
|
||||||
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(s))
|
||||||
|
}
|
||||||
|
op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops)
|
||||||
|
if t.EditMode == EditTracks && t.Cursor.SongRow.Row == patRow && t.Cursor.SongRow.Pattern == pat {
|
||||||
|
paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops)
|
||||||
|
} else {
|
||||||
|
paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
c := trk.Patterns[s][patRow]
|
||||||
|
if t.TrackShowHex[trkIndex] {
|
||||||
|
var text string
|
||||||
|
switch c {
|
||||||
|
case 0:
|
||||||
|
text = "--"
|
||||||
|
case 1:
|
||||||
|
text = ".."
|
||||||
|
default:
|
||||||
|
text = fmt.Sprintf("%02x", c)
|
||||||
|
}
|
||||||
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(text))
|
||||||
|
} else {
|
||||||
|
widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, valueAsNote(c))
|
||||||
|
}
|
||||||
|
op.Offset(f32.Pt(-patmarkWidth, trackRowHeight)).Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
stack.Load()
|
||||||
|
op.Offset(f32.Pt(trackColWidth, 0)).Add(gtx.Ops)
|
||||||
|
}
|
||||||
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,15 @@ import (
|
|||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EditMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
EditPatterns EditMode = iota
|
||||||
|
EditTracks
|
||||||
|
EditUnits
|
||||||
|
EditParameters
|
||||||
|
)
|
||||||
|
|
||||||
type Tracker struct {
|
type Tracker struct {
|
||||||
QuitButton *widget.Clickable
|
QuitButton *widget.Clickable
|
||||||
songPlayMutex sync.RWMutex // protects song and playing
|
songPlayMutex sync.RWMutex // protects song and playing
|
||||||
@ -21,11 +30,13 @@ type Tracker struct {
|
|||||||
// protects PlayPattern and PlayRow
|
// protects PlayPattern and PlayRow
|
||||||
playRowPatMutex sync.RWMutex // protects song and playing
|
playRowPatMutex sync.RWMutex // protects song and playing
|
||||||
PlayPosition SongRow
|
PlayPosition SongRow
|
||||||
|
EditMode EditMode
|
||||||
SelectionCorner SongPoint
|
SelectionCorner SongPoint
|
||||||
Cursor SongPoint
|
Cursor SongPoint
|
||||||
CursorColumn int
|
CursorColumn int
|
||||||
CurrentInstrument int
|
CurrentInstrument int
|
||||||
CurrentUnit int
|
CurrentUnit int
|
||||||
|
CurrentParam int
|
||||||
UnitGroupMenuVisible bool
|
UnitGroupMenuVisible bool
|
||||||
UnitGroupMenuIndex int
|
UnitGroupMenuIndex int
|
||||||
UnitSubMenuIndex int
|
UnitSubMenuIndex int
|
||||||
@ -85,12 +96,6 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
|||||||
defer t.songPlayMutex.Unlock()
|
defer t.songPlayMutex.Unlock()
|
||||||
t.song = song
|
t.song = song
|
||||||
t.ClampPositions()
|
t.ClampPositions()
|
||||||
if l := len(t.song.Patch.Instruments); t.CurrentInstrument >= l {
|
|
||||||
t.CurrentInstrument = l - 1
|
|
||||||
}
|
|
||||||
if l := len(t.song.Patch.Instruments[t.CurrentInstrument].Units); t.CurrentUnit >= l {
|
|
||||||
t.CurrentUnit = l - 1
|
|
||||||
}
|
|
||||||
if t.sequencer != nil {
|
if t.sequencer != nil {
|
||||||
t.sequencer.SetPatch(song.Patch)
|
t.sequencer.SetPatch(song.Patch)
|
||||||
t.sequencer.SetRowLength(song.SamplesPerRow())
|
t.sequencer.SetRowLength(song.SamplesPerRow())
|
||||||
@ -98,6 +103,16 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clamp(a, min, max int) int {
|
||||||
|
if a < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if a >= max {
|
||||||
|
return max - 1
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tracker) Close() {
|
func (t *Tracker) Close() {
|
||||||
t.audioContext.Close()
|
t.audioContext.Close()
|
||||||
t.closer <- struct{}{}
|
t.closer <- struct{}{}
|
||||||
@ -232,6 +247,7 @@ func (t *Tracker) AddInstrument() {
|
|||||||
copy(instr[t.CurrentInstrument+2:], t.song.Patch.Instruments[t.CurrentInstrument+1:])
|
copy(instr[t.CurrentInstrument+2:], t.song.Patch.Instruments[t.CurrentInstrument+1:])
|
||||||
t.song.Patch.Instruments = instr
|
t.song.Patch.Instruments = instr
|
||||||
t.CurrentInstrument++
|
t.CurrentInstrument++
|
||||||
|
t.ClampPositions()
|
||||||
t.sequencer.SetPatch(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +258,7 @@ func (t *Tracker) SwapInstruments(i, j int) {
|
|||||||
t.SaveUndo()
|
t.SaveUndo()
|
||||||
instruments := t.song.Patch.Instruments
|
instruments := t.song.Patch.Instruments
|
||||||
instruments[i], instruments[j] = instruments[j], instruments[i]
|
instruments[i], instruments[j] = instruments[j], instruments[i]
|
||||||
|
t.ClampPositions()
|
||||||
t.sequencer.SetPatch(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,6 +271,7 @@ func (t *Tracker) DeleteInstrument() {
|
|||||||
if t.CurrentInstrument >= len(t.song.Patch.Instruments) {
|
if t.CurrentInstrument >= len(t.song.Patch.Instruments) {
|
||||||
t.CurrentInstrument = len(t.song.Patch.Instruments) - 1
|
t.CurrentInstrument = len(t.song.Patch.Instruments) - 1
|
||||||
}
|
}
|
||||||
|
t.ClampPositions()
|
||||||
t.sequencer.SetPatch(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,6 +361,7 @@ func (t *Tracker) AddUnit() {
|
|||||||
copy(units[t.CurrentUnit+2:], 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.song.Patch.Instruments[t.CurrentInstrument].Units = units
|
||||||
t.CurrentUnit++
|
t.CurrentUnit++
|
||||||
|
t.ClampPositions()
|
||||||
t.sequencer.SetPatch(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,6 +369,7 @@ func (t *Tracker) ClearUnit() {
|
|||||||
t.SaveUndo()
|
t.SaveUndo()
|
||||||
t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type = ""
|
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.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters = make(map[string]int)
|
||||||
|
t.ClampPositions()
|
||||||
t.sequencer.SetPatch(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,6 +385,30 @@ func (t *Tracker) DeleteUnit() {
|
|||||||
if t.CurrentUnit > 0 {
|
if t.CurrentUnit > 0 {
|
||||||
t.CurrentUnit--
|
t.CurrentUnit--
|
||||||
}
|
}
|
||||||
|
t.ClampPositions()
|
||||||
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracker) GetUnitParam() int {
|
||||||
|
unit := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit]
|
||||||
|
paramtype := sointu.UnitTypes[unit.Type][t.CurrentParam]
|
||||||
|
return unit.Parameters[paramtype.Name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracker) SetUnitParam(value int) {
|
||||||
|
unit := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit]
|
||||||
|
unittype := sointu.UnitTypes[unit.Type][t.CurrentParam]
|
||||||
|
if value < unittype.MinValue {
|
||||||
|
value = unittype.MinValue
|
||||||
|
} else if value > unittype.MaxValue {
|
||||||
|
value = unittype.MaxValue
|
||||||
|
}
|
||||||
|
if unit.Parameters[unittype.Name] == value {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.SaveUndo()
|
||||||
|
unit.Parameters[unittype.Name] = value
|
||||||
|
t.ClampPositions()
|
||||||
t.sequencer.SetPatch(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,6 +419,7 @@ func (t *Tracker) SwapUnits(i, j int) {
|
|||||||
t.SaveUndo()
|
t.SaveUndo()
|
||||||
units := t.song.Patch.Instruments[t.CurrentInstrument].Units
|
units := t.song.Patch.Instruments[t.CurrentInstrument].Units
|
||||||
units[i], units[j] = units[j], units[i]
|
units[i], units[j] = units[j], units[i]
|
||||||
|
t.ClampPositions()
|
||||||
t.sequencer.SetPatch(t.song.Patch)
|
t.sequencer.SetPatch(t.song.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,6 +427,38 @@ func (t *Tracker) ClampPositions() {
|
|||||||
t.PlayPosition.Clamp(t.song)
|
t.PlayPosition.Clamp(t.song)
|
||||||
t.Cursor.Clamp(t.song)
|
t.Cursor.Clamp(t.song)
|
||||||
t.SelectionCorner.Clamp(t.song)
|
t.SelectionCorner.Clamp(t.song)
|
||||||
|
if t.Cursor.Track >= len(t.TrackShowHex) || !t.TrackShowHex[t.Cursor.Track] {
|
||||||
|
t.CursorColumn = 0
|
||||||
|
}
|
||||||
|
t.CurrentInstrument = clamp(t.CurrentInstrument, 0, len(t.song.Patch.Instruments))
|
||||||
|
t.CurrentUnit = clamp(t.CurrentUnit, 0, len(t.song.Patch.Instruments[t.CurrentInstrument].Units))
|
||||||
|
numSettableParams := 0
|
||||||
|
for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] {
|
||||||
|
if t.CanSet {
|
||||||
|
numSettableParams++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.CurrentParam < 0 && t.CurrentUnit > 0 {
|
||||||
|
t.CurrentUnit--
|
||||||
|
numSettableParams = 0
|
||||||
|
for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] {
|
||||||
|
if t.CanSet {
|
||||||
|
numSettableParams++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.CurrentParam = numSettableParams - 1
|
||||||
|
}
|
||||||
|
if t.CurrentParam >= numSettableParams && t.CurrentUnit < len(t.song.Patch.Instruments[t.CurrentInstrument].Units)-1 {
|
||||||
|
t.CurrentUnit++
|
||||||
|
numSettableParams = 0
|
||||||
|
for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] {
|
||||||
|
if t.CanSet {
|
||||||
|
numSettableParams++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.CurrentParam = 0
|
||||||
|
}
|
||||||
|
t.CurrentParam = clamp(t.CurrentParam, 0, numSettableParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) getSelectionRange() (int, int, int, int) {
|
func (t *Tracker) getSelectionRange() (int, int, int, int) {
|
||||||
@ -443,6 +520,10 @@ func (t *Tracker) DeleteSelection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tracker) Unselect() {
|
||||||
|
t.SelectionCorner = t.Cursor
|
||||||
|
}
|
||||||
|
|
||||||
func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tracker {
|
func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tracker {
|
||||||
t := &Tracker{
|
t := &Tracker{
|
||||||
Theme: material.NewTheme(gofont.Collection()),
|
Theme: material.NewTheme(gofont.Collection()),
|
||||||
@ -484,6 +565,8 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tr
|
|||||||
VerticalSplit: new(Split),
|
VerticalSplit: new(Split),
|
||||||
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
|
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
|
||||||
}
|
}
|
||||||
|
t.UnitDragList.HoverItem = -1
|
||||||
|
t.InstrumentDragList.HoverItem = -1
|
||||||
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
|
||||||
|
@ -21,11 +21,12 @@ func (t *Tracker) layoutUnitEditor(gtx C) D {
|
|||||||
if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" {
|
if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" {
|
||||||
editorFunc = t.layoutUnitTypeChooser
|
editorFunc = t.layoutUnitTypeChooser
|
||||||
}
|
}
|
||||||
paint.FillShape(gtx.Ops, unitSurfaceColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}.Op())
|
return Surface{Gray: 24, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Flexed(1, editorFunc),
|
layout.Flexed(1, editorFunc),
|
||||||
layout.Rigid(t.layoutUnitFooter()),
|
layout.Rigid(t.layoutUnitFooter()))
|
||||||
)
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutUnitSliders(gtx C) D {
|
func (t *Tracker) layoutUnitSliders(gtx C) D {
|
||||||
@ -41,12 +42,6 @@ func (t *Tracker) layoutUnitSliders(gtx C) D {
|
|||||||
t.ParameterSliders = append(t.ParameterSliders, new(widget.Float))
|
t.ParameterSliders = append(t.ParameterSliders, new(widget.Float))
|
||||||
}
|
}
|
||||||
params := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters
|
params := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters
|
||||||
for t.ParameterSliders[index].Changed() {
|
|
||||||
params[ut[index].Name] = int(t.ParameterSliders[index].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[index].Value = float32(params[ut[index].Name])
|
t.ParameterSliders[index].Value = float32(params[ut[index].Name])
|
||||||
sliderStyle := material.Slider(t.Theme, t.ParameterSliders[index], float32(ut[index].MinValue), float32(ut[index].MaxValue))
|
sliderStyle := material.Slider(t.Theme, t.ParameterSliders[index], float32(ut[index].MinValue), float32(ut[index].MaxValue))
|
||||||
sliderStyle.Color = t.Theme.Fg
|
sliderStyle.Color = t.Theme.Fg
|
||||||
@ -65,7 +60,19 @@ func (t *Tracker) layoutUnitSliders(gtx C) D {
|
|||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
gtx.Constraints.Min.X = gtx.Px(unit.Dp(200))
|
gtx.Constraints.Min.X = gtx.Px(unit.Dp(200))
|
||||||
return sliderStyle.Layout(gtx)
|
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40))
|
||||||
|
if t.EditMode == EditParameters && t.CurrentParam == index {
|
||||||
|
paint.FillShape(gtx.Ops, cursorColor, clip.Rect{
|
||||||
|
Max: gtx.Constraints.Min,
|
||||||
|
}.Op())
|
||||||
|
}
|
||||||
|
dims := sliderStyle.Layout(gtx)
|
||||||
|
for sliderStyle.Float.Changed() {
|
||||||
|
t.EditMode = EditParameters
|
||||||
|
t.CurrentParam = index
|
||||||
|
t.SetUnitParam(int(t.ParameterSliders[index].Value))
|
||||||
|
}
|
||||||
|
return dims
|
||||||
}),
|
}),
|
||||||
layout.Rigid(Label(valueText, white)),
|
layout.Rigid(Label(valueText, white)),
|
||||||
)
|
)
|
||||||
@ -108,7 +115,6 @@ func (t *Tracker) layoutUnitFooter() layout.Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) layoutUnitTypeChooser(gtx C) D {
|
func (t *Tracker) layoutUnitTypeChooser(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 {
|
listElem := func(gtx C, i int) D {
|
||||||
for t.ChooseUnitTypeBtns[i].Clicked() {
|
for t.ChooseUnitTypeBtns[i].Clicked() {
|
||||||
t.SetUnit(allUnits[i])
|
t.SetUnit(allUnits[i])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user