def __init__(self): 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 for _ in xrange(hc.GPIOLEN)], dtype='float64') std = np.array([1.5 for _ in xrange(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)
def __init__(self, show="preshow", hardware=None): """ :param show: which show should be preformed :type show: str :param hardware: an instance of hardware_controller.py :type hardware: object """ if hardware: self.hc = hardware else: self.hc = __import__('hardware_controller') self.hc.initialize() self.config = cm.lightshow()[show] self.show = show self.audio = None
def main(): """Process sms messages Download and process all sms messages from a Google Voice account. Runs in a loop that is executed every 15 seconds """ parser = argparse.ArgumentParser() parser.add_argument('--playlist', default=cm.lightshow()['playlist_path'].replace("$SYNCHRONIZED_LIGHTS_HOME", cm.HOME_DIR), help='filename with the song playlist, one song per line in the format: ' '<song name><tab><path to song>') parser.add_argument('--setup', default=False, help='use this option to setup the default configuration file for Google Voice') parser.add_argument('--log', default='INFO', help='Set the logging level. levels:INFO, DEBUG, WARNING, ERROR, CRITICAL') args = parser.parse_args() logging.basicConfig(filename=cm.LOG_DIR + '/music_and_lights.check.dbg', format='[%(asctime)s] %(levelname)s {%(pathname)s:%(lineno)d}' ' - %(message)s', level=logging.INFO) # logging levels levels = {'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL} level = levels.get(parser.parse_args().log.upper()) logging.getLogger().setLevel(level) # Load playlist from file, notifying users of any of their requests that have now played logging.info('loading playlist ' + args.playlist) while True: 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: logging.debug(song) 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()) elif len(song) >= 3: # Votes for the song are stored in the 3rd column song[2] = set(song[2].split(',')) if len(song) == 4: # Notification of a song being played is stored in the 4th column song_played(song) del song[3] song[2] = set() songs.append(song) fcntl.lockf(playlist_fp, fcntl.LOCK_UN) logging.info('loaded %d songs from playlist', len(songs)) cm.set_songs(songs) # Parse and act on any new sms messages messages = VOICE.sms().messages for msg in extract_sms(VOICE.sms.html): logging.debug(str(msg)) response = commands.execute(msg['text'], msg['from']) if response: logging.info('Request: "' + msg['text'] + '" from ' + msg['from']) try: if isinstance(response, basestring): VOICE.send_sms(msg['from'], response) else: # Multiple parts, send them with a delay in hopes to avoid # them being received out of order by the recipient. for part in response: VOICE.send_sms(msg['from'], str(part)) time.sleep(2) except: logging.warn('Error sending sms response (command still executed)', exc_info=1) logging.info('Response: "' + str(response) + '"') else: logging.info('Unknown request: "' + msg['text'] + '" from ' + msg['from']) VOICE.send_sms(msg['from'], _CONFIG['unknown_command_response']) # 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 len(song[2]) > 0: song[2] = ",".join(song[2]) else: del song[2] writer.writerows(songs) fcntl.lockf(playlist_fp, fcntl.LOCK_UN) # Delete all messages now that we've processed them for msg in messages: msg.delete(1) if args.setup: break time.sleep(15)
def __init__(self, show="preshow"): hc.initialize() self.config = cm.lightshow()[show] self.show = show self.audio = None
def main(): """Process sms messages Download and process all sms messages from a Google Voice account. Runs in a loop that is executed every 15 seconds """ parser = argparse.ArgumentParser() parser.add_argument( '--playlist', default=cm.lightshow()['playlist_path'].replace( "$SYNCHRONIZED_LIGHTS_HOME", cm.HOME_DIR), help='filename with the song playlist, one song per line in the format: ' '<song name><tab><path to song>') parser.add_argument( '--setup', default=False, help= 'use this option to setup the default configuration file for Google Voice' ) parser.add_argument( '--log', default='INFO', help= 'Set the logging level. levels:INFO, DEBUG, WARNING, ERROR, CRITICAL') args = parser.parse_args() logging.basicConfig( filename=cm.LOG_DIR + '/music_and_lights.check.dbg', format='[%(asctime)s] %(levelname)s {%(pathname)s:%(lineno)d}' ' - %(message)s', level=logging.INFO) # logging levels levels = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL } level = levels.get(parser.parse_args().log.upper()) logging.getLogger().setLevel(level) # Load playlist from file, notifying users of any of their requests that have now played logging.info('loading playlist ' + args.playlist) while True: 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: logging.debug(song) 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()) elif len(song) >= 3: # Votes for the song are stored in the 3rd column song[2] = set(song[2].split(',')) if len(song) == 4: # Notification of a song being played is stored in the 4th column song_played(song) del song[3] song[2] = set() songs.append(song) fcntl.lockf(playlist_fp, fcntl.LOCK_UN) logging.info('loaded %d songs from playlist', len(songs)) cm.set_songs(songs) # Parse and act on any new sms messages messages = VOICE.sms().messages for msg in extract_sms(VOICE.sms.html): logging.debug(str(msg)) response = commands.execute(msg['text'], msg['from']) if response: logging.info('Request: "' + msg['text'] + '" from ' + msg['from']) try: if isinstance(response, basestring): VOICE.send_sms(msg['from'], response) else: # Multiple parts, send them with a delay in hopes to avoid # them being received out of order by the recipient. for part in response: VOICE.send_sms(msg['from'], str(part)) time.sleep(2) except: logging.warn( 'Error sending sms response (command still executed)', exc_info=1) logging.info('Response: "' + str(response) + '"') else: logging.info('Unknown request: "' + msg['text'] + '" from ' + msg['from']) VOICE.send_sms(msg['from'], _CONFIG['unknown_command_response']) # 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 len(song[2]) > 0: song[2] = ",".join(song[2]) else: del song[2] writer.writerows(songs) fcntl.lockf(playlist_fp, fcntl.LOCK_UN) # Delete all messages now that we've processed them for msg in messages: msg.delete(1) if args.setup: break time.sleep(15)
if height < .55: height = .05 elif height > 1.0: height = 1.0 led.fillRGB(r=102,g=0,b=204,start=44,end = 63) bright = height * 180 if bright < 20: bright = 20 if bright > 200: bright = 200 led.setMasterBrightness(int(round(bright))) # Configurations - TODO(todd): Move more of this into configuration manager _CONFIG = cm.CONFIG _MODE = cm.lightshow()['mode'] _MIN_FREQUENCY = _CONFIG.getfloat('audio_processing', 'min_frequency') _MAX_FREQUENCY = _CONFIG.getfloat('audio_processing', 'max_frequency') _RANDOMIZE_PLAYLIST = _CONFIG.getboolean('lightshow', 'randomize_playlist') try: _CUSTOM_CHANNEL_MAPPING = [int(channel) for channel in _CONFIG.get('audio_processing', 'custom_channel_mapping').split(',')] except: _CUSTOM_CHANNEL_MAPPING = 0 try: _CUSTOM_CHANNEL_FREQUENCIES = [int(channel) for channel in _CONFIG.get('audio_processing', 'custom_channel_frequencies').split(',')] except: _CUSTOM_CHANNEL_FREQUENCIES = 0 try:
is_a_raspberryPI = platform.platform_detect() == 1 if is_a_raspberryPI: import wiringpi2 as wiringpi else: # if this is not a RPi you can't run wiringpi so lets load # something in its place import wiring_pi_stub as wiringpi logging.debug("Not running on a raspberryPI") # Get Configurations - TODO(todd): Move more of this into configuration manager _CONFIG = cm.CONFIG _LIGHTSHOW_CONFIG = cm.lightshow() _HARDWARE_CONFIG = cm.hardware() _GPIO_PINS = [int(gpio_pin) for gpio_pin in _CONFIG.get('hardware', 'gpio_pins').split(',')] _PWM_MAX = int(_CONFIG.get('hardware', 'pwm_range')) _ACTIVE_LOW_MODE = _CONFIG.getboolean('hardware', 'active_low_mode') _ALWAYS_ON_CHANNELS = [int(channel) for channel in _LIGHTSHOW_CONFIG['always_on_channels'].split(',')] _ALWAYS_OFF_CHANNELS = [int(channel) for channel in _LIGHTSHOW_CONFIG['always_off_channels'].split(',')] _INVERTED_CHANNELS = [int(channel) for channel in _LIGHTSHOW_CONFIG['invert_channels'].split(',')] _EXPORT_PINS = _CONFIG.getboolean('hardware', 'export_pins') _GPIO_UTILITY_PATH = _CONFIG.get('hardware', 'gpio_utility_path') I2C_DEVICES = ["mcp23017", "mcp23016", "mcp23008", "pcf8574"]
import configuration_manager as cm import platform is_a_raspberryPI = platform.platform_detect() == 1 if is_a_raspberryPI: import wiringpi2 as wiringpi else: # if this is not a RPi you can't run wiringpi so lets load # something in its place import wiring_pi_stub as wiringpi logging.debug("Not running on a raspberryPI") # Get Configurations - TODO(todd): Move more of this into configuration manager _CONFIG = cm.CONFIG _LIGHTSHOW_CONFIG = cm.lightshow() _HARDWARE_CONFIG = cm.hardware() _GPIO_PINS = [ int(gpio_pin) for gpio_pin in _CONFIG.get('hardware', 'gpio_pins').split(',') ] _PWM_MAX = int(_CONFIG.get('hardware', 'pwm_range')) _ACTIVE_LOW_MODE = _CONFIG.getboolean('hardware', 'active_low_mode') _ALWAYS_ON_CHANNELS = [ int(channel) for channel in _LIGHTSHOW_CONFIG['always_on_channels'].split(',') ] _ALWAYS_OFF_CHANNELS = [ int(channel) for channel in _LIGHTSHOW_CONFIG['always_off_channels'].split(',') ]
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()
import subprocess import sys import wave import alsaaudio as aa import fft import configuration_manager as cm import decoder import hardware_controller as hc import numpy as np from prepostshow import PrePostShow # Configurations - TODO(todd): Move more of this into configuration manager _CONFIG = cm.CONFIG _MODE = cm.lightshow()['mode'] _MIN_FREQUENCY = _CONFIG.getfloat('audio_processing', 'min_frequency') _MAX_FREQUENCY = _CONFIG.getfloat('audio_processing', 'max_frequency') _RANDOMIZE_PLAYLIST = _CONFIG.getboolean('lightshow', 'randomize_playlist') try: _CUSTOM_CHANNEL_MAPPING = \ [int(channel) for channel in _CONFIG.get('audio_processing',\ 'custom_channel_mapping').split(',')] except: _CUSTOM_CHANNEL_MAPPING = 0 try: _CUSTOM_CHANNEL_FREQUENCIES = [ int(channel) for channel in _CONFIG.get( 'audio_processing', 'custom_channel_frequencies').split(',') ] except:
def get_audio_input_handler(song_filename, chunk_size): cfg = cm.CONFIG if cm.lightshow()['mode'] == 'audio-in': return LineInput() else: return StreamInput(song_filename, chunk_size)
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 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 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 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()