Advertisement
tom_enos

synchronized_lights.py

Feb 28th, 2015
255
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 23.56 KB | None | 0 0
  1. #!/usr/bin/env python
  2. #
  3. # Licensed under the BSD license.  See full license in LICENSE file.
  4. # http://www.lightshowpi.com/
  5. #
  6. # Author: Todd Giles (todd@lightshowpi.com)
  7. # Author: Chris Usey (chris.usey@gmail.com)
  8. # Author: Ryan Jennings
  9. # Author: Paul Dunn (dunnsept@gmail.com)
  10. """
  11. Play any audio file and synchronize lights to the music
  12.  
  13. When executed, this script will play an audio file, as well as turn on
  14. and off N channels of lights to the music (by default the first 8 GPIO
  15. channels on the Rasberry Pi), based upon music it is playing. Many
  16. types of audio files are supported (see decoder.py below), but it has
  17. only been tested with wav and mp3 at the time of this writing.
  18.  
  19. The timing of the lights turning on and off is based upon the frequency
  20. response of the music being played.  A short segment of the music is
  21. analyzed via FFT to get the frequency response across each defined
  22. channel in the audio range.  Each light channel is then faded in and
  23. out based upon the amplitude of the frequency response in the
  24. corresponding audio channel.  Fading is accomplished with a software
  25. PWM output.  Each channel can also be configured to simply turn on and
  26. off as the frequency response in the corresponding channel crosses a
  27. threshold.
  28.  
  29. FFT calculation can be CPU intensive and in some cases can adversely
  30. affect playback of songs (especially if attempting to decode the song
  31. as well, as is the case for an mp3).  For this reason, the FFT
  32. cacluations are cached after the first time a new song is played.
  33. The values are cached in a gzip'd text file in the same location as the
  34. song itself.  Subsequent requests to play the same song will use the
  35. cached information and not recompute the FFT, thus reducing CPU
  36. utilization dramatically and allowing for clear music playback of all
  37. audio file types.
  38.  
  39. Recent optimizations have improved this dramatically and most users are
  40. no longer reporting adverse playback of songs even on the first
  41. playback.
  42.  
  43. Sample usage:
  44. To play an entire list -
  45. sudo python synchronized_lights.py --playlist=/home/pi/music/.playlist
  46.  
  47. To play a specific song -
  48. sudo python synchronized_lights.py --file=/home/pi/music/jingle_bells.mp3
  49.  
  50. Third party dependencies:
  51.  
  52. alsaaudio: for audio input/output
  53.    http://pyalsaaudio.sourceforge.net/
  54.  
  55. decoder.py: decoding mp3, ogg, wma, ...
  56.    https://pypi.python.org/pypi/decoder.py/1.5XB
  57.  
  58. numpy: for FFT calcuation
  59.    http://www.numpy.org/
  60. """
  61.  
  62. import argparse
  63. import atexit
  64. import csv
  65. import fcntl
  66. import json
  67. import logging
  68. import os
  69. import random
  70. import subprocess
  71. import sys
  72. import wave
  73.  
  74. import time
  75.  
  76. import alsaaudio as aa
  77. import fft
  78. import configuration_manager as cm
  79. import decoder
  80. import hardware_controller as hc
  81. import numpy as np
  82.  
  83. from prepostshow import PrePostShow
  84.  
  85.  
  86. # Configurations - TODO(todd): Move more of this into configuration manager
  87. _CONFIG = cm.CONFIG
  88. _MODE = cm.lightshow()['mode']
  89. _MIN_FREQUENCY = _CONFIG.getfloat('audio_processing', 'min_frequency')
  90. _MAX_FREQUENCY = _CONFIG.getfloat('audio_processing', 'max_frequency')
  91. _RANDOMIZE_PLAYLIST = _CONFIG.getboolean('lightshow', 'randomize_playlist')
  92. try:
  93.     _CUSTOM_CHANNEL_MAPPING = \
  94.         [int(channel) for channel in _CONFIG.get('audio_processing',\
  95.             'custom_channel_mapping').split(',')]
  96. except:
  97.     _CUSTOM_CHANNEL_MAPPING = 0
  98. try:
  99.     _CUSTOM_CHANNEL_FREQUENCIES = [int(channel) for channel in
  100.                                    _CONFIG.get('audio_processing',
  101.                                                'custom_channel_frequencies').split(',')]
  102. except:
  103.     _CUSTOM_CHANNEL_FREQUENCIES = 0
  104. try:
  105.     _PLAYLIST_PATH = \
  106.         cm.lightshow()['playlist_path'].replace('$SYNCHRONIZED_LIGHTS_HOME', cm.HOME_DIR)
  107. except:
  108.     _PLAYLIST_PATH = "/home/pi/music/.playlist"
  109. try:
  110.     _usefm=_CONFIG.get('audio_processing','fm');
  111.     frequency =_CONFIG.get('audio_processing','frequency');
  112.     play_stereo = True
  113.     music_pipe_r,music_pipe_w = os.pipe()  
  114. except:
  115.     _usefm='false'
  116. CHUNK_SIZE = 2048  # Use a multiple of 8 (move this to config)
  117.  
  118. def end_early():
  119.     hc.clean_up()
  120.    
  121. atexit.register(end_early)
  122.  
  123. def calculate_channel_frequency(min_frequency, max_frequency, custom_channel_mapping,
  124.                                 custom_channel_frequencies):
  125.     """
  126.    Calculate frequency values
  127.  
  128.    Calculate frequency values for each channel,
  129.    taking into account custom settings.
  130.    """
  131.  
  132.     # How many channels do we need to calculate the frequency for
  133.     if custom_channel_mapping != 0 and len(custom_channel_mapping) == hc.GPIOLEN:
  134.         logging.debug("Custom Channel Mapping is being used: %s", str(custom_channel_mapping))
  135.         channel_length = max(custom_channel_mapping)
  136.     else:
  137.         logging.debug("Normal Channel Mapping is being used.")
  138.         channel_length = hc.GPIOLEN
  139.  
  140.     logging.debug("Calculating frequencies for %d channels.", channel_length)
  141.     octaves = (np.log(max_frequency / min_frequency)) / np.log(2)
  142.     logging.debug("octaves in selected frequency range ... %s", octaves)
  143.     octaves_per_channel = octaves / channel_length
  144.     frequency_limits = []
  145.     frequency_store = []
  146.  
  147.     frequency_limits.append(min_frequency)
  148.     if custom_channel_frequencies != 0 and (len(custom_channel_frequencies) >= channel_length + 1):
  149.         logging.debug("Custom channel frequencies are being used")
  150.         frequency_limits = custom_channel_frequencies
  151.     else:
  152.         logging.debug("Custom channel frequencies are not being used")
  153.         for i in range(1, hc.GPIOLEN + 1):
  154.             frequency_limits.append(frequency_limits[-1]
  155.                                     * 10 ** (3 / (10 * (1 / octaves_per_channel))))
  156.     for i in range(0, channel_length):
  157.         frequency_store.append((frequency_limits[i], frequency_limits[i + 1]))
  158.         logging.debug("channel %d is %6.2f to %6.2f ", i, frequency_limits[i],
  159.                       frequency_limits[i + 1])
  160.  
  161.     # we have the frequencies now lets map them if custom mapping is defined
  162.     if custom_channel_mapping != 0 and len(custom_channel_mapping) == hc.GPIOLEN:
  163.         frequency_map = []
  164.         for i in range(0, hc.GPIOLEN):
  165.             mapped_channel = custom_channel_mapping[i] - 1
  166.             mapped_frequency_set = frequency_store[mapped_channel]
  167.             mapped_frequency_set_low = mapped_frequency_set[0]
  168.             mapped_frequency_set_high = mapped_frequency_set[1]
  169.             logging.debug("mapped channel: " + str(mapped_channel) + " will hold LOW: "
  170.                           + str(mapped_frequency_set_low) + " HIGH: "
  171.                           + str(mapped_frequency_set_high))
  172.             frequency_map.append(mapped_frequency_set)
  173.         return frequency_map
  174.     else:
  175.         return frequency_store
  176.  
  177. def update_lights(matrix, mean, std):
  178.     """
  179.    Update the state of all the lights
  180.  
  181.    Update the state of all the lights based upon the current
  182.    frequency response matrix
  183.    """
  184.     for i in range(0, hc.GPIOLEN):
  185.         # Calculate output pwm, where off is at some portion of the std below
  186.         # the mean and full on is at some portion of the std above the mean.
  187.         brightness = matrix[i] - mean[i] + 0.5 * std[i]
  188.         brightness = brightness / (1.25 * std[i])
  189.         if brightness > 1.0:
  190.             brightness = 1.0
  191.         if brightness < 0:
  192.             brightness = 0
  193.         if not hc.is_pin_pwm(i):
  194.             # If pin is on / off mode we'll turn on at 1/2 brightness
  195.             if (brightness > 0.5):
  196.                 hc.turn_on_light(i, True)
  197.             else:
  198.                 hc.turn_off_light(i, True)
  199.         else:
  200.             hc.turn_on_light(i, True, brightness)
  201.  
  202. def audio_in():
  203.     """Control the lightshow from audio coming in from a USB audio card"""
  204.     sample_rate = cm.lightshow()['audio_in_sample_rate']
  205.     input_channels = cm.lightshow()['audio_in_channels']
  206.  
  207.     # Open the input stream from default input device
  208.     stream = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, cm.lightshow()['audio_in_card'])
  209.     stream.setchannels(input_channels)
  210.     stream.setformat(aa.PCM_FORMAT_S16_LE) # Expose in config if needed
  211.     stream.setrate(sample_rate)
  212.     stream.setperiodsize(CHUNK_SIZE)
  213.          
  214.     logging.debug("Running in audio-in mode - will run until Ctrl+C is pressed")
  215.     print "Running in audio-in mode, use Ctrl+C to stop"
  216.     try:
  217.         hc.initialize()
  218.         frequency_limits = calculate_channel_frequency(_MIN_FREQUENCY,
  219.                                                        _MAX_FREQUENCY,
  220.                                                        _CUSTOM_CHANNEL_MAPPING,
  221.                                                        _CUSTOM_CHANNEL_FREQUENCIES)
  222.  
  223.         # Start with these as our initial guesses - will calculate a rolling mean / std
  224.         # as we get input data.
  225.         mean = [12.0 for _ in range(hc.GPIOLEN)]
  226.         std = [0.5 for _ in range(hc.GPIOLEN)]
  227.         recent_samples = np.empty((250, hc.GPIOLEN))
  228.         num_samples = 0
  229.         switch_time = time.time() + random.randrange(5, 16)
  230.  
  231.         # Listen on the audio input device until CTRL-C is pressed
  232.         while True:
  233.            
  234.             if switch_time < time.time():
  235.                 # NOTE: place your new custom channel mappings here
  236.                 new_maps = ["1,2,4,2,1,2",
  237.                             "1,1,1,1,1,1",
  238.                             "5,1,5,1,5,1",
  239.                             "1,1,1,1,1,1",
  240.                             "4,1,1,1,1,4"]
  241.                 # NOTE: you must have the same number of entries in this
  242.                 #       list as in new_maps
  243.                 new_invert = ["0,0,0,0,0,0",
  244.                               "0,0,0,0,0,0",
  245.                               "0,0,0,0,0,0",
  246.                               "0,2,0,4,0,6",
  247.                               "0,0,3,0,0,0"]
  248.                 rchoice = random.randrange(len(new_maps))
  249.  
  250.                 _CUSTOM_CHANNEL_MAPPING = map(int, new_maps[rchoice].split(","))
  251.                 hc._INVERTED_CHANNELS = map(int, new_invert[rchoice].split(","))
  252.                
  253.                 frequency_limits = calculate_channel_frequency(_MIN_FREQUENCY,
  254.                                                                _MAX_FREQUENCY,
  255.                                                                _CUSTOM_CHANNEL_MAPPING,
  256.                                                                _CUSTOM_CHANNEL_FREQUENCIES)
  257.                 mean = [12.0 for _ in range(GPIOLEN)]
  258.                 std = [0.5 for _ in range(GPIOLEN)]
  259.                 recent_samples = np.empty((250, GPIOLEN))
  260.                 num_samples = 0
  261.                
  262.                 switch_time = time.time() + random.randrange(5, 16)
  263.  
  264.             l, data = stream.read()
  265.            
  266.             if l:
  267.                 try:
  268.                     matrix = fft.calculate_levels(data,
  269.                                                   CHUNK_SIZE,
  270.                                                   sample_rate,
  271.                                                   frequency_limits,
  272.                                                   hc.GPIOLEN,
  273.                                                   input_channels)
  274.                     if not np.isfinite(np.sum(matrix)):
  275.                         # Bad data --- skip it
  276.                         continue
  277.                 except ValueError as e:
  278.                     # TODO(todd): This is most likely occuring due to extra time in calculating
  279.                     # mean/std every 250 samples which causes more to be read than expected the
  280.                     # next time around.  Would be good to update mean/std in separate thread to
  281.                     # avoid this --- but for now, skip it when we run into this error is good
  282.                     # enough ;)
  283.                     logging.debug("skipping update: " + str(e))
  284.                     continue
  285.  
  286.                 update_lights(matrix, mean, std)
  287.  
  288.                 # Keep track of the last N samples to compute a running std / mean
  289.                 #
  290.                 # TODO(todd): Look into using this algorithm to compute this on a per sample basis:
  291.                 # http://www.johndcook.com/blog/standard_deviation/                
  292.                 if num_samples >= 250:
  293.                     no_connection_ct = 0
  294.                     for i in range(0, hc.GPIOLEN):
  295.                         mean[i] = np.mean([item for item in recent_samples[:, i] if item > 0])
  296.                         std[i] = np.std([item for item in recent_samples[:, i] if item > 0])
  297.                        
  298.                         # Count how many channels are below 10,
  299.                         # if more than 1/2, assume noise (no connection)
  300.                         if mean[i] < 10.0:
  301.                             no_connection_ct += 1
  302.                            
  303.                     # If more than 1/2 of the channels appear to be not connected, turn all off
  304.                     if no_connection_ct > hc.GPIOLEN / 2:
  305.                         logging.debug("no input detected, turning all lights off")
  306.                         mean = [20 for _ in range(hc.GPIOLEN)]
  307.                     else:
  308.                         logging.debug("std: " + str(std) + ", mean: " + str(mean))
  309.                     num_samples = 0
  310.                 else:
  311.                     for i in range(0, hc.GPIOLEN):
  312.                         recent_samples[num_samples][i] = matrix[i]
  313.                     num_samples += 1
  314.  
  315.     except KeyboardInterrupt:
  316.         pass
  317.     finally:
  318.         print "\nStopping"
  319.         hc.clean_up()
  320.  
  321. # TODO(todd): Refactor more of this to make it more readable / modular.
  322. def play_song():
  323.     """Play the next song from the play list (or --file argument)."""
  324.     song_to_play = int(cm.get_state('song_to_play', 0))
  325.     play_now = int(cm.get_state('play_now', 0))
  326.  
  327.     # Arguments
  328.     parser = argparse.ArgumentParser()
  329.     filegroup = parser.add_mutually_exclusive_group()
  330.     filegroup.add_argument('--playlist', default=_PLAYLIST_PATH,
  331.                            help='Playlist to choose song from.')
  332.     filegroup.add_argument('--file', help='path to the song to play (required if no'
  333.                            'playlist is designated)')
  334.     parser.add_argument('--readcache', type=int, default=1,
  335.                         help='read light timing from cache if available. Default: true')
  336.     args = parser.parse_args()
  337.  
  338.     # Make sure one of --playlist or --file was specified
  339.     if args.file == None and args.playlist == None:
  340.         print "One of --playlist or --file must be specified"
  341.         sys.exit()
  342.  
  343.     # Handle the pre/post show
  344.     if not play_now:
  345.         result = PrePostShow('preshow').execute()
  346.         # now unused.  if play_now = True
  347.         # song to play is always the first song in the playlist
  348.        
  349.         if result == PrePostShow.play_now_interrupt:
  350.             play_now = int(cm.get_state('play_now', 0))
  351.  
  352.     # Initialize Lights
  353.     hc.initialize()
  354.  
  355.     # Determine the next file to play
  356.     song_filename = args.file
  357.     if args.playlist != None and args.file == None:
  358.         most_votes = [None, None, []]
  359.         current_song = None
  360.         with open(args.playlist, 'rb') as playlist_fp:
  361.             fcntl.lockf(playlist_fp, fcntl.LOCK_SH)
  362.             playlist = csv.reader(playlist_fp, delimiter='\t')
  363.             songs = []
  364.             for song in playlist:
  365.                 if len(song) < 2 or len(song) > 4:
  366.                     logging.error('Invalid playlist.  Each line should be in the form: '
  367.                                  '<song name><tab><path to song>')
  368.                     sys.exit()
  369.                 elif len(song) == 2:
  370.                     song.append(set())
  371.                 else:
  372.                     song[2] = set(song[2].split(','))
  373.                     if len(song) == 3 and len(song[2]) >= len(most_votes[2]):
  374.                         most_votes = song
  375.                 songs.append(song)
  376.             fcntl.lockf(playlist_fp, fcntl.LOCK_UN)
  377.  
  378.         if most_votes[0] != None:
  379.             logging.info("Most Votes: " + str(most_votes))
  380.             current_song = most_votes
  381.  
  382.             # Update playlist with latest votes
  383.             with open(args.playlist, 'wb') as playlist_fp:
  384.                 fcntl.lockf(playlist_fp, fcntl.LOCK_EX)
  385.                 writer = csv.writer(playlist_fp, delimiter='\t')
  386.                 for song in songs:
  387.                     if current_song == song and len(song) == 3:
  388.                         song.append("playing!")
  389.                     if len(song[2]) > 0:
  390.                         song[2] = ",".join(song[2])
  391.                     else:
  392.                         del song[2]
  393.                 writer.writerows(songs)
  394.                 fcntl.lockf(playlist_fp, fcntl.LOCK_UN)
  395.  
  396.         else:
  397.             # Get a "play now" requested song
  398.             if play_now > 0 and play_now <= len(songs):
  399.                 current_song = songs[play_now - 1]
  400.             # Get random song
  401.             elif _RANDOMIZE_PLAYLIST:
  402.                 # Use python's random.randrange() to get a random song
  403.                 current_song = songs[random.randrange(0, len(songs))]
  404.  
  405.             # Play next song in the lineup
  406.             else:
  407.                 song_to_play = song_to_play if (song_to_play <= len(songs) - 1) else 0
  408.                 current_song = songs[song_to_play]
  409.                 next_song = (song_to_play + 1) if ((song_to_play + 1) <= len(songs) - 1) else 0
  410.                 cm.update_state('song_to_play', next_song)
  411.  
  412.         # Get filename to play and store the current song playing in state cfg
  413.         song_filename = current_song[1]
  414.         cm.update_state('current_song', songs.index(current_song))
  415.  
  416.     song_filename = song_filename.replace("$SYNCHRONIZED_LIGHTS_HOME", cm.HOME_DIR)
  417.  
  418.     # Ensure play_now is reset before beginning playback
  419.     if play_now:
  420.         cm.update_state('play_now', 0)
  421.         play_now = 0
  422.  
  423.     # Initialize FFT stats
  424.     matrix = [0 for _ in range(hc.GPIOLEN)]
  425.  
  426.     # Set up audio
  427.     if song_filename.endswith('.wav'):
  428.         musicfile = wave.open(song_filename, 'r')
  429.     else:
  430.         musicfile = decoder.open(song_filename)
  431.  
  432.     sample_rate = musicfile.getframerate()
  433.     num_channels = musicfile.getnchannels()
  434.  
  435.     if _usefm=='true':
  436.         logging.info("Sending output as fm transmission")
  437.        
  438.         with open(os.devnull, "w") as dev_null:
  439.             # play_stereo is always True as coded, Should it be changed to
  440.             # an option in the config file?
  441.             fm_process = subprocess.Popen(["sudo",
  442.                                            cm.HOME_DIR + "/bin/pifm",
  443.                                            "-",
  444.                                            str(frequency),
  445.                                            "44100",
  446.                                            "stereo" if play_stereo else "mono"],\
  447.                                                stdin=music_pipe_r,\
  448.                                                    stdout=dev_null)
  449.     else:
  450.         output = aa.PCM(aa.PCM_PLAYBACK, aa.PCM_NORMAL)
  451.         output.setchannels(num_channels)
  452.         output.setrate(sample_rate)
  453.         output.setformat(aa.PCM_FORMAT_S16_LE)
  454.         output.setperiodsize(CHUNK_SIZE)
  455.    
  456.     logging.info("Playing: " + song_filename + " (" + str(musicfile.getnframes() / sample_rate)
  457.                  + " sec)")
  458.     # Output a bit about what we're about to play to the logs
  459.     song_filename = os.path.abspath(song_filename)
  460.    
  461.     # create empty array for the cache_matrix
  462.     cache_matrix = np.empty(shape=[0, hc.GPIOLEN])
  463.     cache_found = False
  464.     cache_filename = \
  465.         os.path.dirname(song_filename) + "/." + os.path.basename(song_filename) + ".sync"
  466.    
  467.     # The values 12 and 1.5 are good estimates for first time playing back
  468.     # (i.e. before we have the actual mean and standard deviations
  469.     # calculated for each channel).
  470.     mean = [12.0 for _ in range(hc.GPIOLEN)]
  471.     std = [1.5 for _ in range(hc.GPIOLEN)]
  472.    
  473.     if args.readcache:
  474.         # Read in cached fft
  475.         try:
  476.             # load cache from file using numpy loadtxt
  477.             cache_matrix = np.loadtxt(cache_filename)
  478.             cache_found = True
  479.  
  480.             # get std from matrix / located at index 0
  481.             std = np.array(cache_matrix[0])
  482.  
  483.             # get mean from matrix / located at index 1
  484.             mean = np.array(cache_matrix[1])
  485.  
  486.             # delete mean and std from the array
  487.             cache_matrix = np.delete(cache_matrix, (0), axis = 0)
  488.             cache_matrix = np.delete(cache_matrix, (0), axis = 0)
  489.  
  490.             logging.debug("std: " + str(std) + ", mean: " + str(mean))
  491.         except IOError:
  492.             logging.warn("Cached sync data song_filename not found: '"
  493.                          + cache_filename
  494.                          + ".  One will be generated.")
  495.  
  496.     # Process audio song_filename
  497.     row = 0
  498.     data = musicfile.readframes(CHUNK_SIZE)
  499.     frequency_limits = calculate_channel_frequency(_MIN_FREQUENCY,
  500.                                                    _MAX_FREQUENCY,
  501.                                                    _CUSTOM_CHANNEL_MAPPING,
  502.                                                    _CUSTOM_CHANNEL_FREQUENCIES)
  503.  
  504.     while data != '' and not play_now:
  505.         if _usefm=='true':
  506.             os.write(music_pipe_w, data)
  507.         else:
  508.             output.write(data)
  509.  
  510.         # Control lights with cached timing values if they exist
  511.         matrix = None
  512.         if cache_found and args.readcache:
  513.             if row < len(cache_matrix):
  514.                 matrix = cache_matrix[row]
  515.             else:
  516.                 logging.warning("Ran out of cached FFT values, will update the cache.")
  517.                 cache_found = False
  518.  
  519.         if matrix == None:
  520.             # No cache - Compute FFT in this chunk, and cache results
  521.             matrix = fft.calculate_levels(data, CHUNK_SIZE, sample_rate, frequency_limits, hc.GPIOLEN)
  522.  
  523.             # Add the matrix to the end of the cache
  524.             cache_matrix = np.vstack([cache_matrix, matrix])
  525.            
  526.         update_lights(matrix, mean, std)
  527.  
  528.         # Read next chunk of data from music song_filename
  529.         data = musicfile.readframes(CHUNK_SIZE)
  530.         row = row + 1
  531.  
  532.         # Load new application state in case we've been interrupted
  533.         cm.load_state()
  534.         play_now = int(cm.get_state('play_now', 0))
  535.  
  536.     if not cache_found:
  537.         # Compute the standard deviation and mean values for the cache
  538.         for i in range(0, hc.GPIOLEN):
  539.             std[i] = np.std([item for item in cache_matrix[:, i] if item > 0])
  540.             mean[i] = np.mean([item for item in cache_matrix[:, i] if item > 0])
  541.  
  542.         # Add mean and std to the top of the cache
  543.         cache_matrix = np.vstack([mean, cache_matrix])
  544.         cache_matrix = np.vstack([std, cache_matrix])
  545.  
  546.         # Save the cache using numpy savetxt
  547.         np.savetxt(cache_filename, cache_matrix)
  548.        
  549.         logging.info("Cached sync data written to '." + cache_filename
  550.                         + "' [" + str(len(cache_matrix)) + " rows]")
  551.  
  552.     # Cleanup the pifm process
  553.     if _usefm=='true':
  554.         fm_process.kill()
  555.  
  556.     # check for postshow
  557.     done = PrePostShow('postshow').execute()
  558.  
  559.     # We're done, turn it all off and clean up things ;)
  560.     hc.clean_up()
  561.  
  562. if __name__ == "__main__":
  563.     # Log everything to our log file
  564.     # TODO(todd): Add logging configuration options.
  565.     logging.basicConfig(filename=cm.LOG_DIR + '/music_and_lights.play.dbg',
  566.                         format='[%(asctime)s] %(levelname)s {%(pathname)s:%(lineno)d}'
  567.                         ' - %(message)s',
  568.                         level=logging.DEBUG)
  569.  
  570.     if cm.lightshow()['mode'] == 'audio-in':
  571.         audio_in()
  572.     else:
  573.         play_song()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement