# HG changeset patch # User Daniel O'Connor # Date 1464003687 -34200 # Node ID c490fecec0efac47b44561310d8d07b9dc94fc12 # Parent 6874140c9c11fe5c82fc8a317f39dcd0018a748e Have another crack at fixing transposition. Now searches down and up 3 octaves. Refactor a bunch of code. diff -r 6874140c9c11 -r c490fecec0ef musiccutter.py --- a/musiccutter.py Tue May 17 22:30:54 2016 +0930 +++ b/musiccutter.py Mon May 23 21:11:27 2016 +0930 @@ -1,5 +1,6 @@ #!/usr/bin/env python +from IPython.core.debugger import Tracer import exceptions import itertools import math @@ -13,7 +14,14 @@ CUT_COLOUR = reportlab.lib.colors.red ENGRAVE_COLOUR = reportlab.lib.colors.black -def test(filename = None): +class Stats(object): + pass + +class EVWrap(object): + def __init__(self, ev): + self.ev = ev + +def test(filename = None, shift = 0): if filename == None: filename = 'test.midi' # Card layout from http://www.orgues-de-barbarie.com/wp-content/uploads/2014/09/format-cartons.pdf @@ -26,7 +34,7 @@ # | | | | # +---+---+---+ highest note # - m = Midi2PDF('notes', 120, 155, 5.5, 3.3, 6.0, 50, False, False, False, False, False, 0, 30, 0.9, 'Helvetica', 12) + m = Midi2PDF('notes', 120, 155, 5.5, 3.3, 6.0, 50, False, True, False, False, False, shift, 1, 30, 0.9, 'Helvetica', 12) base, ext = os.path.splitext(filename) base = os.path.basename(base) m.processMidi(filename, base + '-%02d.pdf') @@ -56,10 +64,11 @@ self.pdfwidth = self.pagewidth * self.pagesperpdf def processMidi(self, midifile, outpat): - playablecount = 0 - unplayablecount = 0 - transposeupcount = 0 - transposedowncount = 0 + stats = Stats() + stats.playablecount = 0 + stats.unplayablecount = 0 + stats.transposeupcount = 0 + stats.transposedowncount = 0 midi = mido.MidiFile(midifile) ctime = 0 channels = [] @@ -79,65 +88,44 @@ title = os.path.basename(midifile) title, ext = os.path.splitext(title) for ev in midi: - # Adjust pitch of note (if it's a note) - if hasattr(ev, 'note'): - ev.note += self.noteoffset - if ev.type == 'text' and ctime == 0: - title = ev.text + evw = EVWrap(ev) + # Find a slot (plus adjust pitch, attempt transposition etc) + if hasattr(evw.ev, 'note'): + self.getslotfornote(evw, stats, ctime) + # Check if it was unplayable + if not evw.slot: + continue + if evw.ev.type == 'text' and ctime == 0: + title = evw.ev.text - ctime += ev.time - if ev.type == 'note_on' or ev.type == 'note_off': - if ev.note not in self.midi2note: - print 'Input MIDI number %d out of range' % (ev.note) - unplayablecount += 1 - continue - else: - note = self.midi2note[ev.note] - #print ctime, ev - if ev.type == 'note_on' and ev.velocity > 0: - if ev.note in channels[ev.channel]: - print 'Duplicate note_on message %d (%s)' % (ev.note, note) + ctime += evw.ev.time + #print ctime, evw.ev + if evw.ev.type == 'note_on' and evw.ev.velocity > 0: + if evw.ev.note in channels[evw.ev.channel]: + print 'Duplicate note_on message %d (%s)' % (evw.ev.note, evw.notename) else: - channels[ev.channel][ev.note] = ctime - elif ev.type == 'note_off' or (ev.type == 'note_on' and ev.velocity == 0): - if ev.note not in channels[ev.channel]: - print 'note_off with no corresponding note_on for channel %d note %d' % (ev.channel, ev.note) - else: - start = channels[ev.channel][ev.note] - notelen = ctime - start - playable = True + channels[evw.ev.channel][evw.ev.note] = ctime + elif evw.ev.type == 'note_off' or (evw.ev.type == 'note_on' and evw.ev.velocity == 0): + if evw.ev.note not in channels[evw.ev.channel]: + print 'note_off with no corresponding note_on for channel %d note %d' % (evw.ev.channel, evw.ev.note) + else: + start = channels[evw.ev.channel][evw.ev.note] + notelen = ctime - start + #print 'Note %s (%d) at %.2f length %.2f' % (evw.notename, evw.ev.slot, start, notelen) + self.emitnote(pdfs, evw.slot, start, notelen * self.notescale) - if note in self.note2slot: - slot = self.note2slot[note] - elif self.trytranspose and ((ev.note - 12) in self.midi2note and self.midi2note[ev.note - 12] in self.note2slot): - print 'Transposing note %d (%s) down' % (ev.note, note) - slot = self.note2slot[self.midi2note[ev.note - 12]] - transposedowncount += 1 - elif self.trytranspose and (ev.note + 12 in self.midi2note and self.midi2note[ev.note + 12] in self.note2slot): - print 'Transposing note %d (%s) up' % (ev.note, note) - slot = self.note2slot[self.midi2note[ev.note + 12]] - transposeupcount += 1 - else: - unplayablecount += 1 - playable = False - - if playable: - #print 'Note %s (%d) at %.2f length %.2f' % (note, slot, start, notelen) - self.emitnote(pdfs, slot, start, notelen * self.notescale) - playablecount += 1 - - del channels[ev.channel][ev.note] - elif ev.type == 'end_of_track': + del channels[evw.ev.channel][evw.ev.note] + elif evw.ev.type == 'end_of_track': print 'EOT, not flushing, check for missed notes' for chan in channels: - for ev in chan: - print ev + for evw.ev in chan: + print evw.ev - print 'Playable count:', playablecount - print 'Unplayable count:', unplayablecount + print 'Playable count:', stats.playablecount + print 'Unplayable count:', stats.unplayablecount if self.trytranspose: - print 'Transpose down:', transposedowncount - print 'Transpose up:', transposeupcount + print 'Transpose down:', stats.transposedowncount + print 'Transpose up:', stats.transposeupcount for pindx in range(npages): pdf = pdfs[pindx / self.pagesperpdf] # PDF for this page @@ -182,6 +170,57 @@ for pdf in pdfs: pdf.save() + def noteisplayable(self, midi): + slot = None + if midi in self.midi2note: + notename = self.midi2note[midi] + if notename in self.note2slot: + slot = self.note2slot[notename] + + return slot + + def transposenote(self, evw, amount): + evw.ev.note += amount + evw.notename = self.midi2note[evw.ev.note] + evw.slot = self.note2slot[evw.notename] + + def getslotfornote(self, evw, stats, ctime): + evw.slot = None + evw.notename = None + + evw.ev.note += self.noteoffset + + # First off, is the note in our midi table? + if evw.ev.note in self.midi2note: + evw.notename = self.midi2note[evw.ev.note] + # Is it playable? + if self.noteisplayable(evw.ev.note) != None: + evw.slot = self.note2slot[evw.notename] + # Nope, maybe we can transpose? + elif self.trytranspose: + # Go for -3 to +3 octaves (going down first) + for i in [-12, -24, -36, 12, 24, 36]: + if self.noteisplayable(evw.ev.note + i) != None: + self.transposenote(evw, i) + if i < 0: + stats.transposedowncount += 1 + tmp = 'down' + else: + stats.transposeupcount += 1 + tmp = 'up' + print 'Transposed note at %.1f sec %s (%d) %s %d octave(s) to %s (%d)' % ( + ctime, self.midi2note[evw.ev.note - i], evw.ev.note - i, tmp, + abs(i / 12), evw.notename, evw.ev.note) + break + if evw.slot != None: + stats.playablecount += 1 + else: + print 'Note at %.1f sec %d (%s) not playable' % (ctime, evw.ev.note, self.midi2note[evw.ev.note]) + stats.unplayablecount += 1 + else: + print 'Note at %.1f sec %d not in MIDI table' % (ctime, evw.ev.note) + stats.unplayablecount += 1 + # http://newt.phys.unsw.edu.au/jw/notes.html @staticmethod def genmidi2note(): @@ -224,7 +263,7 @@ y = self.pageheight - (self.heel + slot * self.pitch - self.slotsize / 2) - self.slotsize w = notelen * self.timescale # Convert note length in time to distance - print 'pdf = %d x = %.3f y = %.3f w = %.3f h = %.3f' % (pdfidx, x, y, w, h) + #print 'pdf = %d x = %.3f y = %.3f w = %.3f h = %.3f' % (pdfidx, x, y, w, h) w1 = w # Check if the note crosses a pdf if x + w > self.pdfwidth: @@ -232,7 +271,7 @@ w2 = w - w1 # Calculate length of second note assert w2 <= self.pdfwidth, 'note extends for more than a pdf' # Emit second half of note - print 'split note, pdf %d w2 = %.3f' % (pdfidx + 1, w2) + #print 'split note, pdf %d w2 = %.3f' % (pdfidx + 1, w2) Midi2PDF._emitnote(pdfs[pdfidx + 1], self.pdfwidth - w2, y, w2, h) Midi2PDF._emitnote(pdfs[pdfidx], self.pdfwidth - x - w1, y, w1, h)