Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
        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.
Ejemplo n.º 5
0
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)