예제 #1
0
    def play_song(self):
        """Play the next song from the play list (or --file argument)."""

        # get the next song to play
        self.get_song()

        # load custom configuration from file
        self.load_custom_config()

        # Initialize Lights
        self.network.set_playing()
        hc.initialize()

        # Handle the pre/post show
        play_now = int(cm.get_state('play_now', "0"))

        self.network.unset_playing()

        if not play_now:
            result = PrePostShow('preshow', hc).execute()

            if result == PrePostShow.play_now_interrupt:
                play_now = int(cm.get_state('play_now', "0"))

        self.network.set_playing()

        # Ensure play_now is reset before beginning playback
        if play_now:
            cm.update_state('play_now', "0")
            play_now = 0

        # setup audio file and output device
        self.setup_audio()

        # setup our cache_matrix, std, mean
        self.setup_cache()

        matrix_buffer = deque([], 1000)

        # Process audio song_filename
        row = 0
        data = self.music_file.readframes(self.chunk_size)

        if args.createcache:
            total_frames = self.music_file.getnframes() / 100

            counter = 0
            percentage = 0

            while data != '':
                # Compute FFT in this chunk, and cache results
                matrix = self.fft_calc.calculate_levels(data)

                # Add the matrix to the end of the cache
                self.cache_matrix = np.vstack([self.cache_matrix, matrix])
                data = self.music_file.readframes(self.chunk_size)

                if counter > total_frames:
                    percentage += 1
                    counter = 0

                counter += self.chunk_size

                sys.stdout.write("\rGenerating sync file for :%s %d%%" %
                                 (self.song_filename, percentage))
                sys.stdout.flush()

            sys.stdout.write("\rGenerating sync file for :%s %d%%" %
                             (self.song_filename, 100))
            sys.stdout.flush()

            data = ''
            self.cache_found = False
            play_now = False
            print "\nsaving sync file"

        while data != '' and not play_now:
            # output data to sound device
            self.output(data)

            # Control lights with cached timing values if they exist
            matrix = None
            if self.cache_found and args.readcache:
                if row < len(self.cache_matrix):
                    matrix = self.cache_matrix[row]
                else:
                    log.warning(
                        "Ran out of cached FFT values, will update the cache.")
                    self.cache_found = False

            if matrix is None:
                # No cache - Compute FFT in this chunk, and cache results
                matrix = self.fft_calc.calculate_levels(data)

                # Add the matrix to the end of the cache
                self.cache_matrix = np.vstack([self.cache_matrix, matrix])

            matrix_buffer.appendleft(matrix)

            if len(matrix_buffer) > self.light_delay:
                matrix = matrix_buffer[self.light_delay]
                self.update_lights(matrix)

            # Read next chunk of data from music song_filename
            data = self.music_file.readframes(self.chunk_size)
            row += 1

            # Load new application state in case we've been interrupted
            cm.load_state()
            play_now = int(cm.get_state('play_now', "0"))

        if not self.cache_found and not play_now:
            self.save_cache()

        # Cleanup the pifm process
        if cm.fm.enabled:
            self.fm_process.kill()

        # check for postshow
        self.network.unset_playing()

        if not play_now:
            PrePostShow('postshow', hc).execute()

        # We're done, turn it all off and clean up things ;)
        hc.clean_up()
예제 #2
0
def play_song():
    """Play the next song from the play list (or --file argument)."""

    # get the next song to play
    song_filename, config_filename, cache_filename = get_song()

    # load custom configuration from file
    load_custom_config(config_filename)

    # Initialize Lights
    network.set_playing()
    hc.initialize()

    # Handle the pre/post show
    play_now = int(cm.get_state('play_now', "0"))

    network.unset_playing()

    if not play_now:
        result = PrePostShow('preshow', hc).execute()

        if result == PrePostShow.play_now_interrupt:
            play_now = int(cm.get_state('play_now', "0"))

    network.set_playing()

    # Ensure play_now is reset before beginning playback
    if play_now:
        cm.update_state('play_now', "0")
        play_now = 0

    # setup audio file and output device
    output, fft_calc, music_file, light_delay = setup_audio(song_filename)

    # setup our cache_matrix, std, mean
    cache_found, cache_matrix, std, mean = setup_cache(cache_filename,
                                                       fft_calc)

    matrix_buffer = deque([], 1000)

    # Process audio song_filename
    row = 0
    data = music_file.readframes(CHUNK_SIZE)

    while data != '' and not play_now:
        # output data to sound device
        output(data)

        # Control lights with cached timing values if they exist
        matrix = None
        if cache_found and args.readcache:
            if row < len(cache_matrix):
                matrix = cache_matrix[row]
            else:
                log.warning(
                    "Ran out of cached FFT values, will update the cache.")
                cache_found = False

        if matrix is None:
            # No cache - Compute FFT in this chunk, and cache results
            matrix = fft_calc.calculate_levels(data)

            # Add the matrix to the end of the cache
            cache_matrix = np.vstack([cache_matrix, matrix])

        matrix_buffer.appendleft(matrix)

        if len(matrix_buffer) > light_delay:
            matrix = matrix_buffer[light_delay]
            update_lights(matrix, mean, std)

        # Read next chunk of data from music song_filename
        data = music_file.readframes(CHUNK_SIZE)
        row += 1

        # Load new application state in case we've been interrupted
        cm.load_state()
        play_now = int(cm.get_state('play_now', "0"))

    if not cache_found and not play_now:
        save_cache(cache_matrix, cache_filename, fft_calc)

    # Cleanup the pifm process
    if cm.audio_processing.fm:
        fm_process.kill()

    # check for postshow
    network.unset_playing()

    if not play_now:
        PrePostShow('postshow', hc).execute()

    # We're done, turn it all off and clean up things ;)
    hc.clean_up()
def play_song():
    """Play the next song from the play list (or --file argument)."""
    song_to_play = int(cm.get_state('song_to_play', 0))
    play_now = int(cm.get_state('play_now', 0))

    # Arguments
    parser = argparse.ArgumentParser()
    filegroup = parser.add_mutually_exclusive_group()
    filegroup.add_argument('--playlist',
                           default=_PLAYLIST_PATH,
                           help='Playlist to choose song from.')
    filegroup.add_argument('--file',
                           help='path to the song to play (required if no'
                           'playlist is designated)')
    parser.add_argument(
        '--readcache',
        type=int,
        default=1,
        help='read light timing from cache if available. Default: true')
    args = parser.parse_args()

    # Make sure one of --playlist or --file was specified
    if args.file == None and args.playlist == None:
        print "One of --playlist or --file must be specified"
        sys.exit()

    # Handle the pre/post show
    if not play_now:
        result = PrePostShow('preshow').execute()
        # now unused.  if play_now = True
        # song to play is always the first song in the playlist

        if result == PrePostShow.play_now_interrupt:
            play_now = int(cm.get_state('play_now', 0))

    # Initialize Lights
    hc.initialize()

    # Determine the next file to play
    song_filename = args.file
    if args.playlist != None and args.file == None:
        most_votes = [None, None, []]
        current_song = None
        with open(args.playlist, 'rb') as playlist_fp:
            fcntl.lockf(playlist_fp, fcntl.LOCK_SH)
            playlist = csv.reader(playlist_fp, delimiter='\t')
            songs = []
            for song in playlist:
                if len(song) < 2 or len(song) > 4:
                    logging.error(
                        'Invalid playlist.  Each line should be in the form: '
                        '<song name><tab><path to song>')
                    sys.exit()
                elif len(song) == 2:
                    song.append(set())
                else:
                    song[2] = set(song[2].split(','))
                    if len(song) == 3 and len(song[2]) >= len(most_votes[2]):
                        most_votes = song
                songs.append(song)
            fcntl.lockf(playlist_fp, fcntl.LOCK_UN)

        if most_votes[0] != None:
            logging.info("Most Votes: " + str(most_votes))
            current_song = most_votes

            # Update playlist with latest votes
            with open(args.playlist, 'wb') as playlist_fp:
                fcntl.lockf(playlist_fp, fcntl.LOCK_EX)
                writer = csv.writer(playlist_fp, delimiter='\t')
                for song in songs:
                    if current_song == song and len(song) == 3:
                        song.append("playing!")
                    if len(song[2]) > 0:
                        song[2] = ",".join(song[2])
                    else:
                        del song[2]
                writer.writerows(songs)
                fcntl.lockf(playlist_fp, fcntl.LOCK_UN)

        else:
            # Get a "play now" requested song
            if play_now > 0 and play_now <= len(songs):
                current_song = songs[play_now - 1]
            # Get random song
            elif _RANDOMIZE_PLAYLIST:
                # Use python's random.randrange() to get a random song
                current_song = songs[random.randrange(0, len(songs))]

            # Play next song in the lineup
            else:
                song_to_play = song_to_play if (
                    song_to_play <= len(songs) - 1) else 0
                current_song = songs[song_to_play]
                next_song = (song_to_play + 1) if (
                    (song_to_play + 1) <= len(songs) - 1) else 0
                cm.update_state('song_to_play', next_song)

        # Get filename to play and store the current song playing in state cfg
        song_filename = current_song[1]
        cm.update_state('current_song', songs.index(current_song))

    song_filename = song_filename.replace("$SYNCHRONIZED_LIGHTS_HOME",
                                          cm.HOME_DIR)

    # Ensure play_now is reset before beginning playback
    if play_now:
        cm.update_state('play_now', 0)
        play_now = 0

    # Initialize FFT stats
    matrix = [0 for _ in range(hc.GPIOLEN)]

    # Set up audio
    if song_filename.endswith('.wav'):
        musicfile = wave.open(song_filename, 'r')
    else:
        musicfile = decoder.open(song_filename)

    sample_rate = musicfile.getframerate()
    num_channels = musicfile.getnchannels()

    if _usefm == 'true':
        logging.info("Sending output as fm transmission")

        with open(os.devnull, "w") as dev_null:
            # play_stereo is always True as coded, Should it be changed to
            # an option in the config file?
            fm_process = subprocess.Popen(["sudo",
                                           cm.HOME_DIR + "/bin/pifm",
                                           "-",
                                           str(frequency),
                                           "44100",
                                           "stereo" if play_stereo else "mono"],\
                                               stdin=music_pipe_r,\
                                                   stdout=dev_null)
    else:
        output = aa.PCM(aa.PCM_PLAYBACK, aa.PCM_NORMAL)
        output.setchannels(num_channels)
        output.setrate(sample_rate)
        output.setformat(aa.PCM_FORMAT_S16_LE)
        output.setperiodsize(CHUNK_SIZE)

    logging.info("Playing: " + song_filename + " (" +
                 str(musicfile.getnframes() / sample_rate) + " sec)")
    # Output a bit about what we're about to play to the logs
    song_filename = os.path.abspath(song_filename)

    # create empty array for the cache_matrix
    cache_matrix = np.empty(shape=[0, hc.GPIOLEN])
    cache_found = False
    cache_filename = \
        os.path.dirname(song_filename) + "/." + os.path.basename(song_filename) + ".sync"

    # The values 12 and 1.5 are good estimates for first time playing back
    # (i.e. before we have the actual mean and standard deviations
    # calculated for each channel).
    mean = [12.0 for _ in range(hc.GPIOLEN)]
    std = [1.5 for _ in range(hc.GPIOLEN)]

    if args.readcache:
        # Read in cached fft
        try:
            # load cache from file using numpy loadtxt
            cache_matrix = np.loadtxt(cache_filename)
            cache_found = True

            # get std from matrix / located at index 0
            std = np.array(cache_matrix[0])

            # get mean from matrix / located at index 1
            mean = np.array(cache_matrix[1])

            # delete mean and std from the array
            cache_matrix = np.delete(cache_matrix, (0), axis=0)
            cache_matrix = np.delete(cache_matrix, (0), axis=0)

            logging.debug("std: " + str(std) + ", mean: " + str(mean))
        except IOError:
            logging.warn("Cached sync data song_filename not found: '" +
                         cache_filename + ".  One will be generated.")

    # Process audio song_filename
    row = 0
    data = musicfile.readframes(CHUNK_SIZE)
    frequency_limits = calculate_channel_frequency(
        _MIN_FREQUENCY, _MAX_FREQUENCY, _CUSTOM_CHANNEL_MAPPING,
        _CUSTOM_CHANNEL_FREQUENCIES)

    while data != '' and not play_now:
        if _usefm == 'true':
            os.write(music_pipe_w, data)
        else:
            output.write(data)

        # Control lights with cached timing values if they exist
        matrix = None
        if cache_found and args.readcache:
            if row < len(cache_matrix):
                matrix = cache_matrix[row]
            else:
                logging.warning(
                    "Ran out of cached FFT values, will update the cache.")
                cache_found = False

        if matrix == None:
            # No cache - Compute FFT in this chunk, and cache results
            matrix = fft.calculate_levels(data, CHUNK_SIZE, sample_rate,
                                          frequency_limits, hc.GPIOLEN)

            # Add the matrix to the end of the cache
            cache_matrix = np.vstack([cache_matrix, matrix])

        update_lights(matrix, mean, std)

        # Read next chunk of data from music song_filename
        data = musicfile.readframes(CHUNK_SIZE)
        row = row + 1

        # Load new application state in case we've been interrupted
        cm.load_state()
        play_now = int(cm.get_state('play_now', 0))

    if not cache_found:
        # Compute the standard deviation and mean values for the cache
        for i in range(0, hc.GPIOLEN):
            std[i] = np.std([item for item in cache_matrix[:, i] if item > 0])
            mean[i] = np.mean(
                [item for item in cache_matrix[:, i] if item > 0])

        # Add mean and std to the top of the cache
        cache_matrix = np.vstack([mean, cache_matrix])
        cache_matrix = np.vstack([std, cache_matrix])

        # Save the cache using numpy savetxt
        np.savetxt(cache_filename, cache_matrix)

        logging.info("Cached sync data written to '." + cache_filename +
                     "' [" + str(len(cache_matrix)) + " rows]")

    # Cleanup the pifm process
    if _usefm == 'true':
        fm_process.kill()

    # check for postshow
    done = PrePostShow('postshow').execute()

    # We're done, turn it all off and clean up things ;)
    hc.clean_up()