This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-07-03 18:54:02 +03:00
parent c09a3f04db
commit 4e295a3a2f
8 changed files with 206 additions and 112 deletions

View File

@ -386,8 +386,8 @@ var stackUseMonoStereo = map[string][2]StackUse{
}, },
} }
var stackUseSendNoPop = [2]StackUse{ var stackUseSendNoPop = [2]StackUse{
{Inputs: [][]int{{0}}, Modifies: []bool{false}, NumOutputs: 1}, {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 1},
{Inputs: [][]int{{0}, {1}}, Modifies: []bool{false, false}, NumOutputs: 2}, {Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2},
} }
var stackUseSendPop = [2]StackUse{ var stackUseSendPop = [2]StackUse{
{Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0}, // mono {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0}, // mono
@ -544,19 +544,19 @@ func (p Patch) FindUnit(id int) (instrIndex int, unitIndex int, err error) {
return 0, 0, fmt.Errorf("could not find a unit with id %v", id) return 0, 0, fmt.Errorf("could not find a unit with id %v", id)
} }
func FindParamForModulationPort(unitName string, index int) (UnitParameter, bool) { func FindParamForModulationPort(unitName string, index int) (up UnitParameter, upIndex int, ok bool) {
unitType, ok := UnitTypes[unitName] unitType, ok := UnitTypes[unitName]
if !ok { if !ok {
return UnitParameter{}, false return UnitParameter{}, 0, false
} }
for _, param := range unitType { for i, param := range unitType {
if !param.CanModulate { if !param.CanModulate {
continue continue
} }
if index == 0 { if index == 0 {
return param, true return param, i, true
} }
index-- index--
} }
return UnitParameter{}, false return UnitParameter{}, 0, false
} }

View File

@ -153,7 +153,7 @@ func (m *Model) updateDerivedScoreData() {
} }
func (m *Model) updateDerivedPatchData() { func (m *Model) updateDerivedPatchData() {
m.derived.rail.update(m.d.Song.Patch) m.SignalRail().update()
clear(m.derived.forUnit) clear(m.derived.forUnit)
for i, instr := range m.d.Song.Patch { for i, instr := range m.d.Song.Patch {
for u, unit := range instr.Units { for u, unit := range instr.Units {
@ -177,8 +177,8 @@ func (m *Model) deriveParams(unit *sointu.Unit) []Parameter {
return ret return ret
} }
for i, up := range unitType { for i, up := range unitType {
if !up.CanSet { if !up.CanSet && !up.CanModulate {
continue continue // skip parameters that cannot be set or modulated
} }
if unit.Type == "oscillator" && unit.Parameters["type"] != sointu.Sample && (up.Name == "samplestart" || up.Name == "loopstart" || up.Name == "looplength") { 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 continue // don't show the sample related params unless necessary
@ -230,7 +230,7 @@ func (m *Model) collectSendSources(unit sointu.Unit, paramName string) iter.Seq[
continue continue
} }
port := u.Parameters["port"] port := u.Parameters["port"]
unitParam, ok := sointu.FindParamForModulationPort(unit.Type, port) unitParam, _, ok := sointu.FindParamForModulationPort(unit.Type, port)
if !ok || unitParam.Name != paramName { if !ok || unitParam.Name != paramName {
continue continue
} }

View File

@ -12,12 +12,13 @@ import (
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
const numSignalsDrawn = 8 const maxSignalsDrawn = 16
type ( type (
SignalRailStyle struct { SignalRailStyle struct {
Color color.NRGBA Color color.NRGBA
LineWidth unit.Dp LineWidth unit.Dp
SignalWidth unit.Dp
PortDiameter unit.Dp PortDiameter unit.Dp
PortColor color.NRGBA PortColor color.NRGBA
} }
@ -25,7 +26,6 @@ type (
SignalRailWidget struct { SignalRailWidget struct {
Style *SignalRailStyle Style *SignalRailStyle
Signal tracker.Signal Signal tracker.Signal
Width unit.Dp
Height unit.Dp Height unit.Dp
} }
) )
@ -34,63 +34,69 @@ func SignalRail(th *Theme, signal tracker.Signal) SignalRailWidget {
return SignalRailWidget{ return SignalRailWidget{
Style: &th.SignalRail, Style: &th.SignalRail,
Signal: signal, Signal: signal,
Width: th.UnitEditor.Width,
Height: th.UnitEditor.Height, Height: th.UnitEditor.Height,
} }
} }
func (s SignalRailWidget) Layout(gtx C) D { func (s SignalRailWidget) Layout(gtx C) D {
w := gtx.Dp(s.Width) sw := gtx.Dp(s.Style.SignalWidth)
h := gtx.Dp(s.Height) h := gtx.Dp(s.Height)
l := gtx.Dp(s.Style.LineWidth) lw := gtx.Dp(s.Style.LineWidth)
d := gtx.Dp(s.Style.PortDiameter) pd := gtx.Dp(s.Style.PortDiameter)
c := max(l, d) / 2 center := sw / 2
stride := (w - c*2) / numSignalsDrawn
var path clip.Path var path clip.Path
path.Begin(gtx.Ops) path.Begin(gtx.Ops)
// Draw pass through signals // Draw pass through signals
for i := range min(numSignalsDrawn, s.Signal.PassThrough) { for i := range min(maxSignalsDrawn, s.Signal.PassThrough) {
x := float32(i*stride + c) x := float32(i*sw + center)
path.MoveTo(f32.Pt(x, 0)) path.MoveTo(f32.Pt(x, 0))
path.LineTo(f32.Pt(x, float32(h))) path.LineTo(f32.Pt(x, float32(h)))
} }
// Draw the routing of input signals // Draw the routing of input signals
for i := range min(len(s.Signal.StackUse.Inputs), numSignalsDrawn-s.Signal.PassThrough) { for i := range min(len(s.Signal.StackUse.Inputs), maxSignalsDrawn-s.Signal.PassThrough) {
input := s.Signal.StackUse.Inputs[i] input := s.Signal.StackUse.Inputs[i]
x1 := float32((i+s.Signal.PassThrough)*stride + c) x1 := float32((i+s.Signal.PassThrough)*sw + center)
for _, link := range input { for _, link := range input {
x2 := float32((link+s.Signal.PassThrough)*stride + c) x2 := float32((link+s.Signal.PassThrough)*sw + center)
path.MoveTo(f32.Pt(x1, 0)) path.MoveTo(f32.Pt(x1, 0))
path.LineTo(f32.Pt(x2, float32(h/2))) path.LineTo(f32.Pt(x2, float32(h/2)))
} }
} }
if s.Signal.Send {
for i := range min(len(s.Signal.StackUse.Inputs), maxSignalsDrawn-s.Signal.PassThrough) {
from := f32.Pt(float32((i+s.Signal.PassThrough)*sw+center), float32(h/2))
to := f32.Pt(float32(gtx.Constraints.Max.X), float32(h)-float32(sw/2))
ctrl := f32.Pt(from.X, to.Y)
path.MoveTo(from)
path.QuadTo(ctrl, to)
}
}
// Draw the routing of output signals // Draw the routing of output signals
for i := range min(s.Signal.StackUse.NumOutputs, numSignalsDrawn-s.Signal.PassThrough) { for i := range min(s.Signal.StackUse.NumOutputs, maxSignalsDrawn-s.Signal.PassThrough) {
x := float32((i+s.Signal.PassThrough)*stride + c) x := float32((i+s.Signal.PassThrough)*sw + center)
path.MoveTo(f32.Pt(x, float32(h/2))) path.MoveTo(f32.Pt(x, float32(h/2)))
path.LineTo(f32.Pt(x, float32(h))) path.LineTo(f32.Pt(x, float32(h)))
} }
// Signal paths finished
paint.FillShape(gtx.Ops, s.Style.Color, paint.FillShape(gtx.Ops, s.Style.Color,
clip.Stroke{ clip.Stroke{
Path: path.End(), Path: path.End(),
Width: float32(l), Width: float32(lw),
}.Op()) }.Op())
// Draw the circles on modified signals // Draw the circles on signals that get modified
var circle clip.Path
for i := range min(len(s.Signal.StackUse.Modifies), numSignalsDrawn-s.Signal.PassThrough) { circle.Begin(gtx.Ops)
for i := range min(len(s.Signal.StackUse.Modifies), maxSignalsDrawn-s.Signal.PassThrough) {
if !s.Signal.StackUse.Modifies[i] { if !s.Signal.StackUse.Modifies[i] {
continue continue
} }
var circle clip.Path f := f32.Pt(float32((i+s.Signal.PassThrough)*sw+center), float32(h/2))
x := float32((i + s.Signal.PassThrough) * stride) circle.MoveTo(f32.Pt(f.X-float32(pd/2), float32(h/2)))
circle.Begin(gtx.Ops)
circle.MoveTo(f32.Pt(x, float32(h/2)))
f := f32.Pt(x+float32(c), float32(h/2))
circle.ArcTo(f, f, float32(2*math.Pi)) circle.ArcTo(f, f, float32(2*math.Pi))
p := clip.Outline{Path: circle.End()}.Op().Push(gtx.Ops)
paint.ColorOp{Color: s.Style.PortColor}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
p.Pop()
} }
return D{Size: image.Pt(w, h)} p := clip.Outline{Path: circle.End()}.Op().Push(gtx.Ops)
paint.ColorOp{Color: s.Style.PortColor}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
p.Pop()
return D{Size: image.Pt(sw, h)}
} }

View File

@ -85,16 +85,16 @@ type Theme struct {
} }
} }
UnitEditor struct { UnitEditor struct {
Name LabelStyle Name LabelStyle
Chooser LabelStyle Chooser LabelStyle
Hint LabelStyle Hint LabelStyle
InvalidParam color.NRGBA InvalidParam color.NRGBA
SendTarget color.NRGBA SendTarget color.NRGBA
Width unit.Dp Width unit.Dp
Height unit.Dp Height unit.Dp
RowTitleWidth unit.Dp RowTitle LabelStyle
ColumnTitleHeight unit.Dp RowTitleWidth unit.Dp
RowTitle LabelStyle Error color.NRGBA
} }
Cursor CursorStyle Cursor CursorStyle
Selection CursorStyle Selection CursorStyle

View File

@ -211,12 +211,12 @@ uniteditor:
name: name:
{ textsize: 12, alignment: 2, color: *highemphasis, shadowcolor: *black } { textsize: 12, alignment: 2, color: *highemphasis, shadowcolor: *black }
invalidparam: { r: 120, g: 120, b: 120, a: 190 } invalidparam: { r: 120, g: 120, b: 120, a: 190 }
sendtarget: { r: 120, g: 120, b: 210, a: 63 } sendtarget: *secondarycolor
width: 60 width: 60
height: 60 height: 70
rowtitlewidth: 16
columntitleheight: 16
rowtitle: { textsize: 12, color: *white, alignment: 2 } rowtitle: { textsize: 12, color: *white, alignment: 2 }
rowtitlewidth: 16
error: *errorcolor
knob: knob:
diameter: 36 diameter: 36
value: { textsize: 12, color: *highemphasis } value: { textsize: 12, color: *highemphasis }
@ -226,7 +226,8 @@ knob:
neg: { color: *secondarycolor, bg: { r: 32, g: 55, b: 58, a: 255 } } neg: { color: *secondarycolor, bg: { r: 32, g: 55, b: 58, a: 255 } }
indicator: { color: *white, width: 2, innerdiam: 24, outerdiam: 36 } indicator: { color: *white, width: 2, innerdiam: 24, outerdiam: 36 }
signalrail: signalrail:
color: *primarycolor color: *secondarycolor
signalwidth: 10
linewidth: 2 linewidth: 2
portdiameter: 8 portdiameter: 8
portcolor: *secondarycolor portcolor: *primarycolor

View File

@ -71,7 +71,7 @@ func (pe *UnitEditor) Layout(gtx C) D {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
pe.update(gtx, t) pe.update(gtx, t)
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
editorFunc := pe.layoutSliders editorFunc := pe.layoutRack
if pe.showingChooser() { if pe.showingChooser() {
editorFunc = pe.layoutUnitTypeChooser editorFunc = pe.layoutUnitTypeChooser
} }
@ -152,7 +152,7 @@ func (pe *UnitEditor) ChooseUnitType(t *Tracker) {
} }
} }
func (pe *UnitEditor) layoutSliders(gtx C) D { func (pe *UnitEditor) layoutRack(gtx C) D {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
// create enough parameter widget to match the number of parameters // create enough parameter widget to match the number of parameters
width := pe.paramTable.Table.Width() width := pe.paramTable.Table.Width()
@ -161,8 +161,11 @@ func (pe *UnitEditor) layoutSliders(gtx C) D {
} }
cellWidth := gtx.Dp(t.Theme.UnitEditor.Width) cellWidth := gtx.Dp(t.Theme.UnitEditor.Width)
cellHeight := gtx.Dp(t.Theme.UnitEditor.Height) cellHeight := gtx.Dp(t.Theme.UnitEditor.Height)
rowTitleWidth := gtx.Dp(t.Theme.UnitEditor.RowTitleWidth) rowTitleLabelWidth := gtx.Dp(t.Theme.UnitEditor.RowTitleWidth)
columnTitleHeight := gtx.Dp(t.Theme.UnitEditor.ColumnTitleHeight) rowTitleSignalWidth := gtx.Dp(t.Theme.SignalRail.SignalWidth) * t.SignalRail().MaxWidth()
rowTitleWidth := rowTitleLabelWidth + rowTitleSignalWidth
signalError := t.SignalRail().Error()
columnTitleHeight := gtx.Dp(0)
for i := range pe.Parameters { for i := range pe.Parameters {
for len(pe.Parameters[i]) < width { for len(pe.Parameters[i]) < width {
pe.Parameters[i] = append(pe.Parameters[i], &ParameterWidget{}) pe.Parameters[i] = append(pe.Parameters[i], &ParameterWidget{})
@ -171,20 +174,29 @@ func (pe *UnitEditor) layoutSliders(gtx C) D {
coltitle := func(gtx C, x int) D { coltitle := func(gtx C, x int) D {
return D{Size: image.Pt(cellWidth, columnTitleHeight)} 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 { rowtitle := func(gtx C, y int) D {
//defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() if y < 0 || y >= len(pe.Parameters) {
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(cellHeight)})).Push(gtx.Ops).Pop() return D{}
gtx.Constraints = layout.Exact(image.Pt(cellHeight, rowTitleWidth)) }
Label(t.Theme, &t.Theme.UnitEditor.RowTitle, t.Units().Item(y).Type).Layout(gtx)
sr := SignalRail(t.Theme, t.SignalRail().Item(y))
label := Label(t.Theme, &t.Theme.UnitEditor.RowTitle, t.Units().Item(y).Type)
if signalError.Err != nil && signalError.UnitIndex == y {
label.Color = t.Theme.UnitEditor.Error
}
gtx.Constraints = layout.Exact(image.Pt(rowTitleWidth, cellHeight))
sr.Layout(gtx)
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: float32(rowTitleSignalWidth), Y: float32(cellHeight)})).Push(gtx.Ops).Pop()
gtx.Constraints = layout.Exact(image.Pt(cellHeight, rowTitleLabelWidth))
label.Layout(gtx)
return D{Size: image.Pt(rowTitleWidth, cellHeight)} return D{Size: image.Pt(rowTitleWidth, cellHeight)}
} }
cursor := t.Model.Params().Cursor() cursor := t.Model.Params().Cursor()
cell := func(gtx C, x, y int) D { cell := func(gtx C, x, y int) D {
if x == 0 {
sr := SignalRail(t.Theme, t.SignalRail().Item(y))
return sr.Layout(gtx)
}
x--
gtx.Constraints = layout.Exact(image.Pt(cellWidth, cellHeight)) gtx.Constraints = layout.Exact(image.Pt(cellWidth, cellHeight))
point := tracker.Point{X: x, Y: y} point := tracker.Point{X: x, Y: y}
if y < 0 || y >= len(pe.Parameters) || x < 0 || x >= len(pe.Parameters[y]) { if y < 0 || y >= len(pe.Parameters) || x < 0 || x >= len(pe.Parameters[y]) {
@ -206,63 +218,76 @@ func (pe *UnitEditor) layoutSliders(gtx C) D {
return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)} return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
} }
table := FilledScrollTable(t.Theme, pe.paramTable) table := FilledScrollTable(t.Theme, pe.paramTable)
table.RowTitleWidth = t.Theme.UnitEditor.RowTitleWidth table.RowTitleWidth = gtx.Metric.PxToDp(rowTitleWidth)
table.ColumnTitleHeight = t.Theme.UnitEditor.ColumnTitleHeight table.ColumnTitleHeight = 0
table.CellWidth = t.Theme.UnitEditor.Width table.CellWidth = t.Theme.UnitEditor.Width
table.CellHeight = t.Theme.UnitEditor.Height table.CellHeight = t.Theme.UnitEditor.Height
pe.drawSignals(gtx) pe.drawSignals(gtx, rowTitleWidth)
dims := table.Layout(gtx, cell, coltitle, rowtitle, nil, nil) dims := table.Layout(gtx, cell, coltitle, rowtitle, nil, rowTitleBg)
return dims return dims
} }
func (pe *UnitEditor) drawSignals(gtx C) { func (pe *UnitEditor) drawSignals(gtx C, rowTitleWidth int) {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
units := t.Units() units := t.Units()
colP := pe.paramTable.ColTitleList.List.Position colP := pe.paramTable.ColTitleList.List.Position
rowP := pe.paramTable.RowTitleList.List.Position rowP := pe.paramTable.RowTitleList.List.Position
p := image.Pt(gtx.Dp(t.Theme.UnitEditor.RowTitleWidth), gtx.Dp(t.Theme.UnitEditor.ColumnTitleHeight)) p := image.Pt(rowTitleWidth, 0)
defer op.Offset(p).Push(gtx.Ops).Pop() defer op.Offset(p).Push(gtx.Ops).Pop()
gtx.Constraints.Max = gtx.Constraints.Max.Sub(p) gtx.Constraints.Max = gtx.Constraints.Max.Sub(p)
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() 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() defer op.Offset(image.Pt(-colP.Offset, -rowP.Offset)).Push(gtx.Ops).Pop()
for i := 0; i < units.Count(); i++ { for i := 0; i < units.Count(); i++ {
item := units.Item(i) item := units.Item(i)
if item.TargetUnit > 0 { if item.TargetOk {
pe.drawSignal(gtx, 3-colP.First, i-rowP.First, item.TargetPort-colP.First, item.TargetUnit-1-rowP.First) pe.drawSignal(gtx, i-rowP.First, item.TargetX-colP.First, item.TargetY-rowP.First)
} }
} }
} }
func (pe *UnitEditor) drawSignal(gtx C, sx, sy, ex, ey int) { func (pe *UnitEditor) drawSignal(gtx C, sy, ex, ey int) {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
width := float32(gtx.Dp(t.Theme.UnitEditor.Width)) width := float32(gtx.Dp(t.Theme.UnitEditor.Width))
height := float32(gtx.Dp(t.Theme.UnitEditor.Height)) height := float32(gtx.Dp(t.Theme.UnitEditor.Height))
diam := gtx.Dp(t.Theme.Knob.Diameter) diam := gtx.Dp(t.Theme.Knob.Diameter)
from := f32.Pt((float32(sx)+.5)*width, (float32(sy)+.6)*height) from := f32.Pt(0, float32((sy+1)*gtx.Dp(t.Theme.UnitEditor.Height))-float32(gtx.Dp(t.Theme.SignalRail.SignalWidth)/2))
to := f32.Pt((float32(ex)+.5)*width, (float32(ey)+.6)*height) corner := f32.Pt(1, 1)
var c1, c2 f32.Point if ex > 0 {
if sy < ey { corner.X = -corner.X
from.Y += float32(diam) / 2
to.Y -= float32(diam) / 2
c1 = from.Add(f32.Pt(0, height/2))
c2 = to.Sub(f32.Pt(0, height/2))
} else {
from.Y -= float32(diam) / 2
to.Y += float32(diam) / 2
c1 = from.Sub(f32.Pt(0, height/2))
c2 = to.Add(f32.Pt(0, height/2))
} }
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)
p2 := mulVec(corner, f32.Pt(width/2, height/2)).Add(center)
p1 := f32.Pt(p2.X, float32((sy+1)*gtx.Dp(t.Theme.UnitEditor.Height)))
if sy > ey {
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)
var path clip.Path var path clip.Path
path.Begin(gtx.Ops) path.Begin(gtx.Ops)
path.MoveTo(from) path.MoveTo(from)
path.CubeTo(c1, c2, to) path.CubeTo(from.Add(fromTan), p1.Sub(p1Tan), p1)
path.CubeTo(p1.Add(p1Tan), p2, to)
paint.FillShape(gtx.Ops, t.Theme.UnitEditor.SendTarget, paint.FillShape(gtx.Ops, t.Theme.UnitEditor.SendTarget,
clip.Stroke{ clip.Stroke{
Path: path.End(), Path: path.End(),
Width: float32(gtx.Dp(4)), Width: float32(gtx.Dp(t.Theme.SignalRail.LineWidth)),
}.Op()) }.Op())
} }
func mulVec(a, b f32.Point) f32.Point {
return f32.Pt(a.X*b.X, a.Y*b.Y)
}
func (pe *UnitEditor) layoutFooter(gtx C) D { func (pe *UnitEditor) layoutFooter(gtx C) D {
t := TrackerFromContext(gtx) t := TrackerFromContext(gtx)
st := t.Units().SelectedType() st := t.Units().SelectedType()
@ -426,9 +451,9 @@ func (p ParameterStyle) Layout(gtx C) D {
} }
return D{} return D{}
} }
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, title.Layout(gtx)
layout.Rigid(title.Layout), layout.Center.Layout(gtx, widget)
layout.Flexed(1, func(gtx C) D { return layout.Center.Layout(gtx, widget) }), return D{Size: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y)}
/* layout.Rigid(func(gtx C) D { /* layout.Rigid(func(gtx C) D {
if p.w.Parameter.Type() != tracker.IDParameter { if p.w.Parameter.Type() != tracker.IDParameter {
hint := p.w.Parameter.Hint() hint := p.w.Parameter.Hint()
@ -445,7 +470,6 @@ func (p ParameterStyle) Layout(gtx C) D {
} }
return D{} return D{}
}),*/ }),*/
)
} }
func drawCircle(gtx C, i int, nRGBA color.NRGBA) D { func drawCircle(gtx C, i int, nRGBA color.NRGBA) D {

View File

@ -39,7 +39,8 @@ type (
Type, Comment string Type, Comment string
Disabled bool Disabled bool
StackNeed, StackBefore, StackAfter int StackNeed, StackBefore, StackAfter int
TargetUnit, TargetPort int TargetOk bool // TargetOk indicates if the target unit is valid
TargetX, TargetY int
} }
// Range is used to represent a range [Start,End) of integers // Range is used to represent a range [Start,End) of integers
@ -326,10 +327,17 @@ func (v *Units) Item(index int) UnitListItem {
return UnitListItem{} return UnitListItem{}
} }
unit := v.d.Song.Patch[v.d.InstrIndex].Units[index] unit := v.d.Song.Patch[v.d.InstrIndex].Units[index]
targetUnit := 0 targetOk := false
targetY := 0
targetX := 0
if unit.Type == "send" { if unit.Type == "send" {
if _, tu, err := v.d.Song.Patch.FindUnit(unit.Parameters["target"]); err == nil { if i, y, err := v.d.Song.Patch.FindUnit(unit.Parameters["target"]); err == nil {
targetUnit = tu + 1 targetUnit := v.d.Song.Patch[i].Units[y]
if _, x, ok := sointu.FindParamForModulationPort(targetUnit.Type, unit.Parameters["port"]); ok {
targetX = x
targetY = y
targetOk = true
}
} }
} }
return UnitListItem{ return UnitListItem{
@ -337,8 +345,9 @@ func (v *Units) Item(index int) UnitListItem {
Comment: unit.Comment, Comment: unit.Comment,
Disabled: unit.Disabled, Disabled: unit.Disabled,
StackNeed: unit.StackNeed(), StackNeed: unit.StackNeed(),
TargetUnit: targetUnit, TargetOk: targetOk,
TargetPort: unit.Parameters["port"], TargetY: targetY,
TargetX: targetX,
StackBefore: 0, StackBefore: 0,
StackAfter: 0, StackAfter: 0,
} }

View File

@ -10,17 +10,25 @@ type (
SignalRail struct { SignalRail struct {
signals [][]Signal signals [][]Signal
scratch []signalScratch scratch []signalScratch
error SignalError
} }
signalScratch struct { SignalError struct {
instr, unit int InstrIndex, UnitIndex int
Err error
} }
Signal struct { Signal struct {
PassThrough int PassThrough int
Send bool
StackUse sointu.StackUse StackUse sointu.StackUse
} }
signalScratch struct {
instr, unit int
}
SignalRailType Model SignalRailType Model
) )
@ -36,21 +44,51 @@ func (s *SignalRailType) Item(u int) Signal {
return s.derived.rail.signals[i][u] return s.derived.rail.signals[i][u]
} }
func (s *SignalRail) update(patch sointu.Patch) (err error) { func (s *SignalRailType) Error() SignalError {
i := s.d.InstrIndex
if i < 0 || i >= len(s.derived.rail.signals) {
return SignalError{}
}
if i == s.derived.rail.error.InstrIndex {
return s.derived.rail.error
}
return SignalError{}
}
func (s *SignalRailType) MaxWidth() int {
i := s.d.InstrIndex
if i < 0 || i >= len(s.derived.rail.signals) {
return 0
}
ret := 0
for _, signal := range s.derived.rail.signals[i] {
ret = max(ret, signal.PassThrough+max(len(signal.StackUse.Inputs), signal.StackUse.NumOutputs))
}
return ret
}
func (st *SignalRailType) update() {
s := &st.derived.rail
patch := st.d.Song.Patch
s.scratch = s.scratch[:0] s.scratch = s.scratch[:0]
s.error = SignalError{}
for i, instr := range patch { for i, instr := range patch {
for len(s.signals) <= i { for len(s.signals) <= i {
s.signals = append(s.signals, make([]Signal, len(instr.Units))) s.signals = append(s.signals, make([]Signal, len(instr.Units)))
} }
start := len(s.scratch) start := len(s.scratch)
for u, unit := range instr.Units { for u, unit := range instr.Units {
for len(s.signals[i]) <= i { for len(s.signals[i]) <= u {
s.signals[i] = append(s.signals[i], Signal{}) s.signals[i] = append(s.signals[i], Signal{})
} }
stackUse := unit.StackUse() stackUse := unit.StackUse()
numInputs := len(stackUse.Inputs) numInputs := len(stackUse.Inputs)
if len(s.scratch) < numInputs && err != nil { if len(s.scratch) < numInputs {
err = fmt.Errorf("%s unit in instrument %d / %s needs %d inputs, but got only %d", unit.Type, i, instr.Name, numInputs, len(s.scratch)) if s.error.Err == nil {
s.error.Err = fmt.Errorf("%s unit in instrument %d / %s needs %d inputs, but got only %d", unit.Type, i, instr.Name, numInputs, len(s.scratch))
s.error.InstrIndex = i
s.error.UnitIndex = u
}
s.scratch = s.scratch[:0] s.scratch = s.scratch[:0]
} else { } else {
s.scratch = s.scratch[:len(s.scratch)-numInputs] s.scratch = s.scratch[:len(s.scratch)-numInputs]
@ -58,6 +96,7 @@ func (s *SignalRail) update(patch sointu.Patch) (err error) {
s.signals[i][u] = Signal{ s.signals[i][u] = Signal{
PassThrough: len(s.scratch), PassThrough: len(s.scratch),
StackUse: stackUse, StackUse: stackUse,
Send: unit.Type == "send",
} }
for _ = range stackUse.NumOutputs { for _ = range stackUse.NumOutputs {
s.scratch = append(s.scratch, signalScratch{instr: i, unit: u}) s.scratch = append(s.scratch, signalScratch{instr: i, unit: u})
@ -67,8 +106,12 @@ func (s *SignalRail) update(patch sointu.Patch) (err error) {
if instr.NumVoices > 1 && diff != 0 { if instr.NumVoices > 1 && diff != 0 {
if diff < 0 { if diff < 0 {
morepop := (instr.NumVoices - 1) * diff morepop := (instr.NumVoices - 1) * diff
if morepop > len(s.scratch) && err != nil { if morepop > len(s.scratch) {
err = fmt.Errorf("each voice of instrument %d / %s consumes %d signals, but there was not enough signals available", i, instr.Name, -diff) if s.error.Err == nil {
s.error.Err = fmt.Errorf("each voice of instrument %d / %s consumes %d signals, but there was not enough signals available", i, instr.Name, -diff)
s.error.InstrIndex = i
s.error.UnitIndex = -1
}
s.scratch = s.scratch[:0] s.scratch = s.scratch[:0]
} else { } else {
s.scratch = s.scratch[:len(s.scratch)-morepop] s.scratch = s.scratch[:len(s.scratch)-morepop]
@ -80,5 +123,16 @@ func (s *SignalRail) update(patch sointu.Patch) (err error) {
} }
} }
} }
return err if len(s.scratch) > 0 && s.error.Err == nil {
s.error.Err = fmt.Errorf("instrument %d / %s unit %d / %s leave a signal on stack ", s.scratch[0].instr, patch[s.scratch[0].instr].Name, s.scratch[0].unit, patch[s.scratch[0].instr].Units[s.scratch[0].unit].Type)
s.error.InstrIndex = s.scratch[0].instr
s.error.UnitIndex = s.scratch[0].unit
}
if s.error.Err != nil {
(*Model)(st).Alerts().AddNamed("SignalError", s.error.Error(), Error)
}
}
func (e *SignalError) Error() string {
return e.Err.Error()
} }