class_docstr: FFT based frequency analysis of audio frames.
class_docstr_tok: ["FFT","based","frequency","analysis","of","audio","frames","."]
docstr: Returns a tuple containing the spectral centroid and
  the spectrum (dB scales) of the input audio frames.
  FFT window sizes are adatable to the input frame size.
docstr_tok: ["Returns","a","tuple","containing","the","spectral","centroid","and","the","spectrum","(","dB","scales",")","of","the","input","audio","frames",".","FFT","window","sizes","are","adatable","to","the","input","frame","size","."]
code: def process(self, frames, eod, spec_range=120.0):
      """Returns a tuple containing the spectral centroid and
      the spectrum (dB scales) of the input audio frames.
      FFT window sizes are adatable to the input frame size."""
  
      samples = frames[:, 0]
      nsamples = len(frames[:, 0])
      if nsamples != self.blocksize:
          self.window = self.window_function(nsamples)
      samples *= self.window
  
      while nsamples > self.fft_size:
          self.fft_size = 2 * self.fft_size
  
      zeros_p = numpy.zeros(int(self.fft_size / 2) - int(nsamples / 2))
      if nsamples % 2:
          zeros_n = numpy.zeros(
              int(self.fft_size / 2) - int(nsamples / 2) - 1
          )
      else:
          zeros_n = numpy.zeros(
              int(self.fft_size / 2) - int(nsamples / 2)
          )
      samples = numpy.concatenate((zeros_p, samples, zeros_n), axis=0)
  
      fft = numpy.fft.fft(samples)
      # normalized abs(FFT) between 0 and 1
      spectrum = numpy.abs(fft[: int(fft.shape[0] / 2 + 1)]) / float(
          nsamples
      )
      length = numpy.float64(spectrum.shape[0])
  
      # scale the db spectrum from [- spec_range db ... 0 db] > [0..1]
      db_spectrum = (
          (20 * (numpy.log10(spectrum + 1e-30))).clip(-spec_range, 0.0)
          + spec_range
      ) / spec_range
      energy = spectrum.sum()
      spectral_centroid = 0
  
      if energy > 1e-20:
          # calculate the spectral centroid
          if self.spectrum_range is None:
              self.spectrum_range = numpy.arange(length)
          spectral_centroid = (
              (spectrum * self.spectrum_range).sum()
              / (energy * (length - 1))
              * self.samplerate
              * 0.5
          )
          # clip > log10 > scale between 0 and 1
          spectral_centroid = (
              math.log10(
                  self.clip(spectral_centroid, self.lower, self.higher)
              )
              - self.lower_log
          ) / (self.higher_log - self.lower_log)
  
      return (spectral_centroid, db_spectrum)
  
imports: import math
  import numpy
  from timeside.core import Processor, implements, interfacedoc, abstract
  from timeside.core.processor import FixedSizeInputAdapter
  from timeside.core.api import IGrapher
  from timeside.plugins.grapher.utils import smooth, im_watermark, normalize
  import timeside.plugins.analyzer
label_desc: Use image node_id 2 for calling the Spectrum obj's underlying member method code with example usage: obj.process(frames, eod, spec_range) and returns: spectral_centroid,  db_spectrum