2021-05-10 00:07:58 -04:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# main.py
#
# Copyright 2021 Patrick Marsee <patrickm@patrick-pc>
#
# 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
2021-05-11 23:47:07 -04:00
from tkinter import messagebox as mb
2021-05-10 00:07:58 -04:00
import subprocess as sp
import os
2021-05-11 23:47:07 -04:00
import threading
2021-05-10 00:07:58 -04:00
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 ] ) )
2021-05-11 23:47:07 -04:00
# play control
self . play_mutex = threading . Lock ( )
self . is_playing = False
self . stop_after = False
self . files = ( )
2021-05-10 00:07:58 -04:00
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 ( )
2021-05-11 23:47:07 -04:00
self . midi_file_text = Listbox ( self , listvariable = self . midi_file_var , width = 80 , height = 20 , selectmode = " extended " )
2021-05-10 00:07:58 -04:00
self . midi_file_text . grid ( column = 1 , row = 1 )
self . midi_file_button = ttk . Button ( self ,
text = " Open... " ,
2021-05-11 23:47:07 -04:00
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 ) )
2021-05-10 00:07:58 -04:00
self . play_button = ttk . Button ( self , text = " Play " , command = self . play_midi )
self . play_button . grid ( column = 1 , row = 2 )
2021-05-11 23:47:07 -04:00
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 )
2021-05-10 00:07:58 -04:00
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 : ] ]
2021-05-11 23:47:07 -04:00
def stop_after_current ( self ) :
self . play_mutex . acquire ( )
self . stop_after = self . stop_after_var . get ( ) == " stop "
self . play_mutex . release ( )
2021-05-10 00:07:58 -04:00
def play_midi ( self ) :
2021-05-11 23:47:07 -04:00
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 ( )
2021-05-10 00:07:58 -04:00
def open_file ( self ) :
2021-05-11 23:47:07 -04:00
initial_dir = os . getcwd ( )
2021-05-10 00:07:58 -04:00
if ' HOME ' in os . environ :
initial_dir = os . environ [ ' HOME ' ]
2021-05-11 23:47:07 -04:00
self . files = fd . askopenfilename ( defaultextension = " .mid " , filetypes = [ ( " MIDI " , " .mid " ) ] , initialdir = initial_dir , multiple = True )
self . midi_file_var . set ( self . files )
2021-05-10 00:07:58 -04:00
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 ) )