feat: MIDI velocity, keyboard splits, and fixing instrument channel

Closes #124
Closes #215
Closes #221
This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-02-07 10:03:44 +02:00
parent 0179b24fd4
commit b349474c4d
11 changed files with 369 additions and 24 deletions

View File

@ -27,6 +27,14 @@ type (
voices *NumericUpDownState
splitInstrumentBtn *Clickable
splitInstrumentHint string
ignoreNoteOff *Clickable
velocity *Clickable
change *Clickable
noteStart *NumericUpDownState
noteEnd *NumericUpDownState
transpose *NumericUpDownState
midiChannel *NumericUpDownState
}
)
@ -40,6 +48,13 @@ func NewInstrumentProperties() *InstrumentProperties {
voices: NewNumericUpDownState(),
splitInstrumentBtn: new(Clickable),
threadBtns: [4]*Clickable{new(Clickable), new(Clickable), new(Clickable), new(Clickable)},
ignoreNoteOff: new(Clickable),
velocity: new(Clickable),
change: new(Clickable),
noteStart: NewNumericUpDownState(),
noteEnd: NewNumericUpDownState(),
transpose: NewNumericUpDownState(),
midiChannel: NewNumericUpDownState(),
}
ret.soloHint = makeHint("Solo", " (%s)", "SoloToggle")
ret.unsoloHint = makeHint("Unsolo", " (%s)", "SoloToggle")
@ -81,8 +96,9 @@ func (ip *InstrumentProperties) layout(gtx C) D {
layout.Rigid(thread4btn.Layout),
)
}
return ip.list.Layout(gtx, 11, func(gtx C, index int) D {
gtx.Constraints.Max.X = min(gtx.Dp(300), gtx.Constraints.Max.X)
gtx.Constraints.Min.X = min(gtx.Constraints.Max.X, gtx.Constraints.Min.X)
return ip.list.Layout(gtx, 18, func(gtx C, index int) D {
switch index {
case 0:
return layoutInstrumentPropertyLine(gtx, "Name", func(gtx C) D {
@ -93,12 +109,42 @@ func (ip *InstrumentProperties) layout(gtx C) D {
case 4:
muteBtn := ToggleIconBtn(tr.Instrument().Mute(), tr.Theme, ip.muteBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.muteHint, ip.unmuteHint)
return layoutInstrumentPropertyLine(gtx, "Mute", muteBtn.Layout)
case 6:
case 5:
soloBtn := ToggleIconBtn(tr.Instrument().Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint)
return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout)
case 8:
case 7:
return layoutInstrumentPropertyLine(gtx, "Thread", threadbtnline)
case 9:
l := Label(tr.Theme, &tr.Theme.InstrumentEditor.Properties.Label, "MIDI")
l.Alignment = text.Middle
return l.Layout(gtx)
case 10:
channelLine := NumUpDown(tr.MIDI().Channel(), tr.Theme, ip.midiChannel, "0 = automatic")
return layoutInstrumentPropertyLine(gtx, "Channel", channelLine.Layout)
case 11:
start := NumUpDown(tr.MIDI().NoteStart(), tr.Theme, ip.noteStart, "Lowest note triggering\nthis instrument")
end := NumUpDown(tr.MIDI().NoteEnd(), tr.Theme, ip.noteEnd, "Highest note triggering\nthis instrument")
noteRangeLine := func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(start.Layout),
layout.Rigid(layout.Spacer{Width: 6}.Layout),
layout.Rigid(end.Layout),
)
}
return layoutInstrumentPropertyLine(gtx, "Note range", noteRangeLine)
case 12:
transpose := NumUpDown(tr.MIDI().Transpose(), tr.Theme, ip.transpose, "Transpose of the MIDI values")
return layoutInstrumentPropertyLine(gtx, "Transpose", transpose.Layout)
case 13:
velocityBtn := ToggleIconBtn(tr.MIDI().Velocity(), tr.Theme, ip.velocity, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, "Instrument triggered by\nMIDI note", "Instrument triggered by\nMIDI velocity")
return layoutInstrumentPropertyLine(gtx, "Velocity", velocityBtn.Layout)
case 14:
retriggerBtn := ToggleIconBtn(tr.MIDI().Change(), tr.Theme, ip.change, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, "Every note/velocity retriggers", "Retrigger only when\nnote/velocity changes")
return layoutInstrumentPropertyLine(gtx, "No retrigger", retriggerBtn.Layout)
case 15:
noteOff := ToggleIconBtn(tr.MIDI().IgnoreNoteOff(), tr.Theme, ip.ignoreNoteOff, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, "Notes released", "Notes never released")
return layoutInstrumentPropertyLine(gtx, "Ignore note off", noteOff.Layout)
case 17:
return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
return ip.commentEditor.Layout(gtx, tr.Instrument().Comment(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Comment")
})
@ -112,7 +158,6 @@ func (ip *InstrumentProperties) layout(gtx C) D {
func layoutInstrumentPropertyLine(gtx C, text string, content layout.Widget) D {
tr := TrackerFromContext(gtx)
gtx.Constraints.Max.X = min(gtx.Dp(300), gtx.Constraints.Max.X)
label := Label(tr.Theme, &tr.Theme.InstrumentEditor.Properties.Label, text)
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(layout.Spacer{Width: 6, Height: 36}.Layout),

View File

@ -5,7 +5,6 @@ import (
"image"
"image/color"
"io"
"strconv"
"gioui.org/io/clipboard"
"gioui.org/io/event"
@ -251,9 +250,9 @@ func (il *InstrumentList) Layout(gtx C) D {
gtx.Constraints.Max.Y = gtx.Dp(36)
gtx.Constraints.Min.Y = gtx.Dp(36)
element := func(gtx C, i int) D {
grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1))
name, title, level, mute, ok := t.Instrument().Item(i)
grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, title)
label := func(gtx C) D {
name, level, mute, ok := t.Instrument().Item(i)
if !ok {
labelStyle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, "")
return layout.Center.Layout(gtx, labelStyle.Layout)