diff --git a/cmd/sointu-track/main.go b/cmd/sointu-track/main.go index 75c1baa..add2561 100644 --- a/cmd/sointu-track/main.go +++ b/cmd/sointu-track/main.go @@ -20,8 +20,7 @@ import ( var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") var memprofile = flag.String("memprofile", "", "write memory profile to `file`") -var defaultMidiInput = flag.String("midi-input", "", "connect MIDI input to matching device name") -var firstMidiInput = flag.Bool("first-midi-input", false, "connect MIDI input to first device found") +var defaultMidiInput = flag.String("midi-input", "", "connect MIDI input to matching device name prefix") func main() { flag.Parse() @@ -48,7 +47,17 @@ func main() { broker := tracker.NewBroker() midiContext := cmd.NewMidiContext(broker) defer midiContext.Close() - midiContext.TryToOpenBy(*defaultMidiInput, *firstMidiInput) + if isFlagPassed("midi-input") { + input, ok := tracker.FindMIDIDeviceByPrefix(midiContext, *defaultMidiInput) + if ok { + err := midiContext.Open(input) + if err != nil { + log.Printf("failed to open MIDI input '%s': %v", input, err) + } + } else { + log.Printf("no MIDI input device found with prefix '%s'", *defaultMidiInput) + } + } model := tracker.NewModel(broker, cmd.Synthers, midiContext, recoveryFile) player := tracker.NewPlayer(broker, cmd.Synthers[0]) detector := tracker.NewDetector(broker) @@ -96,3 +105,13 @@ func main() { }() app.Main() } + +func isFlagPassed(name string) bool { + found := false + flag.Visit(func(f *flag.Flag) { + if f.Name == name { + found = true + } + }) + return found +} diff --git a/tracker/action.go b/tracker/action.go index e8bbc2a..f4319fe 100644 --- a/tracker/action.go +++ b/tracker/action.go @@ -620,16 +620,16 @@ func (m *Model) ShowLicense() Action { return MakeAction((*showLicense)(m)) } func (m *showLicense) Do() { m.dialog = License } type selectMidiInput struct { - Item MIDIDevice + Item string *Model } -func (m *Model) SelectMidiInput(item MIDIDevice) Action { +func (m *Model) SelectMidiInput(item string) Action { return MakeAction(selectMidiInput{Item: item, Model: m}) } func (s selectMidiInput) Do() { m := s.Model - if err := s.Item.Open(); err == nil { + if err := s.Model.MIDI.Open(s.Item); err == nil { message := fmt.Sprintf("Opened MIDI device: %s", s.Item) m.Alerts().Add(message, Info) } else { diff --git a/tracker/gioui/song_panel.go b/tracker/gioui/song_panel.go index a32603e..a0d579b 100644 --- a/tracker/gioui/song_panel.go +++ b/tracker/gioui/song_panel.go @@ -480,7 +480,7 @@ func NewMenuBar(tr *Tracker) *MenuBar { } for input := range tr.MIDI.InputDevices { ret.midiMenuItems = append(ret.midiMenuItems, - MenuItem(tr.SelectMidiInput(input), input.String(), "", icons.ImageControlPoint), + MenuItem(tr.SelectMidiInput(input), input, "", icons.ImageControlPoint), ) } return ret diff --git a/tracker/gomidi/midi.go b/tracker/gomidi/midi.go index 922e715..b755f41 100644 --- a/tracker/gomidi/midi.go +++ b/tracker/gomidi/midi.go @@ -9,7 +9,6 @@ import "C" import ( "errors" "fmt" - "strings" "github.com/vsariola/sointu/tracker" "gitlab.com/gomidi/midi/v2" @@ -23,14 +22,9 @@ type ( currentIn drivers.In broker *tracker.Broker } - - RTMIDIDevice struct { - context *RTMIDIContext - in drivers.In - } ) -func (m *RTMIDIContext) InputDevices(yield func(tracker.MIDIDevice) bool) { +func (m *RTMIDIContext) InputDevices(yield func(string) bool) { if m.driver == nil { return } @@ -38,9 +32,8 @@ func (m *RTMIDIContext) InputDevices(yield func(tracker.MIDIDevice) bool) { if err != nil { return } - for i := 0; i < len(ins); i++ { - device := RTMIDIDevice{context: m, in: ins[i]} - if !yield(device) { + for _, in := range ins { + if !yield(in.String()) { break } } @@ -56,34 +49,43 @@ func NewContext(broker *tracker.Broker) *RTMIDIContext { } // Open an input device while closing the currently open if necessary. -func (m RTMIDIDevice) Open() error { - if m.context.currentIn == m.in { +func (m *RTMIDIContext) Open(name string) error { + if m.currentIn != nil && m.currentIn.String() == name { return nil } - if m.context.driver == nil { + if m.driver == nil { return errors.New("no driver available") } - if m.context.HasDeviceOpen() { - m.context.currentIn.Close() + if m.IsOpen() { + m.currentIn.Close() } - m.context.currentIn = m.in - err := m.in.Open() + m.currentIn = nil + ins, err := m.driver.Ins() if err != nil { - m.context.currentIn = nil - return fmt.Errorf("opening MIDI input failed: %W", err) + return fmt.Errorf("retrieving MIDI inputs failed: %w", err) } - _, err = midi.ListenTo(m.in, m.context.HandleMessage) + for _, in := range ins { + if in.String() == name { + m.currentIn = in + } + } + if m.currentIn == nil { + return fmt.Errorf("MIDI input device not found: %s", name) + } + err = m.currentIn.Open() if err != nil { - m.in.Close() - m.context.currentIn = nil + m.currentIn = nil + return fmt.Errorf("opening MIDI input failed: %w", err) + } + _, err = midi.ListenTo(m.currentIn, m.HandleMessage) + if err != nil { + m.currentIn.Close() + m.currentIn = nil + return fmt.Errorf("listening to MIDI input failed: %w", err) } return nil } -func (d RTMIDIDevice) String() string { - return d.in.String() -} - func (m *RTMIDIContext) HandleMessage(msg midi.Message, timestampms int32) { var channel, key, velocity uint8 if msg.GetNoteOn(&channel, &key, &velocity) { @@ -109,23 +111,6 @@ func (c *RTMIDIContext) Close() { c.driver.Close() } -func (c *RTMIDIContext) HasDeviceOpen() bool { +func (c *RTMIDIContext) IsOpen() bool { return c.currentIn != nil && c.currentIn.IsOpen() } - -func (c *RTMIDIContext) TryToOpenBy(namePrefix string, takeFirst bool) { - if namePrefix == "" && !takeFirst { - return - } - for input := range c.InputDevices { - if takeFirst || strings.HasPrefix(input.String(), namePrefix) { - input.Open() - return - } - } - if takeFirst { - fmt.Errorf("Could not find any MIDI Input.\n") - } else { - fmt.Errorf("Could not find any default MIDI Input starting with \"%s\".\n", namePrefix) - } -} diff --git a/tracker/model.go b/tracker/model.go index fe4b149..ff9e91b 100644 --- a/tracker/model.go +++ b/tracker/model.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/vsariola/sointu" ) @@ -126,19 +127,14 @@ type ( Dialog int MIDIContext interface { - InputDevices(yield func(MIDIDevice) bool) + InputDevices(yield func(string) bool) + Open(name string) error Close() - HasDeviceOpen() bool - TryToOpenBy(name string, first bool) + IsOpen() bool } NullMIDIContext struct{} - MIDIDevice interface { - String() string - Open() error - } - InstrumentTab int ) @@ -221,6 +217,15 @@ func NewModel(broker *Broker, synthers []sointu.Synther, midiContext MIDIContext return m } +func FindMIDIDeviceByPrefix(c MIDIContext, prefix string) (input string, ok bool) { + for input := range c.InputDevices { + if strings.HasPrefix(input, prefix) { + return input, true + } + } + return "", false +} + func (m *Model) change(kind string, t ChangeType, severity ChangeSeverity) func() { if m.changeLevel == 0 { m.changeType = NoChange @@ -434,10 +439,10 @@ func (d *modelData) Copy() modelData { return ret } -func (m NullMIDIContext) InputDevices(yield func(MIDIDevice) bool) {} -func (m NullMIDIContext) Close() {} -func (m NullMIDIContext) HasDeviceOpen() bool { return false } -func (m NullMIDIContext) TryToOpenBy(name string, first bool) {} +func (m NullMIDIContext) InputDevices(yield func(string) bool) {} +func (m NullMIDIContext) Open(name string) error { return nil } +func (m NullMIDIContext) Close() {} +func (m NullMIDIContext) IsOpen() bool { return false } func (m *Model) resetSong() { m.d.Song = defaultSong.Copy()