# PyPlayer.py
#
# Works from Python 1.4.0 to 3.13.x WITHOUT ANY modification.
# Dependencies, 'ffplay' from the 'FFMPEG' suite ONLY, IF REQUIRED, for _all_
# modern platforms, and 'Play16' for the Classic AMIGA platform.
# [aplay, paplay and music123 for 'linux', (Raspberry PI)], [afplay for 'darwin'],
# [Play16 for 'amiga'], [SoX, no longer being updated!], [/dev/dsp and padsp for 'OSS'],
# [/dev/dsp for 'cygwin'], [Windows with VLC now added, and it is NOT PRETTY!].
# Tested on OSX 10.15.7 only as of 21-03-2025, using Python 3.13.x and it works.

# Author: Barry Walker, G0LCU.
# $VER PyPlayer.py_(C)2022-2025_B.Walker_G0LCU.
# Issued under the GPLv3 licence.

# The only official import required!
import os
# Generate 'import sys' from the 'os' module as 'sys=os.sys'...
# This has been in Python's 'os' module since at least Python 1.4.0 for the AMIGA.
sys=os.sys

# Check for valid audio players used in this script. If no valid players
# exist then this WILL completely EXIT and close the parent script too.
def Check_Player():
	Player_List=["C:Play16","/bin/aplay","/usr/bin/aplay","/bin/paplay",
"/usr/bin/paplay","/usr/bin/afplay","/bin/vlc","/usr/bin/vlc","/bin/sox",
"/usr/bin/sox","/bin/padsp","/usr/bin/padsp","/bin/audioplay","/dev/dsp",
"/dev/audio","/bin/music123","/usr/bin/music123","/usr/bin/audioplay",
"C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe","/bin/ffplay",
"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe","/usr/bin/ffplay"]
	for Player in range(0, len(Player_List), 1):
		if os.path.exists(Player_List[Player]):
			# print("Player '" + Player_List[Player] + "' is available!")
			return
	print("Error: No valid players exist, quitting...")
	print("This WILL quit the parent script too!")
	# !!! IMPORTANT !!! The line below WILL close the parent script down too, SO BE AWARE! 
	sys.exit(1)

# Check for a valid audio player.
Check_Player()

PlayWave_Version="[PyPlayer.]PlayWave Version 1.00.00..."
PlayWave_File="Error_Beep.wav"

def Create_Error_Beep(Verbose="?"):
	if Verbose=="V": print("Creating 'Error_Beep.wav' as a test file, please wait...")
	header=[ 82, 73, 70, 70, 100, 31, 0, 0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1, 0, 64, 31, 0, 0, 64, 31, 0, 0, 1, 0, 8, 0, 100, 97, 116, 97, 64, 31, 0, 0 ]
	waveform=[ 79, 45, 32, 45, 79, 113, 126, 113 ]
	wavefile=open("Error_Beep.wav", "w+")
	for hdr in range(0, 44, 1):
		wavefile.write(chr(header[hdr]))
	for sample in range(0, 1000, 1):
		for wf in range(0, 8, 1):
			wavefile.write(chr(waveform[wf]))
	wavefile.close()
	PlayWave_File="Error_Beep.wav"
	if Verbose=="V":
		print("Done!")
		print("Saved as 'Error_Beep.wav' inside the current drawer|directory|folder...")

# This is now the default generic player for any system with a VALID command line audio player.
# It defaults to the AMIGA's Play16, playing Error_Beep.wav, non-verbose and in the background.
# 'PlayFile' can handle your player of choice with all switches, options, keywords and file
# formats supported by your audio player so long as the whole lot is inside inverted commas.
#
# It is theoretically possible to use this function for recording but you try it at your own risk.
# If you do try it for recording, then some kind of timeout will be needed to end any recording.
#
# If you want a[n] '.mp3', ('mpeg') player for the AMIGA then there is this one and can be called
# via this 'Generic' function, BUT, it requires a very high specification Classic AMIGA...
#   https://aminet.net/package/util/libs/mpega_library
# Also there is 'mpg123' for Linux flavours, obtained from the relevant repository.
#
# Using very basic VLC when installed in Linux, (and OSX 10.15.x):
# [PyPlayer.]Generic("vlc --verbose 0 --intf dummy --quiet Error_Beep.wav vlc://quit > /dev/null 2>&1")
# Using very basic SoX when installed in Linux and as a _variable_:
# MySong="sox -q -V0 Error_Beep.wav -d &"
# [PyPlayer.]Generic(MySong)
# !!! IMPORTANT !!!
# -----------------
# Before using this try out YOUR full command line version first manually to prove that it works!
# Also be aware that developers love to change things and could well break YOUR command line version!
def Generic(PlayFile="RUN >NIL: C:Play16 Error_Beep.wav QUIET"):
	if not os.path.exists("Error_Beep.wav"):
		Create_Error_Beep()
	os.system(PlayFile)
	return

# ************ List of usable Audio Players. ************
# Works from Python 1.4.0 to 3.13.x without modification!
# If called from the Python prompt as:
# >>> import PyPlayer
# >>> PyPlayer.PlayWave()
# Will play the Error_Beep.wav...
def PlayWave(PlayWave_File="Error_Beep.wav", Run_In_Background="Y", Verbose="?"):
	"""
	Usage methods:
	--------------
	A) As an 'import'ed module...
	-----------------------------

	>>> import PyPlayer

	1) PyPlayer.PlayWave()
	   This Immediately returns to the '>>>' prompt OR the script that calls it.
	   Auto creates 'Error_Beep.wav' inside the current directory, ONLY IF, none exists.
	   And, finally plays 'Error_Beep.wav' in the background.
	2) PyPlayer.PlayWave("/full/path/to/somefile.wav")
	   The same as 1) but playing 'somefile.wav' instead.
	3) PyPlayer.PlayWave("/full/path/to/somefile.wav", "Y")
	   Exactly the same as 2) above.
	4) PyPlayer.PlayWave("/full/path/to/somefile.wav", "Y", "")
	   Exactly the same as 2) above.
	5) PyPlayer.PlayWave("/full/path/to/somefile.wav", "Y", "V")
	   The same as 2) except a verbose quick printout to the screen what is playing.

	6) PyPlayer.PlayWave("/full/path/to/somefile.wav", "")
	   This WAITS for the sound to be played and when finished returns back to
	   the '>>>' prompt OR the script that calls it without verbose mode.
	7) PyPlayer.PlayWave("/full/path/to/somefile.wav", "", "")
	   Exactly the same as 6) above.
	8) PyPlayer.PlayWave("/full/path/to/somefile.wav", "", "Y")
	   The same as 6) except a verbose quick printout to the screen what is playing.

	9) PyPlayer.Create_Error_Beep("")
	   Creates the 'Error_Beep.wav' silently into the current directory.
	10) PyPlayer.Create_Error_Beep("V")
	   Creates the 'Error_Beep.wav' verbosely into the current directory.

	B) As a sourced file...
	-----------------------

	>>> exec(open("/full/path/to/PyPlayer.py").read())

	1) Exactly the same as the 'import'ed method except instead of entering
	   PyPlayer.PlayWave(...) use PlayWave(...) instead...
	   ...AND/OR...
	   PyPlayer.Create_Error_Beep(...) use Create_Error_Beep(...) instead.
	2) For example...
	   PlayWave("/full/path/to/somefile.wav")
	"""

	# Find the relevant platform...
	Platform=sys.platform

	# Use the 'Run_In_Background' as "Y" or "y" and the prompt will immediately appear; the sound will keep playing.
	# Use the 'Verbose' as uppercase 'V' to display what is being played for example:
	# >>> import PyPlayer
	# >>> PyPlayer.PlayWave("Error_Beep.wav", "Y", "V")
	# Using 'Play16', playing Error_Beep.wav...
	# >>>
	# >>> PyPlayer.PlayWave()
	# This will play the 'Error_Beep.wav' and the prompt will immediately appear; the sound will keep playing for 1 second.

	# ***** Test for 'amiga and amigaos3'... *****
	if Platform[0:5]=='amiga':
		# ******************* AMIGA A1200(HD) *******************
		# Classic AMIGA systems, Python Version 1.4.0 to 2.0.1...
		# Play16 is the dependency and is here...
		#   https://aminet.net/package/mus/play/Play16        # For 68EC020 and OS3.0x and greater only.
		#   https://aminet.net/package/mus/play/Play16_v1.9   # For 68000 AMIGA and OS2.0.x and above.
		# Play16 dependency...
		#   https://aminet.net/package/dev/c/AsyncIO
		# Python Version 1.4.0 is here...
		#   https://aminet.net/package/dev/lang/Python_14   #
		# !!! ENSURE the 'Play16' executable is inside the 'C:' Volume or 'SYS:C' drawer. !!!
		# IPORTANT! Sound eXchange, (SoX), Eagleplayer and HippoPlayer are NOT catered for at all.
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			# Wait for a few seconds for the Error_Beep.wav file to be generated.
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background="Run >NIL: "
		else:
			Run_In_Background=""
		if Verbose=="V": print("Using 'Play16', playing " + PlayWave_File + "...")
		os.system(Run_In_Background + "C:Play16 " + PlayWave_File + " QUIET")
		return

	# ***** Test for 'linux, linux2 and linux3'... *****
	if Platform[0:5]=='linux':
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		# !!! Either of these two WILL work on some versions of Raspberry PI. !!! 
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		# ***** Test for ALSA aplay... *****
		player_exists=os.path.exists("/usr/bin/aplay")
		if player_exists==True or player_exists==1:
			if Verbose=="V": print("Using ALSA 'aplay', playing " + PlayWave_File + "...")
			os.system("aplay -q " + PlayWave_File + Run_In_Background)
			return

		# ***** Test for PulseAudio paplay... *****
		player_exists=os.path.exists("/usr/bin/paplay")
		if player_exists==True or player_exists==1:
			if Verbose=="V": print("Using PulseAudio 'paplay', playing " + PlayWave_File + "...")
			os.system("paplay " + PlayWave_File + Run_In_Backgorund)
			return

		# ********* Test for music123... **********
		# *** This should be in your repository! **
		player_exists=os.path.exists("/usr/bin/music123")
		if player_exists==True or player_exists==1:
			if Verbose=="V": print("Using 'music123', playing " + PlayWave_File + "...")
			os.system("music123 -q " + PlayWave_File + Run_In_Backgorund)
			return

	# ***** Test for 'darwin'... *****
	if Platform=='darwin':
		# ***** Darwin's default command line audio player. Limited but OK... *****
		# Known extensions: WAV, MP3, AAC, M4A, AIFC, AIFF, AU, but many more are catered for...
		# Known extensions that do NOT work: RAW, MAUD, VOC...
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		if Verbose=="V": print("Using 'afplay', playing " + PlayWave_File + "...")
		os.system("afplay -q 1 " + PlayWave_File + Run_In_Background)
		return

	# ***** Test for 'cygwin'... *****
	if Platform=='cygwin':
		# Install Sound eXchange - SoX - and place the executable inside the Windows PATH.
		# AND/OR...
		# CygWin's '/bin/' directory for this to work.
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		#   https://sox.sourceforge.net/   #
		player_exists=os.path.exists("/bin/sox")
		if player_exists==True or player_exists==1:
			if Verbose=="V": print("Using 'sox', playing " + PlayWave_File + "...")
			os.system("sox -q -V0 " + PlayWave_File + " -d" + Run_In_Background)
			return

		# ***** Test for OSS '/dev/dsp' in cygwin... *****
		# This mode is limited to any audio file that is uncompressed, unsigned integer, 8000 sps,
		# 1 channel "mono". The default "Error_Beep.wav" will work but with a 5.5mS click at the
		# start because of its 44 byte header. RAW files of the same format will also play.
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		player_exists=os.path.exists("/dev/dsp")
		if player_exists==True or player_exists==1:
			if Verbose=="V": print("Using '/dev/dsp', playing " + PlayWave_File + "...")
			os.system("cat " + PlayWave_File + " > /dev/dsp" + Run_In_Background)
			return

	# ***** Test for SoX in *NIX and AMIGA flavours... *****
	# For a highly expanded AMIGA ONLY; NOTE: NOT tested at all:
	# ----------------------------------------------------------
	# REQUIRES HW...
	# (68040-FPU and 68060-FPU, lots of RAM too.)
	# Also requires AHI and ixemul63.x library...
	# Sound eXchange - SoX - Version 14.3.1...
	#   https://aminet.net/package/mus/play/sox-14.3.1-m68k   #
	# ixemul,library Version 63.x:
	#   https://sourceforge.net/projects/amiga/files/ixemul.library/63.1/   #
	#
	# For *NIX flavours, NOT Windows, see below:
	# ------------------------------------------
	# Obtain SoX from your repository, OR, from here and place the executable into /usr/bin/sox...
	#   https://sox.sourceforge.net/   #
	# !!! One known extension that does NOT work in SoX:
	# "sox FAIL formats: can't open input file `test.aifc':
	# Unsupported AIFC compression type `in24'" !!!
	player_exists=os.path.exists("/usr/bin/sox")
	if player_exists==True or player_exists==1:
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		if Verbose=="V": print("Using 'sox', playing " + PlayWave_File + "...")
		os.system("sox -q -V0 " + PlayWave_File + " -d" + Run_In_Background)
		return

	# ***** Test, (from the FFMPEG suite), for FFPLAY... *****
	# This has taken the place of 'SoX' as it is current as of, 22-03-2025!
	# FFPLAY from the FFMPEG suite can play just about any codec available...
	# FFMPEG suite download from:
	#   https://ffmpeg.org/download.html
	# OR, from your repository, if it exists.
	player_exists=os.path.exists("/usr/bin/ffplay")
	if player_exists==True or player_exists==1:
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		if Verbose=="V": print("Using 'ffplay', playing " + PlayWave_File + "...")
		os.system("ffplay -autoexit -nodisp -loglevel quiet -stats " + PlayWave_File + " > /dev/null 2>&1" + Run_In_Background)
		return

	# ***** Test for OSS '/dev/dsp' OR '/usr/bin/padsp'... *****
	# This will also work as a very limited generic player for CYGWIN, see 'cygwin' above!
	# This mode is limited to any audio file that is uncompressed, unsigned integer, 8000 sps,
	# 1 channel "mono". The default "Error_Beep.wav" will work but with a 5.5mS click at the
	# start because of its 44 byte header. RAW files of the same format will also play.
	player_exists=os.path.exists("/dev/dsp")
	if player_exists==True or player_exists==1:
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		if Verbose=="V": print("Using '/dev/dsp', playing " + PlayWave_File + "...")
		os.system("cat " + PlayWave_File + " > /dev/dsp" + Run_In_Background)
		return
	player_exists=os.path.exists("/usr/bin/padsp")
	if player_exists==True or player_exists==1:
		# Create a default "Error_Beep.wav" inside the current drawer...
		if not os.path.exists("Error_Beep.wav"):
			Create_Error_Beep()
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background=" &"
		else:
			Run_In_Background=""
		if Verbose=="V": print("Using 'padsp', playing " + PlayWave_File + "...")
		# 'player_exists' is not needed now, so re-using to capture the return code created before returning.
		player_exists=os.system("padsp cat " + PlayWave_File + " | padsp tee /dev/dsp > /dev/null 2>&1" + Run_In_Background)
		return

	# ***** Windows 8.1 and 10. No default command line player exists... *****
	# Windows does NOT have a command line audio player so...
	# This is a workaround and NOT PRETTY!
	# Remember! This HAS to compile for Python 1.4.0 to 3.13.x without alteration.
	# Windows CMD.EXE is SOOOO obtuse!
	# Install VideoLAN VLC:
	#   https://www.videolan.org/vlc/download-windows.en_GB.html   #
	if Platform=='win32' or Platform[0:3]=='win':
		if Run_In_Background=="Y" or Run_In_Background=="y":
			Run_In_Background="start "
		else:
			Run_In_Background=""
		if Verbose=="V": print("Using 'VLC', playing " + PlayWave_File + "...")
		PCWD=os.getcwd()
		os.chdir("C:\\Windows\\Temp\\")
		if not os.path.exists("C:\\Windows\\Temp\\Error_Beep.wav"): Create_Error_Beep()
		if PlayWave_File=="Error_Beep.wav": PlayWave_File="C:\\Windows\\Temp\\Error_Beep.wav"
		# ********** !!! Dependent on which Windows OS is in use !!! **********
		# A choice of two installation folders! Set for a Windows 10 machine tested on.
		os.chdir("C:\\Program Files (x86)\\VideoLAN\\VLC\\")
		# Setup for my Windows 8.1 machine...
		#os.chdir("C:\\Program Files\\VideoLAN\\VLC\\")
		# *********************************************************************
		os.system(Run_In_Background + "vlc.exe --verbose 0 --intf dummy --quiet " + PlayWave_File + " vlc://quit > NUL")
		os.chdir(PCWD)
		return

	# ****************************************************************
	# !!! This block is commented out at present. READ the manual. !!!
	# ****************************************************************
	#
	# ***** Assume SunOS 'audioplay' exists and in the PATH... *****
	#if Platform[0:5]=='sunos':
	#	# Create a default "Error_Beep.wav" inside the current drawer...
	#	if not os.path.exists("Error_Beep.wav"):
	#		Create_Error_Beep()
	#	if Run_In_Background=="Y" or Run_In_Background=="y":
	#		Run_In_Background=" &"
	#	else:
	#		Run_In_Background=""
	#	# ***** Check for 'audioplay'... *****
	#	player_exists=os.path.exists("/usr/bin/audioplay")
	#	if player_exists==True or player_exists==1:
	#		if Verbose=="V": print("Using 'audioplay', playing " + PlayWave_File + "...")
	#		os.system("audioplay " + PlayWave_File + Run_In_Background)
	#		return
	#
	#	#  ***** Check for '/dev/audio'... *****
	#	#  *** This uses u-law compression... **
	#	player_exists=os.path.exists("/dev/audio")
	#	if player_exists==True or player_exists==1:
	#		if Verbose=="V": print("Using '/dev/audio', playing " + PlayWave_File + "...")
	#		os.system("cat " + PlayWave_File + " > /dev/audio" + Run_In_Background)
	#		return
	#	
	# ****************************************************************

	# Should never get here but left in case there is a bug and it does...
	print("This should never be reached but left in case there is a bug!")
	print("ERROR! No other usable audio players exist at present!")
	return 

# *******************************************************

