refactor(tracker): harmonize naming and use iterators in MIDI

using iterators requires go 1.23
This commit is contained in:
5684185+vsariola@users.noreply.github.com 2024-10-14 15:00:55 +03:00
parent 577265b250
commit c07d8000c6
5 changed files with 95 additions and 87 deletions

View File

@ -54,8 +54,8 @@ func main() {
recoveryFile = filepath.Join(configDir, "Sointu", "sointu-track-recovery")
}
model, player := tracker.NewModelPlayer(cmd.MainSynther, recoveryFile)
model.MIDI = gomidi.CreateContext()
defer model.MIDI.DestroyContext()
model.MIDI = gomidi.NewContext()
defer model.MIDI.Close()
if a := flag.Args(); len(a) > 0 {
f, err := os.Open(a[0])
if err == nil {

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/vsariola/sointu
go 1.22.2
go 1.23
require (
gioui.org v0.7.1

View File

@ -445,11 +445,14 @@ func (m *Model) Cancel() Action { return Allow(func() { m.dialog = NoDialog
func (m *Model) Export() Action { return Allow(func() { m.dialog = Export }) }
func (m *Model) ExportFloat() Action { return Allow(func() { m.dialog = ExportFloatExplorer }) }
func (m *Model) ExportInt16() Action { return Allow(func() { m.dialog = ExportInt16Explorer }) }
func (m *Model) SelectMidiInput(item MIDIDevicer) Action {
func (m *Model) SelectMidiInput(item MIDIDevice) Action {
return Allow(func() {
if !m.MIDI.OpenInputDevice(item) {
message := fmt.Sprintf("Could not open MIDI device %s\n", item)
if err := item.Open(); err != nil {
message := fmt.Sprintf("Could not open MIDI device: %s", item)
m.Alerts().Add(message, Error)
} else {
message := fmt.Sprintf("Opened MIDI device: %s", item)
m.Alerts().Add(message, Info)
}
})
}

View File

@ -1,8 +1,8 @@
package gomidi
import (
"errors"
"fmt"
"time"
"github.com/vsariola/sointu/tracker"
"gitlab.com/gomidi/midi/v2"
@ -11,105 +11,111 @@ import (
)
type (
MIDIContext struct {
driver *rtmididrv.Driver
inputAvailable bool
driverAvailable bool
currentIn MIDIDevicer
events chan midi.Message
RTMIDIContext struct {
driver *rtmididrv.Driver
currentIn drivers.In
events chan midi.Message
}
RTMIDIDevice struct {
context *RTMIDIContext
in drivers.In
}
MIDIDevicer drivers.In
)
func (m *MIDIContext) ListInputDevices() <-chan tracker.MIDIDevicer {
ins, err := m.driver.Ins()
channel := make(chan tracker.MIDIDevicer, len(ins))
if err != nil {
m.driver.Close()
m.driverAvailable = false
return nil
}
go func() {
for i := 0; i < len(ins); i++ {
channel <- ins[i].(MIDIDevicer)
func (m *RTMIDIContext) ListInputDevices() func(yield func(tracker.MIDIDevice) bool) {
return func(yield func(tracker.MIDIDevice) bool) {
if m.driver == nil {
return
}
close(channel)
}()
return channel
ins, err := m.driver.Ins()
if err != nil {
return
}
for i := 0; i < len(ins); i++ {
device := RTMIDIDevice{context: m, in: ins[i]}
if !yield(device) {
break
}
}
}
}
// Open the driver.
func CreateContext() *MIDIContext {
m := MIDIContext{}
var err error
m.driver, err = rtmididrv.New()
m.driverAvailable = err == nil
if m.driverAvailable {
m.events = make(chan midi.Message)
}
func NewContext() *RTMIDIContext {
m := RTMIDIContext{events: make(chan midi.Message, 1024)}
// there's not much we can do if this fails, so just use m.driver = nil to
// indicate no driver available
m.driver, _ = rtmididrv.New()
return &m
}
// Open an input device while closing the currently open if necessary.
func (m *MIDIContext) OpenInputDevice(in tracker.MIDIDevicer) bool {
fmt.Printf("Opening midi device %s\n.", in)
if m.driverAvailable {
if m.currentIn == in {
return false
}
if m.inputAvailable && m.currentIn.IsOpen() {
m.currentIn.Close()
}
m.currentIn = in.(MIDIDevicer)
m.currentIn.Open()
_, err := midi.ListenTo(m.currentIn, m.HandleMessage)
if err != nil {
m.inputAvailable = false
return false
}
func (m RTMIDIDevice) Open() error {
if m.context.currentIn == m.in {
return nil
}
return true
if m.context.driver == nil {
return errors.New("no driver available")
}
if m.context.currentIn != nil && m.context.currentIn.IsOpen() {
m.context.currentIn.Close()
}
m.context.currentIn = m.in
err := m.in.Open()
if err != nil {
m.context.currentIn = nil
return fmt.Errorf("opening MIDI input failed: %W", err)
}
_, err = midi.ListenTo(m.in, m.context.HandleMessage)
if err != nil {
m.in.Close()
m.context.currentIn = nil
}
return nil
}
func (m *MIDIContext) HandleMessage(msg midi.Message, timestampms int32) {
go func() {
m.events <- msg
time.Sleep(time.Nanosecond)
}()
func (d RTMIDIDevice) String() string {
return d.in.String()
}
func (c *MIDIContext) NextEvent() (event tracker.MIDINoteEvent, ok bool) {
func (m *RTMIDIContext) HandleMessage(msg midi.Message, timestampms int32) {
select {
case m.events <- msg: // if the channel is full, just drop the message
default:
}
}
func (c *RTMIDIContext) NextEvent() (event tracker.MIDINoteEvent, ok bool) {
select {
case msg := <-c.events:
{
var channel uint8
var velocity uint8
var key uint8
var controller uint8
var value uint8
if msg.GetNoteOn(&channel, &key, &velocity) {
return tracker.MIDINoteEvent{Frame: 0, On: true, Channel: int(channel), Note: key}, true
} else if msg.GetNoteOff(&channel, &key, &velocity) {
return tracker.MIDINoteEvent{Frame: 0, On: false, Channel: int(channel), Note: key}, true
} else if msg.GetControlChange(&channel, &controller, &value) {
fmt.Printf("CC @ Channel: %d, Controller: %d, Value: %d\n", channel, controller, value)
} else {
fmt.Printf("Unhandled MIDI message: %s\n", msg)
}
var channel uint8
var velocity uint8
var key uint8
if msg.GetNoteOn(&channel, &key, &velocity) {
return tracker.MIDINoteEvent{Frame: 0, On: true, Channel: int(channel), Note: key}, true
} else if msg.GetNoteOff(&channel, &key, &velocity) {
return tracker.MIDINoteEvent{Frame: 0, On: false, Channel: int(channel), Note: key}, true
}
// TODO: handle control messages with something like:
// if msg.GetControlChange(&channel, &controller, &value) {
// ....
default:
// Note (@LeStahL): This empty select case is needed to make the implementation non-blocking.
}
return tracker.MIDINoteEvent{}, false
}
func (c *MIDIContext) BPM() (bpm float64, ok bool) {
func (c *RTMIDIContext) BPM() (bpm float64, ok bool) {
return 0, false
}
func (c *MIDIContext) DestroyContext() {
close(c.events)
c.currentIn.Close()
func (c *RTMIDIContext) Close() {
if c.driver == nil {
return
}
if c.currentIn != nil && c.currentIn.IsOpen() {
c.currentIn.Close()
}
c.driver.Close()
}

View File

@ -76,7 +76,7 @@ type (
PlayerMessages chan PlayerMsg
modelMessages chan<- interface{}
MIDI MIDIContexter
MIDI MIDIContext
}
// Cursor identifies a row and a track in a song score.
@ -123,16 +123,15 @@ type (
Dialog int
MIDIContexter interface {
ListInputDevices() <-chan MIDIDevicer
OpenInputDevice(item MIDIDevicer) bool
DestroyContext()
BPM() (bpm float64, ok bool)
NextEvent() (event MIDINoteEvent, ok bool)
MIDIContext interface {
ListInputDevices() func(yield func(MIDIDevice) bool)
Close()
PlayerProcessContext
}
MIDIDevicer interface {
MIDIDevice interface {
String() string
Open() error
}
)