Mercurial > ~darius > hgwebdir.cgi > musiccutter
annotate musiccutter.py @ 10:b9727813e029
add more notes to generate >1 page
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Thu, 07 Apr 2016 23:35:19 +0930 |
parents | e9760224e992 |
children | 9faad813e39e |
rev | line source |
---|---|
0 | 1 #!/usr/bin/env python |
2 | |
3 import exceptions | |
4
9f4fa5f231e6
Rework to use mido iterator and paginate.
Daniel O'Connor <darius@dons.net.au>
parents:
3
diff
changeset
|
4 import itertools |
7 | 5 import math |
0 | 6 import mido |
7 import svgwrite | |
8 import sys | |
9 | |
3
49a33c431b45
Accept filename for test function
Daniel O'Connor <darius@dons.net.au>
parents:
2
diff
changeset
|
10 def test(filename = None): |
49a33c431b45
Accept filename for test function
Daniel O'Connor <darius@dons.net.au>
parents:
2
diff
changeset
|
11 if filename == None: |
49a33c431b45
Accept filename for test function
Daniel O'Connor <darius@dons.net.au>
parents:
2
diff
changeset
|
12 filename = 'test.midi' |
7 | 13 # Card layout from http://www.orgues-de-barbarie.com/wp-content/uploads/2014/09/format-cartons.pdf |
14 m = Midi2SVG('notes', 20, 15.5, 0.55, 0.6, 1.0) | |
15 m.processMidi(filename, 'test%02d.svg') | |
0 | 16 |
7 | 17 class Midi2SVG(object): |
18 def __init__(self, notefile, pagewidth, pageheight, pitch, offset, timescale): | |
19 self.midi2note, self.note2midi = Midi2SVG.genmidi2note() | |
20 self.note2slot = Midi2SVG.loadnote2slot(notefile, self.note2midi) | |
21 self.pagewidth = pagewidth | |
22 self.pageheight = pageheight | |
23 self.pitch = pitch | |
24 self.offset = offset | |
25 self.timescale = timescale | |
0 | 26 |
7 | 27 def processMidi(self, midifile, outpat): |
28 playablecount = 0 | |
29 unplayablecount = 0 | |
30 midi = mido.MidiFile(midifile) | |
31 ctime = 0 | |
32 channels = [] | |
33 for i in range(16): | |
34 channels.append({}) | |
0 | 35 |
7 | 36 npages = int(math.ceil(midi.length / self.timescale / self.pagewidth)) |
37 print 'npages', npages | |
38 svgs = [] | |
39 for i in range(npages): | |
40 svg = svgwrite.Drawing(outpat % i, profile = 'full', size = ('%.3fcm' % (self.pagewidth), '%.3fcm' % (self.pageheight))) | |
41 svgs.append(svg) | |
0 | 42 |
7 | 43 for ev in midi: |
44 ctime += ev.time | |
45 if ev.type == 'note_on' or ev.type == 'note_off': | |
46 note = self.midi2note[ev.note] | |
47 print ctime, ev | |
48 if ev.type == 'note_on' and ev.velocity > 0: | |
49 if ev.note in channels[ev.channel]: | |
50 print 'Duplicate note_on message %d (%s)' % (ev.note, note) | |
0 | 51 else: |
7 | 52 channels[ev.channel][ev.note] = ctime |
53 elif ev.type == 'note_off' or (ev.type == 'note_on' and ev.velocity == 0): | |
54 if ev.note not in channels[ev.channel]: | |
55 print 'note_off with no corresponding note_on for channel %d note %d' % (ev.channel, ev.note) | |
0 | 56 else: |
7 | 57 if note not in self.note2slot: |
58 print 'Skipping unplayable note %s' % (note) | |
59 unplayablecount += 1 | |
60 else: | |
61 start = channels[ev.channel][ev.note] | |
62 notelen = ctime - start | |
63 slot = self.note2slot[note] | |
64 print 'Note %s (%d) at %.2f length %.2f' % (note, slot, start, notelen) | |
65 self.emitnote(svgs, slot, start, notelen) | |
66 playablecount += 1 | |
67 del channels[ev.channel][ev.note] | |
68 elif ev.type == 'end_of_track': | |
69 print 'EOT, not flushing, check for missed notes' | |
70 for chan in channels: | |
71 for ev in chan: | |
72 print ev | |
73 | |
74 print 'Playable count:', playablecount | |
75 print 'Unplayable count:', unplayablecount | |
76 | |
8
f07a997e9f79
Engrave filename and page number.
Daniel O'Connor <darius@dons.net.au>
parents:
7
diff
changeset
|
77 for i in range(len(svgs)): |
f07a997e9f79
Engrave filename and page number.
Daniel O'Connor <darius@dons.net.au>
parents:
7
diff
changeset
|
78 svg = svgs[i] |
f07a997e9f79
Engrave filename and page number.
Daniel O'Connor <darius@dons.net.au>
parents:
7
diff
changeset
|
79 svg.add(svg.text('%s (%d / %d)' % (midifile, i + 1, npages), fill = 'black', stroke = 'black', |
f07a997e9f79
Engrave filename and page number.
Daniel O'Connor <darius@dons.net.au>
parents:
7
diff
changeset
|
80 insert = ('%.3fcm' % (0), '%.3fcm' % (self.pageheight)))) |
7 | 81 svg.save() |
82 | |
83 # http://www.electronics.dit.ie/staff/tscarff/Music_technology/midi/midi_note_numbers_for_octaves.htm | |
84 @staticmethod | |
85 def genmidi2note(): | |
86 '''Create forward & reverse tables for midi number to note name (assuming 69 == A440)''' | |
87 names = ['C%d', 'C%d#', 'D%d', 'D%d#', 'E%d', 'F%d', 'F%d#', 'G%d', 'G%d#', 'A%d', 'A%d#', 'B%d'] | |
88 midi2note = {} | |
89 note2midi = {} | |
90 for midi in range(128): | |
91 octave = midi / len(names) | |
92 index = midi % len(names) | |
93 name = names[index] % (octave) | |
94 midi2note[midi] = name | |
95 note2midi[name] = midi | |
4
9f4fa5f231e6
Rework to use mido iterator and paginate.
Daniel O'Connor <darius@dons.net.au>
parents:
3
diff
changeset
|
96 |
7 | 97 return midi2note, note2midi |
98 | |
99 @staticmethod | |
100 def loadnote2slot(fname, note2midi): | |
101 note2slot = {} | |
102 index = 0 | |
103 | |
104 for note in file(fname): | |
105 note = note.strip() | |
106 if note[0] == '#': | |
107 continue | |
108 if note not in note2midi: | |
109 raise exceptions.ValueError('Note \'%s\' not valid' % note) | |
110 note2slot[note] = index | |
111 index += 1 | |
112 | |
113 return note2slot | |
4
9f4fa5f231e6
Rework to use mido iterator and paginate.
Daniel O'Connor <darius@dons.net.au>
parents:
3
diff
changeset
|
114 |
7 | 115 def emitnote(self, svgs, slot, start, notelen): |
116 startx = start / self.timescale | |
117 startpageidx = int(startx / self.pagewidth) | |
118 endx = (start + notelen) / self.timescale | |
119 endpageidx = int(endx / self.pagewidth) | |
120 startx = startx % self.pagewidth | |
121 y = self.pageheight - (self.offset + slot * self.pitch) | |
122 w = notelen / self.timescale | |
123 h = self.pitch | |
124 | |
125 if startpageidx != endpageidx: | |
126 print 'page crossed from %d to %d' % (startpageidx, endpageidx) | |
127 print 'page = %d x = %.3f y = %.3f w = %.3f h = %.3f' % (startpageidx, startx, y, w, h) | |
128 Midi2SVG._emitnote(svgs[startpageidx], startx, y, w, h) | |
129 | |
130 @staticmethod | |
131 def _emitnote(svg, x, y, w, h): | |
132 svg.add(svgwrite.shapes.Rect(insert = ('%.3fcm' % (x), '%.3fcm' % (y)), | |
133 size = ('%.3fcm' % (w), '%.3fcm' % (h)), | |
9 | 134 stroke = 'red', stroke_width = '1px', fill = 'none')) |
0 | 135 |
136 if __name__ == '__main__': | |
137 main() |