From e1aa9c0d26ca251b28de13b4808d0c3822f0baf7 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:38:07 +0300 Subject: [PATCH] drafting --- patch.go | 4 +- tracker/derived.go | 31 ++++--- tracker/gioui/draglist.go | 2 + tracker/gioui/knob.go | 5 +- tracker/gioui/order_editor.go | 4 +- tracker/gioui/patch_panel.go | 2 +- tracker/gioui/scroll_table.go | 13 ++- tracker/gioui/theme.go | 3 +- tracker/gioui/theme.yml | 3 +- tracker/gioui/unit_editor.go | 102 ++++++++++++++++------- tracker/params.go | 151 +++++++++++++++++++++++----------- 11 files changed, 218 insertions(+), 102 deletions(-) diff --git a/patch.go b/patch.go index 6ce37e6..59ca734 100644 --- a/patch.go +++ b/patch.go @@ -380,8 +380,8 @@ var stackUseMonoStereo = map[string][2]StackUse{ {Inputs: [][]int{{0, 2, 3}, {1, 2, 3}}, Modifies: []bool{false, false, true, true}, NumOutputs: 4}, // stereo }, "pan": { - {Inputs: [][]int{{0, 1}}, Modifies: []bool{true, true}, NumOutputs: 2}, // mono - {Inputs: [][]int{{0, 1}, {0, 1}}, Modifies: []bool{true, true}, NumOutputs: 2}, // mono + {Inputs: [][]int{{0, 1}}, Modifies: []bool{true, true}, NumOutputs: 2}, // mono + {Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2}, // mono }, "speed": { {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0}, diff --git a/tracker/derived.go b/tracker/derived.go index 35eaabd..df34f8a 100644 --- a/tracker/derived.go +++ b/tracker/derived.go @@ -39,7 +39,7 @@ type ( derivedInstrument struct { wires []Wire rails []Rail - railsWidth int + railWidth int params [][]Parameter paramsWidth int } @@ -59,7 +59,7 @@ func (s *Model) RailWidth() int { if i < 0 || i >= len(s.derived.patch) { return 0 } - return s.derived.patch[i].railsWidth + return s.derived.patch[i].railWidth } func (m *Model) Wires(yield func(wire Wire) bool) { @@ -121,9 +121,10 @@ func (m *Model) updateParams() { for i, instr := range m.d.Song.Patch { setSliceLength(&m.derived.patch[i].params, len(instr.Units)) paramsWidth := 0 - for u, unit := range instr.Units { - m.derived.patch[i].params[u] = m.deriveParams(&unit, m.derived.patch[i].params[u]) - paramsWidth = max(paramsWidth, len(m.derived.patch[i].params[u])) + for u := range instr.Units { + p := m.deriveParams(&instr.Units[u], m.derived.patch[i].params[u]) + m.derived.patch[i].params[u] = p + paramsWidth = max(paramsWidth, len(p)) } m.derived.patch[i].paramsWidth = paramsWidth } @@ -264,7 +265,7 @@ func (m *Model) updateRails() { scratch = append(scratch, stackElem{instr: i, unit: u}) } } - m.derived.patch[i].railsWidth = maxWidth + m.derived.patch[i].railWidth = maxWidth diff := len(scratch) - start if instr.NumVoices > 1 && diff != 0 { if diff < 0 { @@ -314,7 +315,7 @@ func (m *Model) updateWires() { if err != nil { continue } - _, tX, ok := sointu.FindParamForModulationPort(m.d.Song.Patch[tI].Units[tU].Type, unit.Parameters["port"]) + up, tX, ok := sointu.FindParamForModulationPort(m.d.Song.Patch[tI].Units[tU].Type, unit.Parameters["port"]) if !ok { continue } @@ -325,20 +326,28 @@ func (m *Model) updateWires() { FromSet: true, To: Point{X: tX, Y: tU}, ToSet: true, - Hint: "TBW", }) } else { // remote send m.derived.patch[i].wires = append(m.derived.patch[i].wires, Wire{ From: u, FromSet: true, - Hint: "TBW", + Hint: fmt.Sprintf("To instrument #%d (%s), unit #%d (%s), port %s", tI, m.d.Song.Patch[tI].Name, tU, m.d.Song.Patch[tI].Units[tU].Type, up.Name), }) + toPt := Point{X: tX, Y: tU} + hint := fmt.Sprintf("From instrument #%d (%s), send #%d", i, m.d.Song.Patch[i].Name, u) + for i, w := range m.derived.patch[tI].wires { + if !w.FromSet && w.ToSet && w.To == toPt { + m.derived.patch[tI].wires[i].Hint += "; " + hint + goto skipAppend + } + } m.derived.patch[tI].wires = append(m.derived.patch[tI].wires, Wire{ - To: Point{X: tX, Y: tU}, + To: toPt, ToSet: true, - Hint: "TBW", + Hint: hint, }) + skipAppend: } } } diff --git a/tracker/gioui/draglist.go b/tracker/gioui/draglist.go index 3725021..92f891f 100644 --- a/tracker/gioui/draglist.go +++ b/tracker/gioui/draglist.go @@ -111,6 +111,8 @@ func (s FilledDragListStyle) Layout(gtx C, element, bg func(gtx C, i int) D) D { case key.FocusEvent: if !ke.Focus { s.dragList.TrackerList.SetSelected2(s.dragList.TrackerList.Selected()) + } else { + s.dragList.EnsureVisible(s.dragList.TrackerList.Selected()) } case key.Event: if ke.State != key.Press { diff --git a/tracker/gioui/knob.go b/tracker/gioui/knob.go index 16e7404..13b33c2 100644 --- a/tracker/gioui/knob.go +++ b/tracker/gioui/knob.go @@ -9,6 +9,7 @@ import ( "gioui.org/f32" "gioui.org/gesture" "gioui.org/io/event" + "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/op/clip" @@ -142,8 +143,8 @@ func (k *KnobWidget) update(gtx C) { break } if ev, ok := e.(pointer.Event); ok && ev.Kind == pointer.Scroll { - delta := math.Min(math.Max(float64(ev.Scroll.Y), -1), 1) - k.Value.SetValue(k.Value.Value() - int(delta)) + delta := -int(math.Min(math.Max(float64(ev.Scroll.Y), -1), 1)) + k.Value.Add(delta, ev.Modifiers.Contain(key.ModShortcut)) k.State.tipArea.Appear(gtx.Now) } } diff --git a/tracker/gioui/order_editor.go b/tracker/gioui/order_editor.go index 6e7caa6..e4731ec 100644 --- a/tracker/gioui/order_editor.go +++ b/tracker/gioui/order_editor.go @@ -120,7 +120,9 @@ func (oe *OrderEditor) Layout(gtx C) D { table := FilledScrollTable(t.Theme, oe.scrollTable) table.ColumnTitleHeight = orderTitleHeight - return table.Layout(gtx, cell, colTitle, rowTitle, nil, rowTitleBg) + return Surface{Gray: 24, Focus: oe.scrollTable.TreeFocused(gtx)}.Layout(gtx, func(gtx C) D { + return table.Layout(gtx, cell, colTitle, rowTitle, nil, rowTitleBg) + }) } func (oe *OrderEditor) handleEvents(gtx C, t *Tracker) { diff --git a/tracker/gioui/patch_panel.go b/tracker/gioui/patch_panel.go index c087111..5bc0a2b 100644 --- a/tracker/gioui/patch_panel.go +++ b/tracker/gioui/patch_panel.go @@ -444,7 +444,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.paramTable.Focus() + t.PatchPanel.unitEditor.paramTable.RowTitleList.Focus() case key.NameDeleteBackward: t.Units().SetSelectedType("") t.UnitSearching().SetValue(true) diff --git a/tracker/gioui/scroll_table.go b/tracker/gioui/scroll_table.go index 529153d..2cb1105 100644 --- a/tracker/gioui/scroll_table.go +++ b/tracker/gioui/scroll_table.go @@ -176,12 +176,17 @@ func (s *ScrollTableStyle) handleEvents(gtx layout.Context, p image.Point) { } case key.Event: if e.State == key.Press { - s.ScrollTable.command(gtx, e) + s.ScrollTable.command(gtx, e, p) } case transfer.DataEvent: if b, err := io.ReadAll(e.Open()); err == nil { s.ScrollTable.Table.Paste(b) } + case key.FocusEvent: + if e.Focus { + s.ScrollTable.ColTitleList.EnsureVisible(s.ScrollTable.Table.Cursor().X) + s.ScrollTable.RowTitleList.EnsureVisible(s.ScrollTable.Table.Cursor().Y) + } } } @@ -250,7 +255,7 @@ func (s *ScrollTableStyle) layoutColTitles(gtx C, p image.Point, fg, bg func(gtx s.ColTitleStyle.Layout(gtx, fg, bg) } -func (s *ScrollTable) command(gtx C, e key.Event) { +func (s *ScrollTable) command(gtx C, e key.Event, p image.Point) { stepX := 1 stepY := 1 if e.Modifiers.Contain(key.ModAlt) { @@ -265,13 +270,13 @@ func (s *ScrollTable) command(gtx C, e key.Event) { s.Table.Clear() return case key.NameUpArrow: - if !s.Table.MoveCursor(0, -stepY) && stepY == 1 { + if !s.Table.MoveCursor(0, -stepY) && stepY == 1 && p.Y > 0 { s.ColTitleList.Focus() } case key.NameDownArrow: s.Table.MoveCursor(0, stepY) case key.NameLeftArrow: - if !s.Table.MoveCursor(-stepX, 0) && stepX == 1 { + if !s.Table.MoveCursor(-stepX, 0) && stepX == 1 && p.X > 0 { s.RowTitleList.Focus() } case key.NameRightArrow: diff --git a/tracker/gioui/theme.go b/tracker/gioui/theme.go index 630a2db..7104d5c 100644 --- a/tracker/gioui/theme.go +++ b/tracker/gioui/theme.go @@ -98,7 +98,8 @@ type Theme struct { Disabled LabelStyle Error color.NRGBA } - Error color.NRGBA + Error color.NRGBA + Divider color.NRGBA } Cursor CursorStyle Selection CursorStyle diff --git a/tracker/gioui/theme.yml b/tracker/gioui/theme.yml index fc77cd4..9c83e2e 100644 --- a/tracker/gioui/theme.yml +++ b/tracker/gioui/theme.yml @@ -220,6 +220,7 @@ uniteditor: disabled: { textsize: 12, color: *disabled, font: { style: 1 }, alignment: 2 } error: *errorcolor + divider: { r: 255, g: 255, b: 255, a: 5 } knob: diameter: 36 value: { textsize: 12, color: *highemphasis } @@ -237,4 +238,4 @@ signalrail: port: diameter: 36 strokewidth: 4 - color: *secondarycolor + color: { r: 32, g: 55, b: 58, a: 255 } diff --git a/tracker/gioui/unit_editor.go b/tracker/gioui/unit_editor.go index 1811f52..251cb55 100644 --- a/tracker/gioui/unit_editor.go +++ b/tracker/gioui/unit_editor.go @@ -10,7 +10,6 @@ import ( "gioui.org/f32" "gioui.org/io/clipboard" - "gioui.org/io/event" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/layout" @@ -127,22 +126,23 @@ func (pe *UnitEditor) update(gtx C, t *Tracker) { item := params.Item(params.Cursor()) switch e.Name { case key.NameLeftArrow: - if e.Modifiers.Contain(key.ModShortcut) { - item.SetValue(item.Value() - item.LargeStep()) - } else { - item.SetValue(item.Value() - 1) - } + item.Add(-1, e.Modifiers.Contain(key.ModShortcut)) case key.NameRightArrow: - if e.Modifiers.Contain(key.ModShortcut) { - item.SetValue(item.Value() + item.LargeStep()) - } else { - item.SetValue(item.Value() + 1) - } + item.Add(1, e.Modifiers.Contain(key.ModShortcut)) case key.NameDeleteBackward, key.NameDeleteForward: item.Reset() } } } + for { + e, ok := gtx.Event(key.Filter{Focus: pe.paramTable.RowTitleList, Name: key.NameLeftArrow}) + if !ok { + break + } + if e, ok := e.(key.Event); ok && e.State == key.Press { + t.PatchPanel.unitList.dragList.Focus() + } + } } func (pe *UnitEditor) ChooseUnitType(t *Tracker) { @@ -174,10 +174,6 @@ func (pe *UnitEditor) layoutRack(gtx C) D { coltitle := func(gtx C, x int) D { return D{Size: image.Pt(cellWidth, columnTitleHeight)} } - rowTitleBg := func(gtx C, j int) D { - paint.FillShape(gtx.Ops, t.Theme.NoteEditor.Play, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, 1)}.Op()) - return D{} - } rowtitle := func(gtx C, y int) D { if y < 0 || y >= len(pe.Parameters) { return D{} @@ -223,8 +219,9 @@ func (pe *UnitEditor) layoutRack(gtx C) D { table.ColumnTitleHeight = 0 table.CellWidth = t.Theme.UnitEditor.Width table.CellHeight = t.Theme.UnitEditor.Height + pe.drawBackGround(gtx) pe.drawSignals(gtx, rowTitleWidth) - dims := table.Layout(gtx, cell, coltitle, rowtitle, nil, rowTitleBg) + dims := table.Layout(gtx, cell, coltitle, rowtitle, nil, nil) return dims } @@ -238,27 +235,71 @@ func (pe *UnitEditor) drawSignals(gtx C, rowTitleWidth int) { defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() defer op.Offset(image.Pt(-colP.Offset, -rowP.Offset)).Push(gtx.Ops).Pop() for wire := range t.Wires { - pe.drawSignal(gtx, wire, colP.First, rowP.First) + switch { + case wire.FromSet && !wire.ToSet: + pe.drawRemoteSendSignal(gtx, wire, colP.First, rowP.First) + case !wire.FromSet && wire.ToSet: + pe.drawRemoteReceiveSignal(gtx, wire, colP.First, rowP.First) + case wire.FromSet && wire.ToSet: + pe.drawSignal(gtx, wire, colP.First, rowP.First) + } } } +func (pe *UnitEditor) drawBackGround(gtx C) { + t := TrackerFromContext(gtx) + rowP := pe.paramTable.RowTitleList.List.Position + defer op.Offset(image.Pt(0, -rowP.Offset)).Push(gtx.Ops).Pop() + for range pe.paramTable.RowTitleList.List.Position.Count + 1 { + paint.FillShape(gtx.Ops, t.Theme.UnitEditor.Divider, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, 1)}.Op()) + op.Offset(image.Pt(0, gtx.Dp(t.Theme.UnitEditor.Height))).Add(gtx.Ops) + } +} + +func (pe *UnitEditor) drawRemoteSendSignal(gtx C, wire tracker.Wire, col, row int) { + sy := wire.From - row + t := TrackerFromContext(gtx) + defer op.Offset(image.Pt(0, (sy+1)*gtx.Dp(t.Theme.UnitEditor.Height)-gtx.Dp(16))).Push(gtx.Ops).Pop() + Label(t.Theme, &t.Theme.UnitEditor.WireHint, wire.Hint).Layout(gtx) +} + +func (pe *UnitEditor) drawRemoteReceiveSignal(gtx C, wire tracker.Wire, col, row int) { + ex := wire.To.X - col + ey := wire.To.Y - row + t := TrackerFromContext(gtx) + width := float32(gtx.Dp(t.Theme.UnitEditor.Width)) + height := float32(gtx.Dp(t.Theme.UnitEditor.Height)) + topLeft := f32.Pt(float32(ex)*width, float32(ey)*height) + center := topLeft.Add(f32.Pt(width/2, height/2)) + c := float32(gtx.Dp(t.Theme.Knob.Diameter)) / 2 / float32(math.Sqrt2) + from := f32.Pt(c, c).Add(center) + q := c + c1 := f32.Pt(c+q, c+q).Add(center) + o := float32(gtx.Dp(8)) + c2 := f32.Pt(width-q, height-o).Add(topLeft) + to := f32.Pt(width, height-o).Add(topLeft) + var path clip.Path + path.Begin(gtx.Ops) + path.MoveTo(from) + path.CubeTo(c1, c2, to) + paint.FillShape(gtx.Ops, t.Theme.UnitEditor.WireColor, + clip.Stroke{ + Path: path.End(), + Width: float32(gtx.Dp(t.Theme.SignalRail.LineWidth)), + }.Op()) + defer op.Offset(image.Pt((ex+1)*gtx.Dp(t.Theme.UnitEditor.Width)+gtx.Dp(5), (ey+1)*gtx.Dp(t.Theme.UnitEditor.Height)-gtx.Dp(16))).Push(gtx.Ops).Pop() + Label(t.Theme, &t.Theme.UnitEditor.WireHint, wire.Hint).Layout(gtx) +} + func (pe *UnitEditor) drawSignal(gtx C, wire tracker.Wire, col, row int) { sy := wire.From - row ex := wire.To.X - col ey := wire.To.Y - row t := TrackerFromContext(gtx) - if wire.FromSet && !wire.ToSet { - defer op.Offset(image.Pt(0, (sy+1)*gtx.Dp(t.Theme.UnitEditor.Height)-gtx.Dp(16))).Push(gtx.Ops).Pop() - Label(t.Theme, &t.Theme.UnitEditor.WireHint, wire.Hint).Layout(gtx) - return - } - if !wire.FromSet && wire.ToSet { - Label(t.Theme, &t.Theme.UnitEditor.WireHint, wire.Hint).Layout(gtx) - return - } + diam := gtx.Dp(t.Theme.Knob.Diameter) + c := float32(diam) / 2 / float32(math.Sqrt2) width := float32(gtx.Dp(t.Theme.UnitEditor.Width)) height := float32(gtx.Dp(t.Theme.UnitEditor.Height)) - diam := gtx.Dp(t.Theme.Knob.Diameter) from := f32.Pt(0, float32((sy+1)*gtx.Dp(t.Theme.UnitEditor.Height))-float32(gtx.Dp(t.Theme.SignalRail.SignalWidth)/2)) corner := f32.Pt(1, 1) if ex > 0 { @@ -267,7 +308,6 @@ func (pe *UnitEditor) drawSignal(gtx C, wire tracker.Wire, col, row int) { if sy < ey { corner.Y = -corner.Y } - c := float32(diam) / 2 / float32(math.Sqrt2) topLeft := f32.Pt(float32(ex)*width, float32(ey)*height) center := topLeft.Add(f32.Pt(width/2, height/2)) to := mulVec(corner, f32.Pt(c, c)).Add(center) @@ -277,7 +317,6 @@ func (pe *UnitEditor) drawSignal(gtx C, wire tracker.Wire, col, row int) { p1 = f32.Pt(p2.X, (float32(sy)+0.5)*float32(gtx.Dp(t.Theme.UnitEditor.Height))+float32(diam)/2) } k := float32(width) / 4 - //toTan := mulVec(corner, f32.Pt(-k, -k)) p2Tan := mulVec(corner, f32.Pt(-k, -k)) p1Tan := f32.Pt(k, p2Tan.Y) fromTan := f32.Pt(k, 0) @@ -353,11 +392,10 @@ func (pe *UnitEditor) layoutUnitTypeChooser(gtx C) D { } func (t *UnitEditor) Tags(level int, yield TagYieldFunc) bool { - widget := event.Tag(t.paramTable) if t.showingChooser() { - widget = event.Tag(t.searchList) + return yield(level, t.searchList) && yield(level+1, &t.commentEditor.widgetEditor) } - return yield(level, widget) && yield(level+1, &t.commentEditor.widgetEditor) + return yield(level+1, t.paramTable.RowTitleList) && yield(level, t.paramTable) && yield(level+1, &t.commentEditor.widgetEditor) } type ParameterState struct { diff --git a/tracker/params.go b/tracker/params.go index 52107ba..e0bd0d3 100644 --- a/tracker/params.go +++ b/tracker/params.go @@ -30,8 +30,8 @@ type ( Type(*Parameter) ParameterType Name(*Parameter) string Hint(*Parameter) ParameterHint - LargeStep(*Parameter) int Reset(*Parameter) + RoundToGrid(*Parameter, int, bool) int } Params Model @@ -87,6 +87,17 @@ func (p *Parameter) SetValue(value int) bool { } return p.vtable.SetValue(p, value) } +func (p *Parameter) Add(delta int, snapToGrid bool) bool { + if p.vtable == nil { + return false + } + newVal := p.Value() + delta + if snapToGrid && p.vtable != nil { + newVal = p.vtable.RoundToGrid(p, newVal, delta > 0) + } + return p.SetValue(newVal) +} + func (p *Parameter) Range() IntRange { if p.vtable == nil { return IntRange{} @@ -111,12 +122,6 @@ func (p *Parameter) Hint() ParameterHint { } return p.vtable.Hint(p) } -func (p *Parameter) LargeStep() int { - if p.vtable == nil { - return 1 - } - return p.vtable.LargeStep(p) -} func (p *Parameter) Reset() { if p.vtable == nil { return @@ -173,13 +178,24 @@ func (pt *Params) Item(p Point) Parameter { return pt.derived.patch[pt.d.InstrIndex].params[p.Y][p.X] } func (pt *Params) clear(p Point) { - panic("NOT IMPLEMENTED") + q := pt.Item(p) + q.Reset() } func (pt *Params) set(p Point, value int) { - panic("NOT IMPLEMENTED") + q := pt.Item(p) + q.SetValue(value) } func (pt *Params) add(rect Rect, delta int) (ok bool) { - panic("NOT IMPLEMENTED") + for y := rect.TopLeft.Y; y <= rect.BottomRight.Y; y++ { + for x := rect.TopLeft.X; x <= rect.BottomRight.X; x++ { + p := Point{x, y} + q := pt.Item(p) + if !q.SetValue(q.Value() + delta) { + return false + } + } + } + return true } func (pt *Params) marshal(rect Rect) (data []byte, ok bool) { panic("NOT IMPLEMENTED") @@ -191,10 +207,10 @@ 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") + return (*Model)(pt).change(kind, PatchChange, severity) } func (pt *Params) cancel() { - panic("NOT IMPLEMENTED") + pt.changeCancel = true } // namedParameter vtable @@ -232,11 +248,12 @@ func (n *namedParameter) Hint(p *Parameter) ParameterHint { } return ParameterHint{label, true} } -func (n *namedParameter) LargeStep(p *Parameter) int { +func (n *namedParameter) RoundToGrid(p *Parameter, val int, up bool) int { + grid := 16 if p.up.Name == "transpose" { - return 12 + grid = 12 } - return 16 + return roundToGrid(val, grid, up) } func (n *namedParameter) Reset(p *Parameter) { v, ok := defaultUnits[p.unit.Type].Parameters[p.up.Name] @@ -288,13 +305,30 @@ func (g *gmDlsEntryParameter) Hint(p *Parameter) ParameterHint { } return ParameterHint{label, true} } -func (g *gmDlsEntryParameter) LargeStep(p *Parameter) int { - return 16 +func (g *gmDlsEntryParameter) RoundToGrid(p *Parameter, val int, up bool) int { + return roundToGrid(val, 16, up) } func (g *gmDlsEntryParameter) Reset(p *Parameter) {} // delayTimeParameter vtable +var delayNoteTrackGrid, delayBpmTrackGrid []int + +func init() { + for st := -48; st <= 48; st++ { + gridVal := int(math.Exp2(-float64(st)/12)*10787 + 0.5) + delayNoteTrackGrid = append(delayNoteTrackGrid, gridVal) + } + for i := 0; i < 16; i++ { + delayBpmTrackGrid = append(delayBpmTrackGrid, 1<= len(p.unit.VarArgs) { return 1 @@ -312,12 +346,6 @@ func (d *delayTimeParameter) Range(p *Parameter) IntRange { } return IntRange{Min: 1, Max: 65535} } -func (d *delayTimeParameter) Type(p *Parameter) ParameterType { - return IntegerParameter -} -func (d *delayTimeParameter) Name(p *Parameter) string { - return "delaytime" -} func (d *delayTimeParameter) Hint(p *Parameter) ParameterHint { val := d.Value(p) var text string @@ -350,7 +378,7 @@ func (d *delayTimeParameter) Hint(p *Parameter) ParameterHint { text = fmt.Sprintf(" (1/%d dotted)", 1<<(5-k)) } } - text = fmt.Sprintf("%v / %.3f beats%s", val, float32(val)/48.0, text) + text = fmt.Sprintf("%.3f beats%s", float32(val)/48.0, text) } if p.unit.Parameters["stereo"] == 1 { if p.index < len(p.unit.VarArgs)/2 { @@ -361,8 +389,15 @@ func (d *delayTimeParameter) Hint(p *Parameter) ParameterHint { } return ParameterHint{text, true} } -func (d *delayTimeParameter) LargeStep(p *Parameter) int { - return 16 +func (d *delayTimeParameter) RoundToGrid(p *Parameter, val int, up bool) int { + switch p.unit.Parameters["notetracking"] { + default: + return roundToGrid(val, 16, up) + case 1: + return roundToSliceGrid(val, delayNoteTrackGrid, up) + case 2: + return roundToSliceGrid(val, delayBpmTrackGrid, up) + } } func (d *delayTimeParameter) Reset(p *Parameter) {} @@ -387,15 +422,10 @@ func (d *delayLinesParameter) SetValue(p *Parameter, v int) bool { p.unit.VarArgs = p.unit.VarArgs[:targetLines] return true } -func (d *delayLinesParameter) Range(p *Parameter) IntRange { - return IntRange{Min: 1, Max: 32} -} -func (d *delayLinesParameter) Type(p *Parameter) ParameterType { - return IntegerParameter -} -func (d *delayLinesParameter) Name(p *Parameter) string { - return "delaylines" -} +func (d *delayLinesParameter) Range(p *Parameter) IntRange { return IntRange{Min: 1, Max: 32} } +func (d *delayLinesParameter) Type(p *Parameter) ParameterType { return IntegerParameter } +func (d *delayLinesParameter) Name(p *Parameter) string { return "delaylines" } +func (r *delayLinesParameter) RoundToGrid(p *Parameter, val int, up bool) int { return val } func (d *delayLinesParameter) Hint(p *Parameter) ParameterHint { return ParameterHint{strconv.Itoa(d.Value(p)), true} } @@ -424,15 +454,11 @@ func (r *reverbParameter) SetValue(p *Parameter, v int) bool { copy(p.unit.VarArgs, entry.varArgs) return true } -func (r *reverbParameter) Range(p *Parameter) IntRange { - return IntRange{Min: 0, Max: len(reverbs)} -} -func (r *reverbParameter) Type(p *Parameter) ParameterType { - return IntegerParameter -} -func (r *reverbParameter) Name(p *Parameter) string { - return "reverb" -} +func (r *reverbParameter) Range(p *Parameter) IntRange { return IntRange{Min: 0, Max: len(reverbs)} } +func (r *reverbParameter) Type(p *Parameter) ParameterType { return IntegerParameter } +func (r *reverbParameter) Name(p *Parameter) string { return "reverb" } +func (r *reverbParameter) RoundToGrid(p *Parameter, val int, up bool) int { return val } +func (r *reverbParameter) Reset(p *Parameter) {} func (r *reverbParameter) Hint(p *Parameter) ParameterHint { i := r.Value(p) label := "custom" @@ -441,7 +467,38 @@ func (r *reverbParameter) Hint(p *Parameter) ParameterHint { } return ParameterHint{label, true} } -func (r *reverbParameter) LargeStep(p *Parameter) int { - return 1 + +func roundToGrid(value, grid int, up bool) int { + if up { + return value + mod(-value, grid) + } + return value - mod(value, grid) +} + +func mod(a, b int) int { + m := a % b + if a < 0 && b < 0 { + m -= b + } + if a < 0 && b > 0 { + m += b + } + return m +} + +func roundToSliceGrid(value int, grid []int, up bool) int { + if up { + for _, v := range grid { + if value < v { + return v + } + } + } else { + for i := len(grid) - 1; i >= 0; i-- { + if value > grid[i] { + return grid[i] + } + } + } + return value } -func (r *reverbParameter) Reset(p *Parameter) {}