from __future__ import print_function import logging import sys import time from string import Template from random import randrange, choice import subprocess import tkinter as tk from PIL import Image, ImageTk from rtmidi.midiutil import open_midiinput log = logging.getLogger('midiin_callback') logging.basicConfig(level=logging.INFO) notes_sharp = ["c", "cis", "d", "dis", "e", "f", "fis", "g", "gis", "a", "ais", "b"] lilypond = Template( '''\\version "2.24.1" \paper { #(set-paper-size "a10landscape") indent = 0\mm line-width = 110\mm oddHeaderMarkup = "" evenHeaderMarkup = "" oddFooterMarkup = "" evenFooterMarkup = "" } \\relative { \clef ${CLEF} ${NOTE} } ''') window = tk.Tk() class Trainer(): def __init__(self): self.secret = 0 self.lb = 36 # middle C is 60 self.ub = 84 # 108 self.image = None self.image_label = tk.Label(window) self.image_label.pack() self.newNote() # Gets a new random note, outputs it to file def newNote(self): self.secret = randrange(self.lb, self.ub) self.print() if abs(60 - self.secret) <= 4: clef = choice(["bass", "treble"]) else: clef = "bass" if self.secret < 60 else "treble" ly = lilypond.substitute(CLEF=clef, NOTE=f"{{{self.convertNote(self.secret)}}}") with open("lily.txt", "w") as f: f.write(ly) f.close() wait_for = subprocess.Popen(["lilypond", "-dpreview", "-dbackend=eps", "-dresolution=600", "--png", "lily.txt"]) self.refreshImage(wait_for) def refreshImage(self, wait_for=None): if wait_for: wait_for.wait() if self.image: self.image.close() self.image = Image.open("lily.png") photo = ImageTk.PhotoImage(self.image) self.image_label.configure(image=photo) self.image_label.image = photo def guess(self, note): guess = (note == self.secret) if guess: self.newNote() return guess # Convert midi pitch to lilypond notation def convertNote(self, pitch): octave = int(pitch / 12) - 4 oct_str = -octave*"," if octave < 0 else octave*"'" note = notes_sharp[(pitch % 12)] return f"{note}{oct_str}" def print(self): print(f"Current midi note: {self.secret}") print(self.convertNote(self.secret)) t = Trainer() class MidiInputHandler(object): def __init__(self, port): self.port = port def __call__(self, event, data=None): message, deltatime = event (ev, note, vel) = message if ev == 144: # note on print(f"Pressed note {note}") print(t.convertNote(note)) t.guess(note) # Prompts user for MIDI input port, unless a valid port number or name given port = sys.argv[1] if len(sys.argv) > 1 else None try: midiin, port_name = open_midiinput(port) except (EOFError, KeyboardInterrupt): sys.exit() midiin.set_callback(MidiInputHandler(port_name)) window.mainloop() midiin.close_port() del midiin