docs: def frames_adapter(process_func):
      '''
      Pre-processing decorator that adapt frames to match input_blocksize and
      input_stepsize of the decorated analyzer
  
      >>> from timeside.core.preprocessors import frames_adapter
      >>> @frames_adapter
      ... def process(analyzer,frames,eod):
      ...     analyzer.frames.append(frames)
      ...     return frames, eod
      >>> class Fake_Analyzer(object):
      ...     def __init__(self):
      ...         self.input_blocksize = 4
      ...         self.input_stepsize = 3
      ...         self.frames = [] # Container for the frame as viewed by process
      ...     @staticmethod
      ...     def id():
      ...         return 'fake_analyzer'
      >>> import numpy as np
      >>> analyzer = Fake_Analyzer()
      >>> frames = np.asarray(range(0,12))
      >>> eod = False
      >>> frames_, eod_ = process(analyzer,frames,eod)
  
      Inside the process the frames have been adapted to match input_blocksize
      and input_stepsize
  
      >>> analyzer.frames
      [array([0, 1, 2, 3]), array([3, 4, 5, 6]), array([6, 7, 8, 9])]
  
      Outside the process, the original frames and eod are preserved:
  
      >>> frames_
      array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
      >>> eod_
      False
  
      Releasing the process with eod=True will zeropad the last frame if necessary
  
      >>> frames = np.asarray(range(12,14))
      >>> eod = True
      >>> frames_, eod_ = process(analyzer,frames,eod)
      >>> analyzer.frames
      [array([0, 1, 2, 3]), array([3, 4, 5, 6]), array([6, 7, 8, 9]), array([ 9, 10, 11, 12]), array([12, 13,  0,  0])]
      '''
  
      import functools
      import numpy as np
  
      class framesBuffer(object):
  
          def __init__(self, blocksize, stepsize):
              self.blocksize = blocksize
              self.stepsize = stepsize
              self.buffer = None
  
          def frames(self, frames, eod):
              if self.buffer is not None:
                  stack = np.concatenate([self.buffer, frames])
              else:
                  stack = frames.copy()
  
              stack_length = len(stack)
  
              nb_frames = (
                  stack_length - self.blocksize + self.stepsize) // self.stepsize
              nb_frames = max(nb_frames, 0)
              frames_length = nb_frames * self.stepsize + \
                  self.blocksize - self.stepsize
              last_block_size = stack_length - frames_length
  
              if eod:
                  # Final zeropadding
                  pad_shape = tuple(
                      self.blocksize - last_block_size if i == 0 else x
                      for i, x in enumerate(frames.shape))
                  stack = np.concatenate([stack, np.zeros(pad_shape,
                                                          dtype=frames.dtype)])
                  nb_frames += 1
  
              self.buffer = stack[nb_frames * self.stepsize:]
  
              eod_list = np.repeat(False, nb_frames)
              if eod and len(eod_list):
                  eod_list[-1] = eod
  
              for index, eod in zip(xrange(0, nb_frames * self.stepsize, self.stepsize), eod_list):
                  yield (stack[index:index + self.blocksize], eod)
  
      aubio_analyzers = ['aubio_melenergy', 'aubio_mfcc', 'aubio_pitch', 'aubio_specdesc', 'aubio_temporal']
      
      @functools.wraps(process_func)
      def wrapper(analyzer, frames, eod):
          # Pre-processing
          if not hasattr(analyzer, 'frames_buffer'):
              if analyzer.id() in aubio_analyzers:
                  # Aubio analyzers are waiting for stepsize length block
                  # and reconstructs blocksize length frames itself
                  # thus frames_adapter has to provide Aubio Pitch blocksize=stepsize length frames
                  analyzer.frames_buffer = framesBuffer(analyzer.input_stepsize,
                                                        analyzer.input_stepsize)
              else:
                  analyzer.frames_buffer = framesBuffer(analyzer.input_blocksize,
                                                        analyzer.input_stepsize)
  
          # Processing
          for adapted_frames, adapted_eod in analyzer.frames_buffer.frames(frames, eod):
              process_func(analyzer, adapted_frames, adapted_eod)
  
          return frames, eod
      return wrapper