165 lines
3.7 KiB
Go
165 lines
3.7 KiB
Go
package backend_golang
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/mattrtaylor/go-rtmidi"
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
type Port struct {
|
|
Name string `json:"name"`
|
|
}
|
|
type MIDIMessage struct {
|
|
MessageType string `json:"messageType"`
|
|
Channel int `json:"channel"`
|
|
Note int `json:"note"`
|
|
Velocity int `json:"velocity"`
|
|
Control int `json:"control"`
|
|
Value int `json:"value"`
|
|
}
|
|
|
|
var ports []Port
|
|
var input rtmidi.MIDIIn
|
|
var out rtmidi.MIDIOut
|
|
var activeIndex int = -1
|
|
var lastNoteTime time.Time
|
|
|
|
func (a *App) midiLoop() {
|
|
var err error
|
|
input, err = rtmidi.NewMIDIInDefault()
|
|
if err != nil {
|
|
runtime.EventsEmit(a.ctx, "midiError", err.Error())
|
|
return
|
|
}
|
|
out, err = rtmidi.NewMIDIOutDefault()
|
|
if err != nil {
|
|
runtime.EventsEmit(a.ctx, "midiError", err.Error())
|
|
}
|
|
err = out.OpenPort(0, "")
|
|
if err != nil {
|
|
runtime.EventsEmit(a.ctx, "midiError", err.Error())
|
|
}
|
|
ticker := time.NewTicker(500 * time.Millisecond)
|
|
go func() {
|
|
for {
|
|
<-ticker.C
|
|
count, err := input.PortCount()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
ports = make([]Port, count)
|
|
for i := 0; i < count; i++ {
|
|
name, err := input.PortName(i)
|
|
if err == nil {
|
|
ports[i].Name = name
|
|
}
|
|
}
|
|
runtime.EventsEmit(a.ctx, "midiPorts", &ports)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (a *App) OpenMidiPort(index int) error {
|
|
if input == nil {
|
|
return errors.New("failed to initialize MIDI input")
|
|
}
|
|
if activeIndex == index {
|
|
return nil
|
|
}
|
|
input.Destroy()
|
|
var err error
|
|
input, err = rtmidi.NewMIDIInDefault()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = input.SetCallback(func(msg rtmidi.MIDIIn, bytes []byte, t float64) {
|
|
// https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message
|
|
// https://www.rfc-editor.org/rfc/rfc6295.html
|
|
//
|
|
// msgType channel
|
|
// 1001 0000
|
|
//
|
|
msgType := bytes[0] >> 4
|
|
channel := bytes[0] & 0x0f
|
|
switch msgType {
|
|
case 0x8:
|
|
note := bytes[1]
|
|
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
|
|
MessageType: "NoteOff",
|
|
Channel: int(channel),
|
|
Note: int(note),
|
|
})
|
|
case 0x9:
|
|
elapsed := time.Since(lastNoteTime)
|
|
lastNoteTime = time.Now()
|
|
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
|
|
MessageType: "ElapsedTime",
|
|
Value: int(elapsed.Milliseconds()),
|
|
})
|
|
note := bytes[1]
|
|
velocity := bytes[2]
|
|
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
|
|
MessageType: "NoteOn",
|
|
Channel: int(channel),
|
|
Note: int(note),
|
|
Velocity: int(velocity),
|
|
})
|
|
case 0xb:
|
|
// control 12 => K1 knob, control 13 => K2 knob
|
|
control := bytes[1]
|
|
value := bytes[2]
|
|
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
|
|
MessageType: "ControlChange",
|
|
Channel: int(channel),
|
|
Control: int(control),
|
|
Value: int(value),
|
|
})
|
|
default:
|
|
fmt.Printf("Unknown midi message: %v\n", bytes)
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = input.OpenPort(index, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
activeIndex = index
|
|
lastNoteTime = time.Now()
|
|
return nil
|
|
}
|
|
|
|
func (a *App) CloseMidiPort() error {
|
|
if input == nil {
|
|
return errors.New("failed to initialize MIDI input")
|
|
}
|
|
if activeIndex == -1 {
|
|
return nil
|
|
}
|
|
activeIndex = -1
|
|
input.Destroy()
|
|
var err error
|
|
input, err = rtmidi.NewMIDIInDefault()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) PlayNote(msg MIDIMessage) error {
|
|
if out == nil {
|
|
return errors.New("failed to initialize MIDI output")
|
|
}
|
|
channelByte := byte(msg.Channel)
|
|
if msg.MessageType == "NoteOn" {
|
|
out.SendMessage([]byte{0x90 | channelByte, byte(msg.Note), byte(msg.Velocity)})
|
|
} else if msg.MessageType == "NoteOff" {
|
|
out.SendMessage([]byte{0x80 | channelByte, byte(msg.Note), byte(msg.Velocity)})
|
|
}
|
|
return nil
|
|
}
|