]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilymidi.py
Add '-dcrop' option to ps and svg backends
[lilypond.git] / scripts / lilymidi.py
1 #!@TARGET_PYTHON@
2
3 # Copyright (C) 2006--2015 Brailcom, o.p.s.
4 #
5 # Author: Milan Zamazal <pdm@brailcom.org>
6 #
7 # This file is part of LilyPond, the GNU music typesetter.
8 #
9 # LilyPond is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # LilyPond is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
21
22 import optparse
23 import os
24 import sys
25
26 """
27 @relocate-preamble@
28 """
29
30 def process_options (args):
31     parser = optparse.OptionParser (version="@TOPLEVEL_VERSION@")
32     parser.add_option ('', '--filter-tracks', metavar='REGEXP', action='store', type='string', dest='regexp',
33                        help="display only tracks numbers, of those track names matching REGEXP")
34     parser.add_option ('', '--prefix-tracks', metavar='PREFIX', action='store', type='string', dest='prefix',
35                        help="prefix filtered track numbers with PREFIX")
36     parser.add_option ('', '--dump', action='store_true', dest='dump',
37                        help="just dump parsed contents of the MIDI file")
38     parser.add_option ('', '--pretty', action='store_true', dest='pretty',
39                        help="dump parsed contents of the MIDI file in human-readable form (implies --dump)")
40     parser.usage = parser.usage + " FILE"
41     options, args = parser.parse_args (args)
42     if len (args) != 1:
43         parser.print_help ()
44         sys.exit (2)
45     return options, args
46
47 def read_midi (file):
48     import midi
49     return midi.parse (open (file).read ())
50
51 def track_info (data):
52     tracks = data[1]
53     def track_name (track):
54         name = ''
55         for time, event in track:
56             if time > 0:
57                 break
58             if event[0] == 255 and event[1] == 3:
59                 name = event[2]
60                 break
61         return name
62     track_info = []
63     for i in range (len (tracks)):
64         track_info.append ((i, track_name (tracks[i])))
65     return track_info
66
67
68 class formatter:
69    def __init__ (self, txt = ""):
70      self.text = txt
71    def format_vals (self, val1, val2 = ""):
72      return str (val1) + str(val2)
73    def format (self, val1, val2 = ""):
74      return self.text + self.format_vals (val1, val2)
75 class none_formatter (formatter):
76    def format_vals (self, val1, val2 = ""):
77      return ''
78 class meta_formatter (formatter):
79    def format_vals (self, val1, val2):
80      return str (val2);
81 class tempo_formatter (formatter):
82    def format_vals (self, val1, val2):
83     return str (ord (val2[0])*65536 + ord (val2[1])*256 + ord (val2[2])) \
84         + " msec/quarter"
85
86 class time_signature_formatter (formatter):
87    def format_vals (self, val1, val2 = ""):
88        from fractions import Fraction
89        # if there are more notated 32nd notes per midi quarter than 8,
90        # we display a fraction smaller than 1 as scale factor.
91        r = Fraction(8, ord (val2[3]))
92        if r == 1:
93            ratio =""
94        else:
95            ratio = " *" + str (r)
96        return str (ord (val2[0])) + "/" + str(1 << ord (val2[1])) + ratio \
97            + ", metronome "  + str (Fraction (ord (val2[2]), 96))
98 class key_signature_formatter (formatter):
99    def format_vals (self, val1, val2):
100        key_names = ['F', 'C', 'G', 'D', 'A', 'E', 'B']
101        key = (((ord(val2[0])+128)%256)-128) + ord(val2[1])*3 + 1;
102        return (key_names[key%7] + (key/7) * "is" + (-(key/7)) * "es"
103                + " " + ['major','minor'][ord(val2[1])])
104 class channel_formatter (formatter):
105    def __init__ (self, txt, ch):
106      formatter.__init__ (self, txt)
107      self.channel = ch
108    def format (self, val1, val2 = ""):
109      return self.text + "Channel " + str (self.channel) + ", " + \
110             self.format_vals (val1, val2)
111 class control_mode_formatter (formatter):
112    def __init__ (self, txt, ch):
113      formatter.__init__ (self, txt)
114      self.mode = ch
115    def format (self, val1, val2 = ""):
116      return self.text + str (self.mode) + ", " + \
117             self.format_vals (val1, val2)
118 class note_formatter (channel_formatter):
119    def pitch (self, val):
120      pitch_names = ['C', 'Cis', 'D', 'Dis', 'E', 'F', 'Fis', 'G', 'Gis', 'A', 'Ais', 'B'];
121      p = val % 12;
122      oct = val / 12 -1;
123      return pitch_names[p] + str(oct) + "(" + str(val) + ")"
124    def velocity (self, val):
125      #01   #10   #20   #30   #40   #50   #60   #70   #7F
126      pass;
127    def format_vals (self, val1, val2):
128      return self.pitch (val1)
129
130
131 meta_dict = {0x00: meta_formatter ("Seq.Nr.:    "),
132              0x01: meta_formatter ("Text:       "),
133              0x02: meta_formatter ("Copyright:  "),
134              0x03: meta_formatter ("Track name: "),
135              0x04: meta_formatter ("Instrument: "),
136              0x05: meta_formatter ("Lyric:      "),
137              0x06: meta_formatter ("Marker:     "),
138              0x07: meta_formatter ("Cue point:  "),
139              0x2F: none_formatter ("End of Track"),
140              0x51: tempo_formatter ("Tempo:      "),
141              0x54: meta_formatter ("SMPTE Offs.:"),
142              0x58: time_signature_formatter ("Time signature: "),
143              0x59: key_signature_formatter ("Key signature: ")
144 }
145
146 def dump_event (ev, time, padding):
147     ch = ev[0] & 0x0F;
148     func = ev[0] & 0xF0;
149     f = None
150     if (ev[0] == 0xFF):
151         f = meta_dict.get (ev[1], formatter ())
152     if (func == 0x80):
153         f = note_formatter ("Note off: ", ch)
154     elif (func == 0x90):
155         if (ev[2] == 0):
156           desc = "Note off: "
157         else:
158           desc = "Note on: "
159         f = note_formatter (desc, ch)
160     elif (func == 0xA0):
161         f = note_formatter ("Polyphonic aftertouch: ", ch, "Aftertouch pressure: ")
162     elif (func == 0xB0):
163         f = control_mode_formatter ("Control mode change: ", ch)
164     elif (func == 0xC0):
165         f = channel_formatter ("Program Change: ", ch)
166     elif (func == 0xD0):
167         f = channel_formatter ("Channel aftertouch: ", ch)
168     elif (ev[0] in [0xF0, 0xF7]):
169         f = meta_formatter ("System-exclusive event: ")
170
171     if f:
172       if len (ev) > 2:
173         print padding + f.format (ev[1], ev[2])
174       elif len (ev) > 1:
175         print padding + f.format (ev[1])
176       else:
177         print padding + f.format ()
178     else:
179       print padding + "Unrecognized MIDI event: " + str (ev);
180
181 def dump_midi (data, midi_file, options):
182     if not options.pretty:
183         print data
184         return
185     # First, dump general info, #tracks, etc.
186     print "Filename:     " + midi_file;
187     i = data[0];
188     m_formats = {0: 'single multi-channel track',
189                  1: "one or more simultaneous tracks",
190                  2: "one or more sequentially independent single-track patterns"}
191     print "MIDI format:  " + str (i[0]) + " (" + m_formats.get (i[0], "") + ")";
192     print "Divisions:    " + str (i[1]) + " per whole note";
193     print "#Tracks:      " + str ( len (data[1]))
194     n = 0;
195     for tr in data[1]:
196       time = 0;
197       n += 1;
198       print
199       print "Track " + str(n) + ":"
200       print "    Time 0:"
201       for ev in tr:
202         if ev[0]>time:
203            time = ev[0]
204            print "    Time " + str(time) + ": "
205         dump_event (ev[1], time, "        ");
206
207
208
209 def go ():
210     options, args = process_options (sys.argv[1:])
211     midi_file = args[0]
212     midi_data = read_midi (midi_file)
213     info = track_info (midi_data)
214     if (options.dump or options.pretty):
215         dump_midi (midi_data, midi_file, options);
216     elif options.regexp:
217         import re
218         regexp = re.compile (options.regexp)
219         numbers = [str(n+1) for n, name in info if regexp.search (name)]
220         if numbers:
221             if options.prefix:
222                 sys.stdout.write ('%s ' % (options.prefix,))
223             import string
224             sys.stdout.write (string.join (numbers, ','))
225             sys.stdout.write ('\n')
226     else:
227         for n, name in info:
228             sys.stdout.write ('%d %s\n' % (n+1, name,))
229
230 if __name__ == '__main__':
231     go ()