<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:

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