Files
sointu/tracker/midi.go
2026-01-31 13:57:09 +02:00

140 lines
3.5 KiB
Go

package tracker
import (
"fmt"
)
type MIDIModel Model
func (m *Model) MIDI() *MIDIModel { return (*MIDIModel)(m) }
type (
midiState struct {
currentInput MIDIInputDevice
context MIDIContext
inputs []MIDIInputDevice
}
MIDIContext interface {
Inputs(yield func(input MIDIInputDevice) bool)
Close()
Support() MIDISupport
}
MIDIInputDevice interface {
Open() error
Close() error
IsOpen() bool
String() string
}
MIDISupport int
)
const (
MIDISupportNotCompiled MIDISupport = iota
MIDISupportNoDriver
MIDISupported
)
// Refresh
func (m *MIDIModel) Refresh() Action { return MakeAction((*midiRefresh)(m)) }
type midiRefresh MIDIModel
func (m *midiRefresh) Do() {
if m.midi.context == nil {
return
}
m.midi.inputs = m.midi.inputs[:0]
for i := range m.midi.context.Inputs {
m.midi.inputs = append(m.midi.inputs, i)
if m.midi.currentInput != nil && i.String() == m.midi.currentInput.String() {
m.midi.currentInput.Close()
m.midi.currentInput = nil
if err := i.Open(); err != nil {
(*Model)(m).Alerts().Add(fmt.Sprintf("Failed to reopen MIDI input port: %s", err.Error()), Error)
continue
}
m.midi.currentInput = i
}
}
}
// InputDevices can be iterated to get string names of all the MIDI input
// devices.
func (m *MIDIModel) Input() Int { return MakeInt((*midiInputDevices)(m)) }
type midiInputDevices MIDIModel
func (m *midiInputDevices) Value() int {
if m.midi.currentInput == nil {
return 0
}
for i, d := range m.midi.inputs {
if d == m.midi.currentInput {
return i + 1
}
}
return 0
}
func (m *midiInputDevices) SetValue(val int) bool {
if val < 0 || val > len(m.midi.inputs) {
return false
}
if m.midi.currentInput != nil {
if err := m.midi.currentInput.Close(); err != nil {
(*Model)(m).Alerts().Add(fmt.Sprintf("Failed to close current MIDI input port: %s", err.Error()), Error)
}
m.midi.currentInput = nil
}
if val == 0 {
return true
}
newInput := m.midi.inputs[val-1]
if err := newInput.Open(); err != nil {
(*Model)(m).Alerts().Add(fmt.Sprintf("Failed to open MIDI input port: %s", err.Error()), Error)
return false
}
m.midi.currentInput = newInput
(*Model)(m).Alerts().Add(fmt.Sprintf("Opened MIDI input port: %s", newInput.String()), Info)
return true
}
func (m *midiInputDevices) Range() RangeInclusive {
return RangeInclusive{Min: 0, Max: len(m.midi.inputs)}
}
func (m *midiInputDevices) StringOf(value int) string {
if value < 0 || value > len(m.midi.inputs) {
return ""
}
if value == 0 {
switch m.midi.context.Support() {
case MIDISupportNotCompiled:
return "Not compiled"
case MIDISupportNoDriver:
return "No driver"
default:
return "Closed"
}
}
return m.midi.inputs[value-1].String()
}
// InputtingNotes returns a Bool controlling whether the MIDI events are used
// just to trigger instruments, or if the note events are used to input notes to
// the note table.
func (m *MIDIModel) InputtingNotes() Bool { return MakeBool((*midiInputtingNotes)(m)) }
type midiInputtingNotes Model
func (m *midiInputtingNotes) Value() bool { return m.broker.mIDIEventsToGUI.Load() }
func (m *midiInputtingNotes) SetValue(val bool) { m.broker.mIDIEventsToGUI.Store(val) }
// NullMIDIContext is a mockup MIDIContext if you don't want to create a real
// one.
type NullMIDIContext struct{}
func (m NullMIDIContext) Inputs(yield func(input MIDIInputDevice) bool) {}
func (m NullMIDIContext) Close() {}
func (m NullMIDIContext) Support() MIDISupport { return MIDISupportNotCompiled }