class Scope_Widget(QtWidgets.QWidget): name = "Oscilloscope" def __init__(self, parent, logger=PrintLogger()): super().__init__(parent) self.logger = logger self.setObjectName(self.name) self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.settings_dialog = Scope_Settings_Dialog(self, self.logger) self.time_range_s = DEFAULT_TIMERANGE * 0.001 self.num_sample_points = int(self.time_range_s * SAMPLING_RATE) self.time = numpy.linspace(0, self.time_range_s, self.num_sample_points) # Keep a small display buffer so we can use triggering in the data display. self.display_buffer = RingBuffer(1, 2*self.num_sample_points) def handle_new_data(self, floatdata): self.display_buffer.push(floatdata) data = self.display_buffer.unwound_data() is_dual_channel = data.shape[0] == 2 # trigger on the first channel only trigger_data = data[0, :] # trigger on half of the waveform half_sample = int(self.num_sample_points/2) trig_search_start = half_sample trig_search_stop = -half_sample trigger_data = trigger_data[trig_search_start:trig_search_stop] trigger_level = data.max() * 2. / 3. trigger_pos = numpy.where((trigger_data[:-1] < trigger_level) * (trigger_data[1:] >= trigger_level))[0] if len(trigger_pos) > 0: shift = trigger_pos[0] else: return shift += trig_search_start data = data[:, shift - half_sample: shift + half_sample] y = data[0, :] if is_dual_channel: y2 = data[1, :] else: y2 = None dBscope = False if dBscope: dBmin = -50. y = numpy.sign(y) * (20 * numpy.log10( abs(y))).clip(dBmin, 0.) / (-dBmin) + numpy.sign(y) * 1. if is_dual_channel: y2 = numpy.sign(y2) * (20 * numpy.log10( abs(y2))).clip(dBmin, 0.) / (-dBmin) + numpy.sign(y2) * 1. else: y2 = None if y2 is not None: self.PlotZoneUp.setdataTwoChannels(self.time, y, y2) else: self.PlotZoneUp.setdata(self.time, y) # method def canvasUpdate(self): return def pause(self): self.PlotZoneUp.pause() def restart(self): self.PlotZoneUp.restart() # slot def set_timerange(self, timerange): self.time_range_s = timerange*0.001 self.num_sample_points = int(self.time_range_s * SAMPLING_RATE) self.time = numpy.linspace(0, self.time_range_s, self.num_sample_points) self.display_buffer.reset(self.display_buffer.num_channels, self.num_sample_points) # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class Scope_Widget(QtGui.QWidget): def __init__(self, parent = None, logger = None): QtGui.QWidget.__init__(self, parent) self.audiobuffer = None # store the logger instance if logger is None: self.logger = parent.parent.logger else: self.logger = logger self.setObjectName("Scope_Widget") self.gridLayout = QtGui.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.setStyleSheet(STYLESHEET) self.settings_dialog = Scope_Settings_Dialog(self, self.logger) # method def set_buffer(self, buffer): self.audiobuffer = buffer # method def update(self): if not self.isVisible(): return time = 2*SMOOTH_DISPLAY_TIMER_PERIOD_MS/1000. width = time*SAMPLING_RATE #basic trigger capability on leading edge floatdata = self.audiobuffer.data(2*width) #number of data points received at the last audio buffer update #newpoints = self.audiobuffer.newpoints #print newpoints # because of the buffering, sometimes we have not got any data #if newpoints==0: # return #floatdata = self.audiobuffer.data(newpoints + width) twoChannels = False if floatdata.shape[0] > 1: twoChannels = True # trigger on the first channel only triggerdata = floatdata[0,:] # trigger on half of the waveform trig_search_start = width/2 trig_search_stop = -width/2 triggerdata = triggerdata[trig_search_start : trig_search_stop] max = floatdata.max() trigger_level = max*2./3. #trigger_level = 0.6 trigger_pos = where((triggerdata[:-1] < trigger_level)*(triggerdata[1:] >= trigger_level))[0] if len(trigger_pos)==0: return if len(trigger_pos) > 0: shift = trigger_pos[0] else: #return shift = 0 shift += trig_search_start datarange = width floatdata = floatdata[:, shift - datarange/2: shift + datarange/2] y = floatdata[0,:] #- floatdata.mean() if twoChannels: y2 = floatdata[1,:] #- floatdata.mean() dBscope = False if dBscope: dBmin = -50. y = sign(y)*(20*log10(abs(y))).clip(dBmin, 0.)/(-dBmin) + sign(y)*1. if twoChannels: y2 = sign(y2)*(20*log10(abs(y2))).clip(dBmin, 0.)/(-dBmin) + sign(y2)*1. time = (arange(len(y)) - datarange/2)/float(SAMPLING_RATE) if twoChannels: self.PlotZoneUp.setdataTwoChannels(time, y, y2) else: self.PlotZoneUp.setdata(time, y) # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class LongLevelWidget(QtWidgets.QWidget): def __init__(self, parent=None): super().__init__(parent) self.setObjectName("LongLevels_Widget") self.setObjectName("Scope_Widget") self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self) self.PlotZoneUp.setObjectName("PlotZoneUp") self.PlotZoneUp.setverticaltitle("Level (dB FS RMS)") self.PlotZoneUp.sethorizontaltitle("Time (min)") self.PlotZoneUp.setTrackerFormatter(lambda x, y: "%.3g min, %.3g" % (x, y)) self.level_min = DEFAULT_LEVEL_MIN self.level_max = DEFAULT_LEVEL_MAX self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.audiobuffer = None # initialize the settings dialog self.settings_dialog = LongLevels_Settings_Dialog(self) # initialize the class instance that will do the fft self.proc = audioproc() self.level = None # 1e-30 self.level_rms = -200. self.two_channels = False self.i = 0 self.old_index = 0 # self.response_time = 60. # 1 minute self.response_time = 20. # how many times we should decimate to end up with 100 points in the kernel self.Ndec = int( max(0, np.floor( (np.log2(self.response_time * SAMPLING_RATE / 100.))))) Ngauss = 4 self.b = np.array(gauss(10 * Ngauss + 1, 2. * Ngauss)) self.a = np.zeros(self.b.shape) self.a[0] = 1. self.zf = np.zeros(max(len(self.b), len(self.a)) - 1) self.subsampled_sampling_rate = SAMPLING_RATE / 2**(self.Ndec) self.subsampler = Subsampler(self.Ndec) self.length_seconds = 60. * 10 # actually this should be linked to the pixel width of the plot area self.length_samples = int(self.length_seconds * self.subsampled_sampling_rate) # ringbuffer for the subsampled data self.ringbuffer = RingBuffer() # method def set_buffer(self, buffer): self.audiobuffer = buffer def handle_new_data(self, floatdata): # we need to maintain an index of where we are in the buffer index = self.audiobuffer.ringbuffer.offset self.last_data_time = self.audiobuffer.lastDataTime available = index - self.old_index if available < 0: # ringbuffer must have grown or something... available = 0 self.old_index = index # if we have enough data to add a frequency column in the time-frequency plane, compute it needed = int(2**self.Ndec) realizable = int(np.floor(available / needed)) if realizable > 0: for i in range(realizable): floatdata = self.audiobuffer.data_indexed( self.old_index, needed) # first channel y0 = floatdata[0, :] y0_squared = y0**2 # subsample y0_squared_dec = self.subsampler.push(y0_squared) self.level, self.zf = pyx_lfilter_float64_1D( self.b, self.a, y0_squared_dec, self.zf) self.level_rms = 10. * np.log10(max(self.level, 1e-150)) l = np.array([self.level_rms]) l.shape = (1, 1) self.ringbuffer.push(l) self.old_index += needed self.time = np.arange( self.length_samples) / self.subsampled_sampling_rate levels = self.ringbuffer.data(self.length_samples) self.PlotZoneUp.setdata(self.time / 60., levels[0, :]) # method def canvasUpdate(self): # nothing to do here return def setmin(self, value): self.level_min = value self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) def setmax(self, value): self.level_max = value self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class Scope_Widget(QtWidgets.QWidget): def __init__(self, parent, sharedGLWidget, logger=PrintLogger()): super().__init__(parent) self.audiobuffer = None self.logger = logger self.setObjectName("Scope_Widget") self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, sharedGLWidget, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.settings_dialog = Scope_Settings_Dialog(self, self.logger) self.timerange = DEFAULT_TIMERANGE # method def set_buffer(self, buffer): self.audiobuffer = buffer # method def update(self): if not self.isVisible(): return time = self.timerange * 1e-3 width = int(time * SAMPLING_RATE) #basic trigger capability on leading edge floatdata = self.audiobuffer.data(2 * width) #number of data points received at the last audio buffer update #newpoints = self.audiobuffer.newpoints #print newpoints # because of the buffering, sometimes we have not got any data #if newpoints==0: # return #floatdata = self.audiobuffer.data(newpoints + width) twoChannels = False if floatdata.shape[0] > 1: twoChannels = True # trigger on the first channel only triggerdata = floatdata[0, :] # trigger on half of the waveform trig_search_start = width / 2 trig_search_stop = -width / 2 triggerdata = triggerdata[trig_search_start:trig_search_stop] max = floatdata.max() trigger_level = max * 2. / 3. #trigger_level = 0.6 trigger_pos = where((triggerdata[:-1] < trigger_level) * (triggerdata[1:] >= trigger_level))[0] if len(trigger_pos) == 0: return if len(trigger_pos) > 0: shift = trigger_pos[0] else: #return shift = 0 shift += trig_search_start datarange = width floatdata = floatdata[:, shift - datarange / 2:shift + datarange / 2] y = floatdata[0, :] #- floatdata.mean() if twoChannels: y2 = floatdata[1, :] #- floatdata.mean() dBscope = False if dBscope: dBmin = -50. y = sign(y) * (20 * log10(abs(y))).clip( dBmin, 0.) / (-dBmin) + sign(y) * 1. if twoChannels: y2 = sign(y2) * (20 * log10(abs(y2))).clip( dBmin, 0.) / (-dBmin) + sign(y2) * 1. time = (arange(len(y)) - datarange / 2) / float(SAMPLING_RATE) if twoChannels: self.PlotZoneUp.setdataTwoChannels(time, y, y2) else: self.PlotZoneUp.setdata(time, y) # slot def set_timerange(self, timerange): self.timerange = timerange # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class Scope_Widget(QtGui.QWidget): def __init__(self, parent = None, logger = None): QtGui.QWidget.__init__(self, parent) self.audiobuffer = None # store the logger instance if logger is None: self.logger = parent.parent.logger else: self.logger = logger self.setObjectName("Scope_Widget") self.gridLayout = QtGui.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.setStyleSheet(STYLESHEET) self.settings_dialog = Scope_Settings_Dialog(self, self.logger) # method def set_buffer(self, buffer): self.audiobuffer = buffer # method def update(self): if not self.isVisible(): return time = SMOOTH_DISPLAY_TIMER_PERIOD_MS/1000. #basic trigger capability on leading edge floatdata = self.audiobuffer.data(time*SAMPLING_RATE) twoChannels = False if floatdata.shape[0] > 1: twoChannels = True # trigger on the first channel only floatdata = floatdata[0,:] max = floatdata.max() trigger_level = max*2./3. trigger_pos = where((floatdata[:-1] < trigger_level)*(floatdata[1:] >= trigger_level))[0] if len(trigger_pos) > 0: shift = time*SAMPLING_RATE - trigger_pos[0] else: shift = 0 floatdata = self.audiobuffer.data(time*SAMPLING_RATE + shift) floatdata = floatdata[:, 0 : time*SAMPLING_RATE] y = floatdata[0,::-1] #- floatdata.mean() if twoChannels: y2 = floatdata[1,::-1] #- floatdata.mean() dBscope = False if dBscope: dBmin = -50. y = sign(y)*(20*log10(abs(y))).clip(dBmin, 0.)/(-dBmin) + sign(y)*1. if twoChannels: y2 = sign(y2)*(20*log10(abs(y2))).clip(dBmin, 0.)/(-dBmin) + sign(y2)*1. time = linspace(0., len(y)/float(SAMPLING_RATE), len(y)) if twoChannels: self.PlotZoneUp.setdataTwoChannels(time, y, y2) else: self.PlotZoneUp.setdata(time, y) # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class LongLevelWidget(QtWidgets.QWidget): def __init__(self, parent=None, logger=PrintLogger()): super().__init__(parent) self.setObjectName("LongLevels_Widget") self.logger = logger self.setObjectName("Scope_Widget") self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.PlotZoneUp.setverticaltitle("Level (dB FS RMS)") self.PlotZoneUp.sethorizontaltitle("Time (min)") self.PlotZoneUp.setTrackerFormatter(lambda x, y: "%.3g min, %.3g" % (x, y)) self.level_min = DEFAULT_LEVEL_MIN self.level_max = DEFAULT_LEVEL_MAX self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.logger = logger self.audiobuffer = None # initialize the settings dialog self.settings_dialog = LongLevels_Settings_Dialog(self, self.logger) # initialize the class instance that will do the fft self.proc = audioproc(self.logger) self.level = None # 1e-30 self.level_rms = -200. self.two_channels = False self.i = 0 self.old_index = 0 #self.response_time = 60. # 1 minute self.response_time = 20. # how many times we should decimate to end up with 100 points in the kernel self.Ndec = int(max(0, np.floor((np.log2(self.response_time * SAMPLING_RATE/100.))))) Ngauss = 4 self.b = np.array(gauss(10*Ngauss+1, 2.*Ngauss)) self.a = np.zeros(self.b.shape) self.a[0] = 1. self.zf = np.zeros(max(len(self.b), len(self.a)) - 1) self.subsampled_sampling_rate = SAMPLING_RATE / 2 ** (self.Ndec) self.subsampler = Subsampler(self.Ndec) self.length_seconds = 60.*10 # actually this should be linked to the pixel width of the plot area self.length_samples = self.length_seconds * self.subsampled_sampling_rate # ringbuffer for the subsampled data self.ringbuffer = RingBuffer(self.logger) # method def set_buffer(self, buffer): self.audiobuffer = buffer def handle_new_data(self, floatdata): # we need to maintain an index of where we are in the buffer index = self.audiobuffer.ringbuffer.offset self.last_data_time = self.audiobuffer.lastDataTime available = index - self.old_index if available < 0: # ringbuffer must have grown or something... available = 0 self.old_index = index # if we have enough data to add a frequency column in the time-frequency plane, compute it needed = 2**self.Ndec realizable = int(np.floor(available / needed)) if realizable > 0: for i in range(realizable): floatdata = self.audiobuffer.data_indexed(self.old_index, needed) # first channel y0 = floatdata[0, :] y0_squared = y0**2 # subsample y0_squared_dec = self.subsampler.push(y0_squared) self.level, self.zf = pyx_lfilter_float64_1D(self.b, self.a, y0_squared_dec, self.zf) self.level_rms = 10. * np.log10(max(self.level, 1e-150)) l = np.array([self.level_rms]) l.shape = (1, 1) self.ringbuffer.push(l) self.old_index += int(needed) self.time = np.arange(self.length_samples) / self.subsampled_sampling_rate levels = self.ringbuffer.data(self.length_samples) self.PlotZoneUp.setdata(self.time/60., levels[0,:]) # method def canvasUpdate(self): # nothing to do here return def setmin(self, value): self.level_min = value self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) def setmax(self, value): self.level_max = value self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class Scope_Widget(QtWidgets.QWidget): def __init__(self, parent, logger = PrintLogger()): super().__init__(parent) self.audiobuffer = None self.logger = logger self.setObjectName("Scope_Widget") self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.settings_dialog = Scope_Settings_Dialog(self, self.logger) self.timerange = DEFAULT_TIMERANGE self.time = zeros(10) self.y = zeros(10) self.y2 = zeros(10) # method def set_buffer(self, buffer): self.audiobuffer = buffer def handle_new_data(self, floatdata): time = self.timerange*1e-3 width = int(time*SAMPLING_RATE) #basic trigger capability on leading edge floatdata = self.audiobuffer.data(2*width) #number of data points received at the last audio buffer update #newpoints = self.audiobuffer.newpoints #print newpoints # because of the buffering, sometimes we have not got any data #if newpoints==0: # return #floatdata = self.audiobuffer.data(newpoints + width) twoChannels = False if floatdata.shape[0] > 1: twoChannels = True # trigger on the first channel only triggerdata = floatdata[0,:] # trigger on half of the waveform trig_search_start = width/2 trig_search_stop = -width/2 triggerdata = triggerdata[trig_search_start : trig_search_stop] max = floatdata.max() trigger_level = max*2./3. #trigger_level = 0.6 trigger_pos = where((triggerdata[:-1] < trigger_level)*(triggerdata[1:] >= trigger_level))[0] if len(trigger_pos)==0: return if len(trigger_pos) > 0: shift = trigger_pos[0] else: #return shift = 0 shift += trig_search_start datarange = width floatdata = floatdata[:, shift - datarange/2: shift + datarange/2] self.y = floatdata[0,:] #- floatdata.mean() if twoChannels: self.y2 = floatdata[1,:] #- floatdata.mean() else: self.y2 = None dBscope = False if dBscope: dBmin = -50. self.y = sign(self.y)*(20*log10(abs(self.y))).clip(dBmin, 0.)/(-dBmin) + sign(self.y)*1. if twoChannels: self.y2 = sign(self.y2)*(20*log10(abs(self.y2))).clip(dBmin, 0.)/(-dBmin) + sign(self.y2)*1. else: self.y2 = None self.time = (arange(len(self.y)) - datarange/2)/float(SAMPLING_RATE) if self.y2 is not None: self.PlotZoneUp.setdataTwoChannels(self.time, self.y, self.y2) else: self.PlotZoneUp.setdata(self.time, self.y) # method def canvasUpdate(self): return def pause(self): self.PlotZoneUp.pause() def restart(self): self.PlotZoneUp.restart() # slot def set_timerange(self, timerange): self.timerange = timerange # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class Scope_Widget(QtWidgets.QWidget): def __init__(self, parent, logger=PrintLogger()): super().__init__(parent) self.audiobuffer = None self.logger = logger self.setObjectName("Scope_Widget") self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.settings_dialog = Scope_Settings_Dialog(self, self.logger) self.timerange = DEFAULT_TIMERANGE self.time = zeros(10) self.y = zeros(10) self.y2 = zeros(10) # method def set_buffer(self, buffer): self.audiobuffer = buffer def handle_new_data(self, floatdata): time = self.timerange * 1e-3 width = int(time * SAMPLING_RATE) # basic trigger capability on leading edge floatdata = self.audiobuffer.data(2 * width) twoChannels = False if floatdata.shape[0] > 1: twoChannels = True # trigger on the first channel only triggerdata = floatdata[0, :] # trigger on half of the waveform trig_search_start = width / 2 trig_search_stop = -width / 2 triggerdata = triggerdata[trig_search_start:trig_search_stop] trigger_level = floatdata.max() * 2. / 3. trigger_pos = where((triggerdata[:-1] < trigger_level) * (triggerdata[1:] >= trigger_level))[0] if len(trigger_pos) == 0: return if len(trigger_pos) > 0: shift = trigger_pos[0] else: shift = 0 shift += trig_search_start datarange = width floatdata = floatdata[:, shift - datarange / 2:shift + datarange / 2] self.y = floatdata[0, :] if twoChannels: self.y2 = floatdata[1, :] else: self.y2 = None dBscope = False if dBscope: dBmin = -50. self.y = sign(self.y) * (20 * log10(abs(self.y))).clip( dBmin, 0.) / (-dBmin) + sign(self.y) * 1. if twoChannels: self.y2 = sign(self.y2) * (20 * log10(abs(self.y2))).clip( dBmin, 0.) / (-dBmin) + sign(self.y2) * 1. else: self.y2 = None self.time = (arange(len(self.y)) - datarange / 2) / float(SAMPLING_RATE) if self.y2 is not None: self.PlotZoneUp.setdataTwoChannels(self.time * 1e3, self.y, self.y2) else: self.PlotZoneUp.setdata(self.time * 1e3, self.y) # method def canvasUpdate(self): return def pause(self): self.PlotZoneUp.pause() def restart(self): self.PlotZoneUp.restart() # slot def set_timerange(self, timerange): self.timerange = timerange # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)
class LongLevelWidget(QtWidgets.QWidget): def __init__(self, parent=None, logger=PrintLogger()): super().__init__(parent) self.setObjectName("LongLevels_Widget") self.logger = logger self.setObjectName("Scope_Widget") self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.PlotZoneUp = TimePlot(self, self.logger) self.PlotZoneUp.setObjectName("PlotZoneUp") self.PlotZoneUp.setverticaltitle("Level (dB FS RMS)") self.PlotZoneUp.sethorizontaltitle("Time (min)") self.PlotZoneUp.setTrackerFormatter(lambda x, y: "%.3g min, %.3g" % (x, y)) self.level_min = DEFAULT_LEVEL_MIN self.level_max = DEFAULT_LEVEL_MAX self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) self.gridLayout.addWidget(self.PlotZoneUp, 0, 0, 1, 1) self.logger = logger self.audiobuffer = None # initialize the settings dialog self.settings_dialog = LongLevels_Settings_Dialog(self, self.logger) # initialize the class instance that will do the fft self.proc = audioproc(self.logger) #self.response_time = 60. # 1 minute self.response_time = 1. # an exponential smoothing filter is a simple IIR filter # s_i = alpha*x_i + (1-alpha)*s_{i-1} # we compute alpha so that the n most recent samples represent 100*w percent of the output w = 0.65 # how many times we should decimate to end up with 100 points in the kernel self.Ndec = int( max(0, np.floor( (np.log2(self.response_time * SAMPLING_RATE / 100.))))) n = self.response_time * SAMPLING_RATE / 2**self.Ndec N = int(5 * n) self.alpha = 1. - (1. - w)**(1. / (n + 1)) self.kernel = (1. - self.alpha)**(np.arange(0, N)[::-1]) self.level = None # 1e-30 self.level_rms = -200. self.two_channels = False self.i = 0 self.subsampled_sampling_rate = SAMPLING_RATE / 2**(self.Ndec) self.subsampler = Subsampler(self.Ndec) self.old_index = 0 self.length_seconds = 60. * 10 # actually this should be linked to the pixel width of the plot area self.length_samples = self.length_seconds * self.subsampled_sampling_rate # ringbuffer for the subsampled data self.ringbuffer = RingBuffer(self.logger) # method def set_buffer(self, buffer): self.audiobuffer = buffer def handle_new_data(self, floatdata): # we need to maintain an index of where we are in the buffer index = self.audiobuffer.ringbuffer.offset self.last_data_time = self.audiobuffer.lastDataTime available = index - self.old_index if available < 0: # ringbuffer must have grown or something... available = 0 self.old_index = index # if we have enough data to add a frequency column in the time-frequency plane, compute it needed = 2**self.Ndec realizable = int(np.floor(available / needed)) if realizable > 0: for i in range(realizable): floatdata = self.audiobuffer.data_indexed( self.old_index, needed) # first channel y0 = floatdata[0, :] y0_squared = y0**2 # subsample y0_squared_dec = self.subsampler.push(y0_squared) # exponential smoothing if self.level is None: self.level = y0_squared_dec[0] else: self.level = self.alpha * y0_squared_dec[0] + ( 1. - self.alpha) * self.level self.level_rms = 10. * np.log10(max(self.level, 1e-150)) l = np.array([self.level_rms]) l.shape = (1, 1) self.ringbuffer.push(l) self.old_index += int(needed) self.time = np.arange( self.length_samples) / self.subsampled_sampling_rate levels = self.ringbuffer.data(self.length_samples) self.PlotZoneUp.setdata(self.time / 60., levels[0, :]) # method def canvasUpdate(self): # nothing to do here return def setmin(self, value): self.level_min = value self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) def setmax(self, value): self.level_max = value self.PlotZoneUp.setverticalrange(self.level_min, self.level_max) # slot def settings_called(self, checked): self.settings_dialog.show() # method def saveState(self, settings): self.settings_dialog.saveState(settings) # method def restoreState(self, settings): self.settings_dialog.restoreState(settings)