Example #1
0
    def __init__(self, parent, audiobackend, logger=PrintLogger()):
        super().__init__(parent)

        self.logger = logger

        self.setObjectName("Spectrogram_Widget")
        self.gridLayout = QtWidgets.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.PlotZoneImage = ImagePlot(self, self.logger, audiobackend)
        self.PlotZoneImage.setObjectName("PlotZoneImage")
        self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

        self.audiobuffer = None
        self.audiobackend = audiobackend

        # initialize the class instance that will do the fft
        self.proc = audioproc(self.logger)

        self.maxfreq = DEFAULT_MAXFREQ
        self.proc.set_maxfreq(self.maxfreq)
        self.minfreq = DEFAULT_MINFREQ
        self.fft_size = 2 ** DEFAULT_FFT_SIZE * 32
        self.proc.set_fftsize(self.fft_size)
        self.spec_min = DEFAULT_SPEC_MIN
        self.spec_max = DEFAULT_SPEC_MAX
        self.weighting = DEFAULT_WEIGHTING

        self.update_weighting()
        self.freq = self.proc.get_freq_scale()

        self.timerange_s = DEFAULT_TIMERANGE
        self.canvas_width = 100.

        self.old_index = 0
        self.overlap = 3. / 4.
        self.overlap_frac = Fraction(3, 4)
        self.dT_s = self.fft_size * (1. - self.overlap) / float(SAMPLING_RATE)

        self.PlotZoneImage.setlog10freqscale()  # DEFAULT_FREQ_SCALE = 1 #log10
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
        self.PlotZoneImage.setweighting(self.weighting)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)
        self.update_jitter()

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size) / (Fraction(1) - self.overlap_frac) / 1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)

        # initialize the settings dialog
        self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)

        self.audiobackend.underflow.connect(self.PlotZoneImage.plotImage.canvasscaledspectrogram.syncOffsets)

        self.last_data_time = 0.

        self.mustRestart = False
Example #2
0
	def __init__(self, parent, logger = None):
		QtGui.QWidget.__init__(self, parent)

		# store the logger instance
		if logger is None:
		    self.logger = parent.parent().logger
		else:
		    self.logger = logger
		
		self.parent = parent

		self.setObjectName("Spectrogram_Widget")
		self.gridLayout = QtGui.QGridLayout(self)
		self.gridLayout.setObjectName("gridLayout")
		self.PlotZoneImage = ImagePlot(self, self.logger)
		self.PlotZoneImage.setObjectName("PlotZoneImage")
		self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

		self.audiobuffer = None
		
		# initialize the class instance that will do the fft
		self.proc = audioproc(self.logger)

		self.maxfreq = DEFAULT_MAXFREQ
		self.minfreq = DEFAULT_MINFREQ
		self.fft_size = 2**DEFAULT_FFT_SIZE*32
		self.spec_min = DEFAULT_SPEC_MIN
		self.spec_max = DEFAULT_SPEC_MAX
		self.weighting = DEFAULT_WEIGHTING
		
		self.spectrogram_timer_time = 0.
		
		self.timerange_s = DEFAULT_TIMERANGE
		self.canvas_width = 100.
		
		self.PlotZoneImage.setlog10freqscale() #DEFAULT_FREQ_SCALE = 1 #log10
		self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
		self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
		self.PlotZoneImage.setweighting(self.weighting)
		self.PlotZoneImage.settimerange(self.timerange_s)
		
		# this timer is used to update the spectrogram widget, whose update period
		# is fixed by the time scale and the width of the widget canvas
		self.timer = QtCore.QTimer()
		self.period_ms = SMOOTH_DISPLAY_TIMER_PERIOD_MS
		self.timer.setInterval(self.period_ms) # variable timing
		
		# initialize the settings dialog
		self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)
		
		# timer ticks
		self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.timer_slot)
		
		# window resize
		self.connect(self.PlotZoneImage.plotImage.canvasscaledspectrogram, QtCore.SIGNAL("canvasWidthChanged"), self.canvasWidthChanged)

		# we do not use the display timer since we have a special one
		# tell the caller by setting this variable as None
		self.update = None
Example #3
0
    def __init__(self, parent, audiobackend, logger = None):
        QtGui.QWidget.__init__(self, parent)

        # store the logger instance
        if logger is None:
            self.logger = parent.parent().logger
        else:
            self.logger = logger
        			
        self.parent = parent

        self.setObjectName("Spectrogram_Widget")
        self.gridLayout = QtGui.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.PlotZoneImage = ImagePlot(self, self.logger, audiobackend)
        #self.PlotZoneImage = GLRollingCanvasWidget(self, self.logger)
        self.PlotZoneImage.setObjectName("PlotZoneImage")
        self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

        self.audiobuffer = None
        self.audiobackend = audiobackend
        
        # initialize the class instance that will do the fft
        self.proc = audioproc(self.logger)

        self.maxfreq = DEFAULT_MAXFREQ
        self.proc.set_maxfreq(self.maxfreq)
        self.minfreq = DEFAULT_MINFREQ
        self.fft_size = 2**DEFAULT_FFT_SIZE*32
        self.proc.set_fftsize(self.fft_size)
        self.spec_min = DEFAULT_SPEC_MIN
        self.spec_max = DEFAULT_SPEC_MAX
        self.weighting = DEFAULT_WEIGHTING
        
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()
              
        self.timerange_s = DEFAULT_TIMERANGE
        self.canvas_width = 100.
        
        self.old_index = 0        
        self.overlap = 3./4.
        self.overlap_frac = Fraction(3, 4)
        self.dT_s = self.fft_size*(1. - self.overlap)/float(SAMPLING_RATE)
        
        self.PlotZoneImage.setlog10freqscale() #DEFAULT_FREQ_SCALE = 1 #log10
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
        self.PlotZoneImage.setweighting(self.weighting)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size)/(Fraction(1) - self.overlap_frac)/1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)
        
        # initialize the settings dialog
        self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)
Example #4
0
class Spectrogram_Widget(QtGui.QWidget):
    def __init__(self, parent, audiobackend, logger = PrintLogger()):
        QtGui.QWidget.__init__(self, parent)

        self.logger = logger

        self.setObjectName("Spectrogram_Widget")
        self.gridLayout = QtGui.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.PlotZoneImage = ImagePlot(self, self.logger, audiobackend)
        #self.PlotZoneImage = GLRollingCanvasWidget(self, self.logger)
        self.PlotZoneImage.setObjectName("PlotZoneImage")
        self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

        self.audiobuffer = None
        self.audiobackend = audiobackend
        
        # initialize the class instance that will do the fft
        self.proc = audioproc(self.logger)

        self.maxfreq = DEFAULT_MAXFREQ
        self.proc.set_maxfreq(self.maxfreq)
        self.minfreq = DEFAULT_MINFREQ
        self.fft_size = 2**DEFAULT_FFT_SIZE*32
        self.proc.set_fftsize(self.fft_size)
        self.spec_min = DEFAULT_SPEC_MIN
        self.spec_max = DEFAULT_SPEC_MAX
        self.weighting = DEFAULT_WEIGHTING
        
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()
              
        self.timerange_s = DEFAULT_TIMERANGE
        self.canvas_width = 100.
        
        self.old_index = 0        
        self.overlap = 3./4.
        self.overlap_frac = Fraction(3, 4)
        self.dT_s = self.fft_size*(1. - self.overlap)/float(SAMPLING_RATE)
        
        self.PlotZoneImage.setlog10freqscale() #DEFAULT_FREQ_SCALE = 1 #log10
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
        self.PlotZoneImage.setweighting(self.weighting)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size)/(Fraction(1) - self.overlap_frac)/1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)
        
        # initialize the settings dialog
        self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)

    # method
    def set_buffer(self, buffer):
        self.audiobuffer = buffer
        self.old_index = self.audiobuffer.ringbuffer.offset

    def log_spectrogram(self, sp):
        # Note: implementing the log10 of the array in Cython did not bring
        # any speedup.
        # Idea: Instead of computing the log of the data, I could pre-compute
        # a list of values associated with the colormap, and then do a search...
        epsilon = 1e-30
        return 10.*log10(sp + epsilon)

    # scale the db spectrum from [- spec_range db ... 0 db] > [0..1]    
    def scale_spectrogram(self, sp):
        return (sp.clip(min = self.spec_min, max = self.spec_max) - self.spec_min)/(self.spec_max - self.spec_min)

    # method
    def update(self):
        if not self.isVisible():
            return        
        
        # we need to maintain an index of where we are in the buffer
        index = self.audiobuffer.ringbuffer.offset

        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 = self.fft_size*(1. - self.overlap)        
        realizable = int(floor(available/needed))

        if realizable > 0:
            spn = zeros((len(self.freq), realizable), dtype=float64)
        
            for i in range(realizable):
                floatdata = self.audiobuffer.data_indexed(self.old_index, self.fft_size)
    
                # for now, take the first channel only
                floatdata = floatdata[0,:]
    
                # FIXME We should allow here for more intelligent transforms, especially when the log freq scale is selected
                spn[:, i] = self.proc.analyzelive(floatdata)
    
                self.old_index += int(needed)
                    
            w = tile(self.w, (1, realizable))
            norm_spectrogram = self.scale_spectrogram(self.log_spectrogram(spn) + w)            
            self.PlotZoneImage.addData(self.freq, norm_spectrogram)
        self.PlotZoneImage.updatePlot()
            
        # thickness of a frequency column depends on FFT size and window overlap
        # hamming window with 75% overlap provides good quality (Perfect reconstruction,
        # aliasing from side lobes only, 42 dB channel isolation)
        
        # number of frequency columns that we keep depends on the time history that the user has chosen
        
        # actual displayed spectrogram is a scaled version of the time-frequency plane
        
    def setminfreq(self, freq):
        self.minfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)

    def setmaxfreq(self, freq):
        self.maxfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.proc.set_maxfreq(freq)
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()
    
    def setfftsize(self, fft_size):
        self.fft_size = fft_size
        
        self.proc.set_fftsize(fft_size)
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()
                    
        self.dT_s = self.fft_size*(1. - self.overlap)/float(SAMPLING_RATE)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size)/(Fraction(1) - self.overlap_frac)/1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)

    def setmin(self, value):
        self.spec_min = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
    
    def setmax(self, value):
        self.spec_max = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
    
    def setweighting(self, weighting):
        self.weighting = weighting
        self.PlotZoneImage.setweighting(weighting)
        self.update_weighting()
    
    def update_weighting(self):    
        A, B, C = self.proc.get_freq_weighting()
        if self.weighting is 0:
            self.w = 0.
        elif self.weighting is 1:
            self.w = A
        elif self.weighting is 2:
            self.w = B
        else:
            self.w = C
        self.w.shape = (len(self.w), 1)

    def settings_called(self, checked):
        self.settings_dialog.show()
    
    def saveState(self, settings):
        self.settings_dialog.saveState(settings)

    def restoreState(self, settings):
        self.settings_dialog.restoreState(settings)

    # slot
    def timerangechanged(self, value):
        self.timerange_s = value
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

    # slot
    def canvasWidthChanged(self, width):
        self.canvas_width = width
Example #5
0
class Spectrogram_Widget(QtGui.QWidget):
    def __init__(self, parent, logger = None):
        QtGui.QWidget.__init__(self, parent)

        # store the logger instance
        if logger is None:
            self.logger = parent.parent().logger
        else:
            self.logger = logger
        
        self.parent = parent

        self.setObjectName("Spectrogram_Widget")
        self.gridLayout = QtGui.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.PlotZoneImage = ImagePlot(self, self.logger)
        self.PlotZoneImage.setObjectName("PlotZoneImage")
        self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

        self.audiobuffer = None
        
        # initialize the class instance that will do the fft
        self.proc = audioproc(self.logger)

        self.maxfreq = DEFAULT_MAXFREQ
        self.minfreq = DEFAULT_MINFREQ
        self.fft_size = 2**DEFAULT_FFT_SIZE*32
        self.spec_min = DEFAULT_SPEC_MIN
        self.spec_max = DEFAULT_SPEC_MAX
        self.weighting = DEFAULT_WEIGHTING
        
        self.spectrogram_timer_time = 0.
        
        self.timerange_s = DEFAULT_TIMERANGE
        self.canvas_width = 100.
        
        self.PlotZoneImage.setlog10freqscale() #DEFAULT_FREQ_SCALE = 1 #log10
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
        self.PlotZoneImage.setweighting(self.weighting)
        self.PlotZoneImage.settimerange(self.timerange_s)
        
        # this timer is used to update the spectrogram widget, whose update period
        # is fixed by the time scale and the width of the widget canvas
        self.timer = QtCore.QTimer()
        self.period_ms = SMOOTH_DISPLAY_TIMER_PERIOD_MS
        self.timer.setInterval(self.period_ms) # variable timing
        
        # initialize the settings dialog
        self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)
        
        # timer ticks
        self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.timer_slot)
        
        # window resize
        self.connect(self.PlotZoneImage.plotImage.canvasscaledspectrogram, QtCore.SIGNAL("canvasWidthChanged"), self.canvasWidthChanged)

        # we do not use the display timer since we have a special one
        # tell the caller by setting this variable as None
        self.update = None
  
        self.timer_time = QtCore.QTime()
  
 # FIXME
 # for smoothness, the following shoudl be observed
 # - the FFT should be done with Hamming, or Hanning or Kaiser windows
 #   with 50% or more overlap.
 # - the animation should be advanced according to the actual time elapsed
 #   since the last update. Proper advancement is done through interpolation.
 #   (Linear or quadratic (causal!) interpolation should be fine first)
 # - ideally timer should be removed altogether and replaced by bloacking OpenGL
 #   paintings synchronized to vsync

    # method
    def set_buffer(self, buffer):
        self.audiobuffer = buffer

    # method
    def custom_update(self):
        if not self.isVisible():
            return
        
        # FIXME We should allow here for more intelligent transforms, especially when the log freq scale is selected
        floatdata = self.audiobuffer.data(self.fft_size)

        # for now, take the first channel only
        floatdata = floatdata[0,:]

        sp, freq, A, B, C = self.proc.analyzelive(floatdata, self.fft_size, self.maxfreq)
        # scale the db spectrum from [- spec_range db ... 0 db] > [0..1]
        epsilon = 1e-30
        
        if self.weighting is 0:
            w = 0.
        elif self.weighting is 1:
            w = A
        elif self.weighting is 2:
            w = B
        else:
            w = C
        
        db_spectrogram = 20*log10(sp + epsilon) + w
        norm_spectrogram = (db_spectrogram.clip(min = self.spec_min, max = self.spec_max) - self.spec_min)/(self.spec_max - self.spec_min)
        
        self.PlotZoneImage.addData(freq, norm_spectrogram)

    def setminfreq(self, freq):
        self.minfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)

    def setmaxfreq(self, freq):
        self.maxfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
    
    def setfftsize(self, fft_size):
        self.fft_size = fft_size

    def setmin(self, value):
        self.spec_min = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
    
    def setmax(self, value):
        self.spec_max = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
    
    def setweighting(self, weighting):
        self.weighting = weighting
        self.PlotZoneImage.setweighting(weighting)
    
    def settings_called(self, checked):
        self.settings_dialog.show()
    
    def saveState(self, settings):
        self.settings_dialog.saveState(settings)

    def restoreState(self, settings):
        self.settings_dialog.restoreState(settings)

    # slot
    def timerangechanged(self, value):
        self.timerange_s = value
        self.PlotZoneImage.settimerange(value)
        self.reset_timer()

    # slot
    def canvasWidthChanged(self, width):
        self.canvas_width = width
        self.reset_timer()

    # method
    def reset_timer(self):
        # FIXME millisecond resolution is limiting !
        # need to find a way to integrate this cleverly in the GUI
        # When the period is smaller than 25 ms, we can reasonably
        # try to draw as many columns at once as possible
        self.period_ms = 1000.*self.timerange_s/self.canvas_width
        self.logger.push("Resetting the timer, will fire every %d ms" %(self.period_ms))
        self.timer.setInterval(self.period_ms)
        
    # slot
    def timer_slot(self):
        #(chunks, t) = self.audiobuffer.update(self.audiobackend.stream)
        #self.chunk_number += chunks
        #self.buffer_timer_time = (95.*self.buffer_timer_time + 5.*t)/100.

        t = QtCore.QTime()
        t.start()

        self.custom_update()
        
        self.spectrogram_timer_time = (95.*self.spectrogram_timer_time + 5.*t.elapsed())/100.
Example #6
0
	def __init__(self, parent, logger = None):
		QtGui.QWidget.__init__(self, parent)

		# store the logger instance
		if logger is None:
		    self.logger = parent.parent().logger
		else:
		    self.logger = logger
		
		self.parent = parent

		self.setObjectName("Spectrogram_Widget")
		self.gridLayout = QtGui.QGridLayout(self)
		self.gridLayout.setObjectName("gridLayout")
		self.PlotZoneImage = ImagePlot(self, self.logger)
		self.PlotZoneImage.setObjectName("PlotZoneImage")
		self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

		self.audiobuffer = None
		
		# initialize the class instance that will do the fft
		self.proc = audioproc(self.logger)

		self.maxfreq = DEFAULT_MAXFREQ
		self.minfreq = DEFAULT_MINFREQ
		self.fft_size = 2**DEFAULT_FFT_SIZE*32
		self.spec_min = DEFAULT_SPEC_MIN
		self.spec_max = DEFAULT_SPEC_MAX
		self.weighting = DEFAULT_WEIGHTING
		
		self.spectrogram_timer_time = 0.
		
		self.timerange_s = DEFAULT_TIMERANGE
		self.canvas_width = 100.
		
		self.PlotZoneImage.setlog10freqscale() #DEFAULT_FREQ_SCALE = 1 #log10
		self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
		self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
		self.PlotZoneImage.setweighting(self.weighting)
		self.PlotZoneImage.settimerange(self.timerange_s)
		
		# this timer is used to update the spectrogram widget, whose update period
		# is fixed by the time scale and the width of the widget canvas
		self.timer = QtCore.QTimer()
		self.period_ms = SMOOTH_DISPLAY_TIMER_PERIOD_MS
		self.timer.setInterval(self.period_ms) # variable timing
		
		# initialize the settings dialog
		self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)
		
		# timer ticks
		self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.timer_slot)
		
		# window resize
		self.connect(self.PlotZoneImage.plotImage.canvasscaledspectrogram, QtCore.SIGNAL("canvasWidthChanged"), self.canvasWidthChanged)

		# we do not use the display timer since we have a special one
		# tell the caller by setting this variable as None
		self.update = None
  
		self.timer_time = QtCore.QTime()
Example #7
0
class Spectrogram_Widget(QtGui.QWidget):
	def __init__(self, parent, logger = None):
		QtGui.QWidget.__init__(self, parent)

		# store the logger instance
		if logger is None:
		    self.logger = parent.parent().logger
		else:
		    self.logger = logger
		
		self.parent = parent

		self.setObjectName("Spectrogram_Widget")
		self.gridLayout = QtGui.QGridLayout(self)
		self.gridLayout.setObjectName("gridLayout")
		self.PlotZoneImage = ImagePlot(self, self.logger)
		self.PlotZoneImage.setObjectName("PlotZoneImage")
		self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

		self.audiobuffer = None
		
		# initialize the class instance that will do the fft
		self.proc = audioproc(self.logger)

		self.maxfreq = DEFAULT_MAXFREQ
		self.minfreq = DEFAULT_MINFREQ
		self.fft_size = 2**DEFAULT_FFT_SIZE*32
		self.spec_min = DEFAULT_SPEC_MIN
		self.spec_max = DEFAULT_SPEC_MAX
		self.weighting = DEFAULT_WEIGHTING
		
		self.spectrogram_timer_time = 0.
		
		self.timerange_s = DEFAULT_TIMERANGE
		self.canvas_width = 100.
		
		self.PlotZoneImage.setlog10freqscale() #DEFAULT_FREQ_SCALE = 1 #log10
		self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
		self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
		self.PlotZoneImage.setweighting(self.weighting)
		self.PlotZoneImage.settimerange(self.timerange_s)
		
		# this timer is used to update the spectrogram widget, whose update period
		# is fixed by the time scale and the width of the widget canvas
		self.timer = QtCore.QTimer()
		self.period_ms = SMOOTH_DISPLAY_TIMER_PERIOD_MS
		self.timer.setInterval(self.period_ms) # variable timing
		
		# initialize the settings dialog
		self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)
		
		# timer ticks
		self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.timer_slot)
		
		# window resize
		self.connect(self.PlotZoneImage.plotImage.canvasscaledspectrogram, QtCore.SIGNAL("canvasWidthChanged"), self.canvasWidthChanged)

		# we do not use the display timer since we have a special one
		# tell the caller by setting this variable as None
		self.update = None
  
		self.timer_time = QtCore.QTime()
  
 # FIXME
 # for smoothness, the following shoudl be observed
 # - the FFT should be done with Hamming, or Hanning or Kaiser windows
 #   with 50% or more overlap.
 # - the animation should be advanced according to the actual time elapsed
 #   since the last update. Proper advancement is done through interpolation.
 #   (Linear or quadratic (causal!) interpolation should be fine first)
 # - ideally timer should be removed altogether and replaced by bloacking OpenGL
 #   paintings synchronized to vsync

	# method
	def set_buffer(self, buffer):
		self.audiobuffer = buffer

	# method
	def custom_update(self):
		if not self.isVisible():
			return
		
		# FIXME We should allow here for more intelligent transforms, especially when the log freq scale is selected
		floatdata = self.audiobuffer.data(self.fft_size)

		# for now, take the first channel only
		floatdata = floatdata[0,:]

		sp, freq, A, B, C = self.proc.analyzelive(floatdata, self.fft_size, self.maxfreq)
		# scale the db spectrum from [- spec_range db ... 0 db] > [0..1]
		epsilon = 1e-30
		
		if self.weighting is 0:
			w = 0.
		elif self.weighting is 1:
			w = A
		elif self.weighting is 2:
			w = B
		else:
			w = C
		
		db_spectrogram = 20*log10(sp + epsilon) + w
		norm_spectrogram = (db_spectrogram.clip(min = self.spec_min, max = self.spec_max) - self.spec_min)/(self.spec_max - self.spec_min)
		
		self.PlotZoneImage.addData(freq, norm_spectrogram)

	def setminfreq(self, freq):
		self.minfreq = freq
		self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)

	def setmaxfreq(self, freq):
		self.maxfreq = freq
		self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
	
	def setfftsize(self, fft_size):
		self.fft_size = fft_size

	def setmin(self, value):
		self.spec_min = value
		self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
	
	def setmax(self, value):
		self.spec_max = value
		self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
	
	def setweighting(self, weighting):
		self.weighting = weighting
		self.PlotZoneImage.setweighting(weighting)
	
	def settings_called(self, checked):
		self.settings_dialog.show()
	
	def saveState(self, settings):
		self.settings_dialog.saveState(settings)

	def restoreState(self, settings):
		self.settings_dialog.restoreState(settings)

	# slot
	def timerangechanged(self, value):
		self.timerange_s = value
		self.PlotZoneImage.settimerange(value)
		self.reset_timer()

	# slot
	def canvasWidthChanged(self, width):
		self.canvas_width = width
		self.reset_timer()

	# method
	def reset_timer(self):
		# FIXME millisecond resolution is limiting !
		# need to find a way to integrate this cleverly in the GUI
		# When the period is smaller than 25 ms, we can reasonably
		# try to draw as many columns at once as possible
		self.period_ms = 1000.*self.timerange_s/self.canvas_width
		self.logger.push("Resetting the timer, will fire every %d ms" %(self.period_ms))
		self.timer.setInterval(self.period_ms)
		
	# slot
	def timer_slot(self):
		#(chunks, t) = self.audiobuffer.update(self.audiobackend.stream)
		#self.chunk_number += chunks
		#self.buffer_timer_time = (95.*self.buffer_timer_time + 5.*t)/100.

		t = QtCore.QTime()
		t.start()

		self.custom_update()
		
		self.spectrogram_timer_time = (95.*self.spectrogram_timer_time + 5.*t.elapsed())/100.
Example #8
0
class Spectrogram_Widget(QtWidgets.QWidget):

    name = "Spectrogram"

    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.PlotZoneImage = ImagePlot(self, self.logger)
        self.PlotZoneImage.setObjectName("PlotZoneImage")
        self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)


        # initialize the class instance that will do the fft
        self.proc = audioproc(self.logger)

        self.maxfreq = DEFAULT_MAXFREQ
        self.proc.set_maxfreq(self.maxfreq)
        self.minfreq = DEFAULT_MINFREQ
        self.fft_size = 2 ** DEFAULT_FFT_SIZE * 32
        self.proc.set_fftsize(self.fft_size)
        self.spec_min = DEFAULT_SPEC_MIN
        self.spec_max = DEFAULT_SPEC_MAX
        self.weighting = DEFAULT_WEIGHTING

        self.update_weighting()
        self.freq = self.proc.get_freq_scale()

        self.timerange_s = DEFAULT_TIMERANGE
        self.canvas_width = 100.

        self.overlap = 3. / 4.
        self.overlap_frac = Fraction(3, 4)
        self.dT_s = self.fft_size * (1. - self.overlap) / float(SAMPLING_RATE)
        self.data_buffer = RingBuffer(1, 4 * self.fft_size)
        self.smoothing_buffer = RingBuffer(1, self.fft_size * self.overlap)

        self.PlotZoneImage.setlog10freqscale()  # DEFAULT_FREQ_SCALE = 1 #log10
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
        self.PlotZoneImage.setweighting(self.weighting)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)
        self.update_jitter()

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size) / (Fraction(1) - self.overlap_frac) / 1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)

        # initialize the settings dialog
        self.settings_dialog = Spectrogram_Settings_Dialog(self, self.logger)

    @staticmethod
    def log_spectrogram(sp):
        # Note: implementing the log10 of the array in Cython did not bring
        # any speedup.
        # Idea: Instead of computing the log of the data, I could pre-compute
        # a list of values associated with the colormap, and then do a search...
        epsilon = 1e-30
        return 10. * log10(sp + epsilon)

    # scale the db spectrum from [- spec_range db ... 0 db] to [0..1] (do not clip, will be down after resampling)
    def scale_spectrogram(self, sp):
        return (sp - self.spec_min) / (self.spec_max - self.spec_min)

    def handle_new_data(self, floatdata):
        self.data_buffer.push(floatdata)
        new_sample_points = self.data_buffer.num_unread_data_points()

        # if we have enough data to add a frequency column in the time-frequency plane, compute it
        needed = int(self.fft_size * (1. - self.overlap))
        realizable = int(floor(new_sample_points / needed))

        if realizable > 0:
            spn = zeros((len(self.freq), realizable), dtype=float64)

            data = append(self.smoothing_buffer.unwound_data(), self.data_buffer.pop(realizable * needed))

            # append flattens 1d arrays, so reshape if necessary
            if data.size == data.shape[0]:
                data = data.reshape([1, data.size])

            for i in range(realizable):
                floatdata = data[:, i * needed:i * needed + self.fft_size]
                self.smoothing_buffer.push(floatdata[:, (self.fft_size - needed):])

                # for now, take the first channel only
                floatdata = floatdata[0, :]

                # FFT transform
                spn[:, i] = self.proc.analyzelive(floatdata, "Power")

            w = tile(self.w, (1, realizable))
            norm_spectrogram = self.scale_spectrogram(self.log_spectrogram(spn) + w)
            self.PlotZoneImage.addData(self.freq, norm_spectrogram)

        # thickness of a frequency column depends on FFT size and window overlap
        # hamming window with 75% overlap provides good quality (Perfect reconstruction,
        # aliasing from side lobes only, 42 dB channel isolation)

        # number of frequency columns that we keep depends on the time history that the user has chosen

        # actual displayed spectrogram is a scaled version of the time-frequency plane

    def canvasUpdate(self):
        if not self.isVisible():
            return

        self.PlotZoneImage.draw()

    def update_jitter(self):
        audio_jitter = 2 * float(FRAMES_PER_BUFFER) / SAMPLING_RATE
        analysis_jitter = self.fft_size * (1. - self.overlap) / SAMPLING_RATE
        canvas_jitter = audio_jitter + analysis_jitter
        # print audio_jitter, analysis_jitter, canvas_jitter
        self.PlotZoneImage.plotImage.set_jitter(canvas_jitter)

    def pause(self):
        pass

    def setminfreq(self, freq):
        self.minfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)

    def setmaxfreq(self, freq):
        self.maxfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.proc.set_maxfreq(freq)
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()

    def setfftsize(self, fft_size):
        self.fft_size = fft_size

        self.proc.set_fftsize(fft_size)
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()

        self.dT_s = self.fft_size * (1. - self.overlap) / float(SAMPLING_RATE)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size) / (Fraction(1) - self.overlap_frac) / 1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)

        self.update_jitter()

    def set_spect_min(self, value):
        self.spec_min = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)

    def set_spect_max(self, value):
        self.spec_max = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)

    def setweighting(self, weighting):
        self.weighting = weighting
        self.PlotZoneImage.setweighting(weighting)
        self.update_weighting()

    def update_weighting(self):
        A, B, C = self.proc.get_freq_weighting()
        if self.weighting is 0:
            self.w = array([0.])
        elif self.weighting is 1:
            self.w = A
        elif self.weighting is 2:
            self.w = B
        else:
            self.w = C
        self.w.shape = (len(self.w), 1)

    def settings_called(self, checked):
        self.settings_dialog.show()

    def saveState(self, settings):
        self.settings_dialog.saveState(settings)

    def restoreState(self, settings):
        self.settings_dialog.restoreState(settings)

    # slot
    def timerangechanged(self, value):
        self.timerange_s = value
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

    # slot
    def canvasWidthChanged(self, width):
        self.canvas_width = width
Example #9
0
class Spectrogram_Widget(QtWidgets.QWidget):
    def __init__(self, parent):
        super().__init__(parent)

        self.setObjectName("Spectrogram_Widget")
        self.gridLayout = QtWidgets.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.PlotZoneImage = ImagePlot(self)
        self.PlotZoneImage.setObjectName("PlotZoneImage")
        self.gridLayout.addWidget(self.PlotZoneImage, 0, 1, 1, 1)

        self.audiobuffer = None

        # initialize the class instance that will do the fft
        self.proc = audioproc()

        self.maxfreq = DEFAULT_MAXFREQ
        self.proc.set_maxfreq(self.maxfreq)
        self.minfreq = DEFAULT_MINFREQ
        self.fft_size = 2**DEFAULT_FFT_SIZE * 32
        self.proc.set_fftsize(self.fft_size)
        self.spec_min = DEFAULT_SPEC_MIN
        self.spec_max = DEFAULT_SPEC_MAX
        self.weighting = DEFAULT_WEIGHTING

        self.update_weighting()
        self.freq = self.proc.get_freq_scale()

        self.timerange_s = DEFAULT_TIMERANGE
        self.canvas_width = 100.

        self.old_index = 0
        self.overlap = 3. / 4.
        self.overlap_frac = Fraction(3, 4)
        self.dT_s = self.fft_size * (1. - self.overlap) / float(SAMPLING_RATE)

        self.PlotZoneImage.setlog10freqscale()  # DEFAULT_FREQ_SCALE = 1 #log10
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)
        self.PlotZoneImage.setweighting(self.weighting)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)
        self.update_jitter()

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size) / (
            Fraction(1) - self.overlap_frac) / 1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)

        # initialize the settings dialog
        self.settings_dialog = Spectrogram_Settings_Dialog(self)

        AudioBackend().underflow.connect(
            self.PlotZoneImage.plotImage.canvasscaledspectrogram.syncOffsets)

        self.last_data_time = 0.

        self.mustRestart = False

    # method
    def set_buffer(self, buffer):
        self.audiobuffer = buffer
        self.old_index = self.audiobuffer.ringbuffer.offset

    def log_spectrogram(self, sp):
        # Note: implementing the log10 of the array in Cython did not bring
        # any speedup.
        # Idea: Instead of computing the log of the data, I could pre-compute
        # a list of values associated with the colormap, and then do a search...
        epsilon = 1e-30
        return 10. * log10(sp + epsilon)

    # scale the db spectrum from [- spec_range db ... 0 db] to [0..1] (do not clip, will be down after resampling)
    def scale_spectrogram(self, sp):
        return (sp - self.spec_min) / (self.spec_max - self.spec_min)

    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 = self.fft_size * (1. - self.overlap)
        realizable = int(floor(available / needed))

        if realizable > 0:
            spn = zeros((len(self.freq), realizable), dtype=float64)

            for i in range(realizable):
                floatdata = self.audiobuffer.data_indexed(
                    self.old_index, self.fft_size)

                # for now, take the first channel only
                floatdata = floatdata[0, :]

                # FFT transform
                spn[:, i] = self.proc.analyzelive(floatdata)

                self.old_index += int(needed)

            w = tile(self.w, (1, realizable))
            norm_spectrogram = self.scale_spectrogram(
                self.log_spectrogram(spn) + w)
            self.PlotZoneImage.addData(self.freq, norm_spectrogram,
                                       self.last_data_time)

            if self.mustRestart:
                self.PlotZoneImage.restart()
                self.mustRestart = False

        # thickness of a frequency column depends on FFT size and window overlap
        # hamming window with 75% overlap provides good quality (Perfect reconstruction,
        # aliasing from side lobes only, 42 dB channel isolation)

        # number of frequency columns that we keep depends on the time history that the user has chosen

        # actual displayed spectrogram is a scaled version of the time-frequency plane

    def canvasUpdate(self):
        if not self.isVisible():
            return

        self.PlotZoneImage.draw()

    def update_jitter(self):
        audio_jitter = 2 * float(FRAMES_PER_BUFFER) / SAMPLING_RATE
        analysis_jitter = self.fft_size * (1. - self.overlap) / SAMPLING_RATE
        canvas_jitter = audio_jitter + analysis_jitter
        # print audio_jitter, analysis_jitter, canvas_jitter
        self.PlotZoneImage.plotImage.set_jitter(canvas_jitter)

    def pause(self):
        self.PlotZoneImage.pause()

    def restart(self):
        # defer the restart until we get data from the audio source (so that a fresh lastdatatime is passed to the spectrogram image)
        self.mustRestart = True

    def setminfreq(self, freq):
        self.minfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)

    def setmaxfreq(self, freq):
        self.maxfreq = freq
        self.PlotZoneImage.setfreqrange(self.minfreq, self.maxfreq)
        self.proc.set_maxfreq(freq)
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()

    def setfftsize(self, fft_size):
        self.fft_size = fft_size

        self.proc.set_fftsize(fft_size)
        self.update_weighting()
        self.freq = self.proc.get_freq_scale()

        self.dT_s = self.fft_size * (1. - self.overlap) / float(SAMPLING_RATE)
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

        sfft_rate_frac = Fraction(SAMPLING_RATE, self.fft_size) / (
            Fraction(1) - self.overlap_frac) / 1000
        self.PlotZoneImage.set_sfft_rate(sfft_rate_frac)

        self.update_jitter()

    def setmin(self, value):
        self.spec_min = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)

    def setmax(self, value):
        self.spec_max = value
        self.PlotZoneImage.setspecrange(self.spec_min, self.spec_max)

    def setweighting(self, weighting):
        self.weighting = weighting
        self.PlotZoneImage.setweighting(weighting)
        self.update_weighting()

    def update_weighting(self):
        A, B, C = self.proc.get_freq_weighting()
        if self.weighting is 0:
            self.w = array([0.])
        elif self.weighting is 1:
            self.w = A
        elif self.weighting is 2:
            self.w = B
        else:
            self.w = C
        self.w.shape = (len(self.w), 1)

    def settings_called(self, checked):
        self.settings_dialog.show()

    def saveState(self, settings):
        self.settings_dialog.saveState(settings)

    def restoreState(self, settings):
        self.settings_dialog.restoreState(settings)

    # slot
    def timerangechanged(self, value):
        self.timerange_s = value
        self.PlotZoneImage.settimerange(self.timerange_s, self.dT_s)

    # slot
    def canvasWidthChanged(self, width):
        self.canvas_width = width