示例#1
0
class SpectrumGUI:
    """
    Creates a Spectrum Analyser window which process data from an Arduino Board
    and plots it.

    Parameters
    ----------
    None

    Attributes
    ----------
    board : ArduinoBoard()
        Arduino from which data is gathered.
    sample_no : int
        Number of samples in a frame.
    sample_freq : int
        Sampling Frequency.
    win : KeyPressedWindow()
        Window to display plots.

    Public Methods
    --------------
    keyPressed(self, evt):
        Handles key pressed while the graphs are in focus. Sends the pressed
        command to the Arduino, and re-scales the axis if sampling frequency
        is changed.

    scale_plots(self):
        Scales the figures based on the current sampling frequency.

    align_music(self, freq):
        Scales the self.NOTES such that the given frequency is in range.

    tune(self, sp_data):
        Finds the closest frequency to a natural octave note from the input signal.

    set_plotdata(self, name, data_x, data_y):
        Sets the data for the give plot name.

    update(self):
        Gathers new data and updates all the plots.

    spectrogram_update(self, sp_data):
        Updates the spectrogram plot
    """
    def __init__(self):
        self.board = ArduinoBoard("/dev/ttyACM0", 230400, timeout=5)
        self.data_analyser = DataLogger(self.board.sample_no,
                                        self.board.sample_freq)
        self.f, self.x = self.data_analyser.set_sample_freq(
            self.board.sample_freq)

        self.mode = None
        self.xscale = 1
        self.yscale = 1

        # pyqtgraph stuff
        pg.setConfigOptions(antialias=True)
        self.traces = dict()
        self.app = QtGui.QApplication(sys.argv)
        self.win = KeyPressWindow(title='Spectrum Analyzer')
        self.win.setWindowTitle('Spectrum Analyzer')

        self.win.sigKeyPress.connect(self.keyPressed)

        self.waveform = self.win.addPlot(title='WAVEFORM',
                                         row=1,
                                         col=1,
                                         labels={'bottom': "Time (s)"})
        self.spectrum = self.win.addPlot(title='SPECTRUM',
                                         row=1,
                                         col=2,
                                         labels={'bottom': "Frequency (Hz)"})
        self.specgram = self.win.addPlot(title='SPECTROGRAM',
                                         row=2,
                                         col=1,
                                         colspan=2,
                                         labels={'bottom': "Frequency (Hz)"})

        self.img = pg.ImageItem()
        self.specgram.addItem(self.img)

        # bipolar colormap
        pos = np.array([0., 1., 0.5, 0.25, 0.75])
        color = np.array(
            [[0, 255, 255, 255], [255, 255, 0, 255], [0, 0, 0, 255],
             (0, 0, 255, 255), (255, 0, 0, 255)],
            dtype=np.ubyte)
        cmap = pg.ColorMap(pos, color)
        lut = cmap.getLookupTable(0.0, 1.0, 256)

        self.img.setLookupTable(lut)
        self.img.setLevels([20 * np.log10(10), 20 * np.log10(10000)])

        # waveform and spectrum x points
        self.scale_plots()

        # tell Arduino to start sending data
        self.board.send_command("Send Data")

    def keyPressed(self, evt):
        """
        Handles key pressed while the graphs are in focus. Sends the pressed
        command to the Arduino, and re-scales the axis if sampling frequency
        is changed.
        """
        msg = chr(evt.key())
        if msg == ' ':
            cmd = input('>>\n')
            self.txt_command(cmd)
            return
        else:
            msg = int(msg)
        if msg in {0, 8, 9}:
            if msg == 0:
                self.f, self.x = self.data_analyser.set_sample_freq(4000)
                self.board.sample_freq = 4000
            elif msg == 8:
                self.f, self.x = self.data_analyser.set_sample_freq(7000)
                self.board.sample_freq = 7000
            elif msg == 9:
                self.f, self.x = self.data_analyser.set_sample_freq(9000)
                self.board.sample_freq = 9000

            self.scale_plots()
            self.board.send_command(msg)

    def txt_command(self, cmd):
        """Converts a text based input into a command to send to the board."""
        cmd = cmd.split(' ')
        print(cmd)
        if cmd[0] == 'h':
            print("Text based interface:")
            print(
                "filt   <frequency kHz> - sets the low pass digital filter frequency < 4.5kHz"
            )
            print(
                "sample <frequency kHz> - sets the sampling frequency of 4kHz, 7kHz, or 9kHz"
            )
            print(
                "frame  <frame length>  - number of samples per frame {256, 512, 800, 1024}"
            )

        elif cmd[0] == 'mode':
            if cmd[1] == 'record':
                try:
                    self.file_name = cmd[2]
                except IndexError:
                    print("Record/ Compare must have filename supplied")
                    return
            if cmd[1] == 'compare':
                try:
                    self.file_name = cmd[2]
                except IndexError:
                    self.file_name = None
                try:
                    self.cmp_name = cmd[3]
                except IndexError:
                    self.cmp_name = None

            self.mode = cmd[1]
            return

        elif cmd[0] == 'filter':
            try:
                new_fc = int(cmd[1])
                if new_fc > 4500:
                    raise ValueError('')
                self.data_analyser.set_high_cutoff(new_fc)
            except ValueError:
                print("Filter Frequency must be < 4.5k")
                return

        elif cmd[0] == 'sample':
            try:
                cmd = int(cmd[1])
                if cmd not in {4, 7, 9}:
                    raise ValueError()
                else:
                    self.f, self.x = self.data_analyser.set_sample_freq(cmd *
                                                                        1000)
                    self.board.sample_freq = cmd * 1000
            except ValueError:
                print("Sample rate must be 4, 7, or 9 kHz")
                return

            self.board.send_command("Sample {}k".format(int(cmd)))
            self.scale_plots()

        elif cmd[0] == 'frame':
            try:
                cmd = int(cmd[1])
                if cmd not in {256, 512, 800, 1024}:
                    raise ValueError()
                else:
                    self.f, self.x = self.data_analyser.set_frame_len(cmd)
                    self.board.sample_no = cmd
                self.board.send_command("Frame {}".format(cmd))
                self.scale_plots()
            except ValueError:
                print("Frame length must be 256, 512, 800, 1024")
                return

    def scale_plots(self):
        """Scales the figures based on the current sampling frequency"""
        self.waveform.setXRange(0, self.x.max(), padding=0.005)
        self.spectrum.setXRange(0, self.f.max(), padding=0.005)
        self.specgram.setXRange(0, self.f.max(), padding=0.005)
        yscale = self.data_analyser.sample_freq / (
            self.data_analyser.get_specgram().shape[1] * self.yscale)

        xscale = self.data_analyser.sample_freq / (
            self.data_analyser.frame_len * self.xscale)
        self.img.scale(xscale, yscale)
        self.xscale *= xscale
        self.yscale *= yscale

    def set_plotdata(self, name, data_x, data_y):
        """Sets the data for the given plot name"""
        if name in self.traces:
            self.traces[name].setData(data_x, data_y)
        else:
            if name == 'waveform':
                self.traces[name] = self.waveform.plot(pen='c', width=3)
                self.waveform.setYRange(-150, 150, padding=0)
            if name == 'spectrum':
                self.traces[name] = self.spectrum.plot(pen='m', width=3)
                self.spectrum.setYRange(0, 10000, padding=0)

    def update(self):
        """Gathers new data and updates all the plots"""
        try:
            wf_data = self.board.get_data()
        except struct.error:
            print("[+] Unpacking error")
            return

        sp_data, wf_data = self.data_analyser.process(wf_data)

        self.set_plotdata(
            name='waveform',
            data_x=self.x,
            data_y=wf_data,
        )
        self.set_plotdata(name='spectrum', data_x=self.f, data_y=sp_data)
        self.img.setImage(self.data_analyser.get_specgram().T,
                          autoLevels=False)

        if self.mode == 'tune':
            peak, note, LED = self.data_analyser.tune()
            self.board.send_command(int(LED + 3))
        elif self.mode == 'record':
            if self.data_analyser.record(self.file_name):
                self.mode = 'standby'

        elif self.mode == 'compare':
            if self.data_analyser.audio_match(self.file_name, self.cmp_name):
                self.mode = 'standby'

    def animation(self):
        timer = QtCore.QTimer()
        timer.timeout.connect(self.update)
        timer.start(20)
        self.start()

    def start(self):
        if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
            QtGui.QApplication.instance().exec_()