#!/usr/bin/env python # -*- coding: utf-8 -*- # # main.py # # Copyright 2021 Patrick Marsee # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # from tkinter import * from tkinter import ttk from tkinter import filedialog as fd from tkinter import messagebox as mb import subprocess as sp import os import threading class MidiPort: def __init__(self, port, client_name, port_name): self.port = port self.client_name = client_name self.port_name = port_name def __str__(self): return "{} {}".format(self.port, self.port_name) class App(ttk.Frame): def __init__(self, master): super(App, self).__init__(master) self.devices = self.get_midi_list() self.create_widgets() self.midi_port_var.set(str(self.devices[0])) # play control self.play_mutex = threading.Lock() self.is_playing = False self.stop_after = False self.files = () def create_widgets(self): self.midi_port_label = ttk.Label(self, text="Midi Device") self.midi_port_label.grid(column=0, row=0) self.midi_port_var = StringVar() self.midi_port_selector = ttk.Combobox(self, textvariable=self.midi_port_var) self.midi_port_selector.state(["readonly"]) self.midi_port_selector['values'] = [str(i) for i in self.devices] self.midi_port_selector.grid(column=1, row=0, columnspan=2, sticky=(E, W)) self.midi_file_label = ttk.Label(self, text="File") self.midi_file_label.grid(column=0, row=1) self.midi_file_var = StringVar() self.midi_file_text = Listbox(self, listvariable=self.midi_file_var, width=80, height=20, selectmode="extended") self.midi_file_text.grid(column=1, row=1) self.midi_file_button = ttk.Button(self, text="Open...", command=self.open_file) self.midi_file_button.grid(column=3, row=1, sticky=(E, W)) self.midi_file_scroll = ttk.Scrollbar(self, orient=VERTICAL, command=self.midi_file_text.yview) self.midi_file_text.configure(yscrollcommand=self.midi_file_scroll.set) self.midi_file_scroll.grid(column=2, row=1, sticky=(N, S, W)) self.play_button = ttk.Button(self, text="Play", command=self.play_midi) self.play_button.grid(column=1, row=2) self.stop_after_var = StringVar() self.stop_after_var.set("go") self.stop_after_button = ttk.Checkbutton(self, text="Stop after current", command=self.stop_after_current, variable=self.stop_after_var, onvalue="stop", offvalue="go") self.stop_after_button.grid(column=2, row=2, columnspan=2) self.columnconfigure(1, weight=1) @staticmethod def get_midi_list() -> list: result = sp.run(["aplaymidi", "-l"], stdout=sp.PIPE, encoding="utf-8") devices = result.stdout.split('\n') port_start = devices[0].find("Port") client_name_start = devices[0].find("Client name") port_name_start = devices[0].find("Port name") return [MidiPort(i[port_start:client_name_start].strip(), i[client_name_start:port_name_start].strip(), i[port_name_start:].strip()) for i in devices[1:]] def stop_after_current(self): self.play_mutex.acquire() self.stop_after = self.stop_after_var.get() == "stop" self.play_mutex.release() def play_midi(self): self.play_mutex.acquire() if self.is_playing: self.play_mutex.release() mb.showinfo(message="Another song cannot play until the previous song is finished playing.") else: self.play_mutex.release() port = self.devices[self.midi_port_selector.current()].port midis = [self.files[f] for f in self.midi_file_text.curselection()] if len(midis) == 0: midis = self.files play_thread = threading.Thread(target=self.play_thread, args=(port, midis)) play_thread.start() def play_thread(self, port: str, midis: list): self.play_mutex.acquire() self.is_playing = True self.play_mutex.release() for midi in midis: sp.run(["aplaymidi", "-p", port, midi]) self.play_mutex.acquire() if self.stop_after: self.play_mutex.release() break; self.play_mutex.release() self.play_mutex.acquire() self.is_playing = False self.play_mutex.release() def open_file(self): initial_dir = os.getcwd() if 'HOME' in os.environ: initial_dir = os.environ['HOME'] self.files = fd.askopenfilename(defaultextension=".mid", filetypes=[("MIDI", ".mid"), ("MIDI", ".MID")], initialdir=initial_dir, multiple=True) self.midi_file_var.set(self.files) def main(args): root = Tk() root.title('aplaymidi_gui') #root.geometry("1024x768") #root.resizable(FALSE, FALSE) root.option_add('*tearOff', FALSE) root.columnconfigure(0, weight = 1) root.rowconfigure(0, weight = 1) newApp = App(root) newApp.grid(sticky = (N, S, E, W)) root.mainloop() return 0 if __name__ == '__main__': import sys sys.exit(main(sys.argv))