A midi2human/__init__.py => midi2human/__init__.py +1 -0
@@ 0,0 1,1 @@
+__version__ = "0.1.0"
A midi2human/cli.py => midi2human/cli.py +55 -0
@@ 0,0 1,55 @@
+"""
+Read a MIDI file, map notes to piano keys and draw a piano layout.
+
+See http://www.mathpages.com/home/kmath043.htm for the math behind the piano layout.
+"""
+from pathlib import Path
+
+import click
+import importlib_metadata as metadata
+import mido
+import piano
+
+
+@click.command()
+@click.version_option(version=metadata.version("midi2human"))
+@click.argument("filename", nargs=1, type=click.Path(exists=True))
+@click.option(
+ "--layout",
+ type=click.Choice(["classic", "pure"]),
+ default="classic",
+ show_default=True,
+ help="Choose layout style.",
+)
+@click.option(
+ "--color",
+ type=str,
+ default="grey",
+ show_default=True,
+ help="Choose color for marked piano keys.",
+)
+def cli(filename, layout, color):
+ """Map a notes from a MIDI file onto a piano layout."""
+ filename = Path(filename)
+ mid = mido.MidiFile(filename)
+
+ # Each message in a MIDI file has a delta time,
+ # which tells how many ticks have passed since the last message
+ # (Mido Documentation).
+
+ # Notes which are played at the same time
+ note_pairs = []
+ # All notes and note pairs
+ notes = []
+
+ for msg in mid:
+ if msg.time == 0:
+ if msg.type == "note_on":
+ note_pairs.append(msg.note)
+ else:
+ notes.append(note_pairs)
+ note_pairs = []
+ if msg.type == "note_on":
+ note_pairs.append(msg.note)
+
+ piano.draw(notes, filename, color, layout)
A midi2human/helpers.py => midi2human/helpers.py +16 -0
@@ 0,0 1,16 @@
+import sys
+
+
+def progress_bar(iteration, total):
+ """Print a progress bar to stdout.
+
+ Call from inside loops.
+ """
+ iteration = iteration + 1
+ percent = int(100 * (iteration / total))
+ length = int(100 * iteration // total)
+ bar = "#" * length + " " * (100 - length)
+ sys.stdout.write("\rProgress: |{0}| {1}%\r".format(bar, percent))
+ sys.stdout.flush()
+ if iteration == total:
+ sys.stdout.write("\n")
A midi2human/piano.py => midi2human/piano.py +80 -0
@@ 0,0 1,80 @@
+import sys
+from typing import List
+
+import svgwrite
+
+from midi2human.helpers import progress_bar
+
+
+def draw(notes, filename, color, layout):
+ """Create and save an SVG file to disk."""
+ sys.stdout.write("Start mapping notes and drawing keys.")
+ filename = filename.with_suffix(".svg")
+ dwg = svgwrite.Drawing(filename=filename, size=("100%", "100%"))
+ length = len(notes)
+ for step, note_pairs in enumerate(notes):
+ progress_bar(step, length)
+ dwg = draw_keys(dwg, note_pairs, step, layout)
+ sys.stdout.write("Save drawing to: {0}".format(filename))
+ dwg.save()
+ sys.stdout.write("Done! Enjoy.")
+
+
+def draw_keys(dwg, notes: List, step: int, layout: str):
+ """Draw piano keys as SVG."""
+ if layout in {"classic", "pure"}:
+ height = 120
+ stroke_color = "black"
+ elif layout == "minimal":
+ height = 80
+ stroke_color = "white"
+
+ y = step * height
+
+ number_of_white_keys = 52
+ # number_of_black_keys = 36 # noqa: E800
+
+ width_of_white_key = 23
+ width_of_black_key = 13
+
+ # MIDI numver 21 maps to lowest piano note (A0)
+ midi_number = 21
+ # Start with piano note A0 not with C (C+5=A)
+ for j, i in enumerate(range(5, number_of_white_keys + 5)):
+ # Draw white keys
+ if midi_number in notes:
+ fill_color = "grey"
+ else:
+ fill_color = "white"
+ if layout == "pure":
+ stroke_color = fill_color
+ x = j * width_of_white_key
+ dwg.add(dwg.rect((x, y), (23, height), fill=fill_color, stroke=stroke_color))
+
+ # Do not draw left and right outer black key
+ if j in {0, 52}:
+ continue
+
+ midi_number = midi_number + 1
+
+ # Draw black keys
+ if i % 7 in {1, 2}:
+ if midi_number in notes:
+ fill_color = "grey"
+ else:
+ fill_color = "black"
+ x = x - (width_of_white_key - (width_of_black_key * 2 / 3)) / 2
+ elif i % 7 in {4, 5, 6}:
+ if midi_number in notes:
+ fill_color = "grey"
+ else:
+ fill_color = "black"
+ x = x - (width_of_white_key - (width_of_black_key * 3 / 4)) / 2
+ else:
+ continue
+ if layout == "pure":
+ stroke_color = fill_color
+ dwg.add(dwg.rect((x, y), (13, 80), fill=fill_color, stroke=stroke_color))
+ midi_number = midi_number + 1
+
+ return dwg