class WinampApplication(displayio.Group): """ WinampApplication Helper class that manages song playback and UI components. :param playlist_file: json file containing the playlist of songs :param skin_image: BMP image file for skin background :param skin_config_file: json file containing color values :param pyportal_titano: boolean value. True if using Titano, False otherwise. """ STATE_PLAYING = 0 STATE_PAUSED = 1 # pylint: disable=too-many-statements,too-many-branches def __init__( self, playlist_file="playlist.json", skin_image="/base_240x320.bmp", skin_config_file="base_config.json", pyportal_titano=False, ): self.SKIN_IMAGE = skin_image self.SKIN_CONFIG_FILE = skin_config_file self.PLAYLIST_FILE = playlist_file # read the skin config data into variable f = open(self.SKIN_CONFIG_FILE, "r") self.CONFIG_DATA = json.loads(f.read()) f.close() if self.PLAYLIST_FILE: try: # read the playlist data into variable f = open(self.PLAYLIST_FILE, "r") self.PLAYLIST = json.loads(f.read()) f.close() except OSError: # file not found self.auto_find_tracks() except ValueError: # json parse error self.auto_find_tracks() else: # playlist file argument was None self.auto_find_tracks() if self.PLAYLIST: try: if len(self.PLAYLIST["playlist"]["files"]) == 0: # valid playlist json data, but no tracks self.auto_find_tracks() except KeyError: self.auto_find_tracks() # initialize clock display self.clock_display = ClockDisplay( text_color=self.CONFIG_DATA["time_color"]) if not pyportal_titano: # standard PyPortal and pynt clock display location # and playlist display parameters self.clock_display.x = 44 self.clock_display.y = 22 _max_playlist_display_chars = 30 _rows = 3 else: # PyPortal Titano clock display location # and playlist display parameters self.clock_display.x = 65 self.clock_display.y = 37 _max_playlist_display_chars = 42 _rows = 4 # initialize playlist display self.playlist_display = PlaylistDisplay( text_color=self.CONFIG_DATA["text_color"], max_chars=_max_playlist_display_chars, rows=_rows, ) if not pyportal_titano: # standard PyPortal and pynt playlist display location self.playlist_display.x = 13 self.playlist_display.y = 234 else: # PyPortal Titano playlist display location self.playlist_display.x = 20 self.playlist_display.y = 354 # set playlist into playlist display self.playlist_display.from_files_list( self.PLAYLIST["playlist"]["files"]) self.playlist_display.current_track_number = 1 # get name of current song self.current_song_file_name = self.PLAYLIST["playlist"]["files"][ self.playlist_display.current_track_number - 1] if not pyportal_titano: # standard PyPortal and pynt max characters for track title _max_chars = 22 else: # PyPortal Titano max characters for track title _max_chars = 29 # initialize ScrollingLabel for track name self.current_song_lbl = scrolling_label.ScrollingLabel( terminalio.FONT, text=self.playlist_display.current_track_title, color=self.CONFIG_DATA["text_color"], max_characters=_max_chars, ) self.current_song_lbl.anchor_point = (0, 0) if not pyportal_titano: # standard PyPortal and pynt track title location self.current_song_lbl.anchored_position = (98, 19) else: # PyPortal Titano track title location self.current_song_lbl.anchored_position = (130, 33) # Setup the skin image file as the bitmap data source self.background_bitmap = displayio.OnDiskBitmap(self.SKIN_IMAGE) # Create a TileGrid to hold the bitmap self.background_tilegrid = displayio.TileGrid( self.background_bitmap, pixel_shader=self.background_bitmap.pixel_shader) # initialize parent displayio.Group super().__init__() # Add the TileGrid to the Group self.append(self.background_tilegrid) # add other UI componenets self.append(self.current_song_lbl) self.append(self.clock_display) self.append(self.playlist_display) # Start playing first track self.current_song_file = open(self.current_song_file_name, "rb") self.decoder = MP3Decoder(self.current_song_file) self.audio = AudioOut(board.SPEAKER) self.audio.play(self.decoder) self.CURRENT_STATE = self.STATE_PLAYING # behavior variables. self._start_time = time.monotonic() self._cur_time = time.monotonic() self._pause_time = None self._pause_elapsed = 0 self._prev_time = None self._seconds_elapsed = 0 self._last_increment_time = 0 def auto_find_tracks(self): """ Initialize the song_list by searching for all MP3's within two layers of directories on the SDCard. e.g. It will find all of: /sd/Amazing Song.mp3 /sd/[artist_name]/Amazing Song.mp3 /sd/[artist_name]/[album_name]/Amazing Song.mp3 but won't find: /sd/my_music/[artist_name]/[album_name]/Amazing Song.mp3 :return: None """ # list that holds all files in the root of SDCard _root_sd_all_files = os.listdir("/sd/") # list that will hold all directories in the root of the SDCard. _root_sd_dirs = [] # list that will hold all subdirectories inside of root level directories _second_level_dirs = [] # list that will hold all MP3 file songs that we find _song_list = [] # loop over all files found on SDCard for _file in _root_sd_all_files: try: # Check if the current file is a directory os.listdir("/sd/{}".format(_file)) # add it to a list to look at later _root_sd_dirs.append(_file) except OSError: # current file was not a directory, nothing to do. pass # if current file is an MP3 file if _file.endswith(".mp3"): # we found an MP3 file, add it to the list that will become our playlist _song_list.append("/sd/{}".format(_file)) # loop over root level directories for _dir in _root_sd_dirs: # loop over all files inside of root level directory for _file in os.listdir("/sd/{}".format(_dir)): # check if current file is a directory try: # if it is a directory, loop over all files inside of it for _inner_file in os.listdir("/sd/{}/{}".format( _dir, _file)): # check if inner file is an MP3 if _inner_file.endswith(".mp3"): # we found an MP3 file, add it to the list that will become our playlist _song_list.append("/sd/{}/{}/{}".format( _dir, _file, _inner_file)) except OSError: # current file is not a directory pass # if the current file is an MP3 file if _file.endswith(".mp3"): # we found an MP3 file, add it to the list that will become our playlist _song_list.append("/sd/{}/{}".format(_dir, _file)) # format the songs we found into the PLAYLIST data structure self.PLAYLIST = {"playlist": {"files": _song_list}} # print message to user letting them know we auto-generated the playlist print("Auto Generated Playlist from MP3's found on SDCard:") print(json.dumps(self.PLAYLIST)) def update(self): """ Must be called each iteration from the main loop. Responsible for updating all sub UI components and managing song playback :return: None """ self._cur_time = time.monotonic() if self.CURRENT_STATE == self.STATE_PLAYING: # if it's time to increase the time on the ClockDisplay if self._cur_time >= self._last_increment_time + 1: # increase ClockDisplay by 1 second self._seconds_elapsed += 1 self._last_increment_time = self._cur_time self.clock_display.seconds = int(self._seconds_elapsed) # update the track label (scrolling) self.current_song_lbl.update() if self.CURRENT_STATE == self.STATE_PLAYING: # if we are supposed to be playing but aren't # it means the track ended. if not self.audio.playing: # start the next track self.next_track() # store time for comparison later self._prev_time = self._cur_time def play_current_track(self): """ Update the track label and begin playing the song for current track in the playlist. :return: None """ # set the track title self.current_song_lbl.full_text = self.playlist_display.current_track_title # save start time in a variable self._start_time = self._cur_time # if previous song is still playing if self.audio.playing: # stop playing self.audio.stop() # close previous song file self.current_song_file.close() # open new song file self.current_song_file_name = self.PLAYLIST["playlist"]["files"][ self.playlist_display.current_track_number - 1] self.current_song_file = open(self.current_song_file_name, "rb") self.decoder.file = self.current_song_file # play new song file self.audio.play(self.decoder) # if user paused the playback if self.CURRENT_STATE == self.STATE_PAUSED: # pause so it's loaded, and ready to resume self.audio.pause() def next_track(self): """ Advance to the next track. :return: None """ # reset ClockDisplay to 0 self._seconds_elapsed = 0 self.clock_display.seconds = int(self._seconds_elapsed) # increment current track number self.playlist_display.current_track_number += 1 try: # start playing track self.play_current_track() except OSError as e: # file not found print("Error playing: {}".format(self.current_song_file_name)) print(e) self.next_track() return def previous_track(self): """ Go back to previous track. :return: None """ # reset ClockDisplay to 0 self._seconds_elapsed = 0 self.clock_display.seconds = int(self._seconds_elapsed) # decrement current track number self.playlist_display.current_track_number -= 1 try: # start playing track self.play_current_track() except OSError as e: # file not found print("Error playing: {}".format(self.current_song_file_name)) print(e) self.previous_track() return def pause(self): """ Stop playing song and wait until resume function. :return: None """ if self.audio.playing: self.audio.pause() self.CURRENT_STATE = self.STATE_PAUSED def resume(self): """ Resume playing song after having been paused. :return: None """ self._last_increment_time = self._cur_time if self.audio.paused: self.audio.resume() self.CURRENT_STATE = self.STATE_PLAYING
if mode is "laughing": if switch_timer == 0: switch_timer = time.monotonic() switching_to_mode = "waiting" if switching_to_mode is "waiting" and ( time.monotonic() - switch_timer) > MODE_SWITCH_TIME: print("Switch mode on light to waiting: ", light.value) mode = "waiting" new_mode = True switch_timer = 0 if mode is "waiting": if new_mode is True: display.brightness = 0 if USE_AUDIO is True: audio.stop() display.refresh() new_mode = False if mode is "laughing": if new_mode is True: display.brightness = 1.0 if USE_AUDIO is True: audio.play(decoder, loop=True) display.refresh() new_mode = False # Make the skull laugh open/close once if USE_ANIMATION is True: if (time.monotonic() - last_frame_time) > LAUGHING_SPEED: current_frame = (current_frame + 1) % 4
try: from audioio import AudioOut except ImportError: try: from audiopwmio import PWMAudioOut as AudioOut except ImportError: pass # not always supported by every board! FREQUENCY = 440 # 440 Hz middle 'A' SAMPLERATE = 8000 # 8000 samples/second, recommended! # Generate one period of sine wav. length = SAMPLERATE // FREQUENCY sine_wave = array.array("H", [0] * length) for i in range(length): sine_wave[i] = int(math.sin(math.pi * 2 * i / 18) * (2**15) + 2**15) # Enable the speaker speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) speaker_enable.direction = digitalio.Direction.OUTPUT speaker_enable.value = True audio = AudioOut(board.SPEAKER) sine_wave_sample = RawSample(sine_wave) audio.play(sine_wave_sample, loop=True) # Keep playing the sample over and over time.sleep(1) # until... audio.stop() # We tell the board to stop
try: from audiopwmio import PWMAudioOut as AudioOut except ImportError: pass # not always supported by every board! FREQUENCY = 440 # 440 Hz middle 'A' SAMPLERATE = 8000 # 8000 samples/second, recommended! # Generate one period of sine wav. length = SAMPLERATE // FREQUENCY sine_wave = array.array("H", [0] * length) for i in range(length): sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2**15) + 2**15) # Enable the speaker speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) speaker_enable.direction = digitalio.Direction.OUTPUT speaker_enable.value = True audio = AudioOut(board.SPEAKER) sine_wave_sample = RawSample(sine_wave) # A single sine wave sample is hundredths of a second long. If you set loop=False, it will play # a single instance of the sample (a quick burst of sound) and then silence for the rest of the # duration of the time.sleep(). If loop=True, it will play the single instance of the sample # continuously for the duration of the time.sleep(). audio.play(sine_wave_sample, loop=True) # Play the single sine_wave sample continuously... time.sleep(1) # for the duration of the sleep (in seconds) audio.stop() # and then stop.
stereo_dac = AudioOut(board.A0, right_channel=board.A1, quiescent_value=0) frames = [locals()[f"frame_{i}"] for i in range(18)] buffers = [] for c, points in enumerate(frames): print(f"Building frame {c}") print(f"Number of array cells {((len(points) - 1) * samples) * 2}") print(f"Num lines {len(points) - 1}") line_array = array("H", [0] * ((len(points) - 1) * samples * 2)) i = 0 for pt1, pt2 in pairwise(points): pt1 = int(pt1[0] * 65535 / 400), int(pt1[1] * 65535 / 400) pt2 = int(pt2[0] * 65535 / 400), int(pt2[1] * 65535 / 400) temp = sum(gen_line(pt1, pt2), ()) for val in temp: line_array[i] = val i += 1 buffers.append( RawSample(line_array, channel_count=2, sample_rate=1_000_000)) i = 0 while True: stereo_dac.play(buffers[i], loop=True) sleep(0.08) stereo_dac.stop() i = (i + 1) % len(buffers)