~jocave/checkbox/hybrid-amd-gpu-mods

« back to all changes in this revision

Viewing changes to checkbox-old/scripts/audio_test

  • Committer: Tarmac
  • Author(s): Brendan Donegan
  • Date: 2013-06-03 11:12:58 UTC
  • mfrom: (2154.2.1 bug1185759)
  • Revision ID: tarmac-20130603111258-1b3m5ydvkf1accts
"[r=zkrynicki][bug=1185759][author=brendan-donegan] automatic merge by tarmac"

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
from __future__ import division, print_function
 
4
import argparse
 
5
import collections
 
6
import logging
 
7
import math
 
8
import re
 
9
import sys
 
10
import subprocess
 
11
import time
 
12
#Trick to prevent gst from hijacking argv parsing
 
13
argv = sys.argv
 
14
sys.argv = []
 
15
try:
 
16
    import gobject
 
17
    import gst
 
18
    from gobject import GError
 
19
except ImportError:
 
20
    print("Can't import module: %s. it may not be available for this"
 
21
          "version of Python, which is: " % sys.exc_info()[1], file=sys.stderr)
 
22
    print((sys.version), file=sys.stderr)
 
23
    sys.exit(127)
 
24
sys.argv = argv
 
25
 
 
26
 
 
27
#Frequency bands for FFT
 
28
BINS = 256
 
29
#How often to take a sample and do FFT on it.
 
30
FFT_INTERVAL = 100000000  # In nanoseconds, so this is every 1/10th second
 
31
#Sampling frequency. The effective maximum frequency we can analyze is
 
32
#half of this (see Nyquist's theorem)
 
33
SAMPLING_FREQUENCY = 44100
 
34
#The default test frequency should be in the middle of the frequency band
 
35
#that delimits the first and second thirds of the frequency range.
 
36
#That gives a not-so-ear-piercing tone and should ensure there's no
 
37
#spillout to neighboring frequency bands.
 
38
DEFAULT_TEST_FREQUENCY = (SAMPLING_FREQUENCY / (2 * BINS)) * int(BINS / 3) - \
 
39
                         (SAMPLING_FREQUENCY / (2 * BINS)) / 2
 
40
#only sample a signal when peak level is in this range (in dB attenuation,
 
41
#0 means no attenuation (and horrible clipping).
 
42
REC_LEVEL_RANGE = (-2.0, -12.0)
 
43
#For our test signal to be considered present, it has to be this much higher
 
44
#than the average of the rest of the frequencies (to ensure we have a nice,
 
45
#clear peak). This is in dB.
 
46
MAGNITUDE_THRESHOLD = -5.0
 
47
 
 
48
 
 
49
class PIDController(object):
 
50
    """ A Proportional-Integrative-Derivative controller (PID) controls a
 
51
    process's output to try to maintain a desired output value (known as
 
52
    'setpoint', by continually adjusting the process's input.
 
53
 
 
54
    It does so by calculating the "error" (difference between output and
 
55
    setpoint) and attempting to minimize it manipulating the input.
 
56
 
 
57
    The desired change to the input is calculated based on error and three
 
58
    constants (Kp, Ki and Kd).  These values can be interpreted in terms of
 
59
    time: P depends on the present error, I on the accumulation of past errors,
 
60
    and D is a prediction of future errors, based on current rate of change.
 
61
    The weighted sum of these three actions is used to adjust the process via a
 
62
    control element.
 
63
 
 
64
    In practice, Kp, Ki and Kd are process-dependent and usually have to
 
65
    be tweaked by hand, but once reasonable constants are arrived at, they
 
66
    can apply to a particular process without further modification.
 
67
 
 
68
    """
 
69
    def __init__(self, Kp, Ki, Kd, setpoint=0):
 
70
        """ Creates a PID controller with given constants and setpoint.
 
71
 
 
72
           Arguments:
 
73
           Kp, Ki, Kd: PID constants, see class description.
 
74
           setpoint: desired output value; calls to input_change with
 
75
                     a process output reading will return a desired change
 
76
                     to the input to attempt matching output to this value.
 
77
        """
 
78
        self.setpoint = setpoint
 
79
        self.Kp = Kp
 
80
        self.Ki = Ki
 
81
        self.Kd = Kd
 
82
        self._integral = 0
 
83
        self._previous_error = 0
 
84
        self._change_limit = 0
 
85
 
 
86
    def input_change(self, process_feedback, dt):
 
87
        """ Calculates desired input value change.
 
88
 
 
89
            Based on process feedback and time interval (dt).
 
90
        """
 
91
        error = self.setpoint - process_feedback
 
92
        self._integral = self._integral + (error * dt)
 
93
        derivative = (error - self._previous_error) / dt
 
94
        self._previous_error = error
 
95
        input_change = (self.Kp * error) + \
 
96
                       (self.Ki * self._integral) + \
 
97
                       (self.Kd * derivative)
 
98
        if self._change_limit and abs(input_change) > abs(self._change_limit):
 
99
            sign = input_change / abs(input_change)
 
100
            input_change = sign * self._change_limit
 
101
        return input_change
 
102
 
 
103
    def set_change_limit(self, limit):
 
104
        """Ensures that input value changes are lower than limit.
 
105
 
 
106
           Setting limit of zero disables this.
 
107
        """
 
108
        self._change_limit = limit
 
109
 
 
110
 
 
111
class PAVolumeController(object):
 
112
    pa_types = {'input': 'source', 'output': 'sink'}
 
113
 
 
114
    def __init__(self, type, method=None, logger=None):
 
115
        """Initializes the volume controller.
 
116
 
 
117
           Arguments:
 
118
           type: either input or output
 
119
           method: a method that will run a command and return pulseaudio
 
120
           information in the described format, as a single string with
 
121
           line breaks (to be processed with str.splitlines())
 
122
 
 
123
        """
 
124
        self.type = type
 
125
        self._volume = None
 
126
        self.identifier = None
 
127
        self.method = method
 
128
        if not isinstance(method, collections.Callable):
 
129
            self.method = self._pactl_output
 
130
        self.logger = logger
 
131
 
 
132
    def set_volume(self, volume):
 
133
        if not 0 <= volume <= 100:
 
134
            return False
 
135
        if not self.identifier:
 
136
            return False
 
137
        command = ['pactl',
 
138
                   'set-%s-volume' % (self.pa_types[self.type]),
 
139
                   str(self.identifier[0]),
 
140
                   str(int(volume)) + "%"]
 
141
        if False == self.method(command):
 
142
            return False
 
143
        self._volume = volume
 
144
        return True
 
145
 
 
146
    def get_volume(self):
 
147
        if not self.identifier:
 
148
            return None
 
149
        return self._volume
 
150
 
 
151
    def mute(self, mute):
 
152
        mute = str(int(mute))
 
153
        if not self.identifier:
 
154
            return False
 
155
        command = ['pactl',
 
156
                   'set-%s-mute' % (self.pa_types[self.type]),
 
157
                   str(self.identifier[0]),
 
158
                   mute]
 
159
        if False == self.method(command):
 
160
            return False
 
161
        return True
 
162
 
 
163
    def get_identifier(self):
 
164
        if self.type:
 
165
            self.identifier = self._get_identifier_for(self.type)
 
166
            if self.identifier and self.logger:
 
167
                message = "Using PulseAudio identifier %s (%s) for %s" %\
 
168
                       (self.identifier + (self.type,))
 
169
                self.logger.info(message)
 
170
            return self.identifier
 
171
 
 
172
    def _get_identifier_for(self, type):
 
173
        """Gets default PulseAudio identifier for given type.
 
174
 
 
175
           Arguments:
 
176
           type: either input or output
 
177
 
 
178
           Returns:
 
179
           A tuple: (pa_id, pa_description)
 
180
 
 
181
        """
 
182
 
 
183
        if type not in self.pa_types:
 
184
            return None
 
185
        command = ['pactl', 'list', self.pa_types[type] + "s", 'short']
 
186
 
 
187
        #Expect lines of this form (field separator is tab):
 
188
        #<ID>\t<NAME>\t<MODULE>\t<SAMPLE_SPEC_WITH_SPACES>\t<STATE>
 
189
        #What we need to return is the ID for the first element on this list
 
190
        #that does not contain auto_null or monitor.
 
191
        pa_info = self.method(command)
 
192
        valid_elements = None
 
193
 
 
194
        if pa_info:
 
195
            reject_regex = '.*(monitor|auto_null).*'
 
196
            valid_elements = [element for element in pa_info.splitlines() \
 
197
                              if not re.match(reject_regex, element)]
 
198
        if not valid_elements:
 
199
            if self.logger:
 
200
                self.logger.error("No valid PulseAudio elements"
 
201
                                  " for %s" % (self.type))
 
202
            return None
 
203
        #We only need the pulseaudio numeric ID and long name for each element
 
204
        valid_elements = [(int(element.split()[0]), element.split()[1]) \
 
205
                          for e in valid_elements]
 
206
        return valid_elements[0]
 
207
 
 
208
    def _pactl_output(self, command):
 
209
        #This method mainly calls pactl (hence the name). Since pactl may
 
210
        #return a failure if the audio layer is not yet initialized, we will
 
211
        #try running a few times in case of failure. All our invocations of 
 
212
        #pactl should be "idempotent" so repeating them should not have 
 
213
        #any bad effects.
 
214
        for attempt in range(0, 3):
 
215
            try:
 
216
                return subprocess.check_output(command, 
 
217
                                               universal_newlines=True)
 
218
            except (subprocess.CalledProcessError):
 
219
                time.sleep(5)
 
220
        return False
 
221
 
 
222
 
 
223
class FileDumper(object):
 
224
    def write_to_file(self, filename, data):
 
225
        try:
 
226
            with open(filename, "wb") as f:
 
227
                for i in data:
 
228
                    print(i, file=f)
 
229
            return_value = True
 
230
        except (TypeError, IOError):
 
231
            return_value = False
 
232
        return return_value
 
233
 
 
234
 
 
235
class SpectrumAnalyzer(object):
 
236
    def __init__(self, points, sampling_frequency=44100,
 
237
                 wanted_samples=20):
 
238
        self.spectrum = [0] * points
 
239
        self.number_of_samples = 0
 
240
        self.wanted_samples = wanted_samples
 
241
        self.sampling_frequency = sampling_frequency
 
242
        #Frequencies should contain *real* frequency which is half of
 
243
        #the sampling frequency
 
244
        self.frequencies = [((sampling_frequency / 2.0) / points) * i
 
245
                            for i in range(points)]
 
246
 
 
247
    def _average(self):
 
248
        return sum(self.spectrum) / len(self.spectrum)
 
249
 
 
250
    def sample(self, sample):
 
251
        if len(sample) != len(self.spectrum):
 
252
            return
 
253
        self.spectrum = [((old * self.number_of_samples) + new) /
 
254
                         (self.number_of_samples + 1)
 
255
                         for old, new in zip(self.spectrum, sample)]
 
256
        self.number_of_samples += 1
 
257
 
 
258
    def frequencies_over_average(self, threshold=0.0):
 
259
        return [i for i in range(len(self.spectrum)) \
 
260
                if self.spectrum[i] >= self._average() - threshold]
 
261
 
 
262
    def frequency_band_for(self, frequency):
 
263
        """Convenience function to tell me which band
 
264
           a frequency is contained in
 
265
        """
 
266
        #Note that actual frequencies are half of what the sampling
 
267
        #frequency would tell us. If SF is 44100 then maximum actual
 
268
        #frequency is 22050, and if I have 10 frequency bins each will
 
269
        #contain only 2205 Hz, not 4410 Hz.
 
270
        max_frequency = self.sampling_frequency / 2
 
271
        if frequency > max_frequency or frequency < 0:
 
272
            return None
 
273
        band = float(frequency) / (max_frequency / len(self.spectrum))
 
274
        return int(math.ceil(band)) - 1
 
275
 
 
276
    def frequencies_for_band(self, band):
 
277
        """Convenience function to tell me the delimiting frequencies
 
278
           for a band
 
279
        """
 
280
        if band >= len(self.spectrum) or band < 0:
 
281
            return None
 
282
        lower = self.frequencies[band]
 
283
        upper = lower + ((self.sampling_frequency / 2.0) / len(self.spectrum))
 
284
        return (lower, upper)
 
285
 
 
286
    def sampling_complete(self):
 
287
        return self.number_of_samples >= self.wanted_samples
 
288
 
 
289
 
 
290
class GStreamerRawAudioRecorder(object):
 
291
    def __init__(self):
 
292
        self.raw_buffers = []
 
293
 
 
294
    def buffer_handler(self, sink):
 
295
        buffer = sink.emit('pull-buffer')
 
296
        self.raw_buffers.append(buffer.data)
 
297
 
 
298
    def get_raw_audio(self):
 
299
        return ''.join(self.raw_buffers)
 
300
 
 
301
 
 
302
class GStreamerMessageHandler(object):
 
303
    def __init__(self, rec_level_range, logger, volumecontroller,
 
304
                 pidcontroller, spectrum_analyzer):
 
305
        """Initializes the message handler. It knows how to handle
 
306
           spectrum and level gstreamer messages.
 
307
 
 
308
           Arguments:
 
309
           rec_level_range: tuple with acceptable recording level
 
310
                            ranges
 
311
           logger: logging object with debug, info, error methods.
 
312
           volumecontroller: an instance of VolumeController to use
 
313
                             to adjust RECORDING level
 
314
           pidcontroller: a PID controller instance which helps control
 
315
                          volume
 
316
           spectrum_analyzer: instance of SpectrumAnalyzer to collect
 
317
                              data from spectrum messages
 
318
 
 
319
        """
 
320
        self.current_level = sys.maxsize
 
321
        self.logger = logger
 
322
        self.pid_controller = pidcontroller
 
323
        self.rec_level_range = rec_level_range
 
324
        self.spectrum_analyzer = spectrum_analyzer
 
325
        self.volume_controller = volumecontroller
 
326
 
 
327
    def set_quit_method(self, method):
 
328
        """ Method that will be called when sampling is complete."""
 
329
        self._quit_method = method
 
330
 
 
331
    def bus_message_handler(self, bus, message):
 
332
        if message.type == gst.MESSAGE_ELEMENT:
 
333
            message_name = message.structure.get_name()
 
334
            if message_name == 'spectrum':
 
335
                fft_magnitudes = message.structure['magnitude']
 
336
                self.spectrum_method(self.spectrum_analyzer, fft_magnitudes)
 
337
 
 
338
            if message_name == 'level':
 
339
                #peak_value is our process feedback
 
340
                peak_value = message.structure['peak'][0]
 
341
                self.level_method(peak_value, self.pid_controller,
 
342
                                  self.volume_controller)
 
343
 
 
344
    #Adjust recording level
 
345
    def level_method(self, level, pid_controller, volume_controller):
 
346
        #If volume controller doesn't return a valid volume,
 
347
        #we can't control it :(
 
348
        current_volume = volume_controller.get_volume()
 
349
        if current_volume == None:
 
350
            self.logger.error("Unable to control recording volume."
 
351
                              "Test results may be wrong")
 
352
            return
 
353
        self.current_level = level
 
354
        change = pid_controller.input_change(level, 0.10)
 
355
        if self.logger:
 
356
            self.logger.debug("Peak level: %(peak_level).2f, "
 
357
                         "volume: %(volume)d%%, Volume change: %(change)f%%" %
 
358
                      {'peak_level': level,
 
359
                       'change': change,
 
360
                       'volume': current_volume})
 
361
        volume_controller.set_volume(current_volume + change)
 
362
 
 
363
    #Only sample if level is within the threshold
 
364
    def spectrum_method(self, analyzer, spectrum):
 
365
        if self.rec_level_range[1] <= self.current_level \
 
366
           or self.current_level <= self.rec_level_range[0]:
 
367
            self.logger.debug("Sampling, recorded %d samples" %
 
368
                               analyzer.number_of_samples)
 
369
            analyzer.sample(spectrum)
 
370
        if analyzer.sampling_complete() and self._quit_method:
 
371
            self.logger.info("Sampling complete, ending process")
 
372
            self._quit_method()
 
373
 
 
374
 
 
375
class GstAudioObject(object):
 
376
    def __init__(self):
 
377
        self.class_name = self.__class__.__name__
 
378
 
 
379
    def _set_state(self, state, description):
 
380
        self.pipeline.set_state(state)
 
381
        message = "%s: %s" % (self.class_name, description)
 
382
        if self.logger:
 
383
            self.logger.info(message)
 
384
 
 
385
    def start(self):
 
386
        self._set_state(gst.STATE_PLAYING, "Starting")
 
387
 
 
388
    def stop(self):
 
389
        self._set_state(gst.STATE_NULL, "Stopping")
 
390
 
 
391
 
 
392
class Player(GstAudioObject):
 
393
    def __init__(self, frequency=DEFAULT_TEST_FREQUENCY, logger=None):
 
394
        super(Player, self).__init__()
 
395
        self.pipeline_description = ("audiotestsrc wave=sine freq=%s "
 
396
                                "! audioconvert "
 
397
                                "! audioresample "
 
398
                                "! autoaudiosink" % int(frequency))
 
399
        self.logger = logger
 
400
        if self.logger:
 
401
            self.logger.debug(self.pipeline_description)
 
402
        self.pipeline = gst.parse_launch(self.pipeline_description)
 
403
 
 
404
 
 
405
class Recorder(GstAudioObject):
 
406
    def __init__(self, bins=BINS, sampling_frequency=SAMPLING_FREQUENCY,
 
407
                 fft_interval=FFT_INTERVAL, logger=None):
 
408
        super(Recorder, self).__init__()
 
409
        pipeline_description = ('''autoaudiosrc
 
410
        ! queue
 
411
        ! level message=true
 
412
        ! audioconvert
 
413
        ! audio/x-raw-int, channels=1, rate=%(rate)s
 
414
        ! audioresample
 
415
        ! spectrum interval=%(fft_interval)s bands = %(bands)s
 
416
        ! wavenc
 
417
        ! appsink name=recordersink emit-signals=true''' %
 
418
        {'bands': bins,
 
419
         'rate': sampling_frequency,
 
420
         'fft_interval': fft_interval})
 
421
        self.logger = logger
 
422
        if self.logger:
 
423
            self.logger.debug(pipeline_description)
 
424
        self.pipeline = gst.parse_launch(pipeline_description)
 
425
 
 
426
    def register_message_handler(self, handler_method):
 
427
        if self.logger:
 
428
            message = "Registering message handler: %s" % handler_method
 
429
            self.logger.debug(message)
 
430
        self.bus = self.pipeline.get_bus()
 
431
        self.bus.add_signal_watch()
 
432
        self.bus.connect('message', handler_method)
 
433
 
 
434
    def register_buffer_handler(self, handler_method):
 
435
        if self.logger:
 
436
            message = "Registering buffer handler: %s" % handler_method
 
437
            self.logger.debug(message)
 
438
        self.sink = self.pipeline.get_by_name('recordersink')
 
439
        self.sink.connect('new-buffer', handler_method)
 
440
 
 
441
 
 
442
def process_arguments():
 
443
    description = """
 
444
        Plays a single frequency through the default output, then records on
 
445
        the default input device. Analyzes the recorded signal to test for
 
446
        presence of the played frequency, if present it exits with success.
 
447
    """
 
448
    parser = argparse.ArgumentParser(description=description)
 
449
    parser.add_argument("-t", "--time",
 
450
            dest='test_duration',
 
451
            action='store',
 
452
            default=30,
 
453
            type=int,
 
454
            help="""Maximum test duration, default %(default)s seconds.
 
455
                    It may exit sooner if it determines it has enough data.""")
 
456
    parser.add_argument("-a", "--audio",
 
457
            action='store',
 
458
            default=None,
 
459
            type=str,
 
460
            help="File to save recorded audio in .wav format")
 
461
    parser.add_argument("-q", "--quiet",
 
462
            action='store_true',
 
463
            default=False,
 
464
            help="Be quiet, no output unless there's an error.")
 
465
    parser.add_argument("-d", "--debug",
 
466
            action='store_true',
 
467
            default=False,
 
468
            help="Debugging output")
 
469
    parser.add_argument("-f", "--frequency",
 
470
            action='store',
 
471
            default=DEFAULT_TEST_FREQUENCY,
 
472
            type=int,
 
473
            help="Frequency for test signal, default %(default)s Hz")
 
474
    parser.add_argument("-u", "--spectrum",
 
475
            action='store',
 
476
            type=str,
 
477
            help="""File to save spectrum information for plotting
 
478
                    (one frequency/magnitude pair per line)""")
 
479
    return parser.parse_args()
 
480
 
 
481
 
 
482
#
 
483
def main():
 
484
    #Get arguments.
 
485
    args = process_arguments()
 
486
 
 
487
    #Setup logging
 
488
    level = logging.INFO
 
489
    if args.debug:
 
490
        level = logging.DEBUG
 
491
    if args.quiet:
 
492
        level = logging.ERROR
 
493
    logging.basicConfig(level=level)
 
494
    try:
 
495
        #Launches recording pipeline. I need to hook up into the gst
 
496
        #messages.
 
497
        recorder = Recorder(logger=logging)
 
498
        #Just launches the playing pipeline
 
499
        player = Player(frequency=args.frequency, logger=logging)
 
500
    except GError:
 
501
        logging.critical("Unable to initialize GStreamer pipelines")
 
502
        sys.exit(127)
 
503
 
 
504
    #This just receives a process feedback and tells me how much to change to
 
505
    #achieve the setpoint
 
506
    pidctrl = PIDController(Kp=0.7, Ki=.01, Kd=0.01,
 
507
                            setpoint=REC_LEVEL_RANGE[0])
 
508
    pidctrl.set_change_limit(5)
 
509
    #This  gathers spectrum data.
 
510
    analyzer = SpectrumAnalyzer(points=BINS,
 
511
                                sampling_frequency=SAMPLING_FREQUENCY)
 
512
    #this receives 'buffer' messages and gathers raw audio data
 
513
    rawaudio = GStreamerRawAudioRecorder()
 
514
 
 
515
    #Volume controllers actually set volumes for their device types.
 
516
    #we should at least issue a warning
 
517
    recorder.volumecontroller = PAVolumeController(type='input',
 
518
                                                   logger=logging)
 
519
    if not recorder.volumecontroller.get_identifier():
 
520
        logging.warning("Unable to get input volume control identifier. "
 
521
                       "Test results will probably be invalid")
 
522
    recorder.volumecontroller.set_volume(0)
 
523
    recorder.volumecontroller.mute(False)
 
524
 
 
525
    player.volumecontroller = PAVolumeController(type='output',
 
526
                                                 logger=logging)
 
527
    if not player.volumecontroller.get_identifier():
 
528
        logging.warning("Unable to get output volume control identifier. "
 
529
                       "Test results will probably be invalid")
 
530
    player.volumecontroller.set_volume(70)
 
531
    player.volumecontroller.mute(False)
 
532
 
 
533
    #This handles the messages from gstreamer and orchestrates
 
534
    #the passed volume controllers, pid controller and spectrum analyzer
 
535
    #accordingly.
 
536
    gmh = GStreamerMessageHandler(rec_level_range=REC_LEVEL_RANGE,
 
537
                                  logger=logging,
 
538
                                  volumecontroller=recorder.volumecontroller,
 
539
                                  pidcontroller=pidctrl,
 
540
                                  spectrum_analyzer=analyzer)
 
541
 
 
542
    #I need to tell the recorder which method will handle messages.
 
543
    recorder.register_message_handler(gmh.bus_message_handler)
 
544
    recorder.register_buffer_handler(rawaudio.buffer_handler)
 
545
 
 
546
    #Create the loop and add a few triggers
 
547
    gobject.threads_init()
 
548
    loop = gobject.MainLoop()
 
549
    gobject.timeout_add_seconds(0, player.start)
 
550
    gobject.timeout_add_seconds(0, recorder.start)
 
551
    gobject.timeout_add_seconds(args.test_duration, loop.quit)
 
552
 
 
553
    # Tell the gmh which method to call when enough samples are collected
 
554
    gmh.set_quit_method(loop.quit)
 
555
 
 
556
    loop.run()
 
557
 
 
558
    #When the loop ends, set things back to reasonable states
 
559
    player.stop()
 
560
    recorder.stop()
 
561
    player.volumecontroller.set_volume(50)
 
562
    recorder.volumecontroller.set_volume(10)
 
563
 
 
564
    #See if data gathering was successful.
 
565
    test_band = analyzer.frequency_band_for(args.frequency)
 
566
    candidate_bands = analyzer.frequencies_over_average(MAGNITUDE_THRESHOLD)
 
567
    if test_band in candidate_bands:
 
568
        freqs_for_band = analyzer.frequencies_for_band(test_band)
 
569
        logging.info("PASS: Test frequency of %s in band (%.2f, %.2f) "
 
570
              "which had a magnitude higher than the average" %
 
571
            ((args.frequency,) + freqs_for_band))
 
572
        return_value = 0
 
573
    else:
 
574
        logging.info("FAIL: Test frequency of %s is not in one of the "
 
575
              "bands with higher-than-average magnitude" % args.frequency)
 
576
        return_value = 1
 
577
    #Is the microphone broken?
 
578
    if len(set(analyzer.spectrum)) <= 1:
 
579
        logging.info("WARNING: Microphone seems broken, didn't even "
 
580
                     "record ambient noise")
 
581
 
 
582
    #Write some files to disk for later analysis
 
583
    if args.audio:
 
584
        logging.info("Saving recorded audio as %s" % args.audio)
 
585
        if not FileDumper().write_to_file(args.audio,
 
586
                                          [rawaudio.get_raw_audio()]):
 
587
            logging.error("Couldn't save recorded audio", file=sys.stderr)
 
588
 
 
589
    if args.spectrum:
 
590
        logging.info("Saving spectrum data for plotting as %s" % 
 
591
                     args.spectrum)
 
592
        if not FileDumper().write_to_file(args.spectrum,
 
593
                                       ["%s,%s" % t for t in
 
594
                                        zip(analyzer.frequencies,
 
595
                                            analyzer.spectrum)]):
 
596
            logging.error("Couldn't save spectrum data for plotting", 
 
597
                          file=sys.stderr)
 
598
 
 
599
    return return_value
 
600
 
 
601
if __name__ == "__main__":
 
602
    sys.exit(main())