~matthias_schaub/midi2human

151e098d9dc88c3648d09b9e43b37b45966a8404 — Matthias Schaub 3 years ago 4649a6a
Factor out script into a module
4 files changed, 152 insertions(+), 0 deletions(-)

A midi2human/__init__.py
A midi2human/cli.py
A midi2human/helpers.py
A midi2human/piano.py
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