Exemple #1
0
def check_state():
    """Check State file

    Check the state file to see if play now requested
    """
    # refresh state
    cm.load_state()
    if int(cm.get_state('play_now', "0")):
        # play now requested!
        return True
    return False
def check_state():
    """Check State file

    Check the state file to see if play now requested
    """
    # refresh state
    cm.load_state()
    if int(cm.get_state('play_now', "0")):
        # play now requested!
        return True
    return False
def execute_preshow(config):
    '''Execute the "Preshow" for the given preshow configuration'''
    for transition in config['transitions']:
        start = time.time()
        if transition['type'].lower() == 'on':
            hc.turn_on_lights(True)
        else:
            hc.turn_off_lights(True)
        logging.debug('Transition to ' + transition['type'] + ' for '
            + str(transition['duration']) + ' seconds')
        while transition['duration'] > (time.time() - start):
            cm.load_state()  # Force a refresh of state from file
            play_now = int(cm.get_state('play_now', 0))
            if play_now:
                return  # Skip out on the rest of the preshow

            # Check once every ~ .1 seconds to break out
            time.sleep(0.1)
def execute_preshow(config):
    '''Execute the "Preshow" for the given preshow configuration'''
    for transition in config['transitions']:
        start = time.time()
        if transition['type'].lower() == 'on':
            hc.turn_on_lights(True)
        else:
            hc.turn_off_lights(True)
        logging.debug('Transition to ' + transition['type'] + ' for ' +
                      str(transition['duration']) + ' seconds')
        while transition['duration'] > (time.time() - start):
            cm.load_state()  # Force a refresh of state from file
            play_now = int(cm.get_state('play_now', 0))
            if play_now:
                return  # Skip out on the rest of the preshow

            # Check once every ~ .1 seconds to break out
            time.sleep(0.1)
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()

    # Initialize Lights
    hc.initialize()

    # Handle the pre-show
    if not play_now:
        result = Preshow().execute()
        if result == Preshow.PlayNowInterrupt:
            play_now = True

    # 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:
                current_song = songs[random.randint(0, len(songs) - 1)]
            # 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:
            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)
    

    cache = []
    cache_found = False
    cache_filename = os.path.dirname(song_filename) + "/." + os.path.basename(song_filename) \
        + ".sync.gz"
    # 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:
            with gzip.open(cache_filename, 'rb') as playlist_fp:
                cachefile = csv.reader(playlist_fp, delimiter=',')
                for row in cachefile:
                    cache.append([0.0 if np.isinf(float(item)) else float(item) for item in row])
                cache_found = True
                # TODO(todd): Optimize this and / or cache it to avoid delay here
                cache_matrix = np.array(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])
                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 = cache[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)
            cache.append(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:
        with gzip.open(cache_filename, 'wb') as playlist_fp:
            writer = csv.writer(playlist_fp, delimiter=',')
            writer.writerows(cache)
            logging.info("Cached sync data written to '." + cache_filename
                         + "' [" + str(len(cache)) + " rows]")

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

    # We're done, turn it all off and clean up things ;)
    hc.clean_up()
def main():
    '''main'''
    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()

    # Log everything to our log file
    # TODO(todd): Add logging configuration options.
    logging.basicConfig(filename=cm.LOG_DIR + '/music_and_lights.play.dbg',
                        format='[%(asctime)s] %(levelname)s {%(pathname)s:%(lineno)d}'
                        ' - %(message)s',
                        level=logging.DEBUG)

    # 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()

    # Initialize Lights
    hc.initialize()

    # Only execute preshow if no specific song has been requested to be played right now
    if not play_now:
        execute_preshow(cm.lightshow()['preshow'])

    # 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 random song
            if _RANDOMIZE_PLAYLIST:
                current_song = songs[random.randint(0, len(songs) - 1)]
            # Get a "play now" requested song
            elif play_now > 0 and play_now <= len(songs):
                current_song = songs[play_now - 1]
            # 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)]
    offct = [0 for _ in range(hc.GPIOLEN)]

    # Build the limit list
    if len(_LIMIT_LIST) == 1:
        limit = [_LIMIT_LIST[0] for _ in range(hc.GPIOLEN)]
    else:
        limit = _LIMIT_LIST

    # 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()
    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)

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

    cache = []
    cache_found = False
    cache_filename = os.path.dirname(song_filename) + "/." + os.path.basename(song_filename) \
        + ".sync.gz"
    # 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:
            with gzip.open(cache_filename, 'rb') as playlist_fp:
                cachefile = csv.reader(playlist_fp, delimiter=',')
                for row in cachefile:
                    cache.append([0.0 if np.isinf(float(item)) else float(item) for item in row])
                cache_found = True
                # TODO(todd): Optimize this and / or cache it to avoid delay here
                cache_matrix = np.array(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])
                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:
        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 = cache[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 = calculate_levels(data, sample_rate, frequency_limits)
            cache.append(matrix)


        # blank out the display
        led.fill(Color(0,0,0),0,151)
        for i in range(0, hc.GPIOLEN):
            if hc.is_pin_pwm(i):
                # Output pwm, where off is at 0.5 std below the mean
                # and full on is at 0.75 std above the mean.

                display_column(i,matrix[i])

                #brightness = matrix[i] - mean[i] + 0.5 * std[i]
                #brightness = brightness / (1.25 * std[i])
                #if brightness > 1.0:
                    #brightness = 1.0
                #if brightness < 0:
                    #brightness = 0
                #hc.turn_on_light(i, True, int(brightness * 60))
            else:
                if limit[i] < matrix[i] * _LIMIT_THRESHOLD:
                    limit[i] = limit[i] * _LIMIT_THRESHOLD_INCREASE
                    logging.debug("++++ channel: {0}; limit: {1:.3f}".format(i, limit[i]))
                # Amplitude has reached threshold
                if matrix[i] > limit[i]:
                    hc.turn_on_light(i, True)
                    offct[i] = 0
                else:  # Amplitude did not reach threshold
                    offct[i] = offct[i] + 1
                    if offct[i] > _MAX_OFF_CYCLES:
                        offct[i] = 0
                        limit[i] = limit[i] * _LIMIT_THRESHOLD_DECREASE  # old value 0.8
                    logging.debug("---- channel: {0}; limit: {1:.3f}".format(i, limit[i]))
                    hc.turn_off_light(i, True)

        # send out data to RGB LED Strip
        led.update()
        # 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:
        with gzip.open(cache_filename, 'wb') as playlist_fp:
            writer = csv.writer(playlist_fp, delimiter=',')
            writer.writerows(cache)
            logging.info("Cached sync data written to '." + cache_filename
                         + "' [" + str(len(cache)) + " rows]")

    # We're done, turn it all off ;)
    hc.clean_up()
def main():
    '''main'''
    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()

    # Log everything to our log file
    # TODO(todd): Add logging configuration options.
    logging.basicConfig(
        filename=cm.LOG_DIR + '/music_and_lights.play.dbg',
        format='[%(asctime)s] %(levelname)s {%(pathname)s:%(lineno)d}'
        ' - %(message)s',
        level=logging.DEBUG)

    # 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()

    # Initialize Lights
    hc.initialize()

    # Only execute preshow if no specific song has been requested to be played right now
    if not play_now:
        execute_preshow(cm.lightshow()['preshow'])

    # 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 random song
            if _RANDOMIZE_PLAYLIST:
                current_song = songs[random.randint(0, len(songs) - 1)]
            # Get a "play now" requested song
            elif play_now > 0 and play_now <= len(songs):
                current_song = songs[play_now - 1]
            # 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)]
    offct = [0 for _ in range(hc.GPIOLEN)]

    # Build the limit list
    if len(_LIMIT_LIST) == 1:
        limit = [_LIMIT_LIST[0] for _ in range(hc.GPIOLEN)]
    else:
        limit = _LIMIT_LIST

    # 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()
    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)

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

    cache = []
    cache_found = False
    cache_filename = os.path.dirname(song_filename) + "/." + os.path.basename(song_filename) \
        + ".sync.gz"
    # 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:
            with gzip.open(cache_filename, 'rb') as playlist_fp:
                cachefile = csv.reader(playlist_fp, delimiter=',')
                for row in cachefile:
                    cache.append([
                        0.0 if np.isinf(float(item)) else float(item)
                        for item in row
                    ])
                cache_found = True
                # TODO(todd): Optimize this and / or cache it to avoid delay here
                cache_matrix = np.array(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])
                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:
        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 = cache[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 = calculate_levels(data, sample_rate, frequency_limits)
            cache.append(matrix)

        # blank out the display
        led.fill(Color(0, 0, 0), 0, 151)
        for i in range(0, hc.GPIOLEN):
            if hc.is_pin_pwm(i):
                # Output pwm, where off is at 0.5 std below the mean
                # and full on is at 0.75 std above the mean.

                display_column(i, matrix[i])

                #brightness = matrix[i] - mean[i] + 0.5 * std[i]
                #brightness = brightness / (1.25 * std[i])
                #if brightness > 1.0:
                #brightness = 1.0
                #if brightness < 0:
                #brightness = 0
                #hc.turn_on_light(i, True, int(brightness * 60))
            else:
                if limit[i] < matrix[i] * _LIMIT_THRESHOLD:
                    limit[i] = limit[i] * _LIMIT_THRESHOLD_INCREASE
                    logging.debug("++++ channel: {0}; limit: {1:.3f}".format(
                        i, limit[i]))
                # Amplitude has reached threshold
                if matrix[i] > limit[i]:
                    hc.turn_on_light(i, True)
                    offct[i] = 0
                else:  # Amplitude did not reach threshold
                    offct[i] = offct[i] + 1
                    if offct[i] > _MAX_OFF_CYCLES:
                        offct[i] = 0
                        limit[i] = limit[
                            i] * _LIMIT_THRESHOLD_DECREASE  # old value 0.8
                    logging.debug("---- channel: {0}; limit: {1:.3f}".format(
                        i, limit[i]))
                    hc.turn_off_light(i, True)

        # send out data to RGB LED Strip
        led.update()
        # 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:
        with gzip.open(cache_filename, 'wb') as playlist_fp:
            writer = csv.writer(playlist_fp, delimiter=',')
            writer.writerows(cache)
            logging.info("Cached sync data written to '." + cache_filename +
                         "' [" + str(len(cache)) + " rows]")

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

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

    current_playlist = playlist.Playlist(args.playlist, num_songs)

    # Initialize Lights
    hc.initialize()

    # Fork and warm the cache. Technically race prone but meh
    pool = multiprocessing.pool.Pool(processes=1)

    for (song_title,
         song_filename,
         chunk_size) in current_playlist.get_song():

        cache_proc = pool.apply_async(cache_song, [song_filename, chunk_size])

        # Handle the pre/post show
        if not play_now:
            result = prepostshow.PrePostShow('preshow', hc).execute()

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

        # TODO(mdietz): What the hell is this play_now variable really for?
        # Ensure play_now is reset before beginning playback
        if play_now:
            cm.update_state('play_now', "0")
            play_now = 0

        # Wait for the cache
        cache_proc.wait()
        mean, std, cache_matrix = cache_proc.get()

        # NOTE(mdietz): Adapt this to a standard radio, not an SDR. The SDR
        #               has a clear extra amount of delay
        light_show_delay = _CONFIG.getfloat("lightshow", "light_delay")
        logging.info("Delaying light show by %f seconds" % light_show_delay)

        audio_in_stream = audio_input.get_audio_input_handler(song_filename,
                                                              chunk_size)
        audio_out_stream = audio_output.get_audio_output_handler(
            audio_in_stream.num_channels, audio_in_stream.sample_rate,
            song_title, chunk_size)

        try:
            # Process audio
            row = 0
            start_time = time.time()
            while True:
                data = audio_in_stream.next_chunk()
                if not data or play_now:
                    break

                audio_out_stream.write(data)
                # TODO(mdietz): This actually pretty much works, but it would
                #               be nice to figure out what the actual delay
                #               time is, and also make it a config value
                # TODO(mdietz): I may be able to time the popen to first stdout
                #               from the fm proc for a dynamic delay
                if time.time() - start_time < light_show_delay:
                    continue

                matrix = cache_matrix[row]
                update_lights(matrix, mean, std)

                # Read next chunk of data from music

                # Load new application state in case we've been interrupted
                # TODO(mdietz): not the way to do this. Read from a db,
                #               accept a signal or some other OOB proc
                cm.load_state()
                play_now = int(cm.get_state('play_now', "0"))
                row += 1

            # Cleanup the fm process if there is one
        except Exception:
            logging.exception("Error in playback")
        finally:
            audio_out_stream.cleanup()

        # check for postshow
        prepostshow.PrePostShow('postshow', hc).execute()
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()
Exemple #10
0
def play_song(num_songs):
    """Play the next song from the play list (or --file argument)."""
    play_now = int(cm.get_state('play_now', "0"))

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

    current_playlist = playlist.Playlist(args.playlist, num_songs)

    # Initialize Lights
    hc.initialize()

    # Fork and warm the cache. Technically race prone but meh
    pool = multiprocessing.pool.Pool(processes=1)

    for (song_title, song_filename, chunk_size) in current_playlist.get_song():

        cache_proc = pool.apply_async(cache_song, [song_filename, chunk_size])

        # Handle the pre/post show
        if not play_now:
            result = prepostshow.PrePostShow('preshow', hc).execute()

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

        # TODO(mdietz): What the hell is this play_now variable really for?
        # Ensure play_now is reset before beginning playback
        if play_now:
            cm.update_state('play_now', "0")
            play_now = 0

        # Wait for the cache
        cache_proc.wait()
        mean, std, cache_matrix = cache_proc.get()

        # NOTE(mdietz): Adapt this to a standard radio, not an SDR. The SDR
        #               has a clear extra amount of delay
        light_show_delay = _CONFIG.getfloat("lightshow", "light_delay")
        logging.info("Delaying light show by %f seconds" % light_show_delay)

        audio_in_stream = audio_input.get_audio_input_handler(
            song_filename, chunk_size)
        audio_out_stream = audio_output.get_audio_output_handler(
            audio_in_stream.num_channels, audio_in_stream.sample_rate,
            song_title, chunk_size)

        try:
            # Process audio
            row = 0
            start_time = time.time()
            while True:
                data = audio_in_stream.next_chunk()
                if not data or play_now:
                    break

                audio_out_stream.write(data)
                # TODO(mdietz): This actually pretty much works, but it would
                #               be nice to figure out what the actual delay
                #               time is, and also make it a config value
                # TODO(mdietz): I may be able to time the popen to first stdout
                #               from the fm proc for a dynamic delay
                if time.time() - start_time < light_show_delay:
                    continue

                matrix = cache_matrix[row]
                update_lights(matrix, mean, std)

                # Read next chunk of data from music

                # Load new application state in case we've been interrupted
                # TODO(mdietz): not the way to do this. Read from a db,
                #               accept a signal or some other OOB proc
                cm.load_state()
                play_now = int(cm.get_state('play_now', "0"))
                row += 1

            # Cleanup the fm process if there is one
        except Exception:
            logging.exception("Error in playback")
        finally:
            audio_out_stream.cleanup()

        # check for postshow
        prepostshow.PrePostShow('postshow', hc).execute()