From 89b728a2696a18d72b76bab3e1dee94ff5f7c120 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Thu, 26 Jun 2025 10:38:38 +0300 Subject: [PATCH] drafting multiparamas --- tracker/derived.go | 34 +++++++++ tracker/gioui/patch_panel.go | 2 +- tracker/gioui/unit_editor.go | 89 ++++++++++++++--------- tracker/list.go | 15 ++++ tracker/params.go | 136 ++++++++++++++++++----------------- 5 files changed, 176 insertions(+), 100 deletions(-) diff --git a/tracker/derived.go b/tracker/derived.go index 1c4b789..5be76fa 100644 --- a/tracker/derived.go +++ b/tracker/derived.go @@ -24,6 +24,7 @@ type ( instrument sointu.Instrument instrumentIndex int unitIndex int + params []Parameter // map param by Name forParameter map[string]derivedForParameter @@ -159,6 +160,7 @@ func (m *Model) updateDerivedPatchData() { unitIndex: u, instrument: instr, instrumentIndex: i, + params: m.deriveParams(&instr.Units[u]), forParameter: make(map[string]derivedForParameter), } m.updateDerivedParameterData(unit) @@ -166,6 +168,38 @@ func (m *Model) updateDerivedPatchData() { } } +func (m *Model) deriveParams(unit *sointu.Unit) []Parameter { + ret := make([]Parameter, 0, 10) + unitType, ok := sointu.UnitTypes[unit.Type] + if !ok { + return ret + } + for i, up := range unitType { + if !up.CanSet { + continue + } + if unit.Type == "oscillator" && unit.Parameters["type"] != sointu.Sample && (up.Name == "samplestart" || up.Name == "loopstart" || up.Name == "looplength") { + continue // don't show the sample related params unless necessary + } + ret = append(ret, Parameter{m: m, unit: unit, up: &unitType[i], vtable: &namedParameter{}}) + } + if unit.Type == "oscillator" && unit.Parameters["type"] == sointu.Sample { + ret = append(ret, Parameter{m: m, unit: unit, vtable: &gmDlsEntryParameter{}}) + } + if unit.Type == "delay" { + if unit.Parameters["stereo"] == 1 && len(unit.VarArgs)%2 == 1 { + unit.VarArgs = append(unit.VarArgs, 1) + } + ret = append(ret, + Parameter{m: m, unit: unit, vtable: &reverbParameter{}}, + Parameter{m: m, unit: unit, vtable: &delayLinesParameter{}}) + for i := range unit.VarArgs { + ret = append(ret, Parameter{m: m, unit: unit, index: i, vtable: &delayTimeParameter{}}) + } + } + return ret +} + func (m *Model) updateDerivedParameterData(unit sointu.Unit) { fu := m.derived.forUnit[unit.ID] for name := range fu.unit.Parameters { diff --git a/tracker/gioui/patch_panel.go b/tracker/gioui/patch_panel.go index 8e9063b..34fb634 100644 --- a/tracker/gioui/patch_panel.go +++ b/tracker/gioui/patch_panel.go @@ -478,7 +478,7 @@ func (ul *UnitList) update(gtx C, t *Tracker) { if e, ok := event.(key.Event); ok && e.State == key.Press { switch e.Name { case key.NameRightArrow: - t.PatchPanel.unitEditor.sliderList.Focus() + t.PatchPanel.unitEditor.paramTable.Focus() case key.NameDeleteBackward: t.Units().SetSelectedType("") t.UnitSearching().SetValue(true) diff --git a/tracker/gioui/unit_editor.go b/tracker/gioui/unit_editor.go index 8d33c89..6775fcc 100644 --- a/tracker/gioui/unit_editor.go +++ b/tracker/gioui/unit_editor.go @@ -7,12 +7,15 @@ import ( "io" "math" + "gioui.org/f32" "gioui.org/io/clipboard" "gioui.org/io/event" "gioui.org/io/key" "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" @@ -26,9 +29,9 @@ import ( ) type UnitEditor struct { - sliderList *DragList + paramTable *ScrollTable searchList *DragList - Parameters []*ParameterWidget + Parameters [][]*ParameterWidget DeleteUnitBtn *Clickable CopyUnitBtn *Clickable ClearUnitBtn *Clickable @@ -52,7 +55,7 @@ func NewUnitEditor(m *tracker.Model) *UnitEditor { CopyUnitBtn: new(Clickable), SelectTypeBtn: new(Clickable), commentEditor: NewEditor(true, true, text.Start), - sliderList: NewDragList(m.Params().List(), layout.Vertical), + paramTable: NewScrollTable(m.Params().Table(), m.ParamVertList().List(), m.Units().List()), searchList: NewDragList(m.SearchResults().List(), layout.Vertical), searching: m.UnitSearching(), } @@ -80,7 +83,7 @@ func (pe *UnitEditor) Layout(gtx C) D { } func (pe *UnitEditor) showingChooser() bool { - return pe.searching.Value() || pe.sliderList.TrackerList.Count() == 0 + return pe.searching.Value() } func (pe *UnitEditor) update(gtx C, t *Tracker) { @@ -110,17 +113,17 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) { } for { e, ok := gtx.Event( - key.Filter{Focus: pe.sliderList, Name: key.NameLeftArrow, Optional: key.ModShift}, - key.Filter{Focus: pe.sliderList, Name: key.NameRightArrow, Optional: key.ModShift}, - key.Filter{Focus: pe.sliderList, Name: key.NameDeleteBackward}, - key.Filter{Focus: pe.sliderList, Name: key.NameDeleteForward}, + key.Filter{Focus: pe.paramTable, Name: key.NameLeftArrow, Required: key.ModShift}, + key.Filter{Focus: pe.paramTable, Name: key.NameRightArrow, Required: key.ModShift}, + key.Filter{Focus: pe.paramTable, Name: key.NameDeleteBackward}, + key.Filter{Focus: pe.paramTable, Name: key.NameDeleteForward}, ) if !ok { break } if e, ok := e.(key.Event); ok && e.State == key.Press { params := t.Model.Params() - item := params.SelectedItem() + item := params.Item(params.Cursor()) switch e.Name { case key.NameLeftArrow: if e.Modifiers.Contain(key.ModShift) { @@ -150,32 +153,56 @@ func (pe *UnitEditor) ChooseUnitType(t *Tracker) { func (pe *UnitEditor) layoutSliders(gtx C) D { t := TrackerFromContext(gtx) - numItems := pe.sliderList.TrackerList.Count() // create enough parameter widget to match the number of parameters - for len(pe.Parameters) < numItems { - pe.Parameters = append(pe.Parameters, new(ParameterWidget)) + width := pe.paramTable.Table.Width() + for len(pe.Parameters) < pe.paramTable.Table.Height() { + pe.Parameters = append(pe.Parameters, make([]*ParameterWidget, 0)) } - - index := 0 - for param := range t.Model.Params().Iterate { - pe.Parameters[index].Parameter = param - index++ + for i := range pe.Parameters { + for len(pe.Parameters[i]) < width { + pe.Parameters[i] = append(pe.Parameters[i], &ParameterWidget{}) + } } - element := func(gtx C, index int) D { - if index < 0 || index >= numItems { + coltitle := func(gtx C, x int) D { + return D{Size: image.Pt(gtx.Dp(100), gtx.Dp(10))} + } + rowtitle := func(gtx C, y int) D { + h := gtx.Dp(100) + //defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() + defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop() + gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6)) + Label(t.Theme, &t.Theme.OrderEditor.TrackTitle, t.Units().Item(y).Type).Layout(gtx) + return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)} + } + cursor := t.Model.Params().Cursor() + cell := func(gtx C, x, y int) D { + gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(100), gtx.Dp(100))) + point := tracker.Point{X: x, Y: y} + if y < 0 || y >= len(pe.Parameters) || x < 0 || x >= len(pe.Parameters[y]) { return D{} } - paramStyle := t.ParamStyle(t.Theme, pe.Parameters[index]) - paramStyle.Focus = pe.sliderList.TrackerList.Selected() == index + if point == cursor { + c := t.Theme.Cursor.Inactive + if gtx.Focused(pe.paramTable) { + c = t.Theme.Cursor.Active + } + paint.FillShape(gtx.Ops, c, clip.Rect{Min: image.Pt(0, 0), Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op()) + } + + param := t.Model.Params().Item(tracker.Point{X: x, Y: y}) + pe.Parameters[y][x].Parameter = param + paramStyle := t.ParamStyle(t.Theme, pe.Parameters[y][x]) + paramStyle.Focus = pe.paramTable.Table.Cursor() == tracker.Point{X: x, Y: y} dims := paramStyle.Layout(gtx) return D{Size: image.Pt(gtx.Constraints.Max.X, dims.Size.Y)} } + table := FilledScrollTable(t.Theme, pe.paramTable) + table.RowTitleWidth = 10 + table.ColumnTitleHeight = 10 + table.CellWidth = 100 + table.CellHeight = 100 + return table.Layout(gtx, cell, coltitle, rowtitle, nil, nil) - fdl := FilledDragList(t.Theme, pe.sliderList) - dims := fdl.Layout(gtx, element, nil) - gtx.Constraints = layout.Exact(dims.Size) - fdl.LayoutScrollBar(gtx) - return dims } func (pe *UnitEditor) layoutFooter(gtx C) D { @@ -234,9 +261,9 @@ func (pe *UnitEditor) layoutUnitTypeChooser(gtx C) D { } func (t *UnitEditor) Tags(level int, yield TagYieldFunc) bool { - widget := t.sliderList + widget := event.Tag(t.paramTable) if t.showingChooser() { - widget = t.searchList + widget = event.Tag(t.searchList) } return yield(level, widget) && yield(level+1, &t.commentEditor.widgetEditor) } @@ -299,8 +326,6 @@ func (p ParameterStyle) Layout(gtx C) D { p.w.Parameter.SetValue(p.w.Parameter.Value() - int(delta)) } } - gtx.Constraints.Min.X = gtx.Dp(unit.Dp(200)) - gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(40)) ra := p.w.Parameter.Range() if !p.w.floatWidget.Dragging() { p.w.floatWidget.Value = (float32(p.w.Parameter.Value()) - float32(ra.Min)) / float32(ra.Max-ra.Min) @@ -319,8 +344,6 @@ func (p ParameterStyle) Layout(gtx C) D { p.w.Parameter.SetValue(int(p.w.floatWidget.Value*float32(ra.Max-ra.Min) + float32(ra.Min) + 0.5)) return dims case tracker.BoolParameter: - gtx.Constraints.Min.X = gtx.Dp(unit.Dp(60)) - gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(40)) ra := p.w.Parameter.Range() p.w.boolWidget.Value = p.w.Parameter.Value() > ra.Min boolStyle := material.Switch(&p.Theme.Material, &p.w.boolWidget, "Toggle boolean parameter") @@ -334,8 +357,6 @@ func (p ParameterStyle) Layout(gtx C) D { } return dims case tracker.IDParameter: - gtx.Constraints.Min.X = gtx.Dp(unit.Dp(200)) - gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(40)) instrItems := make([]ActionMenuItem, p.tracker.Instruments().Count()) for i := range instrItems { i := i diff --git a/tracker/list.go b/tracker/list.go index f410830..ed2ac3c 100644 --- a/tracker/list.go +++ b/tracker/list.go @@ -320,6 +320,21 @@ func (v *Units) Iterate(yield UnitYieldFunc) { } } +func (v *Units) Item(index int) UnitListItem { + if index < 0 || index >= v.Count() { + return UnitListItem{} + } + unit := v.d.Song.Patch[v.d.InstrIndex].Units[index] + return UnitListItem{ + Type: unit.Type, + Comment: unit.Comment, + Disabled: unit.Disabled, + StackNeed: unit.StackNeed(), + StackBefore: 0, + StackAfter: 0, + } +} + func (v *Units) Selected() int { return max(min(v.d.UnitIndex, v.Count()-1), 0) } diff --git a/tracker/params.go b/tracker/params.go index adb005a..ec8e6d0 100644 --- a/tracker/params.go +++ b/tracker/params.go @@ -34,7 +34,9 @@ type ( Reset(*Parameter) } - Params Model + Params Model + ParamVertList Model + // different parameter vtables to handle different types of parameters. // Casting struct{} to interface does not cause allocations. namedParameter struct{} @@ -121,78 +123,82 @@ func (p *Parameter) Reset() { p.vtable.Reset(p) } +// + +func (m *Model) ParamVertList() *ParamVertList { return (*ParamVertList)(m) } +func (pt *ParamVertList) List() List { return List{pt} } +func (pt *ParamVertList) Selected() int { return pt.d.ParamIndex } +func (pt *ParamVertList) Selected2() int { return pt.d.ParamIndex } +func (pt *ParamVertList) SetSelected(index int) { pt.d.ParamIndex = index } +func (pt *ParamVertList) SetSelected2(index int) {} +func (pt *ParamVertList) Count() int { return (*Params)(pt).Width() } + // Model and Params methods -func (m *Model) Params() *Params { return (*Params)(m) } -func (pl *Params) List() List { return List{pl} } -func (pl *Params) Selected() int { return pl.d.ParamIndex } -func (pl *Params) Selected2() int { return pl.Selected() } -func (pl *Params) SetSelected(value int) { pl.d.ParamIndex = max(min(value, pl.Count()-1), 0) } -func (pl *Params) SetSelected2(value int) {} - -func (pl *Params) Count() int { - count := 0 - for range pl.Iterate { - count++ - } - return count +func (m *Model) Params() *Params { return (*Params)(m) } +func (pt *Params) Table() Table { return Table{pt} } +func (pt *Params) Cursor() Point { return Point{pt.d.ParamIndex, pt.d.UnitIndex} } +func (pt *Params) Cursor2() Point { return pt.Cursor() } +func (pt *Params) SetCursor(p Point) { + pt.d.ParamIndex = p.X + pt.d.UnitIndex = p.Y } - -func (pl *Params) SelectedItem() (ret Parameter) { - index := pl.Selected() - for param := range pl.Iterate { - if index == 0 { - ret = param - } - index-- +func (pt *Params) SetCursor2(p Point) {} +func (pt *Params) Width() int { + if pt.d.InstrIndex < 0 || pt.d.InstrIndex >= len(pt.d.Song.Patch) { + return 0 } - return + ret := 0 + for _, unit := range pt.d.Song.Patch[pt.d.InstrIndex].Units { + ret = max(ret, len(pt.derived.forUnit[unit.ID].params)) + } + return ret } - -func (pl *Params) Iterate(yield ParamYieldFunc) { - if pl.d.InstrIndex < 0 || pl.d.InstrIndex >= len(pl.d.Song.Patch) { - return +func (pt *Params) Height() int { return (*Model)(pt).Units().Count() } +func (pt *Params) MoveCursor(dx, dy int) (ok bool) { + if pt.d.InstrIndex < 0 || pt.d.InstrIndex >= len(pt.d.Song.Patch) { + return false } - if pl.d.UnitIndex < 0 || pl.d.UnitIndex >= len(pl.d.Song.Patch[pl.d.InstrIndex].Units) { - return + pt.d.ParamIndex += dx + pt.d.UnitIndex += dy + pt.d.ParamIndex = clamp(pt.d.ParamIndex, 0, 7) + pt.d.UnitIndex = clamp(pt.d.UnitIndex, 0, len(pt.d.Song.Patch[pt.d.InstrIndex].Units)-1) + return true +} +func (pt *Params) Item(p Point) Parameter { + if pt.d.InstrIndex < 0 || pt.d.InstrIndex >= len(pt.d.Song.Patch) || + p.Y < 0 || p.Y >= len(pt.d.Song.Patch[pt.d.InstrIndex].Units) { + return Parameter{} } - unit := &pl.d.Song.Patch[pl.d.InstrIndex].Units[pl.d.UnitIndex] - unitType, ok := sointu.UnitTypes[unit.Type] - if !ok { - return - } - for i, up := range unitType { - if !up.CanSet { - continue - } - if unit.Type == "oscillator" && unit.Parameters["type"] != sointu.Sample && (up.Name == "samplestart" || up.Name == "loopstart" || up.Name == "looplength") { - continue // don't show the sample related params unless necessary - } - if !yield(Parameter{m: (*Model)(pl), unit: unit, up: &unitType[i], vtable: &namedParameter{}}) { - return - } - } - if unit.Type == "oscillator" && unit.Parameters["type"] == sointu.Sample { - if !yield(Parameter{m: (*Model)(pl), unit: unit, vtable: &gmDlsEntryParameter{}}) { - return - } - } - if unit.Type == "delay" { - if unit.Parameters["stereo"] == 1 && len(unit.VarArgs)%2 == 1 { - unit.VarArgs = append(unit.VarArgs, 1) - } - if !yield(Parameter{m: (*Model)(pl), unit: unit, vtable: &reverbParameter{}}) { - return - } - if !yield(Parameter{m: (*Model)(pl), unit: unit, vtable: &delayLinesParameter{}}) { - return - } - for i := range unit.VarArgs { - if !yield(Parameter{m: (*Model)(pl), unit: unit, index: i, vtable: &delayTimeParameter{}}) { - return - } - } + id := pt.d.Song.Patch[pt.d.InstrIndex].Units[p.Y].ID + if p.X < 0 || p.X >= len(pt.derived.forUnit[id].params) { + return Parameter{} } + return pt.derived.forUnit[id].params[p.X] +} +func (pt *Params) clear(p Point) { + panic("NOT IMPLEMENTED") +} +func (pt *Params) set(p Point, value int) { + panic("NOT IMPLEMENTED") +} +func (pt *Params) add(rect Rect, delta int) (ok bool) { + panic("NOT IMPLEMENTED") +} +func (pt *Params) marshal(rect Rect) (data []byte, ok bool) { + panic("NOT IMPLEMENTED") +} +func (pt *Params) unmarshalAtCursor(data []byte) (ok bool) { + panic("NOT IMPLEMENTED") +} +func (pt *Params) unmarshalRange(rect Rect, data []byte) (ok bool) { + panic("NOT IMPLEMENTED") +} +func (pt *Params) change(kind string, severity ChangeSeverity) func() { + panic("NOT IMPLEMENTED") +} +func (pt *Params) cancel() { + panic("NOT IMPLEMENTED") } // namedParameter vtable