mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-12 04:46:13 -05:00
feat(tracker): rework the MIDI input and note event handling
This commit is contained in:
parent
7ef868a434
commit
283fbc1171
@ -27,7 +27,6 @@ type DragList struct {
|
||||
dragID pointer.ID
|
||||
tags []bool
|
||||
swapped bool
|
||||
focused bool
|
||||
requestFocus bool
|
||||
}
|
||||
|
||||
@ -57,8 +56,8 @@ func (d *DragList) Focus() {
|
||||
d.requestFocus = true
|
||||
}
|
||||
|
||||
func (d *DragList) Focused() bool {
|
||||
return d.focused
|
||||
func (d *DragList) Focused(gtx C) bool {
|
||||
return gtx.Focused(d)
|
||||
}
|
||||
|
||||
func (s FilledDragListStyle) LayoutScrollBar(gtx C) D {
|
||||
@ -114,12 +113,11 @@ func (s FilledDragListStyle) Layout(gtx C, element, bg func(gtx C, i int) D) D {
|
||||
}
|
||||
switch ke := event.(type) {
|
||||
case key.FocusEvent:
|
||||
s.dragList.focused = ke.Focus
|
||||
if !s.dragList.focused {
|
||||
if !ke.Focus {
|
||||
s.dragList.TrackerList.SetSelected2(s.dragList.TrackerList.Selected())
|
||||
}
|
||||
case key.Event:
|
||||
if !s.dragList.focused || ke.State != key.Press {
|
||||
if !s.dragList.Focused(gtx) || ke.State != key.Press {
|
||||
break
|
||||
}
|
||||
s.dragList.command(gtx, ke)
|
||||
@ -141,13 +139,13 @@ func (s FilledDragListStyle) Layout(gtx C, element, bg func(gtx C, i int) D) D {
|
||||
cursorBg := func(gtx C) D {
|
||||
var color color.NRGBA
|
||||
if s.dragList.TrackerList.Selected() == index {
|
||||
if s.dragList.focused {
|
||||
if gtx.Focused(s.dragList) {
|
||||
color = s.Cursor.Active
|
||||
} else {
|
||||
color = s.Cursor.Inactive
|
||||
}
|
||||
} else if between(s.dragList.TrackerList.Selected(), index, s.dragList.TrackerList.Selected2()) {
|
||||
if s.dragList.focused {
|
||||
if gtx.Focused(s.dragList) {
|
||||
color = s.Selection.Active
|
||||
} else {
|
||||
color = s.Selection.Inactive
|
||||
@ -193,7 +191,7 @@ func (s FilledDragListStyle) Layout(gtx C, element, bg func(gtx C, i int) D) D {
|
||||
area.Pop()
|
||||
if index == s.dragList.TrackerList.Selected() && isMutable {
|
||||
for {
|
||||
target := &s.dragList.focused
|
||||
target := &s.dragList.drag
|
||||
if s.dragList.drag {
|
||||
target = nil
|
||||
}
|
||||
@ -233,7 +231,7 @@ func (s FilledDragListStyle) Layout(gtx C, element, bg func(gtx C, i int) D) D {
|
||||
}
|
||||
}
|
||||
area := clip.Rect(rect).Push(gtx.Ops)
|
||||
event.Op(gtx.Ops, &s.dragList.focused)
|
||||
event.Op(gtx.Ops, &s.dragList.drag)
|
||||
pointer.CursorGrab.Add(gtx.Ops)
|
||||
area.Pop()
|
||||
}
|
||||
|
||||
@ -127,19 +127,19 @@ func (ie *InstrumentEditor) Focus() {
|
||||
ie.unitDragList.Focus()
|
||||
}
|
||||
|
||||
func (ie *InstrumentEditor) Focused() bool {
|
||||
return ie.unitDragList.focused
|
||||
func (ie *InstrumentEditor) Focused(gtx C) bool {
|
||||
return gtx.Focused(ie.unitDragList)
|
||||
}
|
||||
|
||||
func (ie *InstrumentEditor) childFocused(gtx C) bool {
|
||||
return ie.unitEditor.sliderList.Focused() ||
|
||||
ie.instrumentDragList.Focused() || gtx.Source.Focused(ie.commentEditor) || gtx.Source.Focused(ie.nameEditor) || gtx.Source.Focused(ie.searchEditor) ||
|
||||
return ie.unitEditor.sliderList.Focused(gtx) ||
|
||||
ie.instrumentDragList.Focused(gtx) || gtx.Source.Focused(ie.commentEditor) || gtx.Source.Focused(ie.nameEditor) || gtx.Source.Focused(ie.searchEditor) ||
|
||||
gtx.Source.Focused(ie.addUnitBtn.Clickable) || gtx.Source.Focused(ie.commentExpandBtn.Clickable) || gtx.Source.Focused(ie.presetMenuBtn.Clickable) ||
|
||||
gtx.Source.Focused(ie.deleteInstrumentBtn.Clickable) || gtx.Source.Focused(ie.copyInstrumentBtn.Clickable)
|
||||
}
|
||||
|
||||
func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
|
||||
ie.wasFocused = ie.Focused() || ie.childFocused(gtx)
|
||||
ie.wasFocused = ie.Focused(gtx) || ie.childFocused(gtx)
|
||||
fullscreenBtnStyle := ToggleIcon(gtx, t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, ie.enlargeHint, ie.shrinkHint)
|
||||
linkBtnStyle := ToggleIcon(gtx, t.Theme, ie.linkInstrTrackBtn, icons.NotificationSyncDisabled, icons.NotificationSync, ie.linkDisabledHint, ie.linkEnabledHint)
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
"github.com/vsariola/sointu/tracker"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@ -87,7 +88,7 @@ func makeHint(hint, format, action string) string {
|
||||
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
||||
func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
||||
if e.State == key.Release {
|
||||
t.JammingReleased(e)
|
||||
t.KeyNoteMap.Release(e.Name)
|
||||
return
|
||||
}
|
||||
action, ok := keyBindingMap[e]
|
||||
@ -257,11 +258,11 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
||||
t.InstrumentEditor.Focus()
|
||||
case "FocusPrev":
|
||||
switch {
|
||||
case t.OrderEditor.scrollTable.Focused():
|
||||
case t.OrderEditor.scrollTable.Focused(gtx):
|
||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
||||
case t.TrackEditor.scrollTable.Focused():
|
||||
case t.TrackEditor.scrollTable.Focused(gtx):
|
||||
t.OrderEditor.scrollTable.Focus()
|
||||
case t.InstrumentEditor.Focused():
|
||||
case t.InstrumentEditor.Focused(gtx):
|
||||
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
||||
} else {
|
||||
@ -272,11 +273,11 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
||||
}
|
||||
case "FocusNext":
|
||||
switch {
|
||||
case t.OrderEditor.scrollTable.Focused():
|
||||
case t.OrderEditor.scrollTable.Focused(gtx):
|
||||
t.TrackEditor.scrollTable.Focus()
|
||||
case t.TrackEditor.scrollTable.Focused():
|
||||
case t.TrackEditor.scrollTable.Focused(gtx):
|
||||
t.InstrumentEditor.Focus()
|
||||
case t.InstrumentEditor.Focused():
|
||||
case t.InstrumentEditor.Focused(gtx):
|
||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
||||
default:
|
||||
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
||||
@ -291,26 +292,9 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
t.JammingPressed(e, val-12)
|
||||
instr := t.InstrumentEditor.instrumentDragList.TrackerList.Selected()
|
||||
n := noteAsValue(t.OctaveNumberInput.Int.Value(), val-12)
|
||||
t.KeyNoteMap.Press(e.Name, tracker.NoteEvent{Channel: instr, Note: n})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) JammingPressed(e key.Event, val int) byte {
|
||||
if _, ok := t.KeyPlaying[e.Name]; !ok {
|
||||
n := noteAsValue(t.OctaveNumberInput.Int.Value(), val)
|
||||
instr := t.InstrumentEditor.instrumentDragList.TrackerList.Selected()
|
||||
t.KeyPlaying[e.Name] = t.InstrNoteOn(instr, n)
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *Tracker) JammingReleased(e key.Event) bool {
|
||||
if noteID, ok := t.KeyPlaying[e.Name]; ok {
|
||||
noteID.NoteOff()
|
||||
delete(t.KeyPlaying, e.Name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
58
tracker/gioui/keyboard.go
Normal file
58
tracker/gioui/keyboard.go
Normal file
@ -0,0 +1,58 @@
|
||||
package gioui
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/vsariola/sointu/tracker"
|
||||
)
|
||||
|
||||
type (
|
||||
// Keyboard is used to associate the keys of a keyboard (e.g. computer or a
|
||||
// MIDI keyboard) to currently playing notes. You can use any type T to
|
||||
// identify each key; T should be a comparable type.
|
||||
Keyboard[T comparable] struct {
|
||||
broker *tracker.Broker
|
||||
pressed map[T]tracker.NoteEvent
|
||||
}
|
||||
)
|
||||
|
||||
func MakeKeyboard[T comparable](broker *tracker.Broker) Keyboard[T] {
|
||||
return Keyboard[T]{
|
||||
broker: broker,
|
||||
pressed: make(map[T]tracker.NoteEvent),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Keyboard[T]) Press(key T, ev tracker.NoteEvent) {
|
||||
if _, ok := t.pressed[key]; ok {
|
||||
return // already playing a note with this key, do not send a new event
|
||||
}
|
||||
t.Release(key) // unset any previous note
|
||||
if ev.Note > 1 {
|
||||
ev.Source = t // set the source to this keyboard
|
||||
ev.On = true
|
||||
ev.Timestamp = t.now()
|
||||
if tracker.TrySend(t.broker.ToPlayer, any(ev)) {
|
||||
t.pressed[key] = ev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Keyboard[T]) Release(key T) {
|
||||
if ev, ok := t.pressed[key]; ok {
|
||||
ev.Timestamp = t.now()
|
||||
ev.On = false // the pressed contains the event we need to send to release the note
|
||||
tracker.TrySend(t.broker.ToPlayer, any(ev))
|
||||
delete(t.pressed, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Keyboard[T]) ReleaseAll() {
|
||||
for key := range t.pressed {
|
||||
t.Release(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Keyboard[T]) now() int64 {
|
||||
return time.Now().UnixMilli() * 441 / 10 // convert to 44100Hz frames
|
||||
}
|
||||
@ -114,6 +114,10 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (te *NoteEditor) Focused(gtx C) bool {
|
||||
return te.scrollTable.Focused(gtx) || te.scrollTable.ChildFocused(gtx)
|
||||
}
|
||||
|
||||
func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
|
||||
for {
|
||||
e, ok := gtx.Event(te.eventFilters...)
|
||||
@ -123,20 +127,30 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
|
||||
switch e := e.(type) {
|
||||
case key.Event:
|
||||
if e.State == key.Release {
|
||||
if noteID, ok := t.KeyPlaying[e.Name]; ok {
|
||||
noteID.NoteOff()
|
||||
delete(t.KeyPlaying, e.Name)
|
||||
}
|
||||
t.KeyNoteMap.Release(e.Name)
|
||||
continue
|
||||
}
|
||||
te.command(t, e)
|
||||
}
|
||||
}
|
||||
|
||||
for te.Focused(gtx) && len(t.noteEvents) > 0 {
|
||||
ev := t.noteEvents[0]
|
||||
ev.IsTrack = true
|
||||
ev.Channel = t.Model.Notes().Cursor().X
|
||||
ev.Source = te
|
||||
if ev.On {
|
||||
t.Model.Notes().Input(ev.Note)
|
||||
}
|
||||
copy(t.noteEvents, t.noteEvents[1:])
|
||||
t.noteEvents = t.noteEvents[:len(t.noteEvents)-1]
|
||||
tracker.TrySend(t.Broker().ToPlayer, any(ev))
|
||||
}
|
||||
|
||||
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
||||
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
|
||||
|
||||
return Surface{Gray: 24, Focus: te.scrollTable.Focused()}.Layout(gtx, func(gtx C) D {
|
||||
return Surface{Gray: 24, Focus: te.scrollTable.Focused(gtx)}.Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return te.layoutButtons(gtx, t)
|
||||
@ -149,7 +163,7 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
|
||||
}
|
||||
|
||||
func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
|
||||
return Surface{Gray: 37, Focus: te.scrollTable.Focused() || te.scrollTable.ChildFocused()}.Layout(gtx, func(gtx C) D {
|
||||
return Surface{Gray: 37, Focus: te.scrollTable.Focused(gtx) || te.scrollTable.ChildFocused(gtx)}.Layout(gtx, func(gtx C) D {
|
||||
addSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddSemitoneBtn, "+1")
|
||||
subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.SubtractSemitoneBtn, "-1")
|
||||
addOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddOctaveBtn, "+12")
|
||||
@ -276,7 +290,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
||||
point := tracker.Point{X: x, Y: y}
|
||||
if drawSelection && selection.Contains(point) {
|
||||
color := t.Theme.Selection.Inactive
|
||||
if te.scrollTable.Focused() {
|
||||
if te.scrollTable.Focused(gtx) {
|
||||
color = t.Theme.Selection.Active
|
||||
}
|
||||
paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(0, 0), Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
|
||||
@ -284,7 +298,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
||||
// draw the cursor
|
||||
if point == cursor {
|
||||
c := t.Theme.Cursor.Inactive
|
||||
if te.scrollTable.Focused() {
|
||||
if te.scrollTable.Focused(gtx) {
|
||||
c = t.Theme.Cursor.Active
|
||||
}
|
||||
if hasTrackMidiIn {
|
||||
@ -292,14 +306,6 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
||||
}
|
||||
te.paintColumnCell(gtx, x, t, c)
|
||||
}
|
||||
// draw the corresponding "fake cursors" for instrument-track-groups (for polyphony)
|
||||
if hasTrackMidiIn {
|
||||
for _, trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() {
|
||||
if x == trackIndex && y == cursor.Y {
|
||||
te.paintColumnCell(gtx, x, t, t.Theme.Selection.ActiveAlt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the pattern marker
|
||||
rpp := max(t.RowsPerPattern().Value(), 1)
|
||||
@ -366,9 +372,8 @@ func (te *NoteEditor) command(t *Tracker, e key.Event) {
|
||||
var n byte
|
||||
if t.Model.Notes().Effect(te.scrollTable.Table.Cursor().X) {
|
||||
if nibbleValue, err := strconv.ParseInt(string(e.Name), 16, 8); err == nil {
|
||||
t.Model.Notes().FillNibble(byte(nibbleValue), t.Model.Notes().LowNibble())
|
||||
n = t.Model.Notes().Value(te.scrollTable.Table.Cursor())
|
||||
te.finishNoteInsert(t, n, e.Name)
|
||||
ev := t.Model.Notes().InputNibble(byte(nibbleValue))
|
||||
t.KeyNoteMap.Press(e.Name, ev)
|
||||
}
|
||||
} else {
|
||||
action, ok := keyBindingMap[e]
|
||||
@ -376,8 +381,8 @@ func (te *NoteEditor) command(t *Tracker, e key.Event) {
|
||||
return
|
||||
}
|
||||
if action == "NoteOff" {
|
||||
t.Model.Notes().Table().Fill(0)
|
||||
te.finishNoteInsert(t, 0, "")
|
||||
ev := t.Model.Notes().Input(0)
|
||||
t.KeyNoteMap.Press(e.Name, ev)
|
||||
return
|
||||
}
|
||||
if action[:4] == "Note" {
|
||||
@ -386,42 +391,8 @@ func (te *NoteEditor) command(t *Tracker, e key.Event) {
|
||||
return
|
||||
}
|
||||
n = noteAsValue(t.OctaveNumberInput.Int.Value(), val-12)
|
||||
t.Model.Notes().Table().Fill(int(n))
|
||||
te.finishNoteInsert(t, n, e.Name)
|
||||
ev := t.Model.Notes().Input(n)
|
||||
t.KeyNoteMap.Press(e.Name, ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (te *NoteEditor) finishNoteInsert(t *Tracker, note byte, keyName key.Name) {
|
||||
if step := t.Model.Step().Value(); step > 0 {
|
||||
te.scrollTable.Table.MoveCursor(0, step)
|
||||
te.scrollTable.Table.SetCursor2(te.scrollTable.Table.Cursor())
|
||||
}
|
||||
te.scrollTable.EnsureCursorVisible()
|
||||
|
||||
if keyName == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := t.KeyPlaying[keyName]; !ok {
|
||||
trk := te.scrollTable.Table.Cursor().X
|
||||
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())
|
||||
}
|
||||
|
||||
@ -102,12 +102,12 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
|
||||
point := tracker.Point{X: x, Y: y}
|
||||
if selection.Contains(point) {
|
||||
color = t.Theme.Selection.Inactive
|
||||
if oe.scrollTable.Focused() {
|
||||
if oe.scrollTable.Focused(gtx) {
|
||||
color = t.Theme.Selection.Active
|
||||
}
|
||||
if point == oe.scrollTable.Table.Cursor() {
|
||||
color = t.Theme.Cursor.Inactive
|
||||
if oe.scrollTable.Focused() {
|
||||
if oe.scrollTable.Focused(gtx) {
|
||||
color = t.Theme.Cursor.Active
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ type ScrollTable struct {
|
||||
ColTitleList *DragList
|
||||
RowTitleList *DragList
|
||||
Table tracker.Table
|
||||
focused bool
|
||||
requestFocus bool
|
||||
cursorMoved bool
|
||||
eventFilters []event.Filter
|
||||
@ -94,8 +93,8 @@ func (st *ScrollTable) Focus() {
|
||||
st.requestFocus = true
|
||||
}
|
||||
|
||||
func (st *ScrollTable) Focused() bool {
|
||||
return st.focused
|
||||
func (st *ScrollTable) Focused(gtx C) bool {
|
||||
return gtx.Source.Focused(st)
|
||||
}
|
||||
|
||||
func (st *ScrollTable) EnsureCursorVisible() {
|
||||
@ -103,8 +102,8 @@ func (st *ScrollTable) EnsureCursorVisible() {
|
||||
st.RowTitleList.EnsureVisible(st.Table.Cursor().Y)
|
||||
}
|
||||
|
||||
func (st *ScrollTable) ChildFocused() bool {
|
||||
return st.ColTitleList.Focused() || st.RowTitleList.Focused()
|
||||
func (st *ScrollTable) ChildFocused(gtx C) bool {
|
||||
return st.ColTitleList.Focused(gtx) || st.RowTitleList.Focused(gtx)
|
||||
}
|
||||
|
||||
func (s ScrollTableStyle) Layout(gtx C, element func(gtx C, x, y int) D, colTitle, rowTitle, colTitleBg, rowTitleBg func(gtx C, i int) D) D {
|
||||
@ -114,7 +113,7 @@ func (s ScrollTableStyle) Layout(gtx C, element func(gtx C, x, y int) D, colTitl
|
||||
p := image.Pt(gtx.Dp(s.RowTitleWidth), gtx.Dp(s.ColumnTitleHeight))
|
||||
s.handleEvents(gtx, p)
|
||||
|
||||
return Surface{Gray: 24, Focus: s.ScrollTable.Focused() || s.ScrollTable.ChildFocused()}.Layout(gtx, func(gtx C) D {
|
||||
return Surface{Gray: 24, Focus: s.ScrollTable.Focused(gtx) || s.ScrollTable.ChildFocused(gtx)}.Layout(gtx, func(gtx C) D {
|
||||
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
|
||||
dims := gtx.Constraints.Max
|
||||
s.layoutColTitles(gtx, p, colTitle, colTitleBg)
|
||||
@ -135,8 +134,6 @@ func (s *ScrollTableStyle) handleEvents(gtx layout.Context, p image.Point) {
|
||||
break
|
||||
}
|
||||
switch e := e.(type) {
|
||||
case key.FocusEvent:
|
||||
s.ScrollTable.focused = e.Focus
|
||||
case pointer.Event:
|
||||
switch e.Kind {
|
||||
case pointer.Press:
|
||||
|
||||
@ -33,8 +33,7 @@ type (
|
||||
TopHorizontalSplit *Split
|
||||
BottomHorizontalSplit *Split
|
||||
VerticalSplit *Split
|
||||
KeyPlaying map[key.Name]tracker.NoteID
|
||||
MidiNotePlaying []byte
|
||||
KeyNoteMap Keyboard[key.Name]
|
||||
PopupAlert *PopupAlert
|
||||
Zoom int
|
||||
|
||||
@ -50,6 +49,7 @@ type (
|
||||
SongPanel *SongPanel
|
||||
|
||||
filePathString tracker.String
|
||||
noteEvents []tracker.NoteEvent
|
||||
|
||||
execChan chan func()
|
||||
preferences Preferences
|
||||
@ -78,8 +78,6 @@ func NewTracker(model *tracker.Model) *Tracker {
|
||||
BottomHorizontalSplit: &Split{Ratio: -.6, MinSize1: 180, MinSize2: 180},
|
||||
VerticalSplit: &Split{Axis: layout.Vertical, MinSize1: 180, MinSize2: 180},
|
||||
|
||||
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),
|
||||
@ -93,6 +91,7 @@ func NewTracker(model *tracker.Model) *Tracker {
|
||||
|
||||
filePathString: model.FilePath(),
|
||||
}
|
||||
t.KeyNoteMap = MakeKeyboard[key.Name](model.Broker())
|
||||
t.PopupAlert = NewPopupAlert(model.Alerts())
|
||||
var warn error
|
||||
if t.Theme, warn = NewTheme(); warn != nil {
|
||||
@ -138,6 +137,19 @@ func (t *Tracker) Main() {
|
||||
F:
|
||||
for {
|
||||
select {
|
||||
case e := <-t.Broker().ToGUI:
|
||||
switch e := e.(type) {
|
||||
case tracker.NoteEvent:
|
||||
t.noteEvents = append(t.noteEvents, e)
|
||||
case tracker.MsgToGUI:
|
||||
switch e.Kind {
|
||||
case tracker.GUIMessageCenterOnRow:
|
||||
t.TrackEditor.scrollTable.RowTitleList.CenterOn(e.Param)
|
||||
case tracker.GUIMessageEnsureCursorVisible:
|
||||
t.TrackEditor.scrollTable.EnsureCursorVisible()
|
||||
}
|
||||
}
|
||||
w.Invalidate()
|
||||
case e := <-t.Broker().ToModel:
|
||||
t.ProcessMsg(e)
|
||||
w.Invalidate()
|
||||
@ -158,9 +170,6 @@ func (t *Tracker) Main() {
|
||||
w.Option(app.Title(titleFromPath(titlePath)))
|
||||
}
|
||||
gtx := app.NewContext(&ops, e)
|
||||
if t.Playing().Value() && t.Follow().Value() {
|
||||
t.TrackEditor.scrollTable.RowTitleList.CenterOn(t.PlaySongRow())
|
||||
}
|
||||
t.Layout(gtx, w)
|
||||
e.Frame(gtx.Ops)
|
||||
if t.Quitted() {
|
||||
@ -240,7 +249,16 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) {
|
||||
t.ReadSong(e.Open())
|
||||
}
|
||||
}
|
||||
|
||||
// if no-one else handled the note events, we handle them here
|
||||
for len(t.noteEvents) > 0 {
|
||||
ev := t.noteEvents[0]
|
||||
ev.IsTrack = false
|
||||
ev.Channel = t.Model.Instruments().Selected()
|
||||
ev.Source = t
|
||||
copy(t.noteEvents, t.noteEvents[1:])
|
||||
t.noteEvents = t.noteEvents[:len(t.noteEvents)-1]
|
||||
tracker.TrySend(t.Broker().ToPlayer, any(ev))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) showDialog(gtx C) {
|
||||
@ -328,46 +346,3 @@ 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:]...,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user