mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-29 10:25:01 -04:00
feat: input midi velocity into a separate track (includes many structural changes)
This commit is contained in:
@ -2,6 +2,7 @@ package gioui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gioui.org/x/component"
|
||||
"image"
|
||||
"image/color"
|
||||
"strconv"
|
||||
@ -64,6 +65,7 @@ type NoteEditor struct {
|
||||
EffectBtn *BoolClickable
|
||||
UniqueBtn *BoolClickable
|
||||
TrackMidiInBtn *BoolClickable
|
||||
TrackForMidiVelIn *MenuClickable
|
||||
|
||||
scrollTable *ScrollTable
|
||||
eventFilters []event.Filter
|
||||
@ -88,6 +90,7 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
||||
EffectBtn: NewBoolClickable(model.Effect().Bool()),
|
||||
UniqueBtn: NewBoolClickable(model.UniquePatterns().Bool()),
|
||||
TrackMidiInBtn: NewBoolClickable(model.TrackMidiIn().Bool()),
|
||||
TrackForMidiVelIn: &MenuClickable{Selected: model.TrackForMidiVelIn().OptionalInt()},
|
||||
scrollTable: NewScrollTable(
|
||||
model.Notes().Table(),
|
||||
model.Tracks().List(),
|
||||
@ -162,6 +165,8 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
|
||||
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex")
|
||||
uniqueBtnStyle := ToggleIcon(gtx, t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
|
||||
midiInBtnStyle := ToggleButton(gtx, t.Theme, te.TrackMidiInBtn, "MIDI")
|
||||
midiInBtnStyle.Hidden = !t.HasAnyMidiInput()
|
||||
trackForMidiVelInSelector := te.layoutMidiVelInTrackSelector(t, " vel:")
|
||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D { return layout.Dimensions{Size: image.Pt(gtx.Dp(unit.Dp(12)), 0)} }),
|
||||
layout.Rigid(addSemitoneBtnStyle.Layout),
|
||||
@ -176,12 +181,58 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
|
||||
layout.Rigid(splitTrackBtnStyle.Layout),
|
||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||
layout.Rigid(midiInBtnStyle.Layout),
|
||||
layout.Rigid(trackForMidiVelInSelector),
|
||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||
layout.Rigid(deleteTrackBtnStyle.Layout),
|
||||
layout.Rigid(newTrackBtnStyle.Layout))
|
||||
})
|
||||
}
|
||||
|
||||
func (te *NoteEditor) layoutMidiVelInTrackSelector(t *Tracker, label string) func(gtx C) D {
|
||||
if !t.HasAnyMidiInput() {
|
||||
return layout.Spacer{}.Layout
|
||||
}
|
||||
tracks := t.Model.Tracks().List()
|
||||
trackItems := make([]MenuItem, tracks.Count()+1)
|
||||
trackForMidiVelIn := t.Model.TrackForMidiVelIn()
|
||||
offText := "\u2014off\u2014"
|
||||
currentText := offText
|
||||
for i := range trackItems {
|
||||
trackItems[i] = MenuItem{
|
||||
Text: offText,
|
||||
Doer: tracker.Check(
|
||||
func() { trackForMidiVelIn.OptionalInt().Set(i-1, i > 0) },
|
||||
func() bool { return t.Model.CanUseTrackForMidiVelInput(i - 1) },
|
||||
),
|
||||
}
|
||||
if i > 0 {
|
||||
trackItems[i].Text = fmt.Sprintf("%d %s", i-1, t.Model.TrackTitle(i-1))
|
||||
}
|
||||
|
||||
if trackForMidiVelIn.OptionalInt().Equals(i-1, i > 0) {
|
||||
trackItems[i].IconBytes = icons.NavigationChevronRight
|
||||
if trackForMidiVelIn.IsValid() {
|
||||
currentText = trackItems[i].Text
|
||||
}
|
||||
}
|
||||
}
|
||||
return func(gtx C) D {
|
||||
tooltip := component.PlatformTooltip(t.Theme, "Record MIDI VEL into chosen track. This can not be one of the selected tracks (where MIDI Notes go).")
|
||||
return te.TrackForMidiVelIn.TipArea.Layout(gtx, tooltip, func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(SizedLabel(label, white, t.Theme.Shaper, unit.Sp(12))),
|
||||
layout.Rigid(t.layoutMenu(gtx,
|
||||
currentText,
|
||||
&te.TrackForMidiVelIn.Clickable,
|
||||
&te.TrackForMidiVelIn.menu,
|
||||
unit.Dp(200),
|
||||
trackItems...,
|
||||
)),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const baseNote = 24
|
||||
|
||||
var notes = []string{
|
||||
@ -294,6 +345,9 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
||||
te.paintColumnCell(gtx, x, t, trackMidiInAdditionalColor, hasTrackMidiIn)
|
||||
}
|
||||
}
|
||||
if t.Model.TrackForMidiVelIn().Equals(x) {
|
||||
te.paintColumnCell(gtx, x, t, trackMidiVelInColor, hasTrackMidiIn)
|
||||
}
|
||||
}
|
||||
|
||||
// draw the pattern marker
|
||||
@ -402,21 +456,3 @@ func (te *NoteEditor) finishNoteInsert(t *Tracker, note byte, keyName key.Name)
|
||||
t.KeyPlaying[keyName] = t.TrackNoteOn(trk, note)
|
||||
}
|
||||
}
|
||||
|
||||
func (te *NoteEditor) HandleMidiInput(t *Tracker) {
|
||||
inputDeactivated := !t.Model.TrackMidiIn().Value()
|
||||
if inputDeactivated {
|
||||
return
|
||||
}
|
||||
te.scrollTable.Table.SetCursor2(te.scrollTable.Table.Cursor())
|
||||
remaining := t.Model.CountNextTracksForCurrentInstrument()
|
||||
for i, note := range t.MidiNotePlaying {
|
||||
t.Model.Notes().Table().Set(note)
|
||||
te.scrollTable.Table.MoveCursor(1, 0)
|
||||
te.scrollTable.EnsureCursorVisible()
|
||||
if i >= remaining {
|
||||
break
|
||||
}
|
||||
}
|
||||
te.scrollTable.Table.SetCursor(te.scrollTable.Table.Cursor2())
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ type (
|
||||
BottomHorizontalSplit *Split
|
||||
VerticalSplit *Split
|
||||
KeyPlaying map[key.Name]tracker.NoteID
|
||||
MidiNotePlaying []byte
|
||||
PopupAlert *PopupAlert
|
||||
|
||||
SaveChangesDialog *Dialog
|
||||
@ -78,7 +77,6 @@ func NewTracker(model *tracker.Model) *Tracker {
|
||||
VerticalSplit: &Split{Axis: layout.Vertical},
|
||||
|
||||
KeyPlaying: make(map[key.Name]tracker.NoteID),
|
||||
MidiNotePlaying: make([]byte, 0, 32),
|
||||
SaveChangesDialog: NewDialog(model.SaveSong(), model.DiscardSong(), model.Cancel()),
|
||||
WaveTypeDialog: NewDialog(model.ExportInt16(), model.ExportFloat(), model.Cancel()),
|
||||
InstrumentEditor: NewInstrumentEditor(model),
|
||||
@ -308,45 +306,9 @@ func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions {
|
||||
)
|
||||
}
|
||||
|
||||
/// Event Handling (for UI updates when playing etc.)
|
||||
|
||||
func (t *Tracker) ProcessMessage(msg interface{}) {
|
||||
switch msg.(type) {
|
||||
case tracker.StartPlayMsg:
|
||||
fmt.Println("Tracker received StartPlayMsg")
|
||||
case tracker.RecordingMsg:
|
||||
fmt.Println("Tracker received RecordingMsg")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) ProcessEvent(event tracker.MIDINoteEvent) {
|
||||
// MIDINoteEvent can be only NoteOn / NoteOff, i.e. its On field
|
||||
if event.On {
|
||||
t.addToMidiNotePlaying(event.Note)
|
||||
} else {
|
||||
t.removeFromMidiNotePlaying(event.Note)
|
||||
}
|
||||
t.TrackEditor.HandleMidiInput(t)
|
||||
}
|
||||
|
||||
func (t *Tracker) addToMidiNotePlaying(note byte) {
|
||||
for _, n := range t.MidiNotePlaying {
|
||||
if n == note {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.MidiNotePlaying = append(t.MidiNotePlaying, note)
|
||||
}
|
||||
|
||||
func (t *Tracker) removeFromMidiNotePlaying(note byte) {
|
||||
for i, n := range t.MidiNotePlaying {
|
||||
if n == note {
|
||||
t.MidiNotePlaying = append(
|
||||
t.MidiNotePlaying[:i],
|
||||
t.MidiNotePlaying[i+1:]...,
|
||||
)
|
||||
}
|
||||
func (t *Tracker) HasAnyMidiInput() bool {
|
||||
for _ = range t.Model.MIDI.InputDevices {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
Reference in New Issue
Block a user