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 {
|
||||
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]
|
||||
if !ok {
|
||||
index = len(c.SampleOffsets)
|
||||
|
@ -255,7 +255,7 @@ var UnitTypes = map[string]([]UnitParameter){
|
||||
{Name: "unison", MinValue: 0, MaxValue: 3, 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: "looplength", MinValue: 1, MaxValue: 65535, CanSet: true, CanModulate: false}},
|
||||
{Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}},
|
||||
"loadval": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
|
@ -24,7 +24,6 @@ type DragList struct {
|
||||
|
||||
type FilledDragListStyle struct {
|
||||
dragList *DragList
|
||||
SurfaceColor color.NRGBA
|
||||
HoverColor color.NRGBA
|
||||
SelectedColor color.NRGBA
|
||||
Count int
|
||||
@ -38,7 +37,6 @@ func FilledDragList(th *material.Theme, dragList *DragList, count int, element f
|
||||
element: element,
|
||||
swap: swap,
|
||||
Count: count,
|
||||
SurfaceColor: dragListSurfaceColor,
|
||||
HoverColor: dragListHoverColor,
|
||||
SelectedColor: dragListSelectedColor,
|
||||
}
|
||||
@ -47,7 +45,6 @@ func FilledDragList(th *material.Theme, dragList *DragList, count int, element f
|
||||
func (s *FilledDragListStyle) Layout(gtx C) D {
|
||||
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()
|
||||
|
||||
if s.dragList.List.Axis == layout.Horizontal {
|
||||
|
@ -6,8 +6,6 @@ import (
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
@ -50,12 +48,6 @@ func (t *Tracker) layoutInstruments(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 {
|
||||
deleteInstrumentBtnStyle := material.IconButton(t.Theme, t.DeleteInstrumentBtn, widgetForIcon(icons.ActionDelete))
|
||||
deleteInstrumentBtnStyle.Background = transparent
|
||||
@ -87,9 +79,7 @@ func (t *Tracker) layoutInstrumentHeader(gtx C) D {
|
||||
for t.DeleteInstrumentBtn.Clicked() {
|
||||
t.DeleteInstrument()
|
||||
}
|
||||
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
||||
layout.Expanded(headerBg),
|
||||
layout.Stacked(header))
|
||||
return Surface{Gray: 37, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, header)
|
||||
}
|
||||
|
||||
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.SelectedColor = instrumentSurfaceColor
|
||||
instrumentList.SelectedColor = color
|
||||
instrumentList.HoverColor = instrumentHoverColor
|
||||
instrumentList.SurfaceColor = transparent
|
||||
|
||||
t.InstrumentDragList.SelectedItem = t.CurrentInstrument
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
@ -174,7 +167,12 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D {
|
||||
|
||||
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
|
||||
return Surface{Gray: 30, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
|
||||
@ -182,6 +180,7 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D {
|
||||
dims := unitList.Layout(gtx)
|
||||
if t.CurrentUnit != t.UnitDragList.SelectedItem {
|
||||
t.CurrentUnit = t.UnitDragList.SelectedItem
|
||||
t.EditMode = EditUnits
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
}
|
||||
return dims
|
||||
@ -191,4 +190,5 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D {
|
||||
}))
|
||||
}),
|
||||
layout.Rigid(t.layoutUnitEditor))
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gioui.org/io/key"
|
||||
)
|
||||
@ -42,6 +42,38 @@ var noteMap = map[string]int{
|
||||
"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.
|
||||
func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
if e.State == key.Press {
|
||||
@ -59,14 +91,15 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
t.Redo()
|
||||
return true
|
||||
}
|
||||
case "A":
|
||||
t.SetCurrentNote(0)
|
||||
return true
|
||||
case key.NameDeleteForward:
|
||||
switch t.EditMode {
|
||||
case EditTracks:
|
||||
t.DeleteSelection()
|
||||
return true
|
||||
case key.NameEscape:
|
||||
os.Exit(0)
|
||||
case EditUnits:
|
||||
t.DeleteUnit()
|
||||
return true
|
||||
}
|
||||
case "Space":
|
||||
t.TogglePlay()
|
||||
return true
|
||||
@ -75,75 +108,161 @@ func (t *Tracker) KeyEvent(e key.Event) bool {
|
||||
return t.ChangeOctave(1)
|
||||
}
|
||||
return t.ChangeOctave(-1)
|
||||
case key.NameUpArrow:
|
||||
delta := -1
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
delta = -t.song.RowsPerPattern
|
||||
case key.NameTab:
|
||||
if e.Modifiers.Contain(key.ModShift) {
|
||||
t.EditMode = (t.EditMode - 1 + 4) % 4
|
||||
} else {
|
||||
t.EditMode = (t.EditMode + 1) % 4
|
||||
}
|
||||
t.Cursor.Row += delta
|
||||
t.Cursor.Clamp(t.song)
|
||||
if !e.Modifiers.Contain(key.ModShift) {
|
||||
t.SelectionCorner = t.Cursor
|
||||
return true
|
||||
case key.NameUpArrow:
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
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.ClampPositions()
|
||||
if !e.Modifiers.Contain(key.ModShift) {
|
||||
t.Unselect()
|
||||
}
|
||||
return true
|
||||
case key.NameDownArrow:
|
||||
delta := 1
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
delta = t.song.RowsPerPattern
|
||||
}
|
||||
t.Cursor.Row += delta
|
||||
t.Cursor.Clamp(t.song)
|
||||
if !e.Modifiers.Contain(key.ModShift) {
|
||||
t.SelectionCorner = t.Cursor
|
||||
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.ClampPositions()
|
||||
if !e.Modifiers.Contain(key.ModShift) {
|
||||
t.Unselect()
|
||||
}
|
||||
return true
|
||||
case key.NameLeftArrow:
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
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.Cursor.Clamp(t.song)
|
||||
if t.TrackShowHex[t.Cursor.Track] {
|
||||
t.CursorColumn = 1
|
||||
} else {
|
||||
t.CursorColumn = 0
|
||||
}
|
||||
if !e.Modifiers.Contain(key.ModShift) {
|
||||
t.SelectionCorner = t.Cursor
|
||||
}
|
||||
} 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
|
||||
case key.NameRightArrow:
|
||||
if t.CursorColumn == 1 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
t.Cursor.Track = len(t.song.Tracks) - 1
|
||||
} else {
|
||||
t.Cursor.Track++
|
||||
t.Cursor.Clamp(t.song)
|
||||
if !e.Modifiers.Contain(key.ModShift) {
|
||||
t.SelectionCorner = t.Cursor
|
||||
}
|
||||
case EditTracks:
|
||||
if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModCtrl) {
|
||||
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
|
||||
}
|
||||
if e.Modifiers.Contain(key.ModCtrl) {
|
||||
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
|
||||
switch t.EditMode {
|
||||
case EditPatterns:
|
||||
if iv, err := strconv.Atoi(e.Name); err == nil {
|
||||
t.SetCurrentPattern(byte(iv))
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if !t.TrackShowHex[t.Cursor.Track] {
|
||||
if val, ok := noteMap[e.Name]; ok {
|
||||
t.NotePressed(val)
|
||||
if b := byte(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 {
|
||||
t.SetCurrentPattern(b + 10)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
case EditTracks:
|
||||
if t.TrackShowHex[t.Cursor.Track] {
|
||||
if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
|
||||
t.NumberPressed(byte(iv))
|
||||
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 (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"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 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) {
|
||||
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.layoutControls,
|
||||
t.layoutTracksAndPatterns)
|
||||
t.layoutTop,
|
||||
t.layoutBottom)
|
||||
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,
|
||||
t.layoutPatterns,
|
||||
t.layoutTracks,
|
||||
func(gtx C) D {
|
||||
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 {
|
||||
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 {
|
||||
func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions {
|
||||
for t.NewInstrumentBtn.Clicked() {
|
||||
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 {
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
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{
|
||||
Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track},
|
||||
Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track},
|
||||
@ -35,15 +34,20 @@ func (t *Tracker) layoutPatterns(gtx C) D {
|
||||
op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops)
|
||||
for i, track := range t.song.Tracks {
|
||||
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}}
|
||||
if t.EditMode == EditPatterns || t.EditMode == EditTracks {
|
||||
if patternRect.Contains(point) {
|
||||
color := patternSelectionColor
|
||||
color := inactiveSelectionColor
|
||||
if t.EditMode == EditPatterns {
|
||||
color = selectionColor
|
||||
if point.Pattern == t.Cursor.Pattern && point.Track == t.Cursor.Track {
|
||||
color = patternCursorColor
|
||||
color = cursorColor
|
||||
}
|
||||
}
|
||||
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(patternCellWidth, patternCellHeight)}.Op())
|
||||
}
|
||||
}
|
||||
op.Offset(f32.Pt(patternCellWidth, 0)).Add(gtx.Ops)
|
||||
}
|
||||
stack.Load()
|
||||
|
@ -24,7 +24,7 @@ func (t *Tracker) layoutRowMarkers(patternRows, sequenceLength, cursorRow, curso
|
||||
}.Op())
|
||||
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/2)-trackRowHeight)).Add(gtx.Ops)
|
||||
op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops)
|
||||
cursorSongRow := cursorPattern*patternRows + cursorRow
|
||||
playSongRow := playPattern*patternRows + playRow
|
||||
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++ {
|
||||
songRow := i*patternRows + j
|
||||
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 {
|
||||
paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
|
||||
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)
|
||||
} else {
|
||||
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 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 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 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}
|
||||
|
240
tracker/track.go
240
tracker/track.go
@ -10,54 +10,221 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
const trackRowHeight = 16
|
||||
const trackWidth = 54
|
||||
const trackColWidth = 54
|
||||
const patmarkWidth = 16
|
||||
|
||||
func (t *Tracker) layoutTrack(trackNo int) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
gtx.Constraints.Min.X = trackWidth
|
||||
gtx.Constraints.Max.X = trackWidth
|
||||
func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions {
|
||||
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,
|
||||
))
|
||||
|
||||
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/2)-trackRowHeight)).Add(gtx.Ops)
|
||||
// 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
|
||||
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)
|
||||
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},
|
||||
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
|
||||
}
|
||||
pointRect := SongRect{
|
||||
Corner1: t.Cursor,
|
||||
Corner2: t.SelectionCorner,
|
||||
if y1 > y2 {
|
||||
y1, y2 = y2, y1
|
||||
}
|
||||
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())
|
||||
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())
|
||||
}
|
||||
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 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 j == 0 {
|
||||
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))
|
||||
}
|
||||
if songRow == t.Cursor.SongRow {
|
||||
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)
|
||||
}
|
||||
op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops)
|
||||
if t.TrackShowHex[trackNo] {
|
||||
c := trk.Patterns[s][patRow]
|
||||
if t.TrackShowHex[trkIndex] {
|
||||
var text string
|
||||
switch c {
|
||||
case 0:
|
||||
@ -68,28 +235,13 @@ func (t *Tracker) layoutTrack(trackNo int) layout.Widget {
|
||||
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)
|
||||
}
|
||||
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"
|
||||
)
|
||||
|
||||
type EditMode int
|
||||
|
||||
const (
|
||||
EditPatterns EditMode = iota
|
||||
EditTracks
|
||||
EditUnits
|
||||
EditParameters
|
||||
)
|
||||
|
||||
type Tracker struct {
|
||||
QuitButton *widget.Clickable
|
||||
songPlayMutex sync.RWMutex // protects song and playing
|
||||
@ -21,11 +30,13 @@ type Tracker struct {
|
||||
// protects PlayPattern and PlayRow
|
||||
playRowPatMutex sync.RWMutex // protects song and playing
|
||||
PlayPosition SongRow
|
||||
EditMode EditMode
|
||||
SelectionCorner SongPoint
|
||||
Cursor SongPoint
|
||||
CursorColumn int
|
||||
CurrentInstrument int
|
||||
CurrentUnit int
|
||||
CurrentParam int
|
||||
UnitGroupMenuVisible bool
|
||||
UnitGroupMenuIndex int
|
||||
UnitSubMenuIndex int
|
||||
@ -85,12 +96,6 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
||||
defer t.songPlayMutex.Unlock()
|
||||
t.song = song
|
||||
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 {
|
||||
t.sequencer.SetPatch(song.Patch)
|
||||
t.sequencer.SetRowLength(song.SamplesPerRow())
|
||||
@ -98,6 +103,16 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
||||
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() {
|
||||
t.audioContext.Close()
|
||||
t.closer <- struct{}{}
|
||||
@ -232,6 +247,7 @@ func (t *Tracker) AddInstrument() {
|
||||
copy(instr[t.CurrentInstrument+2:], t.song.Patch.Instruments[t.CurrentInstrument+1:])
|
||||
t.song.Patch.Instruments = instr
|
||||
t.CurrentInstrument++
|
||||
t.ClampPositions()
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
@ -242,6 +258,7 @@ func (t *Tracker) SwapInstruments(i, j int) {
|
||||
t.SaveUndo()
|
||||
instruments := t.song.Patch.Instruments
|
||||
instruments[i], instruments[j] = instruments[j], instruments[i]
|
||||
t.ClampPositions()
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
@ -254,6 +271,7 @@ func (t *Tracker) DeleteInstrument() {
|
||||
if t.CurrentInstrument >= len(t.song.Patch.Instruments) {
|
||||
t.CurrentInstrument = len(t.song.Patch.Instruments) - 1
|
||||
}
|
||||
t.ClampPositions()
|
||||
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:])
|
||||
t.song.Patch.Instruments[t.CurrentInstrument].Units = units
|
||||
t.CurrentUnit++
|
||||
t.ClampPositions()
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
@ -350,6 +369,7 @@ 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.ClampPositions()
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
@ -365,6 +385,30 @@ func (t *Tracker) DeleteUnit() {
|
||||
if t.CurrentUnit > 0 {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -375,6 +419,7 @@ func (t *Tracker) SwapUnits(i, j int) {
|
||||
t.SaveUndo()
|
||||
units := t.song.Patch.Instruments[t.CurrentInstrument].Units
|
||||
units[i], units[j] = units[j], units[i]
|
||||
t.ClampPositions()
|
||||
t.sequencer.SetPatch(t.song.Patch)
|
||||
}
|
||||
|
||||
@ -382,6 +427,38 @@ func (t *Tracker) ClampPositions() {
|
||||
t.PlayPosition.Clamp(t.song)
|
||||
t.Cursor.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) {
|
||||
@ -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 {
|
||||
t := &Tracker{
|
||||
Theme: material.NewTheme(gofont.Collection()),
|
||||
@ -484,6 +565,8 @@ func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tr
|
||||
VerticalSplit: new(Split),
|
||||
ChooseUnitTypeList: &layout.List{Axis: layout.Vertical},
|
||||
}
|
||||
t.UnitDragList.HoverItem = -1
|
||||
t.InstrumentDragList.HoverItem = -1
|
||||
t.Octave.Value = 4
|
||||
t.VerticalSplit.Axis = layout.Vertical
|
||||
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 == "" {
|
||||
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,
|
||||
layout.Flexed(1, editorFunc),
|
||||
layout.Rigid(t.layoutUnitFooter()),
|
||||
)
|
||||
layout.Rigid(t.layoutUnitFooter()))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
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])
|
||||
sliderStyle := material.Slider(t.Theme, t.ParameterSliders[index], float32(ut[index].MinValue), float32(ut[index].MaxValue))
|
||||
sliderStyle.Color = t.Theme.Fg
|
||||
@ -65,7 +60,19 @@ func (t *Tracker) layoutUnitSliders(gtx C) D {
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
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)),
|
||||
)
|
||||
@ -108,7 +115,6 @@ func (t *Tracker) layoutUnitFooter() layout.Widget {
|
||||
}
|
||||
|
||||
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 {
|
||||
for t.ChooseUnitTypeBtns[i].Clicked() {
|
||||
t.SetUnit(allUnits[i])
|
||||
|
Loading…
x
Reference in New Issue
Block a user