view yetilab/stream/framer.yeti @ 288:16a9ee99efcf

Window overlap scale factor
author Chris Cannam
date Thu, 30 May 2013 20:27:54 +0100
parents d6811e2949ff
children b34960d2b519
line wrap: on
line source

module yetilab.stream.framer;

/**
 * Framer expresses a stream (or a file) as a lazy list of (possibly
 * overlapping) frames of data.
 */

vec = load yetilab.vector;
bf = load yetilab.vector.blockfuncs;
af = load yetilab.stream.audiofile;
win = load yetilab.signal.window;
fft = load yetilab.transform.fft;
mat = load yetilab.matrix;
ch = load yetilab.stream.channels;
syn = load yetilab.stream.syntheticstream;
filt = load yetilab.stream.filter;

blockList framesize stream =
    if stream.finished? then
        stream.close ();
        []
    else
        mat.resizedTo { rows = stream.channels, columns = framesize }
           (stream.read framesize)
            :. \(blockList framesize stream);
    fi;

overlappingBlockList size hop stream valid buffer =
   (
    m = stream.read hop;
    obtained = mat.width m;

    // Retain framesize - hop samples from old buffer, add hop samples
    // (zero-padded if necessary) just read
    buffer = map2
        do buf row:
            vec.concat
               [vec.slice buf hop size,
                vec.resizedTo hop (mat.getRow row m)];
        done buffer [0..stream.channels-1];

    // Number of "valid" elements (not tail-end zero-padding) left in buffer
    remaining = valid - (hop - obtained);

    if remaining <= 0 then
        stream.close ();
        [];
    else
        mat.newMatrix (RowMajor ()) buffer
            :. \(overlappingBlockList size hop stream remaining buffer);
    fi);

frames { framesize, hop } stream =
    if framesize == hop then
        blockList framesize stream
    else
        overlappingBlockList framesize hop stream 
            framesize (map \(vec.zeros framesize) [0..stream.channels-1]);
    fi;

streamContiguous rate framesize frames =
   (var remaining = frames;
    var buffered = mat.zeroSizeMatrix ();
    var position = 0;
    channels = mat.height (head frames); // so we don't need to keep a head ptr
    {
        get position () = position,
        get channels () = channels,
        get sampleRate () = rate,
        get finished? () = empty? remaining and mat.empty? buffered,
        get available () = 
            // Don't take length of frames -- we don't want to lose laziness.
            // If the caller cares that much, they can measure frames themselves
            if empty? remaining then
                Known (mat.width buffered) 
            else
                Unknown ()
            fi,
        read count =
           (framesFor samples acc =
                if samples <= 0 or empty? remaining then
                    acc
                else
                    this = head remaining;
                    remaining := tail remaining;
                    framesFor (samples - mat.width this) (acc ++ [this])
                fi;
            source = mat.concat (Horizontal ()) (framesFor count [buffered]);
            toReturn = mat.columnSlice source 0 count;
            position := position + mat.width toReturn;
            buffered := mat.columnSlice source count (mat.width source);
            toReturn),
        close = \(),
    });

overlapAdd channels framesize hop frames =
   (ola fr acc =
        if empty? fr then
           [acc]
        else
            frameSized = mat.resizedTo
               { columns = framesize, rows = channels };
            extended = frameSized
               (mat.columnSlice acc hop (mat.width acc));
            added = mat.sum (frameSized (head fr)) extended;
           (mat.columnSlice acc 0 hop) :: ola (tail fr) added;
        fi;
    mat.concat (Horizontal ()) (ola frames (mat.zeroSizeMatrix ())));

streamOverlapping rate framesize hop frames =
   (var remaining = frames;
    var buffered = mat.zeroSizeMatrix ();
    var position = 0;
    factor = hop / (framesize/2);
    w = bf.scaled factor (win.hann framesize);
    channels = mat.height (head frames); // so we don't need to keep a head ptr
    filt.delayedBy (- framesize)
        {
            get position () = position,
            get channels () = channels,
            get sampleRate () = rate,
            get finished? () = empty? remaining and mat.empty? buffered,
            get available () = 
                // Don't take length of frames: we don't want to lose
                // laziness.  If the caller cares that much, they can
                // measure frames themselves
                if empty? remaining then
                    Known (mat.width buffered) 
                else
                    Unknown ()
                fi,
            read count =
               (framesFor samples acc =
                    if samples <= 0 or empty? remaining then
                        acc
                    else
                        this = mat.newMatrix (RowMajor ())
                           (map (bf.multiply w) (mat.asRows (head remaining)));
                        remaining := tail remaining;
                        framesFor (samples - hop) (acc ++ [this])
                    fi;
                source = overlapAdd channels framesize hop
                   (framesFor count [buffered]);
//                println "source = \(source)";
                toReturn = mat.columnSlice source 0 count;
                position := position + mat.width toReturn;
                buffered := mat.columnSlice source count (mat.width source);
                toReturn),
            close = \(),
        });
    
//!!! doc: convert frames back to a stream
streamed rate { framesize, hop } frames =
    if framesize == hop then
        streamContiguous rate framesize frames
    else
        streamOverlapping rate framesize hop frames
    fi;

monoFrames params stream =
    map ch.mixedDown (frames params stream);

windowedFrames { framesize, hop, window } stream =
   (win = window framesize;
    map (bf.multiply win) (monoFrames { framesize, hop } stream));

frequencyDomainFrames { framesize, hop } stream =
   (f = fft.realForward framesize;
    map f (windowedFrames { framesize, hop, window = win.hann } stream));

{ 
    frames,
    monoFrames,
    windowedFrames,
    frequencyDomainFrames,

    framesOfFile parameters filename =
        frames parameters (af.open filename),

    monoFramesOfFile parameters filename =
        monoFrames parameters (af.open filename),

    windowedFramesOfFile parameters filename = 
        windowedFrames parameters (af.open filename),

    frequencyDomainFramesOfFile parameters filename = 
        frequencyDomainFrames parameters (af.open filename),

    overlapAdd,

    streamed,
}