diff musiccutter.py @ 37:c490fecec0ef

Have another crack at fixing transposition. Now searches down and up 3 octaves. Refactor a bunch of code.
author Daniel O'Connor <darius@dons.net.au>
date Mon, 23 May 2016 21:11:27 +0930
parents 6874140c9c11
children 9e8ed92b477c
line wrap: on
line diff
--- 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)