mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
refactor(tracker): harmonize naming and use iterators in MIDI
using iterators requires go 1.23
This commit is contained in:
parent
577265b250
commit
c07d8000c6
@ -54,8 +54,8 @@ func main() {
|
|||||||
recoveryFile = filepath.Join(configDir, "Sointu", "sointu-track-recovery")
|
recoveryFile = filepath.Join(configDir, "Sointu", "sointu-track-recovery")
|
||||||
}
|
}
|
||||||
model, player := tracker.NewModelPlayer(cmd.MainSynther, recoveryFile)
|
model, player := tracker.NewModelPlayer(cmd.MainSynther, recoveryFile)
|
||||||
model.MIDI = gomidi.CreateContext()
|
model.MIDI = gomidi.NewContext()
|
||||||
defer model.MIDI.DestroyContext()
|
defer model.MIDI.Close()
|
||||||
if a := flag.Args(); len(a) > 0 {
|
if a := flag.Args(); len(a) > 0 {
|
||||||
f, err := os.Open(a[0])
|
f, err := os.Open(a[0])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/vsariola/sointu
|
module github.com/vsariola/sointu
|
||||||
|
|
||||||
go 1.22.2
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gioui.org v0.7.1
|
gioui.org v0.7.1
|
||||||
|
@ -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) Export() Action { return Allow(func() { m.dialog = Export }) }
|
||||||
func (m *Model) ExportFloat() Action { return Allow(func() { m.dialog = ExportFloatExplorer }) }
|
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) 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() {
|
return Allow(func() {
|
||||||
if !m.MIDI.OpenInputDevice(item) {
|
if err := item.Open(); err != nil {
|
||||||
message := fmt.Sprintf("Could not open MIDI device %s\n", item)
|
message := fmt.Sprintf("Could not open MIDI device: %s", item)
|
||||||
m.Alerts().Add(message, Error)
|
m.Alerts().Add(message, Error)
|
||||||
|
} else {
|
||||||
|
message := fmt.Sprintf("Opened MIDI device: %s", item)
|
||||||
|
m.Alerts().Add(message, Info)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package gomidi
|
package gomidi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vsariola/sointu/tracker"
|
"github.com/vsariola/sointu/tracker"
|
||||||
"gitlab.com/gomidi/midi/v2"
|
"gitlab.com/gomidi/midi/v2"
|
||||||
@ -11,105 +11,111 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
MIDIContext struct {
|
RTMIDIContext struct {
|
||||||
driver *rtmididrv.Driver
|
driver *rtmididrv.Driver
|
||||||
inputAvailable bool
|
currentIn drivers.In
|
||||||
driverAvailable bool
|
events chan midi.Message
|
||||||
currentIn MIDIDevicer
|
}
|
||||||
events chan midi.Message
|
|
||||||
|
RTMIDIDevice struct {
|
||||||
|
context *RTMIDIContext
|
||||||
|
in drivers.In
|
||||||
}
|
}
|
||||||
MIDIDevicer drivers.In
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *MIDIContext) ListInputDevices() <-chan tracker.MIDIDevicer {
|
func (m *RTMIDIContext) ListInputDevices() func(yield func(tracker.MIDIDevice) bool) {
|
||||||
|
return func(yield func(tracker.MIDIDevice) bool) {
|
||||||
ins, err := m.driver.Ins()
|
if m.driver == nil {
|
||||||
channel := make(chan tracker.MIDIDevicer, len(ins))
|
return
|
||||||
if err != nil {
|
|
||||||
m.driver.Close()
|
|
||||||
m.driverAvailable = false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for i := 0; i < len(ins); i++ {
|
|
||||||
channel <- ins[i].(MIDIDevicer)
|
|
||||||
}
|
}
|
||||||
close(channel)
|
ins, err := m.driver.Ins()
|
||||||
}()
|
if err != nil {
|
||||||
return channel
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(ins); i++ {
|
||||||
|
device := RTMIDIDevice{context: m, in: ins[i]}
|
||||||
|
if !yield(device) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the driver.
|
// Open the driver.
|
||||||
func CreateContext() *MIDIContext {
|
func NewContext() *RTMIDIContext {
|
||||||
m := MIDIContext{}
|
m := RTMIDIContext{events: make(chan midi.Message, 1024)}
|
||||||
var err error
|
// there's not much we can do if this fails, so just use m.driver = nil to
|
||||||
m.driver, err = rtmididrv.New()
|
// indicate no driver available
|
||||||
m.driverAvailable = err == nil
|
m.driver, _ = rtmididrv.New()
|
||||||
if m.driverAvailable {
|
|
||||||
m.events = make(chan midi.Message)
|
|
||||||
}
|
|
||||||
return &m
|
return &m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open an input device while closing the currently open if necessary.
|
// Open an input device while closing the currently open if necessary.
|
||||||
func (m *MIDIContext) OpenInputDevice(in tracker.MIDIDevicer) bool {
|
func (m RTMIDIDevice) Open() error {
|
||||||
fmt.Printf("Opening midi device %s\n.", in)
|
if m.context.currentIn == m.in {
|
||||||
if m.driverAvailable {
|
return nil
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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) {
|
func (d RTMIDIDevice) String() string {
|
||||||
go func() {
|
return d.in.String()
|
||||||
m.events <- msg
|
|
||||||
time.Sleep(time.Nanosecond)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
select {
|
||||||
case msg := <-c.events:
|
case msg := <-c.events:
|
||||||
{
|
var channel uint8
|
||||||
var channel uint8
|
var velocity uint8
|
||||||
var velocity uint8
|
var key uint8
|
||||||
var key uint8
|
if msg.GetNoteOn(&channel, &key, &velocity) {
|
||||||
var controller uint8
|
return tracker.MIDINoteEvent{Frame: 0, On: true, Channel: int(channel), Note: key}, true
|
||||||
var value uint8
|
} else if msg.GetNoteOff(&channel, &key, &velocity) {
|
||||||
if msg.GetNoteOn(&channel, &key, &velocity) {
|
return tracker.MIDINoteEvent{Frame: 0, On: false, Channel: int(channel), Note: key}, true
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// TODO: handle control messages with something like:
|
||||||
|
// if msg.GetControlChange(&channel, &controller, &value) {
|
||||||
|
// ....
|
||||||
default:
|
default:
|
||||||
// Note (@LeStahL): This empty select case is needed to make the implementation non-blocking.
|
// Note (@LeStahL): This empty select case is needed to make the implementation non-blocking.
|
||||||
}
|
}
|
||||||
return tracker.MIDINoteEvent{}, false
|
return tracker.MIDINoteEvent{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MIDIContext) BPM() (bpm float64, ok bool) {
|
func (c *RTMIDIContext) BPM() (bpm float64, ok bool) {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MIDIContext) DestroyContext() {
|
func (c *RTMIDIContext) Close() {
|
||||||
close(c.events)
|
if c.driver == nil {
|
||||||
c.currentIn.Close()
|
return
|
||||||
|
}
|
||||||
|
if c.currentIn != nil && c.currentIn.IsOpen() {
|
||||||
|
c.currentIn.Close()
|
||||||
|
}
|
||||||
c.driver.Close()
|
c.driver.Close()
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ type (
|
|||||||
PlayerMessages chan PlayerMsg
|
PlayerMessages chan PlayerMsg
|
||||||
modelMessages chan<- interface{}
|
modelMessages chan<- interface{}
|
||||||
|
|
||||||
MIDI MIDIContexter
|
MIDI MIDIContext
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor identifies a row and a track in a song score.
|
// Cursor identifies a row and a track in a song score.
|
||||||
@ -123,16 +123,15 @@ type (
|
|||||||
|
|
||||||
Dialog int
|
Dialog int
|
||||||
|
|
||||||
MIDIContexter interface {
|
MIDIContext interface {
|
||||||
ListInputDevices() <-chan MIDIDevicer
|
ListInputDevices() func(yield func(MIDIDevice) bool)
|
||||||
OpenInputDevice(item MIDIDevicer) bool
|
Close()
|
||||||
DestroyContext()
|
PlayerProcessContext
|
||||||
BPM() (bpm float64, ok bool)
|
|
||||||
NextEvent() (event MIDINoteEvent, ok bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MIDIDevicer interface {
|
MIDIDevice interface {
|
||||||
String() string
|
String() string
|
||||||
|
Open() error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user