# HG changeset patch # User Daniel O'Connor # Date 1460037944 -34200 # Node ID 9faad813e39e6003d04adacafe1af4f507f7521d # Parent b9727813e0291f1c6b3b86a1f2bfcf82dc5b307c switch to PDF to avoid dealing with CorelDraw crashing diff -r b9727813e029 -r 9faad813e39e musiccutter.py --- a/musiccutter.py Thu Apr 07 23:35:19 2016 +0930 +++ b/musiccutter.py Thu Apr 07 23:35:44 2016 +0930 @@ -4,25 +4,35 @@ import itertools import math import mido -import svgwrite +import os +import reportlab.lib.colors +from reportlab.lib.units import mm import sys +CUT_COLOUR = reportlab.lib.colors.red +ENGRAVE_COLOUR = reportlab.lib.colors.black + + +## XXX: PDF origin bottom left, SVG origin top left def test(filename = None): if filename == None: filename = 'test.midi' # Card layout from http://www.orgues-de-barbarie.com/wp-content/uploads/2014/09/format-cartons.pdf - m = Midi2SVG('notes', 20, 15.5, 0.55, 0.6, 1.0) - m.processMidi(filename, 'test%02d.svg') + m = Midi2PDF('notes', 200, 155, 5.5, 6.0, 10, 'Helvetica', 12) + base, ext = os.path.splitext(filename) + m.processMidi(filename, base + '-%02d.pdf') -class Midi2SVG(object): - def __init__(self, notefile, pagewidth, pageheight, pitch, offset, timescale): - self.midi2note, self.note2midi = Midi2SVG.genmidi2note() - self.note2slot = Midi2SVG.loadnote2slot(notefile, self.note2midi) - self.pagewidth = pagewidth +class Midi2PDF(object): + def __init__(self, notefile, pagewidth, pageheight, pitch, offset, timescale, fontname, fontsize): + self.midi2note, self.note2midi = Midi2PDF.genmidi2note() + self.note2slot = Midi2PDF.loadnote2slot(notefile, self.note2midi) + self.pagewidth = pagewidth # Dimensions are in millimetres self.pageheight = pageheight self.pitch = pitch self.offset = offset self.timescale = timescale + self.fontname = fontname + self.fontsize = fontsize def processMidi(self, midifile, outpat): playablecount = 0 @@ -33,18 +43,22 @@ for i in range(16): channels.append({}) - npages = int(math.ceil(midi.length / self.timescale / self.pagewidth)) + npages = int(math.ceil(midi.length * self.timescale / self.pagewidth)) print 'npages', npages - svgs = [] + pdfs = [] for i in range(npages): - svg = svgwrite.Drawing(outpat % i, profile = 'full', size = ('%.3fcm' % (self.pagewidth), '%.3fcm' % (self.pageheight))) - svgs.append(svg) + pdf = reportlab.pdfgen.canvas.Canvas(file(outpat % (i + 1), 'w'), pagesize = (self.pagewidth * mm, self.pageheight * mm)) + pdfs.append(pdf) + title = midifile for ev in midi: + if ev.type == 'text' and ctime == 0: + title = ev.text + ctime += ev.time if ev.type == 'note_on' or ev.type == 'note_off': note = self.midi2note[ev.note] - print ctime, ev + #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) @@ -62,7 +76,7 @@ notelen = ctime - start slot = self.note2slot[note] print 'Note %s (%d) at %.2f length %.2f' % (note, slot, start, notelen) - self.emitnote(svgs, slot, start, notelen) + self.emitnote(pdfs, slot, start, notelen) playablecount += 1 del channels[ev.channel][ev.note] elif ev.type == 'end_of_track': @@ -74,11 +88,16 @@ print 'Playable count:', playablecount print 'Unplayable count:', unplayablecount - for i in range(len(svgs)): - svg = svgs[i] - svg.add(svg.text('%s (%d / %d)' % (midifile, i + 1, npages), fill = 'black', stroke = 'black', - insert = ('%.3fcm' % (0), '%.3fcm' % (self.pageheight)))) - svg.save() + for i in range(len(pdfs)): + pdf = pdfs[i] + tobj = pdf.beginText() + tobj.setTextOrigin(2 * mm, 1 * mm) + tobj.setFont(self.fontname, self.fontsize) + tobj.setFillColor(ENGRAVE_COLOUR) + tobj.setStrokeColor(ENGRAVE_COLOUR) + tobj.textLine('%s (%d / %d)' % (title, i + 1, npages)) + pdf.drawText(tobj) + pdf.save() # http://www.electronics.dit.ie/staff/tscarff/Music_technology/midi/midi_note_numbers_for_octaves.htm @staticmethod @@ -112,26 +131,34 @@ return note2slot - def emitnote(self, svgs, slot, start, notelen): - startx = start / self.timescale - startpageidx = int(startx / self.pagewidth) - endx = (start + notelen) / self.timescale - endpageidx = int(endx / self.pagewidth) - startx = startx % self.pagewidth - y = self.pageheight - (self.offset + slot * self.pitch) - w = notelen / self.timescale + def emitnote(self, pdfs, slot, start, notelen): + x = start * self.timescale + pageidx = int(x / self.pagewidth) + x = x % self.pagewidth + y = self.offset + slot * self.pitch + w = notelen * self.timescale h = self.pitch - if startpageidx != endpageidx: - print 'page crossed from %d to %d' % (startpageidx, endpageidx) - print 'page = %d x = %.3f y = %.3f w = %.3f h = %.3f' % (startpageidx, startx, y, w, h) - Midi2SVG._emitnote(svgs[startpageidx], startx, y, w, h) + print 'page = %d x = %.3f y = %.3f w = %.3f h = %.3f' % (pageidx, x, y, w, h) + w1 = w + # Check if the note crosses a page + if x + w > self.pagewidth: + w1 = x - self.pagewidth # Crop first note + w2 = w - w1 + assert w1 <= self.pagewidth, 'note extends for more than a page' + # Emit second half of note + print 'split note, page %d w2 = %.3f' % (pageidx + 1, w2) + Midi2PDF._emitnote(pdfs[pageidx + 1], 0, y, w2, h) + + Midi2PDF._emitnote(pdfs[pageidx], x, y, w1, h) @staticmethod - def _emitnote(svg, x, y, w, h): - svg.add(svgwrite.shapes.Rect(insert = ('%.3fcm' % (x), '%.3fcm' % (y)), - size = ('%.3fcm' % (w), '%.3fcm' % (h)), - stroke = 'red', stroke_width = '1px', fill = 'none')) + def _emitnote(pdf, x, y, w, h): + pdf.saveState() + pdf.setStrokeColor(CUT_COLOUR) + pdf.setLineWidth(0) + pdf.rect(x * mm, y * mm, w * mm, h * mm, fill = False, stroke = True) + pdf.restoreState() if __name__ == '__main__': main()