-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
167 lines (145 loc) · 3.83 KB
/
main.go
File metadata and controls
167 lines (145 loc) · 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Abstract implements an interpreter for a higher-level music notation and a player
// that emits MIDI.
package main
import (
"github.com/edemond/abstract/ast"
"github.com/edemond/abstract/drivers"
"github.com/edemond/abstract/drivers/alsa"
"github.com/edemond/abstract/drivers/jack"
"github.com/edemond/abstract/parser"
"github.com/edemond/abstract/types"
"github.com/edemond/midi"
"flag"
"fmt"
"math/rand"
"time"
)
const VERSION = "0.0.2"
// TODO: We need a way of listing potential drivers (and whether or not they'd work on the target system?)
var driverFlag = flag.String("d", "rawmidi", "\tMIDI driver (rawmidi).")
var modeListDevices = flag.Bool("a", false, "\tList available ALSA MIDI device names.")
var modeVersion = flag.Bool("v", false, "\tPrint version information.")
var loopFlag = flag.Bool("l", false, "\tLoop (Ctrl+C to stop).")
func listDevices() error {
// TODO: Let the user choose which driver to list devices for at the command line.
// If they don't pass the -d option, list all devices on all drivers that we can use.
fmt.Println("Available ALSA MIDI devices:")
devices, err := midi.GetDevices("alsa")
if err != nil {
return err
}
for _, device := range devices {
// TODO: Show if device is input or output.
fmt.Println(device.Name())
}
return nil
}
// Open a file and parse it into an AST.
func parse(filename string) (*ast.PlayStatement, error) {
fmt.Printf("Compiling %v...\n", filename)
p, err := parser.FromFile(filename)
if err != nil {
return nil, err
}
return p.Parse()
}
// Perform semantic analysis on an AST.
// Returns root part, total polyphony, BPM, PPQ, error.
func analyze(stmt *ast.PlayStatement, driver drivers.Driver) (types.Part, int, int, int, error) {
a := NewAnalyzer()
part, err := a.Analyze(stmt)
if err != nil {
return nil, 0, 0, 0, err
}
// Open instruments.
insts, err := a.OpenInstruments(driver)
if err != nil {
return nil, 0, 0, 0, err
}
return part, types.TotalVoices(insts), a.bpm, a.ppq, nil
}
func printUsage() {
fmt.Println("usage: abstract <file.abs>\n")
fmt.Println("commands:\n")
flag.PrintDefaults()
fmt.Println("")
}
type supportedDriver struct {
description string
constructor func() (drivers.Driver, error)
}
var _drivers = map[string]supportedDriver{
"rawmidi": {
"ALSA 'rawmidi' driver.",
alsa.NewRawMidiDriver,
},
"jack": {
"JACK 1.x driver.",
jack.NewJACKDriver,
},
}
func printSupportedDrivers() {
fmt.Println("Supported drivers:\n")
for name, driver := range _drivers {
fmt.Printf("\t%v\t\t%v\n", name, driver.description)
}
}
// Instantiate the driver of the given name.
// Returns nil, error if there's no such driver.
func getDriver(name string) (drivers.Driver, error) {
sd, ok := _drivers[name]
if !ok {
return nil, fmt.Errorf("No such driver '%v'.", name)
}
driver, err := sd.constructor()
if err != nil {
return nil, err
}
return driver, nil
}
func main() {
flag.Parse()
rand.Seed(time.Now().UnixNano())
if *modeListDevices {
err := listDevices()
if err != nil {
fmt.Println(err)
}
return
} else if *modeVersion {
fmt.Printf("Abstract v%v\n", VERSION)
return
}
args := flag.Args()
if len(args) < 1 {
printUsage()
return
}
filename := args[0]
stmt, err := parse(filename)
if err != nil {
fmt.Println(err)
return
}
// TODO: To fail fastest, we should validate the driver before parsing, then open it afterwards.
driver, err := getDriver(*driverFlag)
if err != nil {
fmt.Println(err)
printSupportedDrivers()
return
}
defer driver.Close()
// TODO: Really, five return values? Can we put this into a struct
// that analyze and driver.Play use to communicate?
part, polyphony, bpm, ppq, err := analyze(stmt, driver)
if err != nil {
fmt.Println(err)
return
}
err = driver.Play(part, bpm, ppq, *loopFlag, polyphony)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Done.")
}