def main(): """ Empyt script template for you to use """ # initialize our hardware for use hc.initialize() # USE ONE OF THE BELOW LOOPS AS A STARTING POINT # JUST UNCOMMENT ONE AND DELETE THE OTHERS #for count in range(10): #count = 0 #while count < 10: #count += 1 # change number_of_seconds_to_run_for to the number of seconds you what # this to run for 60 is 1 minute 3600 is 1 hour #end_time = time.time() + number_of_seconds_to_run_for #while time.time() < end_time: ####<PUT ALL YOU CODE IN THIS BLOCK> # clean up and end hc.clean_up()
def main(): """Turn all the lights on for 2 minutes""" # initialize your hardware for use hc.initialize() # turn on all the lights hc.turn_on_lights() # run for 2 minutes end = time.time() + 120 # working loop will run as long as time.time() is less then "end" while time.time() < end: # try except block to catch keyboardinterrupt by user to stop try: # do nothing, just wait pass # if the user pressed <CTRL> + C to exit early break out of the loop except KeyboardInterrupt: print "\nstopped" break # This ends and cleans up everything hc.clean_up()
def main(): """ Play a message Play a recorded message for the people and go through the lights one channel at a time in order, then back down to the first """ # initialize your hardware for use hc.initialize() # start with all the lights off hc.turn_off_lights() # Before we start the lights we should start playing the audio # we have installed mpg123 to make this easy # if you do not have mpg123 installed then use this command to install it # sudo apt-get install mpg123 # now all you have to do is use the below command to play an mp3 file message_file = "/home/pi/lightshowpi/py/examples/message.mp3" message = subprocess.Popen(["mpg123", "-q", message_file]) # subprocess.Popen will open mpg123 player and play an audio file for you # and give you a few options that will come in real handy # you can stop mpg123 before the audio has finished using the instance # variable we just created by calling message.kill() # or at any point in the script you can make everything wait for the audio # to finish playing with message.wait() that could be usefull if you # ran a short seuqence like in the default preshow and your audio as longer # then your sequence and you wanted the audio to finish before continuing # and if you use message.poll() or message.returncode you could find out # if it has finished, then you might start something else or end everything # and shutdown your pi. # working loop while True: # try except block to catch keyboardinterrupt by user to stop try: hc.turn_on_lights() except KeyboardInterrupt: print "\nstopped" break # if audio playback has finished break out of the loop if message.poll() != None: break # This ends and cleans up everything hc.clean_up()
def network_client(): """Network client support If in client mode, ignore everything else and just read data from the network and blink the lights """ log.info("Network client mode starting") print "Network client mode starting..." print "press CTRL<C> to end" hc.initialize() print try: channels = network.channels channel_keys = channels.keys() while True: data = network.receive() if isinstance(data[0], int): pin = data[0] if pin in channel_keys: hc.set_light(channels[pin], True, float(data[1])) continue elif isinstance(data[0], np.ndarray): blevels = data[0] else: continue for pin in channel_keys: hc.set_light(channels[pin], True, blevels[pin]) except KeyboardInterrupt: log.info("CTRL<C> pressed, stopping") print "stopping" network.close_connection() hc.clean_up()
def main(): """ Test pattern2 Unlights one channel at a time in order """ # this is a list of all the channels you have access to lights = hc._GPIO_PINS # initialize your hardware for use hc.initialize() # start with all the lights off hc.turn_off_lights() # pause for 1 second time.sleep(2) # working loop for _ in range(50): # try except block to catch keyboardinterrupt by user to stop try: # here we just loop over the gpio pins and do something with them for light in lights: # turn on all the lights hc.turn_on_lights() # then turn off one hc.turn_off_light(light) # wait a little bit before the for loop # starts again and turns off the next light time.sleep(.4) # if the user pressed <CTRL> + C to exit early break out of the loop except KeyboardInterrupt: print "\nstopped" break # This ends and cleans up everything hc.clean_up()
def initialize_interface(): global initialized if not initialized: initialize() initialized = True
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 main(): """ PWM example Start at each end and walk to the other using pwm """ # this is a list of all the channels you have access to lights = hc._GPIO_PINS # the gpio pins in reversed order lights2 = lights[::-1] # get _PWM_MAX from the hc module # this is the max value for the pwm channels pwm_max = hc._PWM_MAX # initialize your hardware for use hc.initialize() # start with all the lights off hc.turn_off_lights() # pause for 1 second time.sleep(1) # working loop, we will do this sequence 10 times then end for _ in range(10): # here we just loop over the gpio pins and turn then on and off # with the pwm feature of lightshowpi for light in range(int(len(lights) / 2)): if hc.is_pin_pwm(lights[light]) and hc.is_pin_pwm(lights2[light]): for brightness in range(0, pwm_max): # fade in hc.turn_on_light(lights[light], 0, float(brightness) / pwm_max) hc.turn_on_light(lights2[light], 0, float(brightness) / pwm_max) time.sleep(.05 / pwm_max) for brightness in range(pwm_max - 1, -1, -1): # fade out hc.turn_on_light(lights[light], 0, float(brightness) / pwm_max) hc.turn_on_light(lights2[light], 0, float(brightness) / pwm_max) time.sleep(.05 / pwm_max) for light in range(int(len(lights) / 2) - 1, -1, -1): if hc.is_pin_pwm(lights[light]) and hc.is_pin_pwm(lights2[light]): for brightness in range(0, pwm_max): # fade in hc.turn_on_light(lights[light], 0, float(brightness) / pwm_max) hc.turn_on_light(lights2[light], 0, float(brightness) / pwm_max) time.sleep(.05 / pwm_max) for brightness in range(pwm_max - 1, -1, -1): # fade out hc.turn_on_light(lights[light], 0, float(brightness) / pwm_max) hc.turn_on_light(lights2[light], 0, float(brightness) / pwm_max) time.sleep(.05 / pwm_max) # This ends and cleans up everything hc.clean_up()
def audio_in(): """Control the lightshow from audio coming in from a USB audio card""" sample_rate = cm.lightshow()['audio_in_sample_rate'] input_channels = cm.lightshow()['audio_in_channels'] # Open the input stream from default input device audio_in_card = cm.lightshow()['audio_in_card'] stream = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, audio_in_card) stream.setchannels(input_channels) stream.setformat(aa.PCM_FORMAT_S16_LE) # Expose in config if needed stream.setrate(sample_rate) stream.setperiodsize(CHUNK_SIZE) logging.debug("Running in audio-in mode - will run until Ctrl+C " "is pressed") print "Running in audio-in mode, use Ctrl+C to stop" # Start with these as our initial guesses - will calculate a rolling # mean / std as we get input data. mean = np.array([12.0] * hc.GPIOLEN, dtype='float64') std = np.array([1.5] * hc.GPIOLEN, dtype='float64') count = 2 running_stats = running_stats.Stats(hc.GPIOLEN) # preload running_stats to avoid errors, and give us a show that looks # good right from the start running_stats.preload(mean, std, count) try: hc.initialize() fft_calc = fft.FFT(CHUNK_SIZE, sample_rate, hc.GPIOLEN, _MIN_FREQUENCY, _MAX_FREQUENCY, _CUSTOM_CHANNEL_MAPPING, _CUSTOM_CHANNEL_FREQUENCIES, input_channels) # Listen on the audio input device until CTRL-C is pressed while True: length, data = stream.read() if length > 0: # if the maximum of the absolute value of all samples in # data is below a threshold we will disreguard it audio_max = audioop.max(data, 2) if audio_max < 250: # we will fill the matrix with zeros and turn the # lights off matrix = np.zeros(hc.GPIOLEN, dtype="float64") logging.debug("below threshold: '" + str( audio_max) + "', turning the lights off") else: matrix = fft_calc.calculate_levels(data) running_stats.push(matrix) mean = running_stats.mean() std = running_stats.std() update_lights(matrix, mean, std) except KeyboardInterrupt: pass finally: print "\nStopping" hc.clean_up()
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 main(): """ Random flashing lights """ # this is a list of all the channels you have access to # I'm also tracking the time here so that I know when I turned a light off # So I'm putting everything in a dict gpio_pins = hc._GPIO_PINS lights = dict.fromkeys(range(0, len(gpio_pins)), [True, time.time()]) # get a number that is about 40% the length of your gpio's # this will be use to make sure that no more then 40% of # the light will be off at any one time max_off = int(round(len(lights) * .4)) # initialize your hardware for use hc.initialize() print "Press <CTRL>-C to stop" # start with all the lights on hc.turn_on_lights() # lets run for 2 minutes end = time.time() + 120 # working loop will run as long as time.time() is less then "end" while time.time() < end: # try except block to catch keyboardinterrupt by user to stop try: # here we just loop over the gpio pins for light in lights: # this is where we check to see if we have any light # that are turned off # if they are off we will check the time to see if we # want to turn them back on yet, if we do then turn it on if not lights[light][0]: if lights[light][1] < time.time(): lights[light][0] = True hc.turn_on_light(light) # count the number of lights that are off off = [k for (k, v) in lights.iteritems() if v.count(1) == False] # if less then out max count of light that we chose # we can turn one off if len(off) < max_off: # pick a light at random to turn off choice = random.randrange(0, len(gpio_pins)) # if it's on then lets turn it off if lights[choice][0]: # pick a duration for that light to be off # default times are between 1/2 and secong and 1.8 seconds duration = random.uniform(0.5, 1.8) # store this informatin in our dict lights[choice] = [False, time.time() + duration] # and turn that light off then continue with the main loop # and do it all over again hc.turn_off_light(choice) # if the user pressed <CTRL> + C to exit early break out of the loop except KeyboardInterrupt: print "\nstopped" break # This ends and cleans up everything hc.clean_up()
def audio_in(): '''Control the lightshow from audio coming in from a USB audio card''' sample_rate = cm.lightshow()['audio_in_sample_rate'] input_channels = cm.lightshow()['audio_in_channels'] # Open the input stream from default input device stream = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, cm.lightshow()['audio_in_card']) stream.setchannels(input_channels) stream.setformat(aa.PCM_FORMAT_S16_LE) # Expose in config if needed stream.setrate(sample_rate) stream.setperiodsize(CHUNK_SIZE) logging.debug("Running in audio-in mode - will run until Ctrl+C is pressed") print "Running in audio-in mode, use Ctrl+C to stop" try: hc.initialize() frequency_limits = calculate_channel_frequency(_MIN_FREQUENCY, _MAX_FREQUENCY, _CUSTOM_CHANNEL_MAPPING, _CUSTOM_CHANNEL_FREQUENCIES) # Start with these as our initial guesses - will calculate a rolling mean / std # as we get input data. mean = [12.0 for _ in range(hc.GPIOLEN)] std = [0.5 for _ in range(hc.GPIOLEN)] recent_samples = np.empty((250, hc.GPIOLEN)) num_samples = 0 # Listen on the audio input device until CTRL-C is pressed while True: l, data = stream.read() if l: try: matrix = fft.calculate_levels(data, CHUNK_SIZE, sample_rate, frequency_limits, input_channels) if not np.isfinite(np.sum(matrix)): # Bad data --- skip it continue except ValueError as e: # TODO(todd): This is most likely occuring due to extra time in calculating # mean/std every 250 samples which causes more to be read than expected the # next time around. Would be good to update mean/std in separate thread to # avoid this --- but for now, skip it when we run into this error is good # enough ;) logging.debug("skipping update: " + str(e)) continue update_lights(matrix, mean, std) # Keep track of the last N samples to compute a running std / mean # # TODO(todd): Look into using this algorithm to compute this on a per sample basis: # http://www.johndcook.com/blog/standard_deviation/ if num_samples >= 250: no_connection_ct = 0 for i in range(0, hc.GPIOLEN): mean[i] = np.mean([item for item in recent_samples[:, i] if item > 0]) std[i] = np.std([item for item in recent_samples[:, i] if item > 0]) # Count how many channels are below 10, if more than 1/2, assume noise (no connection) if mean[i] < 10.0: no_connection_ct += 1 # If more than 1/2 of the channels appear to be not connected, turn all off if no_connection_ct > hc.GPIOLEN / 2: logging.debug("no input detected, turning all lights off") mean = [20 for _ in range(hc.GPIOLEN)] else: logging.debug("std: " + str(std) + ", mean: " + str(mean)) num_samples = 0 else: for i in range(0, hc.GPIOLEN): recent_samples[num_samples][i] = matrix[i] num_samples += 1 except KeyboardInterrupt: pass finally: print "\nStopping" hc.clean_up()
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: 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 audio_in(): """Control the lightshow from audio coming in from a real time audio""" global streaming stream_reader = None streaming = None sample_rate = cm.lightshow.input_sample_rate num_channels = cm.lightshow.input_channels if cm.lightshow.mode == 'audio-in': # Open the input stream from default input device streaming = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, cm.lightshow.audio_in_card) streaming.setchannels(num_channels) streaming.setformat(aa.PCM_FORMAT_S16_LE) # Expose in config if needed streaming.setrate(sample_rate) streaming.setperiodsize(CHUNK_SIZE) stream_reader = lambda: streaming.read()[-1] elif cm.lightshow.mode == 'stream-in': if cm.lightshow.use_fifo: streaming = subprocess.Popen(cm.lightshow.stream_command_string, stdin=subprocess.PIPE, stdout=subprocess.PIPE, preexec_fn=os.setsid) io = os.open(cm.lightshow.fifo, os.O_RDONLY | os.O_NONBLOCK) stream_reader = lambda: os.read(io, CHUNK_SIZE) else: # Open the input stream from command string streaming = subprocess.Popen(cm.lightshow.stream_command_string, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stream_reader = lambda: streaming.stdout.read(CHUNK_SIZE) log.debug("Running in %s mode - will run until Ctrl+C is pressed" % cm.lightshow.mode) print "Running in %s mode, use Ctrl+C to stop" % cm.lightshow.mode # setup light_delay. chunks_per_sec = ((16 * num_channels * sample_rate) / 8) / CHUNK_SIZE light_delay = int(cm.audio_processing.light_delay * chunks_per_sec) matrix_buffer = deque([], 1000) output = set_audio_device(sample_rate, num_channels) # Start with these as our initial guesses - will calculate a rolling mean / std # as we get input data. mean = np.array([12.0 for _ in range(hc.GPIOLEN)], dtype='float32') std = np.array([1.5 for _ in range(hc.GPIOLEN)], dtype='float32') count = 2 running_stats = RunningStats.Stats(hc.GPIOLEN) # preload running_stats to avoid errors, and give us a show that looks # good right from the start running_stats.preload(mean, std, count) hc.initialize() fft_calc = fft.FFT(CHUNK_SIZE, sample_rate, hc.GPIOLEN, cm.audio_processing.min_frequency, cm.audio_processing.max_frequency, cm.audio_processing.custom_channel_mapping, cm.audio_processing.custom_channel_frequencies, 1) if server: network.network.set_playing() # Listen on the audio input device until CTRL-C is pressed while True: try: data = stream_reader() except OSError as err: if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK: continue try: output(data) except aa.ALSAAudioError: continue if len(data): # if the maximum of the absolute value of all samples in # data is below a threshold we will disregard it audio_max = audioop.max(data, 2) if audio_max < 250: # we will fill the matrix with zeros and turn the lights off matrix = np.zeros(hc.GPIOLEN, dtype="float32") log.debug("below threshold: '" + str(audio_max) + "', turning the lights off") else: matrix = fft_calc.calculate_levels(data) running_stats.push(matrix) mean = running_stats.mean() std = running_stats.std() matrix_buffer.appendleft(matrix) if len(matrix_buffer) > light_delay: matrix = matrix_buffer[light_delay] update_lights(matrix, mean, std)
def __init__(self, show="preshow"): hc.initialize() self.config = cm.lightshow()[show] self.show = show self.audio = None
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 audio_in(): """Control the lightshow from audio coming in from a USB audio card""" sample_rate = cm.lightshow()['audio_in_sample_rate'] input_channels = cm.lightshow()['audio_in_channels'] # Open the input stream from default input device audio_in_card = cm.lightshow()['audio_in_card'] stream = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, audio_in_card) stream.setchannels(input_channels) stream.setformat(aa.PCM_FORMAT_S16_LE) # Expose in config if needed stream.setrate(sample_rate) stream.setperiodsize(CHUNK_SIZE) logging.debug("Running in audio-in mode - will run until Ctrl+C " "is pressed") print "Running in audio-in mode, use Ctrl+C to stop" # Start with these as our initial guesses - will calculate a rolling # mean / std as we get input data. mean = np.array([12.0] * hc.GPIOLEN, dtype='float64') std = np.array([1.5] * hc.GPIOLEN, dtype='float64') count = 2 running_stats = running_stats.Stats(hc.GPIOLEN) # preload running_stats to avoid errors, and give us a show that looks # good right from the start running_stats.preload(mean, std, count) try: hc.initialize() fft_calc = fft.FFT(CHUNK_SIZE, sample_rate, hc.GPIOLEN, _MIN_FREQUENCY, _MAX_FREQUENCY, _CUSTOM_CHANNEL_MAPPING, _CUSTOM_CHANNEL_FREQUENCIES, input_channels) # Listen on the audio input device until CTRL-C is pressed while True: length, data = stream.read() if length > 0: # if the maximum of the absolute value of all samples in # data is below a threshold we will disreguard it audio_max = audioop.max(data, 2) if audio_max < 250: # we will fill the matrix with zeros and turn the # lights off matrix = np.zeros(hc.GPIOLEN, dtype="float64") logging.debug("below threshold: '" + str(audio_max) + "', turning the lights off") else: matrix = fft_calc.calculate_levels(data) running_stats.push(matrix) mean = running_stats.mean() std = running_stats.std() update_lights(matrix, mean, std) except KeyboardInterrupt: pass finally: print "\nStopping" 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(): '''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 audio_in(): """Control the lightshow from audio coming in from a USB audio card""" sample_rate = cm.lightshow()['audio_in_sample_rate'] input_channels = cm.lightshow()['audio_in_channels'] # Open the input stream from default input device stream = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, cm.lightshow()['audio_in_card']) stream.setchannels(input_channels) stream.setformat(aa.PCM_FORMAT_S16_LE) # Expose in config if needed stream.setrate(sample_rate) stream.setperiodsize(CHUNK_SIZE) logging.debug( "Running in audio-in mode - will run until Ctrl+C is pressed") print "Running in audio-in mode, use Ctrl+C to stop" try: hc.initialize() frequency_limits = calculate_channel_frequency( _MIN_FREQUENCY, _MAX_FREQUENCY, _CUSTOM_CHANNEL_MAPPING, _CUSTOM_CHANNEL_FREQUENCIES) # Start with these as our initial guesses - will calculate a rolling mean / std # as we get input data. mean = [12.0 for _ in range(hc.GPIOLEN)] std = [0.5 for _ in range(hc.GPIOLEN)] recent_samples = np.empty((250, hc.GPIOLEN)) num_samples = 0 # Listen on the audio input device until CTRL-C is pressed while True: l, data = stream.read() if l: try: matrix = fft.calculate_levels(data, CHUNK_SIZE, sample_rate, frequency_limits, hc.GPIOLEN, input_channels) if not np.isfinite(np.sum(matrix)): # Bad data --- skip it continue except ValueError as e: # TODO(todd): This is most likely occuring due to extra time in calculating # mean/std every 250 samples which causes more to be read than expected the # next time around. Would be good to update mean/std in separate thread to # avoid this --- but for now, skip it when we run into this error is good # enough ;) logging.debug("skipping update: " + str(e)) continue update_lights(matrix, mean, std) # Keep track of the last N samples to compute a running std / mean # # TODO(todd): Look into using this algorithm to compute this on a per sample basis: # http://www.johndcook.com/blog/standard_deviation/ if num_samples >= 250: no_connection_ct = 0 for i in range(0, hc.GPIOLEN): mean[i] = np.mean([ item for item in recent_samples[:, i] if item > 0 ]) std[i] = np.std([ item for item in recent_samples[:, i] if item > 0 ]) # Count how many channels are below 10, # if more than 1/2, assume noise (no connection) if mean[i] < 10.0: no_connection_ct += 1 # If more than 1/2 of the channels appear to be not connected, turn all off if no_connection_ct > hc.GPIOLEN / 2: logging.debug( "no input detected, turning all lights off") mean = [20 for _ in range(hc.GPIOLEN)] else: logging.debug("std: " + str(std) + ", mean: " + str(mean)) num_samples = 0 else: for i in range(0, hc.GPIOLEN): recent_samples[num_samples][i] = matrix[i] num_samples += 1 except KeyboardInterrupt: pass finally: print "\nStopping" hc.clean_up()
def audio_in(): """Control the lightshow from audio coming in from a real time audio""" global streaming stream_reader = None streaming = None songcount = 0 sample_rate = cm.lightshow.input_sample_rate num_channels = cm.lightshow.input_channels if cm.lightshow.mode == 'audio-in': # Open the input stream from default input device streaming = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, cm.lightshow.audio_in_card) streaming.setchannels(num_channels) streaming.setformat(aa.PCM_FORMAT_S16_LE) # Expose in config if needed streaming.setrate(sample_rate) streaming.setperiodsize(CHUNK_SIZE) stream_reader = lambda: streaming.read()[-1] elif cm.lightshow.mode == 'stream-in': outq = Queue() if cm.lightshow.use_fifo: streaming = subprocess.Popen(cm.lightshow.stream_command_string, stdin=subprocess.PIPE, stdout=subprocess.PIPE, preexec_fn=os.setsid) io = os.open(cm.lightshow.fifo, os.O_RDONLY | os.O_NONBLOCK) stream_reader = lambda: os.read(io, CHUNK_SIZE) outthr = Thread(target=enqueue_output, args=(streaming.stdout, outq)) else: # Open the input stream from command string streaming = subprocess.Popen(cm.lightshow.stream_command_string, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stream_reader = lambda: streaming.stdout.read(CHUNK_SIZE) outthr = Thread(target=enqueue_output, args=(streaming.stderr, outq)) outthr.daemon = True outthr.start() log.debug("Running in %s mode - will run until Ctrl+C is pressed" % cm.lightshow.mode) print "Running in %s mode, use Ctrl+C to stop" % cm.lightshow.mode # setup light_delay. chunks_per_sec = ((16 * num_channels * sample_rate) / 8) / CHUNK_SIZE light_delay = int(cm.audio_processing.light_delay * chunks_per_sec) matrix_buffer = deque([], 1000) output = set_audio_device(sample_rate, num_channels) # Start with these as our initial guesses - will calculate a rolling mean / std # as we get input data. mean = np.array([12.0 for _ in range(hc.GPIOLEN)], dtype='float32') std = np.array([1.5 for _ in range(hc.GPIOLEN)], dtype='float32') count = 2 running_stats = RunningStats.Stats(hc.GPIOLEN) # preload running_stats to avoid errors, and give us a show that looks # good right from the start running_stats.preload(mean, std, count) hc.initialize() fft_calc = fft.FFT(CHUNK_SIZE, sample_rate, hc.GPIOLEN, cm.audio_processing.min_frequency, cm.audio_processing.max_frequency, cm.audio_processing.custom_channel_mapping, cm.audio_processing.custom_channel_frequencies, 1) if server: network.set_playing() # Listen on the audio input device until CTRL-C is pressed while True: try: streamout = outq.get_nowait().strip('\n\r') except Empty: pass else: print streamout if cm.lightshow.stream_song_delim in streamout: songcount += 1 if cm.lightshow.songname_command: streamout = streamout.replace('\033[2K', '') streamout = streamout.replace( cm.lightshow.stream_song_delim, '') streamout = streamout.replace('"', '') os.system(cm.lightshow.songname_command + ' "Now Playing ' + streamout + '"') if cm.lightshow.stream_song_exit_count > 0 and songcount > cm.lightshow.stream_song_exit_count: break try: data = stream_reader() except OSError as err: if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK: continue try: output(data) except aa.ALSAAudioError: continue if len(data): # if the maximum of the absolute value of all samples in # data is below a threshold we will disregard it audio_max = audioop.max(data, 2) if audio_max < 250: # we will fill the matrix with zeros and turn the lights off matrix = np.zeros(hc.GPIOLEN, dtype="float32") log.debug("below threshold: '" + str(audio_max) + "', turning the lights off") else: matrix = fft_calc.calculate_levels(data) running_stats.push(matrix) mean = running_stats.mean() std = running_stats.std() matrix_buffer.appendleft(matrix) if len(matrix_buffer) > light_delay: matrix = matrix_buffer[light_delay] update_lights(matrix, mean, std)
def main(): """ ladder Lights one channel at a time in order Then backs down to the first Then repeat everything 20 times """ # this is a list of all the channels you have access to lights = hc._GPIO_PINS # initialize your hardware for use hc.initialize() # start with all the lights off hc.turn_off_lights() # pause for 1 second time.sleep(1) # working loop for _ in range(20): # try except block to catch keyboardinterrupt by user to stop try: # here we just loop over the gpio pins and do something with them # except the last one for light in range(len(lights) - 1): # turn off all the lights hc.turn_off_lights() # then turn on one hc.turn_on_light(lights[light]) # wait a little bit time.sleep(.04) # to make the transition back smoother we handle the last pin here hc.turn_off_lights() hc.turn_on_light(lights[light + 1]) # this loop walks it back the other way for light in range(len(lights) - 1, 0, -1): # turn off all the lights hc.turn_off_lights() # then turn on one hc.turn_on_light(lights[light]) # wait a little bit time.sleep(.04) # again to make it smoother handle the first pin like the last pin hc.turn_off_lights() hc.turn_on_light(lights[light - 1]) # if the user pressed <CTRL> + C to exit early break out of the loop except KeyboardInterrupt: print "\nstopped" break # This ends and cleans up everything hc.clean_up()
def main(): """ ladder Lights one channel at a time in order Then backs down to the first Then repeat everything 20 times """ # this is a list of all the channels you have access to lights = hc._GPIO_PINS # initialize your hardware for use hc.initialize() # start with all the lights off hc.turn_off_lights() # pause for 1 second time.sleep(1) # working loop for _ in range(20): # try except block to catch keyboardinterrupt by user to stop try: # here we just loop over the gpio pins and do something with them # except the last one for light in range(len(lights)-1): # turn off all the lights hc.turn_off_lights() # then turn on one hc.turn_on_light(lights[light]) # wait a little bit time.sleep(.04) # to make the transition back smoother we handle the last pin here hc.turn_off_lights() hc.turn_on_light(lights[light + 1]) # this loop walks it back the other way for light in range(len(lights)-1, 0, -1): # turn off all the lights hc.turn_off_lights() # then turn on one hc.turn_on_light(lights[light]) # wait a little bit time.sleep(.04) # again to make it smoother handle the first pin like the last pin hc.turn_off_lights() hc.turn_on_light(lights[light - 1]) # if the user pressed <CTRL> + C to exit early break out of the loop except KeyboardInterrupt: print "\nstopped" break # This ends and cleans up everything 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()