Traktor/myenv/Lib/site-packages/pygame/examples/music_drop_fade.py
2024-05-26 05:12:46 +02:00

254 lines
8.9 KiB
Python

#!/usr/bin/env python
""" pygame.examples.music_drop_fade
Fade in and play music from a list while observing several events
Adds music files to a playlist whenever played by one of the following methods
Music files passed from the commandline are played
Music files and filenames are played when drag and dropped onto the pygame window
Polls the clipboard and plays music files if it finds one there
Keyboard Controls:
* Press space or enter to pause music playback
* Press up or down to change the music volume
* Press left or right to seek 5 seconds into the track
* Press escape to quit
* Press any other button to skip to the next music file in the list
"""
from typing import List
import pygame as pg
import os, sys
VOLUME_CHANGE_AMOUNT = 0.02 # how fast should up and down arrows change the volume?
def add_file(filename):
"""
This function will check if filename exists and is a music file
If it is the file will be added to a list of music files(even if already there)
Type checking is by the extension of the file, not by its contents
We can only discover if the file is valid when we mixer.music.load() it later
It looks in the file directory and its data subdirectory
"""
if filename.rpartition(".")[2].lower() not in music_file_types:
print(f"{filename} not added to file list")
print("only these files types are allowed: ", music_file_types)
return False
elif os.path.exists(filename):
music_file_list.append(filename)
elif os.path.exists(os.path.join(main_dir, filename)):
music_file_list.append(os.path.join(main_dir, filename))
elif os.path.exists(os.path.join(data_dir, filename)):
music_file_list.append(os.path.join(data_dir, filename))
else:
print("file not found")
return False
print(f"{filename} added to file list")
return True
def play_file(filename):
"""
This function will call add_file and play it if successful
The music will fade in during the first 4 seconds
set_endevent is used to post a MUSIC_DONE event when the song finishes
The main loop will call play_next() when the MUSIC_DONE event is received
"""
global starting_pos
if add_file(filename):
try: # we must do this in case the file is not a valid audio file
pg.mixer.music.load(music_file_list[-1])
except pg.error as e:
print(e) # print description such as 'Not an Ogg Vorbis audio stream'
if filename in music_file_list:
music_file_list.remove(filename)
print(f"{filename} removed from file list")
return
pg.mixer.music.play(fade_ms=4000)
pg.mixer.music.set_volume(volume)
if filename.rpartition(".")[2].lower() in music_can_seek:
print("file supports seeking")
starting_pos = 0
else:
print("file does not support seeking")
starting_pos = -1
pg.mixer.music.set_endevent(MUSIC_DONE)
def play_next():
"""
This function will play the next song in music_file_list
It uses pop(0) to get the next song and then appends it to the end of the list
The song will fade in during the first 4 seconds
"""
global starting_pos
if len(music_file_list) > 1:
nxt = music_file_list.pop(0)
try:
pg.mixer.music.load(nxt)
except pg.error as e:
print(e)
print(f"{nxt} removed from file list")
music_file_list.append(nxt)
print("starting next song: ", nxt)
else:
nxt = music_file_list[0]
pg.mixer.music.play(fade_ms=4000)
pg.mixer.music.set_volume(volume)
pg.mixer.music.set_endevent(MUSIC_DONE)
if nxt.rpartition(".")[2].lower() in music_can_seek:
starting_pos = 0
else:
starting_pos = -1
def draw_text_line(text, y=0):
"""
Draws a line of text onto the display surface
The text will be centered horizontally at the given y position
The text's height is added to y and returned to the caller
"""
screen = pg.display.get_surface()
surf = font.render(text, 1, (255, 255, 255))
y += surf.get_height()
x = (screen.get_width() - surf.get_width()) / 2
screen.blit(surf, (x, y))
return y
def change_music_position(amount):
"""
Changes current playback position by amount seconds.
This only works with OGG and MP3 files.
music.get_pos() returns how many milliseconds the song has played, not
the current position in the file. We must track the starting position
ourselves. music.set_pos() will set the position in seconds.
"""
global starting_pos
if starting_pos >= 0: # will be -1 unless play_file() was OGG or MP3
played_for = pg.mixer.music.get_pos() / 1000.0
old_pos = starting_pos + played_for
starting_pos = old_pos + amount
pg.mixer.music.play(start=starting_pos)
print(f"jumped from {old_pos} to {starting_pos}")
MUSIC_DONE = pg.event.custom_type() # event to be set as mixer.music.set_endevent()
main_dir = os.path.split(os.path.abspath(__file__))[0]
data_dir = os.path.join(main_dir, "data")
starting_pos = 0 # needed to fast forward and rewind
volume = 0.75
music_file_list: List[str] = []
music_file_types = ("mp3", "ogg", "mid", "mod", "it", "xm", "wav")
music_can_seek = ("mp3", "ogg", "mod", "it", "xm")
def main():
global font # this will be used by the draw_text_line function
global volume, starting_pos
running = True
paused = False
# we will be polling for key up and key down events
# users should be able to change the volume by holding the up and down arrows
# the change_volume variable will be set by key down events and cleared by key up events
change_volume = 0
pg.init()
pg.display.set_mode((640, 480))
font = pg.font.SysFont("Arial", 24)
clock = pg.time.Clock()
pg.scrap.init()
pg.SCRAP_TEXT = pg.scrap.get_types()[0] # TODO remove when scrap module is fixed
scrap_get = pg.scrap.get(pg.SCRAP_TEXT)
clipped = "" if scrap_get is None else scrap_get.decode("UTF-8")
# store the current text from the clipboard TODO remove decode
# add the command line arguments to the music_file_list
for arg in sys.argv[1:]:
add_file(arg)
play_file("house_lo.ogg") # play default music included with pygame
# draw instructions on screen
y = draw_text_line("Drop music files or path names onto this window", 20)
y = draw_text_line("Copy file names into the clipboard", y)
y = draw_text_line("Or feed them from the command line", y)
y = draw_text_line("If it's music it will play!", y)
y = draw_text_line("SPACE to pause or UP/DOWN to change volume", y)
y = draw_text_line("LEFT and RIGHT will skip around the track", y)
draw_text_line("Other keys will start the next track", y)
"""
This is the main loop
It will respond to drag and drop, clipboard changes, and key presses
"""
while running:
for ev in pg.event.get():
if ev.type == pg.QUIT:
running = False
elif ev.type == pg.DROPTEXT:
play_file(ev.text)
elif ev.type == pg.DROPFILE:
play_file(ev.file)
elif ev.type == MUSIC_DONE:
play_next()
elif ev.type == pg.KEYDOWN:
if ev.key == pg.K_ESCAPE:
running = False # exit loop
elif ev.key in (pg.K_SPACE, pg.K_RETURN):
if paused:
pg.mixer.music.unpause()
paused = False
else:
pg.mixer.music.pause()
paused = True
elif ev.key == pg.K_UP:
change_volume = VOLUME_CHANGE_AMOUNT
elif ev.key == pg.K_DOWN:
change_volume = -VOLUME_CHANGE_AMOUNT
elif ev.key == pg.K_RIGHT:
change_music_position(+5)
elif ev.key == pg.K_LEFT:
change_music_position(-5)
else:
play_next()
elif ev.type == pg.KEYUP:
if ev.key in (pg.K_UP, pg.K_DOWN):
change_volume = 0
# is the user holding up or down?
if change_volume:
volume += change_volume
volume = min(max(0, volume), 1) # volume should be between 0 and 1
pg.mixer.music.set_volume(volume)
print("volume:", volume)
# TODO remove decode when SDL2 scrap is fixed
scrap_get = pg.scrap.get(pg.SCRAP_TEXT)
new_text = "" if scrap_get is None else scrap_get.decode("UTF-8")
if new_text != clipped: # has the clipboard changed?
clipped = new_text
play_file(clipped) # try to play the file if it has
pg.display.flip()
clock.tick(9) # keep CPU use down by updating screen less often
pg.quit()
if __name__ == "__main__":
main()