PySimpleGUI/Demo_MIDI_Player.py

228 lines
11 KiB
Python
Raw Normal View History

2018-09-27 20:24:09 +00:00
#!/usr/bin/env python
import sys
if sys.version_info[0] >= 3:
2018-09-27 20:24:09 +00:00
import PySimpleGUI as sg
else:
import PySimpleGUI27 as sg
2018-08-24 12:03:56 +00:00
import os
import mido
import time
import sys
2018-08-24 12:03:56 +00:00
PLAYER_COMMAND_NONE = 0
PLAYER_COMMAND_EXIT = 1
PLAYER_COMMAND_PAUSE = 2
PLAYER_COMMAND_NEXT = 3
PLAYER_COMMAND_RESTART_SONG = 4
# ---------------------------------------------------------------------- #
# PlayerGUI CLASS #
# ---------------------------------------------------------------------- #
class PlayerGUI():
'''
Class implementing GUI for both initial screen but the player itself
'''
def __init__(self):
self.Window = None
2018-08-24 12:03:56 +00:00
self.TextElem = None
self.PortList = mido.get_output_names() # use to get the list of midi ports
self.PortList = self.PortList[::-1] # reverse the list so the last one is first
# ---------------------------------------------------------------------- #
# PlayerChooseSongGUI #
# Show a GUI get to the file to playback #
# ---------------------------------------------------------------------- #
def PlayerChooseSongGUI(self):
# ---------------------- DEFINION OF CHOOSE WHAT TO PLAY GUI ----------------------------
2018-09-27 20:24:09 +00:00
layout = [[sg.Text('MIDI File Player', font=("Helvetica", 15), size=(20, 1), text_color='green')],
[sg.Text('File Selection', font=("Helvetica", 15), size=(20, 1))],
[sg.Text('Single File Playback', justification='right'), sg.InputText(size=(65, 1), key='midifile'), sg.FileBrowse(size=(10, 1), file_types=(("MIDI files", "*.mid"),))],
[sg.Text('Or Batch Play From This Folder', auto_size_text=False, justification='right'), sg.InputText(size=(65, 1), key='folder'), sg.FolderBrowse(size=(10, 1))],
[sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
[sg.Text('Choose MIDI Output Device', size=(22, 1)),
sg.Listbox(values=self.PortList, size=(30, len(self.PortList) + 1), key='device')],
[sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
[sg.SimpleButton('PLAY', size=(12, 2), button_color=('red', 'white'), font=("Helvetica", 15), bind_return_key=True), sg.Text(' ' * 2, size=(4, 1)), sg.Cancel(size=(8, 2), font=("Helvetica", 15))]]
window = sg.Window('MIDI File Player', auto_size_text=False, default_element_size=(30, 1), font=("Helvetica", 12)).Layout(layout)
self.Window = window
return window.Read()
2018-08-24 12:03:56 +00:00
def PlayerPlaybackGUIStart(self, NumFiles=1):
# ------- Make a new FlexForm ------- #
image_pause = './ButtonGraphics/Pause.png'
image_restart = './ButtonGraphics/Restart.png'
image_next = './ButtonGraphics/Next.png'
image_exit = './ButtonGraphics/Exit.png'
2018-09-27 20:24:09 +00:00
self.TextElem = sg.T('Song loading....', size=(70, 5 + NumFiles), font=("Helvetica", 14), auto_size_text=False)
self.SliderElem = sg.Slider(range=(1, 100), size=(50, 8), orientation='h', text_color='#f0f0f0')
2018-08-24 12:03:56 +00:00
layout = [
2018-09-27 20:24:09 +00:00
[sg.T('MIDI File Player', size=(30, 1), font=("Helvetica", 25))],
2018-08-24 12:03:56 +00:00
[self.TextElem],
2018-08-31 02:02:29 +00:00
[self.SliderElem],
2018-09-27 20:24:09 +00:00
[sg.ReadFormButton('PAUSE', button_color=sg.TRANSPARENT_BUTTON,
image_filename=image_pause, image_size=(50,50), image_subsample=2, border_width=0), sg.T(' '),
sg.ReadFormButton('NEXT', button_color=sg.TRANSPARENT_BUTTON,
image_filename=image_next, image_size=(50,50), image_subsample=2, border_width=0), sg.T(' '),
sg.ReadFormButton('Restart Song', button_color=sg.TRANSPARENT_BUTTON,
image_filename=image_restart, image_size=(50,50), image_subsample=2, border_width=0), sg.T(' '),
sg.SimpleButton('EXIT', button_color=sg.TRANSPARENT_BUTTON,
image_filename=image_exit, image_size=(50,50), image_subsample=2, border_width=0, )]
2018-08-24 12:03:56 +00:00
]
2018-09-27 20:24:09 +00:00
window = sg.FlexForm('MIDI File Player', default_element_size=(30, 1), font=("Helvetica", 25)).Layout(layout).Finalize()
self.Window = window
2018-08-24 12:03:56 +00:00
# ------------------------------------------------------------------------- #
# PlayerPlaybackGUIUpdate #
# Refresh the GUI for the main playback interface (must call periodically #
# ------------------------------------------------------------------------- #
def PlayerPlaybackGUIUpdate(self, DisplayString):
window = self.Window
if 'window' not in locals() or window is None: # if the form has been destoyed don't mess with it
2018-08-24 12:03:56 +00:00
return PLAYER_COMMAND_EXIT
self.TextElem.Update(DisplayString)
button, (values) = window.ReadNonBlocking()
2018-08-24 12:03:56 +00:00
if values is None:
return PLAYER_COMMAND_EXIT
if button == 'PAUSE':
return PLAYER_COMMAND_PAUSE
elif button == 'EXIT':
return PLAYER_COMMAND_EXIT
elif button == 'NEXT':
return PLAYER_COMMAND_NEXT
elif button == 'Restart Song':
return PLAYER_COMMAND_RESTART_SONG
return PLAYER_COMMAND_NONE
# ---------------------------------------------------------------------- #
# MAIN - our main program... this is it #
# Runs the GUI to get the file / path to play #
# Decodes the MIDI-Video into a MID file #
# Plays the decoded MIDI file #
# ---------------------------------------------------------------------- #
def main():
def GetCurrentTime():
'''
Get the current system time in milliseconds
:return: milliseconds
'''
return int(round(time.time() * 1000))
2018-08-31 02:02:29 +00:00
2018-08-24 12:03:56 +00:00
pback = PlayerGUI()
button, values = pback.PlayerChooseSongGUI()
2018-08-31 02:02:29 +00:00
if button != 'PLAY':
2018-09-27 20:24:09 +00:00
sg.PopupCancel('Cancelled...\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
sys.exit(69)
2018-08-24 12:36:45 +00:00
if values['device']:
2018-08-24 12:03:56 +00:00
midi_port = values['device'][0]
else:
2018-09-27 20:24:09 +00:00
sg.PopupCancel('No devices found\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
2018-08-24 12:03:56 +00:00
batch_folder = values['folder']
midi_filename = values['midifile']
# ------ Build list of files to play --------------------------------------------------------- #
if batch_folder:
filelist = os.listdir(batch_folder)
filelist = [batch_folder+'/'+f for f in filelist if f.endswith(('.mid', '.MID'))]
filetitles = [os.path.basename(f) for f in filelist]
elif midi_filename: # an individual filename
filelist = [midi_filename,]
filetitles = [os.path.basename(midi_filename),]
else:
2018-09-27 20:24:09 +00:00
sg.PopupError('*** Error - No MIDI files specified ***')
sys.exit(666)
2018-08-24 12:03:56 +00:00
# ------ LOOP THROUGH MULTIPLE FILES --------------------------------------------------------- #
pback.PlayerPlaybackGUIStart(NumFiles=len(filelist) if len(filelist) <=10 else 10)
port = None
# Loop through the files in the filelist
for now_playing_number, current_midi_filename in enumerate(filelist):
display_string = 'Playing Local File...\n{} of {}\n{}'.format(now_playing_number+1, len(filelist), current_midi_filename)
midi_title = filetitles[now_playing_number]
# --------------------------------- REFRESH THE GUI ----------------------------------------- #
pback.PlayerPlaybackGUIUpdate(display_string)
# ---===--- Output Filename is .MID --- #
midi_filename = current_midi_filename
# --------------------------------- MIDI - STARTS HERE ----------------------------------------- #
if not port: # if the midi output port not opened yet, then open it
port = mido.open_output(midi_port if midi_port else None)
try:
mid = mido.MidiFile(filename=midi_filename)
except:
print('****** Exception trying to play MidiFile filename = {}***************'.format(midi_filename))
2018-09-27 20:24:09 +00:00
sg.PopupError('Exception trying to play MIDI file:', midi_filename, 'Skipping file')
2018-08-24 12:03:56 +00:00
continue
# Build list of data contained in MIDI File using only track 0
midi_length_in_seconds = mid.length
2018-08-31 02:02:29 +00:00
display_file_list = '>> ' + '\n'.join([f for i, f in enumerate(filetitles[now_playing_number:]) if i < 10])
2018-08-24 12:03:56 +00:00
paused = cancelled = next_file = False
######################### Loop through MIDI Messages ###########################
while(True):
start_playback_time = GetCurrentTime()
port.reset()
for midi_msg_number, msg in enumerate(mid.play()):
#################### GUI - read values ##################
if not midi_msg_number % 4: # update the GUI every 4 MIDI messages
t = (GetCurrentTime() - start_playback_time)//1000
display_midi_len = '{:02d}:{:02d}'.format(*divmod(int(midi_length_in_seconds),60))
display_string = 'Now Playing {} of {}\n{}\n {:02d}:{:02d} of {}\nPlaylist:'.\
format(now_playing_number+1, len(filelist), midi_title, *divmod(t, 60), display_midi_len)
# display list of next 10 files to be played.
2018-08-31 02:02:29 +00:00
pback.SliderElem.Update(t, range=(1,midi_length_in_seconds))
2018-08-24 12:03:56 +00:00
rc = pback.PlayerPlaybackGUIUpdate(display_string + '\n' + display_file_list)
else: # fake rest of code as if GUI did nothing
rc = PLAYER_COMMAND_NONE
if paused:
rc = PLAYER_COMMAND_NONE
while rc == PLAYER_COMMAND_NONE: # TIGHT-ASS loop waiting on a GUI command
rc = pback.PlayerPlaybackGUIUpdate(display_string)
time.sleep(.25)
####################################### MIDI send data ##################################
port.send(msg)
# ------- Execute GUI Commands after sending MIDI data ------- #
if rc == PLAYER_COMMAND_EXIT:
cancelled = True
break
elif rc == PLAYER_COMMAND_PAUSE:
paused = not paused
port.reset()
elif rc == PLAYER_COMMAND_NEXT:
next_file = True
break
elif rc == PLAYER_COMMAND_RESTART_SONG:
break
if cancelled or next_file:
break
#------- DONE playing the song ------- #
port.reset() # reset the midi port when done with the song
if cancelled:
break
# ---------------------------------------------------------------------- #
# LAUNCH POINT -- program starts and ends here #
# ---------------------------------------------------------------------- #
if __name__ == '__main__':
main()