#!/usr/bin/env python """ Oscilloscope + spectrum analyser in Python. ------------------------------------------------------------ Copyright (C) 2008, Roger Fearick, University of Cape Town This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ------------------------------------------------------------ Version 0.7c Dependencies: uumpy -- numerics, fft PyQt4, PyQwt5 -- gui, graphics pyaudio -- sound card -- Enthought unstable branch! This code provides an oscillator and spectrum analyzer using the PC sound card as input. The interface, based on qwt, uses a familar 'knob based' layout so that it approximates an analogue scope. Two traces are provided with imput via the sound card "line in" jack. Traces can be averaged to reduce influence of noise. The cross-correlation between the inputs can be computed. The spectrum analyser has both log (dB) scale and linear scale. A cross hair status display permits the reading ov values off the screen. Printing is provided. """ # dualscope6.py derived from dualscopy5.py 11/8/05 # adds autocorrelation # Update for Qt4: 4-11/10/2007 rwf # dualscope7.py: use pyaudio 27/2/08 rwf import sys from PyQt4 import Qt from PyQt4 import Qwt5 as Qwt from numpy import * import numpy.fft as FFT import pyaudio import icons # part of this package -- toolbar icons # audio setup CHUNK = 8192 # input buffer size in frames FORMAT = pyaudio.paInt16 CHANNELS = 2 RATE = 48000 # depends on sound card: 96000 might be possible # scope configuration BOTHLR=0 LEFT=1 RIGHT=2 soundbuffersize=CHUNK samplerate=float(RATE) scopeheight=350 LRchannel=BOTHLR PENWIDTH=2 # status messages freezeInfo = 'Freeze: Press mouse button and drag' cursorInfo = 'Cursor Pos: Press mouse button in plot region' # utility classes class LogKnob(Qwt.QwtKnob): """ Provide knob with log scale """ def __init__(self, *args): apply(Qwt.QwtKnob.__init__, (self,) + args) self.setScaleEngine(Qwt.QwtLog10ScaleEngine()) def setRange(self,minR,maxR): self.setScale(minR,maxR) Qwt.QwtKnob.setRange(self, log10(minR), log10(maxR), 0.333333) def setValue(self,val): Qwt.QwtKnob.setValue(self,log10(val)) class LblKnob: """ Provide knob with a label """ def __init__(self, wgt, x,y, name, logscale=0): if logscale: self.knob=LogKnob(wgt) else: self.knob=Qwt.QwtKnob(wgt) color=Qt.QColor(200,200,210) self.knob.palette().setColor(Qt.QPalette.Active, Qt.QPalette.Button, color ) self.lbl=Qt.QLabel(name, wgt) self.knob.setGeometry(x, y, 140, 100) # oooh, eliminate this ... if name[0]=='o': self.knob.setKnobWidth(40) self.lbl.setGeometry(x, y+90, 140, 15) self.lbl.setAlignment(Qt.Qt.AlignCenter) def setRange(self,*args): apply(self.knob.setRange, args) def setValue(self,*args): apply(self.knob.setValue, args) def setScaleMaxMajor(self,*args): apply(self.knob.setScaleMaxMajor, args) class Scope(Qwt.QwtPlot): """ Oscilloscope display widget """ def __init__(self, *args): apply(Qwt.QwtPlot.__init__, (self,) + args) self.setTitle('Scope'); self.setCanvasBackground(Qt.Qt.white) # grid self.grid = Qwt.QwtPlotGrid() self.grid.enableXMin(True) self.grid.setMajPen(Qt.QPen(Qt.Qt.gray, 0, Qt.Qt.SolidLine)) self.grid.attach(self) # axes self.enableAxis(Qwt.QwtPlot.yRight); self.setAxisTitle(Qwt.QwtPlot.xBottom, 'Time [s]'); self.setAxisTitle(Qwt.QwtPlot.yLeft, 'Amplitude [V]'); self.setAxisMaxMajor(Qwt.QwtPlot.xBottom, 10); self.setAxisMaxMinor(Qwt.QwtPlot.xBottom, 0); self.setAxisScaleEngine(Qwt.QwtPlot.yRight, Qwt.QwtLinearScaleEngine()); self.setAxisMaxMajor(Qwt.QwtPlot.yLeft, 10); self.setAxisMaxMinor(Qwt.QwtPlot.yLeft, 0); self.setAxisMaxMajor(Qwt.QwtPlot.yRight, 10); self.setAxisMaxMinor(Qwt.QwtPlot.yRight, 0); # curves for scope traces: 2 first so 1 is on top self.curve2 = Qwt.QwtPlotCurve('Trace2') self.curve2.setPen(Qt.QPen(Qt.Qt.magenta,PENWIDTH)) self.curve2.setYAxis(Qwt.QwtPlot.yRight) self.curve2.attach(self) self.curve1 = Qwt.QwtPlotCurve('Trace1') self.curve1.setPen(Qt.QPen(Qt.Qt.blue,PENWIDTH)) self.curve1.setYAxis(Qwt.QwtPlot.yLeft) self.curve1.attach(self) # default settings self.triggerval=0.0 self.maxamp=1.0 self.maxamp2=1.0 self.freeze=0 self.average=0 self.autocorrelation=0 self.avcount=0 self.datastream = None self.offset1=0.0 self.offset2=0.0 # set data # NumPy: f, g, a and p are arrays! self.dt=1.0/samplerate self.f = arange(0.0, 1.0, self.dt) self.a1 = 0.0*self.f self.a2 = 0.0*self.f self.curve1.setData(self.f, self.a1) self.curve2.setData(self.f, self.a2) # start self.timerEvent() callbacks running self.startTimer(100) # plot self.replot() # convenience methods for knob callbacks def setMaxAmp(self, val): self.maxamp=val def setMaxAmp2(self, val): self.maxamp2=val def setMaxTime(self, val): self.maxtime=val def setOffset1(self, val): self.offset1=val def setOffset2(self, val): self.offset2=val def setTriggerLevel(self, val): self.triggerval=val # plot scope traces def setDisplay(self): l=len(self.a1) if LRchannel==BOTHLR: self.curve1.setData(self.f[0:l], self.a1[:l]+self.offset1*self.maxamp) self.curve2.setData(self.f[0:l], self.a2[:l]+self.offset2*self.maxamp2) elif LRchannel==RIGHT: self.curve1.setData([0.0,0.0], [0.0,0.0]) self.curve2.setData(self.f[0:l], self.a2[:l]+self.offset2*self.maxamp2) elif LRchannel==LEFT: self.curve1.setData(self.f[0:l], self.a1[:l]+self.offset1*self.maxamp) self.curve2.setData([0.0,0.0], [0.0,0.0]) self.replot() def getValue(self, index): return self.f[index],self.a[index] def setAverage(self, state): self.average = state self.avcount=0 def setAutoc(self, state): self.autocorrelation = state self.avcount=0 def setFreeze(self, freeze): self.freeze = 1-self.freeze def setDatastream(self, datastream): self.datastream = datastream # timer callback that does the work def timerEvent(self,e): # Scope if self.datastream == None: return x=self.datastream.read(CHUNK) if self.freeze==1 or self.avcount>16: return X=fromstring(x,dtype='h') if len(X) == 0: return P=array(X,dtype='d')/32768.0 val=self.triggerval*self.maxamp i=0 R=P[0::2] L=P[1::2] if self.autocorrelation: lenX=len(R) if lenX == 0: return if lenX!=soundbuffersize: print lenX window=blackman(lenX) A1=FFT.fft(R*window) #lenX A2=FFT.fft(L*window) #lenX B2=(A1*conjugate(A2))/10.0 R=FFT.ifft(B2).real else: # normal scope # set trigger levels for i in range(len(R)-1): if R[i]=val: break if i > len(R)-2: i=0 R=R[i:] L=L[i:] if self.average == 0: self.a1=R self.a2=L else: self.avcount+=1 if self.avcount==1: self.sumR=R self.sumL=L else: lp=min(len(R),len(self.sumR)) self.sumR=self.sumR[:lp]+R[:lp] self.sumL=self.sumL[:lp]+L[:lp] self.a1=self.sumR/self.avcount self.a2=self.sumL/self.avcount self.setDisplay() inittime=0.01 initamp=0.1 class ScopeFrame(Qt.QFrame): """ Oscilloscope widget --- contains controls + display """ def __init__(self, *args): apply(Qt.QFrame.__init__, (self,) + args) # the following: setPal.. doesn't seem to work on Win try: self.setPaletteBackgroundColor( QColor(240,240,245)) except: pass knobpos=scopeheight+30 self.setFixedSize(700, scopeheight+150) self.freezeState = 0 self.knbLevel = LblKnob(self,560,50,"Trigger level") self.knbTime = LblKnob(self,560, 220,"Time", 1) self.knbSignal = LblKnob(self,150, knobpos, "Signal1",1) self.knbSignal2 = LblKnob(self,450, knobpos, "Signal2",1) self.knbOffset1=LblKnob(self,10, knobpos,"offset1") self.knbOffset2=LblKnob(self,310, knobpos,"offset2") self.knbTime.setRange(0.0001, 1.0) self.knbTime.setValue(0.01) self.knbSignal.setRange(0.0001, 1.0) self.knbSignal.setValue(0.1) self.knbSignal2.setRange(0.0001, 1.0) self.knbSignal2.setValue(0.1) self.knbOffset2.setRange(-1.0, 1.0, 0.001) self.knbOffset2.setValue(0.0) self.knbOffset1.setRange(-1.0, 1.0, 0.001) self.knbOffset1.setValue(0.0) self.knbLevel.setRange(-1.0, 1.0, 0.001) self.knbLevel.setValue(0.1) self.knbLevel.setScaleMaxMajor(10) self.plot = Scope(self) self.plot.setGeometry(10, 10, 550, scopeheight) self.picker = Qwt.QwtPlotPicker( Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.DragSelection, Qwt.QwtPlotPicker.CrossRubberBand, Qwt.QwtPicker.ActiveOnly, #AlwaysOn, self.plot.canvas()) self.picker.setRubberBandPen(Qt.QPen(Qt.Qt.green)) self.picker.setTrackerPen(Qt.QPen(Qt.Qt.cyan)) self.connect(self.knbTime.knob, Qt.SIGNAL("valueChanged(double)"), self.setTimebase) self.knbTime.setValue(0.01) self.connect(self.knbSignal.knob, Qt.SIGNAL("valueChanged(double)"), self.setAmplitude) self.connect(self.knbSignal2.knob, Qt.SIGNAL("valueChanged(double)"), self.setAmplitude2) self.knbSignal.setValue(0.1) self.connect(self.knbLevel.knob, Qt.SIGNAL("valueChanged(double)"), self.setTriggerlevel) self.connect(self.knbOffset1.knob, Qt.SIGNAL("valueChanged(double)"), self.plot.setOffset1) self.connect(self.knbOffset2.knob, Qt.SIGNAL("valueChanged(double)"), self.plot.setOffset2) self.knbLevel.setValue(0.1) self.plot.setAxisScale( Qwt.QwtPlot.xBottom, 0.0, 10.0*inittime) self.plot.setAxisScale( Qwt.QwtPlot.yLeft, -5.0*initamp, 5.0*initamp) self.plot.setAxisScale( Qwt.QwtPlot.yRight, -5.0*initamp, 5.0*initamp) self.plot.show() def _calcKnobVal(self,val): ival=floor(val) frac=val-ival if frac >=0.9: frac=1.0 elif frac>=0.66: frac=log10(5.0) elif frac>=log10(2.0): frac=log10(2.0) else: frac=0.0 dt=10**frac*10**ival return dt def setTimebase(self, val): dt=self._calcKnobVal(val) self.plot.setAxisScale( Qwt.QwtPlot.xBottom, 0.0, 10.0*dt) self.plot.replot() def setAmplitude(self, val): dt=self._calcKnobVal(val) self.plot.setAxisScale( Qwt.QwtPlot.yLeft, -5.0*dt, 5.0*dt) self.plot.setMaxAmp( 5.0*dt ) self.plot.replot() def setAmplitude2(self, val): dt=self._calcKnobVal(val) self.plot.setAxisScale( Qwt.QwtPlot.yRight, -5.0*dt, 5.0*dt) self.plot.setMaxAmp2( 5.0*dt ) self.plot.replot() def setTriggerlevel(self, val): self.plot.setTriggerLevel(val) self.plot.setDisplay() #-------------------------------------------------------------------- class FScope(Qwt.QwtPlot): """ Power spectrum display widget """ def __init__(self, *args): apply(Qwt.QwtPlot.__init__, (self,) + args) self.setTitle('Power spectrum'); self.setCanvasBackground(Qt.Qt.white) # grid self.grid = Qwt.QwtPlotGrid() self.grid.enableXMin(True) self.grid.setMajPen(Qt.QPen(Qt.Qt.gray, 0, Qt.Qt.SolidLine)); self.grid.attach(self) # axes self.setAxisTitle(Qwt.QwtPlot.xBottom, 'Frequency [Hz]'); self.setAxisTitle(Qwt.QwtPlot.yLeft, 'Power [dB]'); self.setAxisMaxMajor(Qwt.QwtPlot.xBottom, 10); self.setAxisMaxMinor(Qwt.QwtPlot.xBottom, 0); self.setAxisMaxMajor(Qwt.QwtPlot.yLeft, 10); self.setAxisMaxMinor(Qwt.QwtPlot.yLeft, 0); # curves self.curve2 = Qwt.QwtPlotCurve('PSTrace2') self.curve2.setPen(Qt.QPen(Qt.Qt.magenta,PENWIDTH)) self.curve2.setYAxis(Qwt.QwtPlot.yLeft) self.curve2.attach(self) self.curve1 = Qwt.QwtPlotCurve('PSTrace1') self.curve1.setPen(Qt.QPen(Qt.Qt.blue,PENWIDTH)) self.curve1.setYAxis(Qwt.QwtPlot.yLeft) self.curve1.attach(self) self.triggerval=0.0 self.maxamp=1.0 self.freeze=0 self.average=0 self.avcount=0 self.logy=1 self.datastream=None self.dt=1.0/samplerate self.df=1.0/(soundbuffersize*self.dt) self.f = arange(0.0, samplerate, self.df) self.a = 0.0*self.f self.p = 0.0*self.f self.curve1.setData(self.f, self.a) self.setAxisScale( Qwt.QwtPlot.xBottom, 0.0, 10.0*initfreq) self.setAxisScale( Qwt.QwtPlot.yLeft, -120.0, 0.0) self.startTimer(100) self.replot() def resetBuffer(self): self.df=1.0/(soundbuffersize*self.dt) self.f = arrayrange(0.0, 20000.0, self.df) self.a = 0.0*self.f self.p = 0.0*self.f self.curve1.setData(self.curve1, self.f, self.a) def setMaxAmp(self, val): if val>0.6: self.setAxisScale( Qwt.QwtPlot.yLeft, -120.0, 0.0) self.logy=1 else: self.setAxisScale( Qwt.QwtPlot.yLeft, 0.0, 10.0*val) self.logy=0 self.maxamp=val def setMaxTime(self, val): self.maxtime=val def setTriggerLevel(self, val): self.triggerval=val def setDisplay(self): n=soundbuffersize/2 if LRchannel==BOTHLR: self.curve1.setData(self.f[0:n], self.a[:n]) self.curve2.setData(self.f[0:n], self.a2[:n]) elif LRchannel==RIGHT: self.curve1.setData([0.0,0.0], [0.0,0.0]) self.curve2.setData(self.f[0:n], self.a2[:n]) elif LRchannel==LEFT: self.curve1.setData(self.f[0:n], self.a[:n]) self.curve2.setData([0.0,0.0], [0.0,0.0]) self.replot() def getValue(self, index): return self.f[index],self.a[index] def setAverage(self, state): self.average = state self.avcount=0 def setFreeze(self, freeze): self.freeze = 1-self.freeze def setDatastream(self, datastream): self.datastream = datastream def timerEvent(self,e): # FFT if self.datastream == None: return x=self.datastream.read(CHUNK) if self.freeze==1: return X=fromstring(x,dtype='h') if len(X) == 0: return P=array(X,dtype='d')/32768.0 val=self.triggerval*self.maxamp i=0 R=P[0::2] L=P[1::2] lenX=len(R) if lenX == 0: return if lenX!=(CHUNK): print 'size fail',lenX window=blackman(lenX) sumw=sum(window*window) A=FFT.fft(R*window) #lenX B=(A*conjugate(A)).real A=FFT.fft(L*window) #lenX B2=(A*conjugate(A)).real sumw*=2.0 # sym about Nyquist (*4); use rms (/2) sumw/=self.dt # sample rate B=B/sumw B2=B2/sumw if self.logy: P1=log10(B)*10.0+20.0#60.0 P2=log10(B2)*10.0+20.0#60.0 else: P1=B P2=B2 if self.average == 0: self.a=P1 self.a2=P2 else: self.avcount+=1 if self.avcount==1: self.sumP1=P1 self.sumP2=P2 else: self.sumP1=self.sumP1+P1 self.sumP2=self.sumP2+P2 self.a=self.sumP1/self.avcount self.a2=self.sumP2/self.avcount self.setDisplay() initfreq=100.0 class FScopeFrame(Qt.QFrame): """ Power spectrum widget --- contains controls + display """ def __init__(self , *args): apply(Qt.QFrame.__init__, (self,) + args) knobpos=scopeheight+30 # the following: setPal.. doesn't seem to work on Ein try: self.setPaletteBackgroundColor( QColor(240,240,245)) except: pass self.setFixedSize(700, scopeheight+150) self.freezeState = 0 self.knbSignal = LblKnob(self,160, knobpos, "Signal",1) self.knbTime = LblKnob(self,310, knobpos,"Frequency", 1) self.knbTime.setRange(1.0, 2000.0) self.knbSignal.setRange(0.0000001, 1.0) self.plot = FScope(self) self.plot.setGeometry(10, 10, 500, scopeheight) self.picker = Qwt.QwtPlotPicker( Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.DragSelection, Qwt.QwtPlotPicker.CrossRubberBand, Qwt.QwtPicker.ActiveOnly, #AlwaysOn, self.plot.canvas()) self.picker.setRubberBandPen(Qt.QPen(Qt.Qt.green)) self.picker.setTrackerPen(Qt.QPen(Qt.Qt.cyan)) self.connect(self.knbTime.knob, Qt.SIGNAL("valueChanged(double)"), self.setTimebase) self.knbTime.setValue(1000.0) self.connect(self.knbSignal.knob, Qt.SIGNAL("valueChanged(double)"), self.setAmplitude) self.knbSignal.setValue(1.0) self.plot.show() def _calcKnobVal(self,val): ival=floor(val) frac=val-ival if frac >=0.9: frac=1.0 elif frac>=0.66: frac=log10(5.0) elif frac>=log10(2.0): frac=log10(2.0) else: frac=0.0 dt=10**frac*10**ival return dt def setTimebase(self, val): dt=self._calcKnobVal(val) self.plot.setAxisScale( Qwt.QwtPlot.xBottom, 0.0, 10.0*dt) self.plot.replot() def setAmplitude(self, val): dt=self._calcKnobVal(val) self.plot.setMaxAmp( dt ) self.plot.replot() #--------------------------------------------------------------------- class FScopeDemo(Qt.QMainWindow): """ Application container widget Contains scope and power spectrum analyser in tabbed windows. Enables switching between the two. Handles toolbar and status. """ def __init__(self, *args): apply(Qt.QMainWindow.__init__, (self,) + args) self.freezeState = 0 self.changeState = 0 self.averageState = 0 self.autocState = 0 self.scope = ScopeFrame(self) self.current = self.scope self.pwspec = FScopeFrame(self) self.pwspec.hide() self.stack=Qt.QTabWidget(self) self.stack.addTab(self.scope,"scope") self.stack.addTab(self.pwspec,"fft") self.setCentralWidget(self.stack) toolBar = Qt.QToolBar(self) self.addToolBar(toolBar) sb=self.statusBar() sbfont=Qt.QFont("Helvetica",12) sb.setFont(sbfont) self.btnFreeze = Qt.QToolButton(toolBar) self.btnFreeze.setText("Freeze") self.btnFreeze.setIcon(Qt.QIcon(Qt.QPixmap(icons.stopicon))) self.btnFreeze.setCheckable(True) self.btnFreeze.setToolButtonStyle(Qt.Qt.ToolButtonTextUnderIcon) toolBar.addWidget(self.btnFreeze) self.btnPrint = Qt.QToolButton(toolBar) self.btnPrint.setText("Print") self.btnPrint.setIcon(Qt.QIcon(Qt.QPixmap(icons.print_xpm))) self.btnPrint.setToolButtonStyle(Qt.Qt.ToolButtonTextUnderIcon) toolBar.addWidget(self.btnPrint) self.btnMode = Qt.QToolButton(toolBar) self.btnMode.setText("fft") self.btnMode.setIcon(Qt.QIcon(Qt.QPixmap(icons.pwspec))) self.btnMode.setCheckable(True) self.btnMode.setToolButtonStyle(Qt.Qt.ToolButtonTextUnderIcon) toolBar.addWidget(self.btnMode) self.btnAvge = Qt.QToolButton(toolBar) self.btnAvge.setText("average") self.btnAvge.setIcon(Qt.QIcon(Qt.QPixmap(icons.avge))) self.btnAvge.setCheckable(True) self.btnAvge.setToolButtonStyle(Qt.Qt.ToolButtonTextUnderIcon) toolBar.addWidget(self.btnAvge) self.btnAutoc = Qt.QToolButton(toolBar) self.btnAutoc.setText("correlate") self.btnAutoc.setIcon(Qt.QIcon(Qt.QPixmap(icons.avge))) self.btnAutoc.setCheckable(True) self.btnAutoc.setToolButtonStyle(Qt.Qt.ToolButtonTextUnderIcon) toolBar.addWidget(self.btnAutoc) self.lstLabl = Qt.QLabel("Buffer:",toolBar) toolBar.addWidget(self.lstLabl) self.lstChan = Qt.QComboBox(toolBar) self.lstChan.insertItem(0,"8192") self.lstChan.insertItem(1,"16k") self.lstChan.insertItem(2,"32k") toolBar.addWidget(self.lstChan) self.lstLR = Qt.QLabel("Channels:",toolBar) toolBar.addWidget(self.lstLR) self.lstLRmode = Qt.QComboBox(toolBar) self.lstLRmode.insertItem(0,"LR") self.lstLRmode.insertItem(1,"L") self.lstLRmode.insertItem(2,"R") toolBar.addWidget(self.lstLRmode) self.connect(self.btnPrint, Qt.SIGNAL('clicked()'), self.printPlot) self.connect(self.btnFreeze, Qt.SIGNAL('toggled(bool)'), self.freeze) self.connect(self.btnMode, Qt.SIGNAL('toggled(bool)'), self.mode) self.connect(self.btnAvge, Qt.SIGNAL('toggled(bool)'), self.average) self.connect(self.btnAutoc, Qt.SIGNAL('toggled(bool)'), self.autocorrelation) self.connect(self.lstChan, Qt.SIGNAL('activated(int)'), self.fftsize) self.connect(self.lstLRmode, Qt.SIGNAL('activated(int)'), self.channel) self.connect(self.scope.picker, Qt.SIGNAL('moved(const QPoint&)'), self.moved) self.connect(self.scope.picker, Qt.SIGNAL('appended(const QPoint&)'), self.appended) self.connect(self.pwspec.picker, Qt.SIGNAL('moved(const QPoint&)'), self.moved) self.connect(self.pwspec.picker, Qt.SIGNAL('appended(const QPoint&)'), self.appended) self.connect(self.stack, Qt.SIGNAL('currentChanged(int)'), self.mode) self.showInfo(cursorInfo) def showInfo(self, text): self.statusBar().showMessage(text) def printPlot(self): p = QPrinter() if p.setup(): self.current.plot.printPlot(p)#, Qwt.QwtFltrDim(200)); def fftsize(self, item): pass ## global s, soundbuffersize ## s.stop() ## s.close() ## if item==2: ## soundbuffersize=8192*3 ## elif item==1: ## soundbuffersize=8192*2 ## else: ## soundbuffersize=8192 ## s=f.stream(48000,2,'int16',soundbuffersize,1) ## s.open() ## s.start() ## self.pwspec.plot.resetBuffer() ## if self.current==self.pwspec: ## self.pwspec.plot.setDatastream(s) ## self.pwspec.plot.avcount=0 ## else: ## self.scope.plot.setDatastream(s) def channel(self, item): global LRchannel if item==2: LRchannel=RIGHT elif item==1: LRchannel=LEFT else: LRchannel=BOTHLR def freeze(self, on): if on: self.freezeState = 1 self.btnFreeze.setText("Run") self.btnFreeze.setIcon(Qt.QIcon(Qt.QPixmap(icons.goicon))) else: self.freezeState = 0 self.btnFreeze.setText("Freeze") self.btnFreeze.setIcon(Qt.QIcon(Qt.QPixmap(icons.stopicon))) self.scope.plot.setFreeze(self.freezeState) self.pwspec.plot.setFreeze(self.freezeState) def average(self, on): if on: self.averageState = 1 self.btnAvge.setText("single") self.btnAvge.setIcon(Qt.QIcon(Qt.QPixmap(icons.single))) else: self.averageState = 0 self.btnAvge.setText("average") self.btnAvge.setIcon(Qt.QIcon(Qt.QPixmap(icons.avge))) self.scope.plot.setAverage(self.averageState) self.pwspec.plot.setAverage(self.averageState) def autocorrelation(self, on): if on: self.autocState = 1 self.btnAutoc.setText("normal") self.btnAutoc.setIcon(Qt.QIcon(Qt.QPixmap(icons.single))) else: self.autocState = 0 self.btnAutoc.setText("correlate") self.btnAutoc.setIcon(Qt.QIcon(Qt.QPixmap(icons.avge))) self.scope.plot.setAutoc(self.autocState) def mode(self, on): if on: self.changeState=1 self.current=self.pwspec self.btnMode.setText("scope") self.btnMode.setIcon(Qt.QIcon(Qt.QPixmap(icons.scope))) else: self.changeState=0 self.current=self.scope self.btnMode.setText("fft") self.btnMode.setIcon(Qt.QIcon(Qt.QPixmap(icons.pwspec))) if self.changeState==1: self.stack.setCurrentIndex(self.changeState) self.scope.plot.setDatastream(None) self.pwspec.plot.setDatastream(stream) else: self.stack.setCurrentIndex(self.changeState) self.pwspec.plot.setDatastream(None) self.scope.plot.setDatastream(stream) def moved(self, e): if self.changeState==1: name='Freq' else: name='Time' frequency = self.current.plot.invTransform(Qwt.QwtPlot.xBottom, e.x()) amplitude = self.current.plot.invTransform(Qwt.QwtPlot.yLeft, e.y()) if name=='Time': df=self.scope.plot.dt i=int(frequency/df) ampa=self.scope.plot.a1[i] ampb=self.scope.plot.a2[i] else: df=self.pwspec.plot.df i=int(frequency/df) ampa=self.pwspec.plot.a[i] ampb=self.pwspec.plot.a2[i] self.showInfo('%s=%g, cursor=%g, A=%g, B=%g' % (name,frequency, amplitude,ampa,ampb)) def appended(self, e): print 's' # Python semantics: self.pos = e.pos() does not work; force a copy self.xpos = e.x() self.ypos = e.y() self.moved(e) # fake a mouse move to show the cursor position # open sound card data stream p = pyaudio.PyAudio() stream = p.open(format = FORMAT, channels = CHANNELS, rate = RATE, input = True, frames_per_buffer = CHUNK) # Admire! app = Qt.QApplication(sys.argv) demo=FScopeDemo() demo.scope.plot.setDatastream(stream) demo.show() app.exec_() stream.stop_stream() stream.close() p.terminate()