summaryrefslogtreecommitdiff
path: root/src/mxml2csv.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/mxml2csv.go')
-rw-r--r--src/mxml2csv.go188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/mxml2csv.go b/src/mxml2csv.go
new file mode 100644
index 0000000..94ea5d0
--- /dev/null
+++ b/src/mxml2csv.go
@@ -0,0 +1,188 @@
1package main
2
3import (
4 "encoding/csv"
5 "encoding/xml"
6 "flag"
7 "fmt"
8 "io/ioutil"
9 "math"
10 "os"
11)
12
13const APP_VERSION = "0.1"
14
15var versionFlag *bool = flag.Bool("v", false, "Print the version number.")
16var inputFlag *string = flag.String("i", "", "The MusicXML file.")
17var outputFlag *string = flag.String("o", "", "The CSV file to generate.")
18var partFlag *int = flag.Int("p", 0, "The part to process.")
19
20func init() {
21 flag.Parse()
22
23 if *versionFlag {
24 fmt.Println("Version:", APP_VERSION)
25 return
26 }
27
28 if *outputFlag == "" {
29 *outputFlag = *inputFlag + ".csv"
30 }
31}
32
33/***** MusicXML structure *****/
34
35type XMLFile struct {
36 Parts []Part `xml:"part"`
37}
38
39type Part struct {
40 Measures []Measure `xml:"measure"`
41}
42
43type Measure struct {
44 Attributes Attributes `xml:"attributes"`
45 Directions []Direction `xml:"direction"`
46 Notes []Note `xml:"note"`
47}
48
49type Attributes struct {
50 Divisions int `xml:"divisions"`
51}
52
53type Direction struct {
54 Sound Sound `xml:"sound"`
55}
56
57type Sound struct {
58 Tempo float64 `xml:"tempo,attr"`
59}
60
61type Note struct {
62 Pitch Pitch `xml:"pitch"`
63 Duration int `xml:"duration"`
64}
65
66type Pitch struct {
67 Step string `xml:"step"`
68 Alter int `xml:"alter"`
69 Octave int `xml:"octave"`
70}
71
72/***** Note conversion *****/
73const REFERENCE_PITCH float64 = 440
74
75var pitchOffset = map[string]int{
76 "C": -9,
77 "D": -7,
78 "E": -5,
79 "F": -4,
80 "G": -2,
81 "A": +0,
82 "B": +2,
83}
84
85func calcFrequency(pitch Pitch) float64 {
86 if pitch.Step == "" {
87 return 0
88 }
89 return REFERENCE_PITCH * float64(pitch.Octave-3) * math.Pow(2, float64(pitchOffset[pitch.Step]+pitch.Alter)/12)
90}
91
92/***** Division calculation *****/
93
94const DEFAULT_DIVISION = 24
95
96func readDivisions(part Part) int {
97 for _, measure := range part.Measures {
98 if measure.Attributes.Divisions != 0 {
99 return measure.Attributes.Divisions
100 }
101 }
102 return DEFAULT_DIVISION
103}
104
105/***** Tempo calculation *****/
106
107const DEFAULT_TEMPO = 120
108
109func readTempo(part Part) float64 {
110 for _, measure := range part.Measures {
111 for _, direction := range measure.Directions {
112 if direction.Sound.Tempo != 0 {
113 return direction.Sound.Tempo
114 }
115 }
116 }
117 return DEFAULT_TEMPO
118}
119
120func calcDuration(noteDuration int, divisions int, tempo float64) float64 {
121 return 60000 / tempo * float64(noteDuration) / float64(divisions)
122}
123
124/***** Main program *****/
125
126func main() {
127
128 // read and parse MusicXML file
129
130 inputFile, err := os.Open(*inputFlag)
131 if err != nil {
132 fmt.Println("Error opening MusicXML file:", err)
133 return
134 }
135 defer inputFile.Close()
136
137 data, err := ioutil.ReadAll(inputFile)
138 if err != nil {
139 fmt.Println("Error reading MusicXML file:", err)
140 return
141 }
142
143 var parsed XMLFile
144 xml.Unmarshal(data, &parsed)
145
146 fmt.Println("Parsed data:", parsed)
147
148 // create and write to CSV file
149
150 outputFile, err := os.Create(*outputFlag)
151 if err != nil {
152 fmt.Println("Error creating CSV file:", err)
153 return
154 }
155 defer outputFile.Close()
156
157 writer := csv.NewWriter(outputFile)
158 defer writer.Flush()
159
160 if *partFlag >= len(parsed.Parts) {
161 fmt.Println("Error: part", *partFlag, "does not exist.")
162 return
163 }
164 part := parsed.Parts[*partFlag]
165
166 tempo := readTempo(part)
167 fmt.Println("Tempo:", tempo)
168
169 divisions := readDivisions(part)
170 fmt.Println("Divisions:", divisions)
171
172 for _, measure := range part.Measures {
173 for _, note := range measure.Notes {
174 pitch := calcFrequency(note.Pitch)
175 duration := calcDuration(note.Duration, divisions, tempo)
176 line := []string{fmt.Sprint(pitch), fmt.Sprint(duration)}
177 fmt.Println(line)
178 err := writer.Write(line)
179 if err != nil {
180 fmt.Println("Error writing in CSV file:", err)
181 return
182 }
183 }
184 }
185
186 fmt.Println("Done.")
187
188}