- s = ''
- notes = []
- for i in ch:
- if i.__class__ == Note:
- notes.append (i)
- else:
- s = s + i.dump ()
- if len (notes) == 1:
- s = s + dump (notes[0])
- elif len (notes) > 1:
- s = s + '<' + string.join (map (dump, notes)) + '>'
- return s
-
-# thread?
-def dump_channel (thread):
- global key
-
- key = Key (0, 0, 0)
- last_e = None
- chs = []
- ch = []
-
- for e in thread:
- if last_e and last_e[0] == e[0]:
- ch.append (e[1])
- else:
- if ch:
- chs.append ((last_e[0], ch))
-
- ch = [e[1]]
-
- last_e = e
-
- if ch:
- chs.append ((last_e[0], ch))
- t = 0
- last_t = 0
-
- lines = ['']
- for ch in chs:
- i = string.rfind (lines[-1], '\n')
- if len (lines[-1][i:]) > LINE_BELL:
- lines.append ('')
-
- t = ch[0]
- if t - last_t:
- lines[-1] = lines[-1] + dump_skip (t-last_t)
-
- lines[-1] = lines[-1] + dump_chord (ch[1])
-
- clocks = 0
- for i in ch[1]:
- if i.clocks > clocks:
- clocks = i.clocks
-
- last_t = t + clocks
-
- return string.join (lines, '\n ') + '\n'
-
-def track_name (i):
- return 'track%c' % (i + ord ('A'))
-
-def channel_name (i):
- return 'channel%c' % (i + ord ('A'))
-
-def dump_track (channels, n):
- s = '\n'
- track = track_name (n)
- for i in range (len (channels)):
- channel = channel_name (i)
- s = s + '%s = \\notes {\n' % (track + channel)
- s = s + ' ' + dump_channel (channels[i][0])
- s = s + '}\n\n'
-
- s = s + '%s = <\n' % track
-
- for i in range (len (channels)):
- channel = channel_name (i)
- s = s + ' \\context Voice = %s \\%s\n' % (channel, track + channel)
- s = s + '>\n\n'
- return s
-
-def convert_midi (f, o):
- global clocks_per_1
-
- str = open (f).read ()
- midi_dump = midi.parse (str)
-
- clocks_per_1 = midi_dump[0][1]
-
- global start_quant, start_quant_clocks
- if start_quant:
- start_quant_clocks = clocks_per_1 / start_quant
-
- global duration_quant, duration_quant_clocks
- if duration_quant:
- duration_quant_clocks = clocks_per_1 / duration_quant
-
- global allowed_tuplet_clocks
- allowed_tuplet_clocks = []
- for (dur, num, den) in allowed_tuplets:
- allowed_tuplet_clocks.append (clocks_per_1 * num / (dur * den))
-
- tracks = []
- for t in midi_dump[1]:
- tracks.append (split_track (t))
-
- tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
-
- s = ''
- s = tag + '\n\n'
- for i in range (len (tracks)):
- s = s + dump_track (tracks[i], i)
-
- s = s + '\n\\score {\n <\n'
- for i in range (len (tracks)):
- track = track_name (i)
- s = s + ' \\context Staff=%s \\%s\n' % (track, track)
- s = s + ' >\n}\n'
-
- progress (_ ("%s output to `%s'...") % ('LY', o))
-
- if o == '-':
- h = sys.stdout
- else:
- h = open (o, 'w')
-
- h.write (s)
- h.close ()
-
-
-(sh, long) = getopt_args (__main__.option_definitions)
-try:
- (options, files) = getopt.getopt(sys.argv[1:], sh, long)
-except getopt.error, s:
- errorport.write ('\n')
- errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
- errorport.write ('\n')
- errorport.write ('\n')
- help ()
- sys.exit (2)
-
-for opt in options:
- o = opt[0]
- a = opt[1]
-
- if 0:
- pass
- elif o == '--help' or o == '-h':
- help ()
- errorport.write ('\n')
- errorport.write (_ ("Example:"))
- errorport.write (r'''
- midi2ly --key=-2:1 --duration-quant=32 \
- --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
-''')
- sys.exit (0)
- elif o == '--output' or o == '-o':
- output_name = a
- elif o == '--verbose' or o == '-V':
- verbose_p = 1
- elif o == '--version' or o == '-v':
- identify ()
- sys.exit (0)
- elif o == '--warranty' or o == '-w':
- status = system ('lilypond -w', ignore_error = 1)
- if status:
- warranty ()
- sys.exit (0)
- elif o == '--key' or o == '-k':
- (accidentals, minor) = map (string.atoi, string.split (a + ':0', ':'))[0:2]
- sharps = 0
- flats = 0
- if accidentals >= 0:
- sharps = accidentals
- else:
- flats = - accidentals
- global key
- key = Key (sharps, flats, minor)
- elif o == '--duration-quant' or o == '-d':
- global duration_quant
- duration_quant = string.atoi (a)
- elif o == '--start-quant' or o == '-s':
- global start_quant, start_quant_clocks
- start_quant = string.atoi (a)
- elif o == '--allow-tuplet' or o == '-t':
- global allowed_tuplets
- a = string.replace (a, '/', '*')
- tuplet = map (string.atoi, string.split (a, '*'))
- allowed_tuplets.append (tuplet)
-
-
-if not files or files[0] == '-':
-
- # FIXME: read from stdin when files[0] = '-'
- help ()
- errorport.write (program_name + ":" + _ ("error: ") + _ ("no files specified on command line.") + '\n')
- sys.exit (2)
-
-
-for f in files:
-
- g = f
- g = strip_extension (g, '.midi')
- g = strip_extension (g, '.mid')
- g = strip_extension (g, '.MID')
- (outdir, outbase) = ('','')
-
- if not output_name:
- outdir = '.'
- outbase = os.path.basename (g)
- o = os.path.join (outdir, outbase + '-midi.ly')
- elif output_name[-1] == os.sep:
- outdir = output_name
- outbase = os.path.basename (g)
- os.path.join (outdir, outbase + '-gen.ly')
- else:
- o = output_name
- (outdir, outbase) = os.path.split (o)
-
- if outdir != '.':
- mkdir_p (outdir, 0777)
-
- convert_midi (f, o)
+ s = ''
+ notes = []
+ for i in ch:
+ if i.__class__ == Note:
+ notes.append (i)
+ else:
+ s = s + i.dump ()
+ if len (notes) == 1:
+ s = s + dump (notes[0])
+ elif len (notes) > 1:
+ global reference_note
+ 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 () + ' '
+ reference_note = r
+ return s
+
+def dump_bar_line (last_bar_t, t, bar_count):
+ s = ''
+ bar_t = time.bar_clocks ()
+ if t - last_bar_t >= bar_t:
+ bar_count = bar_count + (t - last_bar_t) / bar_t
+
+ if t - last_bar_t == bar_t:
+ s = '\n | %% %(bar_count)d\n ' % locals ()
+ last_bar_t = t
+ else:
+ # urg, this will barf at meter changes
+ last_bar_t = last_bar_t + (t - last_bar_t) / bar_t * bar_t
+
+ return (s, last_bar_t, bar_count)
+
+
+def dump_voice (thread, skip):
+ global reference_note, time
+ ref = Note (0, 4*12, 0)
+ if not reference_note:
+ reference_note = ref
+ else:
+ ref.duration = reference_note.duration
+ reference_note = ref
+ last_e = None
+ chs = []
+ ch = []
+
+ for e in thread:
+ if last_e and last_e[0] == e[0]:
+ ch.append (e[1])
+ else:
+ if ch:
+ chs.append ((last_e[0], ch))
+
+ ch = [e[1]]
+
+ last_e = e
+
+ if ch:
+ chs.append ((last_e[0], ch))
+ t = 0
+ last_t = 0
+ last_bar_t = 0
+ bar_count = 1
+
+ lines = ['']
+ for ch in chs:
+ t = ch[0]
+
+ i = lines[-1].rfind ('\n') + 1
+ if len (lines[-1][i:]) > LINE_BELL:
+ lines.append ('')
+
+ if t - last_t > 0:
+ d = t - last_t
+ if bar_max and t > time.bar_clocks () * bar_max:
+ d = time.bar_clocks () * bar_max - last_t
+ lines[-1] = lines[-1] + dump_skip (skip, d)
+ elif t - last_t < 0:
+ errorport.write ('BUG: time skew')
+
+ (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
+ t, bar_count)
+
+ if bar_max and bar_count > bar_max:
+ break
+
+ lines[-1] = lines[-1] + s
+ lines[-1] = lines[-1] + dump_chord (ch[1])
+
+ clocks = 0
+ for i in ch[1]:
+ if i.clocks > clocks:
+ clocks = i.clocks
+
+ last_t = t + clocks
+
+ (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
+ last_t, bar_count)
+ lines[-1] = lines[-1] + s
+
+ return '\n '.join (lines) + '\n'
+
+def number2ascii (i):
+ s = ''
+ i += 1
+ while i > 0:
+ m = (i - 1) % 26
+ s = '%c' % (m + ord ('A')) + s
+ i = (i - m)/26
+ return s
+
+def get_track_name (i):
+ return 'track' + number2ascii (i)
+
+def get_channel_name (i):
+ return 'channel' + number2ascii (i)
+
+def get_voice_name (i, zero_too_p=False):
+ if i or zero_too_p:
+ return 'voice' + number2ascii (i)
+ return ''
+
+def lst_append (lst, x):
+ lst.append (x)
+ return lst
+
+def get_voice_layout (average_pitch):
+ d = {}
+ for i in range (len (average_pitch)):
+ d[average_pitch[i]] = lst_append (d.get (average_pitch[i], []), i)
+ s = list (reversed (sorted (average_pitch)))
+ non_empty = len (filter (lambda x: x, s))
+ names = ['One', 'Two']
+ if non_empty > 2:
+ names = ['One', 'Three', 'Four', 'Two']
+ layout = map (lambda x: '', range (len (average_pitch)))
+ for i, n in zip (s, names):
+ if i:
+ v = d[i]
+ if type (v) == list:
+ d[i] = v[1:]
+ v = v[0]
+ layout[v] = n
+ return layout
+
+def dump_track (track, n):
+ s = '\n'
+ track_name = get_track_name (n)
+
+ average_pitch = track_average_pitch (track)
+ voices = len (filter (lambda x: x, average_pitch[1:]))
+ clef = get_best_clef (average_pitch[0])
+
+ c = 0
+ vv = 0
+ for channel in track:
+ v = 0
+ channel_name = get_channel_name (c)
+ c += 1
+ for voice in channel:
+ voice_name = get_voice_name (v)
+ voice_id = track_name + channel_name + voice_name
+ item = voice_first_item (voice)
+
+ if item and item.__class__ == Note:
+ skip = 'r'
+ if global_options.skip:
+ skip = 's'
+ s += '%(voice_id)s = ' % locals ()
+ if not global_options.absolute_pitches:
+ s += '\\relative c '
+ elif item and item.__class__ == Text:
+ skip = '" "'
+ s += '%(voice_id)s = \\lyricmode ' % locals ()
+ else:
+ skip = '\\skip '
+ s += '%(voice_id)s = ' % locals ()
+ s += '{\n'
+ if not n and not vv and global_options.key:
+ s += global_options.key.dump ()
+ if average_pitch[vv+1] and voices > 1:
+ 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
+ vv += 1
+
+ s += '%(track_name)s = <<\n' % locals ()
+
+ if clef.type != 2:
+ s += clef.dump () + '\n'
+
+ c = 0
+ vv = 0
+ for channel in track:
+ v = 0
+ channel_name = get_channel_name (c)
+ c += 1
+ for voice in channel:
+ voice_context_name = get_voice_name (vv, zero_too_p=True)
+ voice_name = get_voice_name (v)
+ v += 1
+ vv += 1
+ voice_id = track_name + channel_name + voice_name
+ item = voice_first_item (voice)
+ context = 'Voice'
+ if item and item.__class__ == Text:
+ context = 'Lyrics'
+ s += ' \\context %(context)s = %(voice_context_name)s \\%(voice_id)s\n' % locals ()
+ s += '>>\n\n'
+ return s
+
+def voice_first_item (voice):
+ for event in voice:
+ if (event[1].__class__ == Note
+ or (event[1].__class__ == Text
+ and event[1].type == midi.LYRIC)):
+ return event[1]
+ return None
+
+def channel_first_item (channel):
+ for voice in channel:
+ first = voice_first_item (voice)
+ if first:
+ return first
+ return None
+
+def track_first_item (track):
+ for channel in track:
+ first = channel_first_item (channel)
+ if first:
+ return first
+ return None
+
+def track_average_pitch (track):
+ i = 0
+ p = [0]
+ v = 1
+ for channel in track:
+ for voice in channel:
+ c = 0
+ p.append (0)
+ for event in voice:
+ if event[1].__class__ == Note:
+ i += 1
+ c += 1
+ p[v] += event[1].pitch
+ if c:
+ p[0] += p[v]
+ p[v] = p[v] / c
+ v += 1
+ if i:
+ p[0] = p[0] / i
+ return p
+
+def get_best_clef (average_pitch):
+ if average_pitch:
+ if average_pitch <= 3*12:
+ return Clef (0)
+ elif average_pitch <= 5*12:
+ return Clef (1)
+ elif average_pitch >= 7*12:
+ return Clef (3)
+ return Clef (2)
+
+class Staff:
+ def __init__ (self, track):
+ self.voices = track.get_voices ()
+ def dump (self, i):
+ return dump_track (self.voices, i)
+
+def convert_midi (in_file, out_file):
+ global clocks_per_1, clocks_per_4, key
+ global start_quant_clocks
+ global duration_quant_clocks
+ global allowed_tuplet_clocks
+ global time
+
+ str = open (in_file, 'rb').read ()
+ clocks_max = bar_max * clocks_per_1 * 2
+ midi_dump = midi.parse (str, clocks_max)
+
+ clocks_per_1 = midi_dump[0][1]
+ clocks_per_4 = clocks_per_1 / 4
+ time = Time (4, 4)
+
+ if global_options.start_quant:
+ start_quant_clocks = clocks_per_1 / global_options.start_quant
+
+ if global_options.duration_quant:
+ duration_quant_clocks = clocks_per_1 / global_options.duration_quant
+
+ allowed_tuplet_clocks = []
+ for (dur, num, den) in global_options.allowed_tuplets:
+ allowed_tuplet_clocks.append (clocks_per_1 / dur * num / den)
+
+ if global_options.verbose:
+ print 'allowed tuplet clocks:', allowed_tuplet_clocks
+
+ 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.14.0"
+'''
+
+ s += r'''
+\layout {
+ \context {
+ \Voice
+ \remove "Note_heads_engraver"
+ \consists "Completion_heads_engraver"
+ \remove "Rest_engraver"
+ \consists "Completion_rest_engraver"
+ }
+}
+'''