annotate yetilab/stream/framer.yeti @ 290:21ec05237c1a

Revise overlap-add
author Chris Cannam
date Fri, 31 May 2013 10:58:00 +0100
parents b34960d2b519
children c40821ff70f8
rev   line source
Chris@8 1
Chris@93 2 module yetilab.stream.framer;
Chris@8 3
Chris@25 4 /**
Chris@25 5 * Framer expresses a stream (or a file) as a lazy list of (possibly
Chris@158 6 * overlapping) frames of data.
Chris@25 7 */
Chris@25 8
Chris@273 9 vec = load yetilab.vector;
Chris@222 10 bf = load yetilab.vector.blockfuncs;
Chris@93 11 af = load yetilab.stream.audiofile;
Chris@264 12 win = load yetilab.signal.window;
Chris@93 13 fft = load yetilab.transform.fft;
Chris@273 14 mat = load yetilab.matrix;
Chris@158 15 ch = load yetilab.stream.channels;
Chris@282 16 syn = load yetilab.stream.syntheticstream;
Chris@284 17 filt = load yetilab.stream.filter;
Chris@8 18
Chris@32 19 blockList framesize stream =
Chris@14 20 if stream.finished? then
Chris@158 21 stream.close ();
Chris@160 22 []
Chris@13 23 else
Chris@158 24 mat.resizedTo { rows = stream.channels, columns = framesize }
Chris@158 25 (stream.read framesize)
Chris@32 26 :. \(blockList framesize stream);
Chris@13 27 fi;
Chris@13 28
Chris@47 29 overlappingBlockList size hop stream valid buffer =
Chris@29 30 (
Chris@158 31 m = stream.read hop;
Chris@158 32 obtained = mat.width m;
Chris@23 33
Chris@32 34 // Retain framesize - hop samples from old buffer, add hop samples
Chris@29 35 // (zero-padded if necessary) just read
Chris@158 36 buffer = map2
Chris@158 37 do buf row:
Chris@218 38 vec.concat
Chris@260 39 [vec.slice buf hop size,
Chris@218 40 vec.resizedTo hop (mat.getRow row m)];
Chris@158 41 done buffer [0..stream.channels-1];
Chris@23 42
Chris@23 43 // Number of "valid" elements (not tail-end zero-padding) left in buffer
Chris@24 44 remaining = valid - (hop - obtained);
Chris@23 45
Chris@23 46 if remaining <= 0 then
Chris@23 47 stream.close ();
Chris@23 48 [];
Chris@12 49 else
Chris@163 50 mat.newMatrix (RowMajor ()) buffer
Chris@158 51 :. \(overlappingBlockList size hop stream remaining buffer);
Chris@23 52 fi);
Chris@14 53
Chris@32 54 frames { framesize, hop } stream =
Chris@32 55 if framesize == hop then
Chris@32 56 blockList framesize stream
Chris@30 57 else
Chris@32 58 overlappingBlockList framesize hop stream
Chris@218 59 framesize (map \(vec.zeros framesize) [0..stream.channels-1]);
Chris@30 60 fi;
Chris@14 61
Chris@284 62 streamContiguous rate framesize frames =
Chris@284 63 (var remaining = frames;
Chris@284 64 var buffered = mat.zeroSizeMatrix ();
Chris@284 65 var position = 0;
Chris@284 66 channels = mat.height (head frames); // so we don't need to keep a head ptr
Chris@284 67 {
Chris@284 68 get position () = position,
Chris@284 69 get channels () = channels,
Chris@284 70 get sampleRate () = rate,
Chris@284 71 get finished? () = empty? remaining and mat.empty? buffered,
Chris@284 72 get available () =
Chris@284 73 // Don't take length of frames -- we don't want to lose laziness.
Chris@284 74 // If the caller cares that much, they can measure frames themselves
Chris@284 75 if empty? remaining then
Chris@284 76 Known (mat.width buffered)
Chris@284 77 else
Chris@284 78 Unknown ()
Chris@284 79 fi,
Chris@284 80 read count =
Chris@284 81 (framesFor samples acc =
Chris@284 82 if samples <= 0 or empty? remaining then
Chris@284 83 acc
Chris@284 84 else
Chris@284 85 this = head remaining;
Chris@284 86 remaining := tail remaining;
Chris@284 87 framesFor (samples - mat.width this) (acc ++ [this])
Chris@284 88 fi;
Chris@284 89 source = mat.concat (Horizontal ()) (framesFor count [buffered]);
Chris@284 90 toReturn = mat.columnSlice source 0 count;
Chris@284 91 position := position + mat.width toReturn;
Chris@284 92 buffered := mat.columnSlice source count (mat.width source);
Chris@284 93 toReturn),
Chris@284 94 close = \(),
Chris@284 95 });
Chris@284 96
Chris@290 97 overlapAdd channels overlap frames =
Chris@285 98 (ola fr acc =
Chris@285 99 if empty? fr then
Chris@285 100 [acc]
Chris@285 101 else
Chris@290 102 pre = mat.columnSlice acc 0 (mat.width acc - overlap);
Chris@290 103 added = mat.sum (head fr)
Chris@290 104 (mat.resizedTo { columns = mat.width (head fr), rows = channels }
Chris@290 105 (mat.columnSlice acc (mat.width acc - overlap) (mat.width acc)));
Chris@290 106 /*
Chris@285 107 frameSized = mat.resizedTo
Chris@285 108 { columns = framesize, rows = channels };
Chris@285 109 extended = frameSized
Chris@285 110 (mat.columnSlice acc hop (mat.width acc));
Chris@285 111 added = mat.sum (frameSized (head fr)) extended;
Chris@285 112 (mat.columnSlice acc 0 hop) :: ola (tail fr) added;
Chris@290 113 */
Chris@290 114 pre :: ola (tail fr) added;
Chris@285 115 fi;
Chris@285 116 mat.concat (Horizontal ()) (ola frames (mat.zeroSizeMatrix ())));
Chris@285 117
Chris@284 118 streamOverlapping rate framesize hop frames =
Chris@284 119 (var remaining = frames;
Chris@284 120 var buffered = mat.zeroSizeMatrix ();
Chris@284 121 var position = 0;
Chris@288 122 factor = hop / (framesize/2);
Chris@290 123 w = bf.scaled factor (win.hann framesize); // periodic window, not symmetric
Chris@290 124 println "window is \(vec.list w)";
Chris@284 125 channels = mat.height (head frames); // so we don't need to keep a head ptr
Chris@286 126 filt.delayedBy (- framesize)
Chris@282 127 {
Chris@282 128 get position () = position,
Chris@282 129 get channels () = channels,
Chris@282 130 get sampleRate () = rate,
Chris@284 131 get finished? () = empty? remaining and mat.empty? buffered,
Chris@289 132 get available () = Unknown (),
Chris@284 133 read count =
Chris@284 134 (framesFor samples acc =
Chris@284 135 if samples <= 0 or empty? remaining then
Chris@284 136 acc
Chris@284 137 else
Chris@290 138 println "multiplying \(head remaining) by window";
Chris@290 139 this = mat.resizedTo
Chris@290 140 { columns = framesize, rows = channels }
Chris@290 141 (mat.newMatrix (RowMajor ())
Chris@290 142 (map (bf.multiply w)
Chris@290 143 (mat.asRows (head remaining))));
Chris@284 144 remaining := tail remaining;
Chris@284 145 framesFor (samples - hop) (acc ++ [this])
Chris@284 146 fi;
Chris@290 147 source = overlapAdd channels (framesize - hop)
Chris@285 148 (framesFor count [buffered]);
Chris@284 149 toReturn = mat.columnSlice source 0 count;
Chris@286 150 position := position + mat.width toReturn;
Chris@284 151 buffered := mat.columnSlice source count (mat.width source);
Chris@290 152 println "count = \(count), framesize = \(framesize), hop = \(hop)";
Chris@290 153 println "leaving position = \(position), buffered = \(buffered), returning \(toReturn)";
Chris@284 154 toReturn),
Chris@282 155 close = \(),
Chris@284 156 });
Chris@284 157
Chris@282 158 //!!! doc: convert frames back to a stream
Chris@282 159 streamed rate { framesize, hop } frames =
Chris@282 160 if framesize == hop then
Chris@282 161 streamContiguous rate framesize frames
Chris@282 162 else
Chris@284 163 streamOverlapping rate framesize hop frames
Chris@282 164 fi;
Chris@282 165
Chris@158 166 monoFrames params stream =
Chris@158 167 map ch.mixedDown (frames params stream);
Chris@158 168
Chris@49 169 windowedFrames { framesize, hop, window } stream =
Chris@49 170 (win = window framesize;
Chris@158 171 map (bf.multiply win) (monoFrames { framesize, hop } stream));
Chris@49 172
Chris@49 173 frequencyDomainFrames { framesize, hop } stream =
Chris@49 174 (f = fft.realForward framesize;
Chris@49 175 map f (windowedFrames { framesize, hop, window = win.hann } stream));
Chris@23 176
Chris@11 177 {
Chris@30 178 frames,
Chris@158 179 monoFrames,
Chris@49 180 windowedFrames,
Chris@49 181 frequencyDomainFrames,
Chris@49 182
Chris@49 183 framesOfFile parameters filename =
Chris@146 184 frames parameters (af.open filename),
Chris@49 185
Chris@158 186 monoFramesOfFile parameters filename =
Chris@158 187 monoFrames parameters (af.open filename),
Chris@158 188
Chris@49 189 windowedFramesOfFile parameters filename =
Chris@146 190 windowedFrames parameters (af.open filename),
Chris@49 191
Chris@49 192 frequencyDomainFramesOfFile parameters filename =
Chris@146 193 frequencyDomainFrames parameters (af.open filename),
Chris@284 194
Chris@285 195 overlapAdd,
Chris@285 196
Chris@284 197 streamed,
Chris@11 198 }
Chris@8 199