sointu/tracker/gomidi/midi.go

116 lines
2.7 KiB
Go

package gomidi
import (
"fmt"
"time"
"github.com/vsariola/sointu/tracker"
"gitlab.com/gomidi/midi/v2"
"gitlab.com/gomidi/midi/v2/drivers"
"gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
type (
MIDIContext struct {
driver *rtmididrv.Driver
inputAvailable bool
driverAvailable bool
currentIn MIDIDevicer
events chan midi.Message
}
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)
}
close(channel)
}()
return channel
}
// 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)
}
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
}
}
return true
}
func (m *MIDIContext) HandleMessage(msg midi.Message, timestampms int32) {
go func() {
m.events <- msg
time.Sleep(time.Nanosecond)
}()
}
func (c *MIDIContext) 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)
}
}
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) {
return 0, false
}
func (c *MIDIContext) DestroyContext() {
close(c.events)
c.currentIn.Close()
c.driver.Close()
}