X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilymidi.py;h=34308f07025e625ed0b0cae086ae6258cfde4edd;hb=HEAD;hp=b8481b3457d5e54ea72d193270f6ce2e919f8f12;hpb=a9d9433bc7b95cb2d4b3d96eefce7a8437c0d44e;p=lilypond.git diff --git a/scripts/lilymidi.py b/scripts/lilymidi.py index b8481b3457..34308f0702 100644 --- a/scripts/lilymidi.py +++ b/scripts/lilymidi.py @@ -1,25 +1,23 @@ #!@TARGET_PYTHON@ -# Copyright (c) 2006--2009 Brailcom, o.p.s. +# Copyright (C) 2006--2015 Brailcom, o.p.s. # # Author: Milan Zamazal # -# COPYRIGHT NOTICE +# This file is part of LilyPond, the GNU music typesetter. # -# This program is free software; you can redistribute it and/or modify +# LilyPond is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# for more details. +# LilyPond is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - +# along with LilyPond. If not, see . import optparse import os @@ -37,6 +35,8 @@ def process_options (args): help="prefix filtered track numbers with PREFIX") parser.add_option ('', '--dump', action='store_true', dest='dump', help="just dump parsed contents of the MIDI file") + parser.add_option ('', '--pretty', action='store_true', dest='pretty', + help="dump parsed contents of the MIDI file in human-readable form (implies --dump)") parser.usage = parser.usage + " FILE" options, args = parser.parse_args (args) if len (args) != 1: @@ -64,13 +64,155 @@ def track_info (data): track_info.append ((i, track_name (tracks[i]))) return track_info + +class formatter: + def __init__ (self, txt = ""): + self.text = txt + def format_vals (self, val1, val2 = ""): + return str (val1) + str(val2) + def format (self, val1, val2 = ""): + return self.text + self.format_vals (val1, val2) +class none_formatter (formatter): + def format_vals (self, val1, val2 = ""): + return '' +class meta_formatter (formatter): + def format_vals (self, val1, val2): + return str (val2); +class tempo_formatter (formatter): + def format_vals (self, val1, val2): + return str (ord (val2[0])*65536 + ord (val2[1])*256 + ord (val2[2])) \ + + " msec/quarter" + +class time_signature_formatter (formatter): + def format_vals (self, val1, val2 = ""): + from fractions import Fraction + # if there are more notated 32nd notes per midi quarter than 8, + # we display a fraction smaller than 1 as scale factor. + r = Fraction(8, ord (val2[3])) + if r == 1: + ratio ="" + else: + ratio = " *" + str (r) + return str (ord (val2[0])) + "/" + str(1 << ord (val2[1])) + ratio \ + + ", metronome " + str (Fraction (ord (val2[2]), 96)) +class key_signature_formatter (formatter): + def format_vals (self, val1, val2): + key_names = ['F', 'C', 'G', 'D', 'A', 'E', 'B'] + key = (((ord(val2[0])+128)%256)-128) + ord(val2[1])*3 + 1; + return (key_names[key%7] + (key/7) * "is" + (-(key/7)) * "es" + + " " + ['major','minor'][ord(val2[1])]) +class channel_formatter (formatter): + def __init__ (self, txt, ch): + formatter.__init__ (self, txt) + self.channel = ch + def format (self, val1, val2 = ""): + return self.text + "Channel " + str (self.channel) + ", " + \ + self.format_vals (val1, val2) +class control_mode_formatter (formatter): + def __init__ (self, txt, ch): + formatter.__init__ (self, txt) + self.mode = ch + def format (self, val1, val2 = ""): + return self.text + str (self.mode) + ", " + \ + self.format_vals (val1, val2) +class note_formatter (channel_formatter): + def pitch (self, val): + pitch_names = ['C', 'Cis', 'D', 'Dis', 'E', 'F', 'Fis', 'G', 'Gis', 'A', 'Ais', 'B']; + p = val % 12; + oct = val / 12 -1; + return pitch_names[p] + str(oct) + "(" + str(val) + ")" + def velocity (self, val): + #01 #10 #20 #30 #40 #50 #60 #70 #7F + pass; + def format_vals (self, val1, val2): + return self.pitch (val1) + + +meta_dict = {0x00: meta_formatter ("Seq.Nr.: "), + 0x01: meta_formatter ("Text: "), + 0x02: meta_formatter ("Copyright: "), + 0x03: meta_formatter ("Track name: "), + 0x04: meta_formatter ("Instrument: "), + 0x05: meta_formatter ("Lyric: "), + 0x06: meta_formatter ("Marker: "), + 0x07: meta_formatter ("Cue point: "), + 0x2F: none_formatter ("End of Track"), + 0x51: tempo_formatter ("Tempo: "), + 0x54: meta_formatter ("SMPTE Offs.:"), + 0x58: time_signature_formatter ("Time signature: "), + 0x59: key_signature_formatter ("Key signature: ") +} + +def dump_event (ev, time, padding): + ch = ev[0] & 0x0F; + func = ev[0] & 0xF0; + f = None + if (ev[0] == 0xFF): + f = meta_dict.get (ev[1], formatter ()) + if (func == 0x80): + f = note_formatter ("Note off: ", ch) + elif (func == 0x90): + if (ev[2] == 0): + desc = "Note off: " + else: + desc = "Note on: " + f = note_formatter (desc, ch) + elif (func == 0xA0): + f = note_formatter ("Polyphonic aftertouch: ", ch, "Aftertouch pressure: ") + elif (func == 0xB0): + f = control_mode_formatter ("Control mode change: ", ch) + elif (func == 0xC0): + f = channel_formatter ("Program Change: ", ch) + elif (func == 0xD0): + f = channel_formatter ("Channel aftertouch: ", ch) + elif (ev[0] in [0xF0, 0xF7]): + f = meta_formatter ("System-exclusive event: ") + + if f: + if len (ev) > 2: + print padding + f.format (ev[1], ev[2]) + elif len (ev) > 1: + print padding + f.format (ev[1]) + else: + print padding + f.format () + else: + print padding + "Unrecognized MIDI event: " + str (ev); + +def dump_midi (data, midi_file, options): + if not options.pretty: + print data + return + # First, dump general info, #tracks, etc. + print "Filename: " + midi_file; + i = data[0]; + m_formats = {0: 'single multi-channel track', + 1: "one or more simultaneous tracks", + 2: "one or more sequentially independent single-track patterns"} + print "MIDI format: " + str (i[0]) + " (" + m_formats.get (i[0], "") + ")"; + print "Divisions: " + str (i[1]) + " per whole note"; + print "#Tracks: " + str ( len (data[1])) + n = 0; + for tr in data[1]: + time = 0; + n += 1; + print + print "Track " + str(n) + ":" + print " Time 0:" + for ev in tr: + if ev[0]>time: + time = ev[0] + print " Time " + str(time) + ": " + dump_event (ev[1], time, " "); + + + def go (): options, args = process_options (sys.argv[1:]) midi_file = args[0] midi_data = read_midi (midi_file) info = track_info (midi_data) - if options.dump: - print midi_data + if (options.dump or options.pretty): + dump_midi (midi_data, midi_file, options); elif options.regexp: import re regexp = re.compile (options.regexp)