# This file is part of LilyPond, the GNU music typesetter.
#
-# Copyright (C) 1998--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
+# Copyright (C) 1998--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
# Jan Nieuwenhuizen <janneke@gnu.org>
#
# LilyPond is free software: you can redistribute it and/or modify
@relocate-preamble@
"""
-import midi
import lilylib as ly
global _;_=ly._
%s
%s
-''' % ( _ ('Copyright (c) %s by') % '1998--2011',
+''' % ( _ ('Copyright (c) %s by') % '1998--2015',
'\n '.join (authors),
_ ('Distributed under terms of the GNU General Public License.'),
_ ('It comes with NO WARRANTY.')))
allowed_durs = (1, 2, 4, 8, 16, 32, 64, 128)
def __init__ (self, clocks):
self.clocks = clocks
- if clocks <= 0:
- self.clocks = duration_quant_clocks
(self.dur, self.num, self.den) = self.dur_num_den (clocks)
def dur_num_den (self, clocks):
s = '%d*%d/%d' % (self.dur, self.num, self.den)
global reference_note
- if reference_note: # debugging
- reference_note.duration = self
+ reference_note.duration = self
return s
elif commas < 0:
s = s + "," * -commas
- if ((dump_dur
- and self.duration.compare (reference_note.duration))
- or global_options.explicit_durations):
+ if (dump_dur
+ and (self.duration.compare (reference_note.duration)
+ or global_options.explicit_durations)):
s = s + self.duration.dump ()
+ # Chords need to handle their reference duration themselves
+
reference_note = self
# TODO: move space
'INSTRUMENT_NAME',
'LYRIC',
'MARKER',
- 'CUE_POINT',)
+ 'CUE_POINT',
+ 'PROGRAM_NAME',
+ 'DEVICE_NAME', )
+
+ @staticmethod
+ def _text_only(chr):
+ if ((' ' <= chr <= '~') or chr in ['\n','\r']):
+ return chr
+ else:
+ return '~'
def __init__ (self, type, text):
self.clocks = 0
self.type = type
- self.text = text
+ self.text =''.join(map(self._text_only, text))
def dump (self):
# urg, we should be sure that we're in a lyrics staff
s = s + ' '
elif (self.text.strip ()
and self.type == midi.SEQUENCE_TRACK_NAME
- and not self.text == 'control track'):
+ and not self.text == 'control track'
+ and not self.track.lyrics_p_):
text = self.text.replace ('(MIDI)', '').strip ()
if text:
s = '\n \\set Staff.instrumentName = "%(text)s"\n ' % locals ()
def __repr__ (self):
return 'Text(%d=%s)' % (self.type, self.text)
-def get_voice (channel, events):
+def get_voice (channel, music):
debug ('channel: ' + str (channel) + '\n')
- music = parse_events (events)
return unthread_notes (music)
class Channel:
def __init__ (self, number):
self.number = number
self.events = []
+ self.music = None
def add (self, event):
self.events.append (event)
- return self
def get_voice (self):
- return get_voice (self.number, self.events)
+ if not self.music:
+ self.music = self.parse ()
+ return get_voice (self.number, self.music)
+ def parse (self):
+ pitches = {}
+ notes = []
+ music = []
+ last_lyric = 0
+ last_time = 0
+ for e in self.events:
+ t = e[0]
+
+ if start_quant_clocks:
+ t = quantise_clocks (t, start_quant_clocks)
+
+ if (e[1][0] == midi.NOTE_OFF
+ or (e[1][0] == midi.NOTE_ON and e[1][2] == 0)):
+ debug ('%d: NOTE OFF: %s' % (t, e[1][1]))
+ if not e[1][2]:
+ debug (' ...treated as OFF')
+ end_note (pitches, notes, t, e[1][1])
+
+ elif e[1][0] == midi.NOTE_ON:
+ if not pitches.has_key (e[1][1]):
+ debug ('%d: NOTE ON: %s' % (t, e[1][1]))
+ pitches[e[1][1]] = (t, e[1][2])
+ else:
+ debug ('...ignored')
+
+ # all include ALL_NOTES_OFF
+ elif (e[1][0] >= midi.ALL_SOUND_OFF
+ and e[1][0] <= midi.POLY_MODE_ON):
+ for i in pitches:
+ end_note (pitches, notes, t, i)
+
+ elif e[1][0] == midi.META_EVENT:
+ if e[1][1] == midi.END_OF_TRACK:
+ for i in pitches:
+ end_note (pitches, notes, t, i)
+ break
+
+ elif e[1][1] == midi.SET_TEMPO:
+ (u0, u1, u2) = map (ord, e[1][2])
+ us_per_4 = u2 + 256 * (u1 + 256 * u0)
+ seconds_per_1 = us_per_4 * 4 / 1e6
+ music.append ((t, Tempo (seconds_per_1)))
+ elif e[1][1] == midi.TIME_SIGNATURE:
+ (num, dur, clocks4, count32) = map (ord, e[1][2])
+ den = 2 ** dur
+ music.append ((t, Time (num, den)))
+ elif e[1][1] == midi.KEY_SIGNATURE:
+ (alterations, minor) = map (ord, e[1][2])
+ sharps = 0
+ flats = 0
+ if alterations < 127:
+ sharps = alterations
+ else:
+ flats = 256 - alterations
+
+ k = Key (sharps, flats, minor)
+ if not t and global_options.key:
+ # At t == 0, a set --key overrides us
+ k = global_options.key
+ music.append ((t, k))
+
+ # ugh, must set key while parsing
+ # because Note init uses key
+ # Better do Note.calc () at dump time?
+ global_options.key = k
+
+ elif (e[1][1] == midi.LYRIC
+ or (global_options.text_lyrics
+ and e[1][1] == midi.TEXT_EVENT)):
+ self.lyrics_p_ = True
+ if last_lyric:
+ last_lyric.clocks = t - last_time
+ music.append ((last_time, last_lyric))
+ last_time = t
+ last_lyric = Text (midi.LYRIC, e[1][2])
+
+ elif (e[1][1] >= midi.SEQUENCE_NUMBER
+ and e[1][1] <= midi.CUE_POINT):
+ text = Text (e[1][1], e[1][2])
+ text.track = self
+ music.append ((t, text))
+ if (text.type == midi.SEQUENCE_TRACK_NAME):
+ self.name = text.text
+ else:
+ if global_options.verbose:
+ sys.stderr.write ("SKIP: %s\n" % `e`)
+ else:
+ if global_options.verbose:
+ sys.stderr.write ("SKIP: %s\n" % `e`)
+
+ if last_lyric:
+ # last_lyric.clocks = t - last_time
+ # hmm
+ last_lyric.clocks = clocks_per_4
+ music.append ((last_time, last_lyric))
+ last_lyric = 0
+
+ i = 0
+ while len (notes):
+ if i < len (music) and notes[0][0] >= music[i][0]:
+ i = i + 1
+ else:
+ music.insert (i, notes[0])
+ del notes[0]
+ return music
-class Track:
+class Track (Channel):
def __init__ (self):
+ Channel.__init__ (self, None)
self.name = None
- self.events = []
self.channels = {}
+ self.lyrics_p_ = False
def _add (self, event):
- if isinstance (event, Text) and event.type == midi.SEQUENCE_TRACK_NAME:
- self.name = event.text
self.events.append (event)
def add (self, event, channel=None):
if channel == None:
self._add (event)
else:
-# self.channels[channel] = self.channels.get (channel, Channel (channel)).add (event)
self.channels[channel] = self.channels.get (channel, Channel (channel))
self.channels[channel].add (event)
- def get_voice (self):
- return get_voice (None, self.events)
def get_voices (self):
return ([self.get_voice ()]
+ [self.channels[k].get_voice ()
for k in sorted (self.channels.keys ())])
-def parse_track (events):
+def create_track (events):
track = Track ()
for e in events:
data = list (e[1])
except KeyError:
pass
-def parse_events (channel):
- pitches = {}
-
- notes = []
- events = []
- last_lyric = 0
- last_time = 0
- for e in channel:
- t = e[0]
-
- if start_quant_clocks:
- t = quantise_clocks (t, start_quant_clocks)
-
- if (e[1][0] == midi.NOTE_OFF
- or (e[1][0] == midi.NOTE_ON and e[1][2] == 0)):
- debug ('%d: NOTE OFF: %s' % (t, e[1][1]))
- if not e[1][2]:
- debug (' ...treated as OFF')
- end_note (pitches, notes, t, e[1][1])
-
- elif e[1][0] == midi.NOTE_ON:
- if not pitches.has_key (e[1][1]):
- debug ('%d: NOTE ON: %s' % (t, e[1][1]))
- pitches[e[1][1]] = (t, e[1][2])
- else:
- debug ('...ignored')
-
- # all include ALL_NOTES_OFF
- elif (e[1][0] >= midi.ALL_SOUND_OFF
- and e[1][0] <= midi.POLY_MODE_ON):
- for i in pitches:
- end_note (pitches, notes, t, i)
-
- elif e[1][0] == midi.META_EVENT:
- if e[1][1] == midi.END_OF_TRACK:
- for i in pitches:
- end_note (pitches, notes, t, i)
- break
-
- elif e[1][1] == midi.SET_TEMPO:
- (u0, u1, u2) = map (ord, e[1][2])
- us_per_4 = u2 + 256 * (u1 + 256 * u0)
- seconds_per_1 = us_per_4 * 4 / 1e6
- events.append ((t, Tempo (seconds_per_1)))
- elif e[1][1] == midi.TIME_SIGNATURE:
- (num, dur, clocks4, count32) = map (ord, e[1][2])
- den = 2 ** dur
- events.append ((t, Time (num, den)))
- elif e[1][1] == midi.KEY_SIGNATURE:
- (alterations, minor) = map (ord, e[1][2])
- sharps = 0
- flats = 0
- if alterations < 127:
- sharps = alterations
- else:
- flats = 256 - alterations
-
- k = Key (sharps, flats, minor)
- if not t and global_options.key:
- # At t == 0, a set --key overrides us
- k = global_options.key
- events.append ((t, k))
-
- # ugh, must set key while parsing
- # because Note init uses key
- # Better do Note.calc () at dump time?
- global_options.key = k
-
- elif (e[1][1] == midi.LYRIC
- or (global_options.text_lyrics and e[1][1] == midi.TEXT_EVENT)):
- if last_lyric:
- last_lyric.clocks = t - last_time
- events.append ((last_time, last_lyric))
- last_time = t
- last_lyric = Text (midi.LYRIC, e[1][2])
-
- elif (e[1][1] >= midi.SEQUENCE_NUMBER
- and e[1][1] <= midi.CUE_POINT):
- events.append ((t, Text (e[1][1], e[1][2])))
- else:
- if global_options.verbose:
- sys.stderr.write ("SKIP: %s\n" % `e`)
- pass
- else:
- if global_options.verbose:
- sys.stderr.write ("SKIP: %s\n" % `e`)
- pass
-
- if last_lyric:
- # last_lyric.clocks = t - last_time
- # hmm
- last_lyric.clocks = clocks_per_4
- events.append ((last_time, last_lyric))
- last_lyric = 0
-
- i = 0
- while len (notes):
- if i < len (events) and notes[0][0] >= events[i][0]:
- i = i + 1
- else:
- events.insert (i, notes[0])
- del notes[0]
- return events
-
def unthread_notes (channel):
threads = []
while channel:
s = s + dump (notes[0])
elif len (notes) > 1:
global reference_note
+ reference_dur = reference_note.duration
s = s + '<'
s = s + notes[0].dump (dump_dur=False)
r = reference_note
for i in notes[1:]:
s = s + i.dump (dump_dur=False)
s = s + '>'
-
- s = s + notes[0].duration.dump () + ' '
+ if (r.duration.compare (reference_dur)
+ or global_options.explicit_durations):
+ s = s + r.duration.dump ()
+ s = s + ' '
reference_note = r
return s
if not n and not vv and global_options.key:
s += global_options.key.dump ()
if average_pitch[vv+1] and voices > 1:
- s += ' \\voice' + get_voice_layout (average_pitch[1:])[vv] + '\n'
+ vl = get_voice_layout (average_pitch[1:])[vv]
+ if vl:
+ s += ' \\voice' + vl + '\n'
+ else:
+ if not global_options.quiet:
+ warning (_ ('found more than 5 voices on a staff, expect bad output'))
s += ' ' + dump_voice (voice, skip)
s += '}\n\n'
v += 1
return dump_track (self.voices, i)
def convert_midi (in_file, out_file):
+ global midi
+ import midi
+
global clocks_per_1, clocks_per_4, key
global start_quant_clocks
global duration_quant_clocks
if global_options.verbose:
print 'allowed tuplet clocks:', allowed_tuplet_clocks
- staves = [Staff (parse_track (t)) for t in midi_dump[1]]
+ tracks = [create_track (t) for t in midi_dump[1]]
+ # urg, parse all global track events, such as Key first
+ # this fixes key in different voice/staff problem
+ for t in tracks:
+ t.music = t.parse ()
+ prev = None
+ staves = []
+ for t in tracks:
+ voices = t.get_voices ()
+ if ((t.name and prev and prev.name)
+ and t.name.split (':')[0] == prev.name.split (':')[0]):
+ # staves[-1].voices += voices
+ # all global track events first
+ staves[-1].voices = ([staves[-1].voices[0]]
+ + [voices[0]]
+ + staves[-1].voices[1:]
+ + voices[1:])
+ else:
+ staves.append (Staff (t))
+ prev = t
tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, in_file)
s = tag
s += r'''
-\version "2.13.53"
+\version "2.14.0"
'''
s += r'''
s += '\n\\score {\n <<\n'
+ control_track = False
i = 0
for i, staff in enumerate (staves):
track_name = get_track_name (i)
staff_name = track_name
context = None
if not i and not item and len (staves) > 1:
- # control track
- staff_name = get_track_name (1)
- context = 'Staff'
+ control_track = track_name
+ continue
elif (item and item.__class__ == Note):
context = 'Staff'
+ if control_track:
+ s += ' \\context %(context)s=%(staff_name)s \\%(control_track)s\n' % locals ()
elif item and item.__class__ == Text:
context = 'Lyrics'
if context:
}
'''
- progress (_ ("%s output to `%s'...") % ('LY', out_file))
+ if not global_options.quiet:
+ progress (_ ("%s output to `%s'...") % ('LY', out_file))
if out_file == '-':
handle = sys.stdout
metavar=_ ('DUR'),
help=_ ('quantise note durations on DUR'))
p.add_option ('-D', '--debug',
- action='store_true',
- help=_ ('debug printing'))
+ action='store_true',
+ help=_ ('debug printing'))
p.add_option ('-e', '--explicit-durations',
action='store_true',
help=_ ('print explicit durations'))
p.add_option('-h', '--help',
- action='help',
- help=_ ('show this help and exit'))
+ action='help',
+ help=_ ('show this help and exit'))
p.add_option('-i', '--include-header',
- help=_ ('prepend FILE to output'),
- action='append',
- default=[],
- metavar=_ ('FILE'))
+ help=_ ('prepend FILE to output'),
+ action='append',
+ default=[],
+ metavar=_ ('FILE'))
p.add_option('-k', '--key', help=_ ('set key: ALT=+sharps|-flats; MINOR=1'),
- metavar=_ ('ALT[:MINOR]'),
- default=None),
+ metavar=_ ('ALT[:MINOR]'),
+ default=None),
p.add_option ('-o', '--output', help=_ ('write output to FILE'),
metavar=_ ('FILE'),
action='store')
p.add_option ('-p', '--preview', help=_ ('preview of first 4 bars'),
action='store_true')
+ p.add_option ('-q', '--quiet',
+ action="store_true",
+ help=_ ("suppress progress messages and warnings about excess voices"))
p.add_option ('-s', '--start-quant',help= _ ('quantise note starts on DUR'),
metavar=_ ('DUR'))
p.add_option ('-S', '--skip',
help=_ ('allow tuplet durations DUR*NUM/DEN'),
default=[])
p.add_option ('-V', '--verbose', help=_ ('be verbose'),
- action='store_true'
- ),
+ action='store_true')
p.version = 'midi2ly (LilyPond) @TOPLEVEL_VERSION@'
p.add_option ('--version',
action='version',
help=_ ('show version number and exit'))
p.add_option ('-w', '--warranty', help=_ ('show warranty and copyright'),
- action='store_true',
- ),
+ action='store_true',)
p.add_option ('-x', '--text-lyrics', help=_ ('treat every text as a lyric'),
action='store_true')