done
This commit is contained in:
parent
3ab177a7fe
commit
a8b3540a37
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"remotePath": "",
|
||||
"port": 2345,
|
||||
"host": "127.0.0.1",
|
||||
"program": "${fileDirname}",
|
||||
"env": {},
|
||||
"args": [],
|
||||
"showLog": true
|
||||
}
|
||||
]
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
Arremi
|
||||
=====
|
||||
|
||||
Arremi is a simple router from serial to MIDI
|
||||
![Screen Shot](ScreenShot.png)
|
||||
|
||||
Arremi is a simple driver/router from serial to MIDI
|
||||
|
||||
Now only for OSX
|
||||
|
||||
|
BIN
ScreenShot.png
Normal file
BIN
ScreenShot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
55
backend/backend.go
Normal file
55
backend/backend.go
Normal file
@ -0,0 +1,55 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/jacobsa/go-serial/serial"
|
||||
"github.com/tonychee7000/Arremi/consts"
|
||||
)
|
||||
|
||||
var (
|
||||
// MidiDev is a global midi device.
|
||||
MidiDev *MIDIDevice
|
||||
|
||||
// MIDIError check this. if not nil, go exit
|
||||
MIDIError error
|
||||
)
|
||||
|
||||
func init() {
|
||||
MidiDev, MIDIError = NewMIDIDevice()
|
||||
}
|
||||
|
||||
// Run called by frontend.
|
||||
func Run(chSerialName chan string, ch chan int, errCh chan error) {
|
||||
serialName := <-chSerialName
|
||||
|
||||
sPort, err := serial.Open(serial.OpenOptions{
|
||||
PortName: "/dev/" + serialName,
|
||||
BaudRate: consts.SerialBaudrate,
|
||||
DataBits: 8,
|
||||
StopBits: 1,
|
||||
ParityMode: serial.PARITY_NONE,
|
||||
MinimumReadSize: 3,
|
||||
})
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
defer MidiDev.AllNoteOff()
|
||||
defer sPort.Close()
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(MidiDev, sPort)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
ch <- 1
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
7
backend/backend_test.go
Normal file
7
backend/backend_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
package backend
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBackendRun(t *testing.T) {
|
||||
|
||||
}
|
50
backend/midiDevice.go
Normal file
50
backend/midiDevice.go
Normal file
@ -0,0 +1,50 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/tonychee7000/Arremi/consts"
|
||||
midi "github.com/youpy/go-coremidi"
|
||||
)
|
||||
|
||||
// MIDIDevice implies a Writer interface.
|
||||
type MIDIDevice struct {
|
||||
client midi.Client
|
||||
source midi.Source
|
||||
Signal chan midi.Packet
|
||||
}
|
||||
|
||||
// NewMIDIDevice construction func
|
||||
func NewMIDIDevice() (*MIDIDevice, error) {
|
||||
var mididev = new(MIDIDevice)
|
||||
err := mididev.Init()
|
||||
return mididev, err
|
||||
}
|
||||
|
||||
// Init the client and source
|
||||
func (midiDev *MIDIDevice) Init() error {
|
||||
var err error
|
||||
|
||||
midiDev.Signal = make(chan midi.Packet, 4096)
|
||||
midiDev.client, err = midi.NewClient(consts.ClientName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
midiDev.source, err = midi.NewSource(midiDev.client, consts.SourceName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (midiDev *MIDIDevice) Write(p []byte) (int, error) {
|
||||
var pack = midi.NewPacket(p, 0)
|
||||
midiDev.Signal <- pack
|
||||
err := pack.Received(&(midiDev.source))
|
||||
return len(p), err
|
||||
}
|
||||
|
||||
// AllNoteOff I don't want panic!
|
||||
func (midiDev *MIDIDevice) AllNoteOff() {
|
||||
for i := 0; i < 16; i++ {
|
||||
for j := 0; j < 128; j++ {
|
||||
midiDev.Write([]byte{byte(0x90 + i), byte(j), 0})
|
||||
}
|
||||
}
|
||||
}
|
15
consts/consts.go
Normal file
15
consts/consts.go
Normal file
@ -0,0 +1,15 @@
|
||||
package consts
|
||||
|
||||
const (
|
||||
// WindowTitle for display
|
||||
WindowTitle = "Arremi"
|
||||
// ClientName for make a client
|
||||
ClientName = "Arremi"
|
||||
// SourceName for register Source
|
||||
SourceName = "Arremi"
|
||||
|
||||
MIDISignalOn = "⚪️"
|
||||
MIDISignalOff = "⚫️"
|
||||
|
||||
SerialBaudrate = 31250
|
||||
)
|
154
frontend/window.go
Normal file
154
frontend/window.go
Normal file
@ -0,0 +1,154 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"regexp"
|
||||
|
||||
"github.com/andlabs/ui"
|
||||
"github.com/tonychee7000/Arremi/consts"
|
||||
"github.com/tonychee7000/Arremi/serialPort"
|
||||
"github.com/tonychee7000/arremi/backend"
|
||||
)
|
||||
|
||||
// WindowMain is to show the UI
|
||||
func WindowMain() {
|
||||
// Let backend stop
|
||||
//var ch = make(chan int)
|
||||
|
||||
var errCh = make(chan error, 0)
|
||||
var chCon = make(chan int, 1)
|
||||
var chSerialName = make(chan string, 1)
|
||||
var serialList []string
|
||||
|
||||
window := ui.NewWindow(consts.WindowTitle, 300, 100, false)
|
||||
|
||||
if backend.MIDIError != nil {
|
||||
ui.MsgBox(window, "MIDI Failure",
|
||||
fmt.Sprint("MIDI driver initialize failed: ", backend.MIDIError))
|
||||
ui.Quit()
|
||||
}
|
||||
|
||||
labelSerialSelection := ui.NewLabel("Serial Port")
|
||||
serialSelection := ui.NewCombobox()
|
||||
buttonRefresh := ui.NewButton("Refresh")
|
||||
buttonRun := ui.NewCheckbox("Run")
|
||||
labelMidiActive := ui.NewLabel("MIDI Active ")
|
||||
labelMidiSignal := ui.NewLabel(consts.MIDISignalOff)
|
||||
labelHint := ui.NewLabel(
|
||||
fmt.Sprint(
|
||||
"HINT:\nBaudrate of Arduino USB serial port should be set to ",
|
||||
consts.SerialBaudrate, " by using\n\n\tSerial.begin(", consts.SerialBaudrate, ")",
|
||||
),
|
||||
)
|
||||
|
||||
serialList, err := loadSerial(serialSelection, buttonRun)
|
||||
|
||||
if err != nil {
|
||||
ui.MsgBox(window, "Serial Failure",
|
||||
fmt.Sprint("Cannot list serial ports for reason: ", err))
|
||||
ui.Quit()
|
||||
}
|
||||
|
||||
buttonRefresh.OnClicked(func(*ui.Button) {
|
||||
serialList, err = loadSerial(serialSelection, buttonRun)
|
||||
if err != nil {
|
||||
ui.MsgBox(window, "Serial Failure",
|
||||
fmt.Sprint("Cannot list serial ports for reason: ", err))
|
||||
ui.Quit()
|
||||
}
|
||||
})
|
||||
|
||||
buttonRun.OnToggled(func(*ui.Checkbox) {
|
||||
if buttonRun.Checked() {
|
||||
serialSelection.Disable()
|
||||
buttonRefresh.Disable()
|
||||
chSerialName <- serialList[serialSelection.Selected()]
|
||||
} else {
|
||||
serialSelection.Enable()
|
||||
buttonRefresh.Enable()
|
||||
chCon <- 1
|
||||
}
|
||||
})
|
||||
|
||||
mainBox := ui.NewVerticalBox()
|
||||
topBox := ui.NewHorizontalBox()
|
||||
buttomBox := ui.NewHorizontalBox()
|
||||
|
||||
topBox.Append(serialSelection, true)
|
||||
topBox.Append(buttonRefresh, false)
|
||||
topBox.Append(ui.NewLabel(" "), false)
|
||||
topBox.Append(buttonRun, false)
|
||||
|
||||
buttomBox.Append(labelSerialSelection, false)
|
||||
buttomBox.Append(ui.NewHorizontalSeparator(), true)
|
||||
buttomBox.Append(labelMidiActive, false)
|
||||
buttomBox.Append(labelMidiSignal, false)
|
||||
|
||||
mainBox.Append(ui.NewHorizontalBox(), true)
|
||||
mainBox.Append(buttomBox, false)
|
||||
mainBox.Append(topBox, false)
|
||||
mainBox.Append(labelHint, true)
|
||||
mainBox.Append(ui.NewHorizontalBox(), true)
|
||||
|
||||
window.SetMargined(true)
|
||||
window.SetChild(mainBox)
|
||||
window.OnClosing(func(*ui.Window) bool {
|
||||
if buttonRun.Checked() {
|
||||
return false
|
||||
}
|
||||
ui.Quit()
|
||||
return true
|
||||
})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case err := <-errCh:
|
||||
ui.QueueMain(func() {
|
||||
if err != nil &&
|
||||
!regexp.MustCompile("file already closed").MatchString(err.Error()) {
|
||||
ui.MsgBox(window, "Backend Failure", fmt.Sprint(err))
|
||||
}
|
||||
serialSelection.Enable()
|
||||
buttonRefresh.Enable()
|
||||
buttonRun.SetChecked(false)
|
||||
labelMidiSignal.SetText(consts.MIDISignalOff)
|
||||
})
|
||||
case <-backend.MidiDev.Signal:
|
||||
ui.QueueMain(func() {
|
||||
labelMidiSignal.SetText(consts.MIDISignalOn)
|
||||
})
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
ui.QueueMain(func() {
|
||||
labelMidiSignal.SetText(consts.MIDISignalOff)
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
backend.Run(chSerialName, chCon, errCh)
|
||||
}
|
||||
}()
|
||||
|
||||
window.Show()
|
||||
}
|
||||
|
||||
func loadSerial(serialSelection *ui.Combobox, buttonRun *ui.Checkbox) ([]string, error) {
|
||||
serialList, err := serialPort.GetSerialPorts()
|
||||
|
||||
if len(serialList) == 0 {
|
||||
buttonRun.Disable()
|
||||
serialSelection.Append("-- No Arduino Found --")
|
||||
serialSelection.Disable()
|
||||
} else {
|
||||
for _, seiral := range serialList {
|
||||
serialSelection.Append(seiral)
|
||||
}
|
||||
}
|
||||
serialSelection.SetSelected(0)
|
||||
return serialList, err
|
||||
}
|
18
main.go
Normal file
18
main.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/andlabs/ui"
|
||||
"github.com/tonychee7000/Arremi/frontend"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
fmt.Println("Arremi Exit.")
|
||||
}()
|
||||
err := ui.Main(frontend.WindowMain)
|
||||
if err != nil {
|
||||
fmt.Print("Got error:", err, "\n")
|
||||
}
|
||||
}
|
23
serialPort/get_serial.go
Normal file
23
serialPort/get_serial.go
Normal file
@ -0,0 +1,23 @@
|
||||
package serialPort
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GetSerialPorts is to list all serial port for Arduino device.
|
||||
func GetSerialPorts() ([]string, error) {
|
||||
f, err := ioutil.ReadDir("/dev")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var fileList []string
|
||||
for _, file := range f {
|
||||
if regexp.MustCompile("^ttyACM([0-9]+)|^cu.usbmodem").MatchString(file.Name()) {
|
||||
fileList = append(fileList, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return fileList, nil
|
||||
}
|
17
serialPort/serialPort_test.go
Normal file
17
serialPort/serialPort_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
package serialPort
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSerialPorts(T *testing.T) {
|
||||
portList, err := GetSerialPorts()
|
||||
if err != nil {
|
||||
T.Error("ERROR: ", err, "\n")
|
||||
}
|
||||
T.Log("Show all serial ports")
|
||||
for _, p := range portList {
|
||||
T.Log("\t", p)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user