05-fft-time-stretch.py - Time stretching using FFT/IFFT.
============================================================================================================================================


The FFT object analyses an input signal and converts it into the
spectral domain. Three audio signals are sent out of the object,
the real part, from bin 0 (DC) to bin size/2 (Nyquist frequency),
the imaginary part, from bin 0 to bin size/2-1, and the bin number,
an increasing count from 0 to size-1.

This script analyses an audio signal in the spectral domain, save the
magnitudes and the phase deltas of each analysis frame in matrices, and
then reads these matrices at a slower speed to do time stretching of the
sound in the spectral domain.

For a simpler and more efficient process, see the Phase Vocoder implementations
of the spectral stretching: `PVBuffer`, `PVBufLoops`, and `PVBufTabLoops`.

.. code-block:: python

    from pyo import *
    
    s = Server(duplex=0).boot()
    
    # The source sound
    snd = "../snds/flute.aif"
    
    # The stretching factor (x times slower than normal speed).
    stretch_factor = 10
    
    # FFT size in samples
    size = 4096
    # Number of overlaps
    olaps = 8
    # The hopsize, i.e. the time, in samples, between two overlapping frames.
    hop = size // olaps
    # Envelope type for windowing (see wintype argument of FFT/IFFT).
    wintype = 7
    
    # Retrieve information about the soundfile
    info = sndinfo(snd)
    # The number of channels
    chnls = info[3]
    # How many analysis frames to cover the entire sound duration.
    nframes = info[0] // size
    
    # Reads the soundfile once.
    a = SfPlayer(snd, mul=0.1)
    
    # Creates "overlaps * chnls" number of matrices of width "FFT size", and
    # of height "number of frames" to record magnitude and phase analysis frames.
    m_mag = [NewMatrix(width=size, height=nframes) for i in range(olaps * chnls)]
    m_pha = [NewMatrix(width=size, height=nframes) for i in range(olaps * chnls)]
    
    # Converts the source signal into its spectral representation.
    fin = FFT(a, size=size, overlaps=olaps, wintype=wintype)
    
    # Cartesian to polar conversion.
    pol = CarToPol(fin["real"], fin["imag"])
    # The delta of phases between successive analysis frame.
    delta = FrameDelta(pol["ang"], framesize=size, overlaps=olaps)
    
    # Record the magnitude frames.
    m_mag_rec = MatrixRec(
        pol["mag"], m_mag, 0, [i * hop for i in range(olaps) for j in range(chnls)]
    ).play()
    # Record the phase delta frames.
    m_pha_rec = MatrixRec(delta, m_pha, 0, [i * hop for i in range(olaps) for j in range(chnls)]).play()
    
    # The playback pointer.
    pos = Phasor(1.0 / info[1] / stretch_factor, mul=nframes)
    
    # Reads magnitude and phase delta matrices.
    m_mag_read = MatrixPointer(m_mag, fin["bin"] / size, pos / nframes)
    m_pha_read = MatrixPointer(m_pha, fin["bin"] / size, pos / nframes)
    
    # Smoothing magnitude and phase delta rate of changes/
    m_mag_smo = Vectral(m_mag_read, framesize=size, overlaps=olaps, up=0.5, down=0.5, damp=1)
    m_pha_smo = Vectral(m_pha_read, framesize=size, overlaps=olaps, up=0.5, down=0.5, damp=1)
    
    # Phase delta accumulator (inverse of FrameDelta).
    accum = FrameAccum(m_pha_smo, framesize=size, overlaps=olaps)
    
    # Polar to cartesian conversion.
    car = PolToCar(m_mag_smo, accum)
    
    # Converts the new real and imag parts into a time domain signal.
    fout = (
        IFFT(car["real"], car["imag"], size=size, overlaps=olaps, wintype=wintype)
        .mix(chnls)
        .mix(2)
        .out()
    )
    
    s.gui(locals())

