To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / src / may / stream / framer.yeti @ 592:e7c5b55aab9a
History | View | Annotate | Download (9.67 KB)
| 1 |
|
|---|---|
| 2 |
module may.stream.framer; |
| 3 |
|
| 4 |
/** |
| 5 |
* Framer expresses a stream (or a file) as a lazy list of (possibly |
| 6 |
* overlapping) frames of data. |
| 7 |
*/ |
| 8 |
|
| 9 |
vec = load may.vector; |
| 10 |
af = load may.stream.audiofile; |
| 11 |
win = load may.signal.window; |
| 12 |
fft = load may.transform.fft; |
| 13 |
mat = load may.matrix; |
| 14 |
complex = load may.complex; |
| 15 |
cm = load may.matrix.complex; |
| 16 |
manip = load may.stream.manipulate; |
| 17 |
syn = load may.stream.syntheticstream; |
| 18 |
|
| 19 |
{ ceil } = load may.mathmisc;
|
| 20 |
|
| 21 |
load may.stream.type; |
| 22 |
|
| 23 |
//!!! todo: synchronized for everything with state assignment |
| 24 |
|
| 25 |
blockList framesize stream = |
| 26 |
if stream.finished? then |
| 27 |
stream.close (); |
| 28 |
[] |
| 29 |
else |
| 30 |
mat.resizedTo { rows = stream.channels, columns = framesize }
|
| 31 |
(stream.read framesize) |
| 32 |
:. \(blockList framesize stream); |
| 33 |
fi; |
| 34 |
|
| 35 |
overlappingBlockList size hop stream valid buffer = |
| 36 |
(m = stream.read hop; |
| 37 |
obtained = mat.width m; |
| 38 |
|
| 39 |
// Retain framesize - hop samples from old buffer, add hop samples |
| 40 |
// (zero-padded if necessary) just read |
| 41 |
buffer = map2 |
| 42 |
do buf row: |
| 43 |
vec.concat |
| 44 |
[vec.slice buf hop size, |
| 45 |
vec.resizedTo hop (mat.getRow row m)]; |
| 46 |
done buffer [0..stream.channels-1]; |
| 47 |
|
| 48 |
// Number of "valid" elements (not tail-end zero-padding) left in buffer |
| 49 |
remaining = valid - (hop - obtained); |
| 50 |
|
| 51 |
if remaining <= 0 then |
| 52 |
stream.close (); |
| 53 |
[]; |
| 54 |
else |
| 55 |
mat.fromRows buffer |
| 56 |
:. \(overlappingBlockList size hop stream remaining buffer); |
| 57 |
fi); |
| 58 |
|
| 59 |
//!!! doc: if hop != framesize, stream will be padded at start with |
| 60 |
// framesize - hop zeros if padded is true. if hop == framesize, no |
| 61 |
// padding will occur at start regardless of the value of padded. The |
| 62 |
// end will always be padded (...?) |
| 63 |
frames' framesize hop padded stream = |
| 64 |
if framesize == hop then |
| 65 |
blockList framesize stream |
| 66 |
else |
| 67 |
initialBuffer = |
| 68 |
if padded then |
| 69 |
map \(vec.zeros framesize) [0..stream.channels-1]; |
| 70 |
else |
| 71 |
mat.asRows |
| 72 |
(mat.concatHorizontal |
| 73 |
[mat.toRowMajor |
| 74 |
(mat.zeroMatrix { rows = stream.channels, columns = hop }),
|
| 75 |
stream.read (framesize - hop)]); |
| 76 |
fi; |
| 77 |
overlappingBlockList framesize hop stream framesize initialBuffer; |
| 78 |
fi; |
| 79 |
|
| 80 |
frames framesize options stream = |
| 81 |
(var hop = framesize; |
| 82 |
var padded = true; |
| 83 |
var windower = id; |
| 84 |
var transform = id; |
| 85 |
var mixer = id; |
| 86 |
for options \case of |
| 87 |
Hop h: hop := h; |
| 88 |
Padded p: padded := p; |
| 89 |
Window w: |
| 90 |
(window = w framesize; |
| 91 |
windower := mat.mapRows (do v: vec.multiply [v, window] done)); |
| 92 |
FrequencyDomain f: |
| 93 |
if f then |
| 94 |
transform := mat.mapRows (fft.realForwardMagnitude framesize); |
| 95 |
fi; |
| 96 |
MixedTo c: |
| 97 |
mixer := manip.mixedTo c; |
| 98 |
esac; |
| 99 |
map transform |
| 100 |
(map windower |
| 101 |
(frames' framesize hop padded |
| 102 |
(mixer stream)))); |
| 103 |
|
| 104 |
complexFrames framesize options stream = |
| 105 |
(var hop = framesize; |
| 106 |
var padded = true; |
| 107 |
var windower = id; |
| 108 |
var rowTransform = |
| 109 |
do r: complex.complexArray r (vec.zeros (vec.length r)) done; |
| 110 |
var mixer = id; |
| 111 |
for options \case of |
| 112 |
Hop h: hop := h; |
| 113 |
Padded p: padded := p; |
| 114 |
Window w: |
| 115 |
(window = w framesize; |
| 116 |
windower := mat.mapRows (do v: vec.multiply [v, window] done)); |
| 117 |
FrequencyDomain f: |
| 118 |
//!!! what if both f and not-f provided in one options list? need reset |
| 119 |
if f then |
| 120 |
rowTransform := fft.realForward framesize; |
| 121 |
fi; |
| 122 |
MixedTo c: |
| 123 |
mixer := manip.mixedTo c; |
| 124 |
esac; |
| 125 |
map do m: |
| 126 |
cm.fromRows (map rowTransform (mat.asRows m)) |
| 127 |
done |
| 128 |
(map windower |
| 129 |
(frames' framesize hop padded |
| 130 |
(mixer stream)))); |
| 131 |
|
| 132 |
streamContiguous rate framesize frames = |
| 133 |
(var remaining = frames; |
| 134 |
var position = 0; |
| 135 |
channels = mat.height (head frames); // so we don't need to keep a head ptr |
| 136 |
var buffered = mat.toRowMajor |
| 137 |
(mat.zeroMatrix { rows = channels, columns = 0 });
|
| 138 |
{
|
| 139 |
get position () = position, |
| 140 |
get channels () = channels, |
| 141 |
get sampleRate () = rate, |
| 142 |
get finished? () = empty? remaining and mat.width buffered == 0, |
| 143 |
get available () = |
| 144 |
// Don't take length of frames -- we don't want to lose laziness. |
| 145 |
// If the caller cares that much, they can measure frames themselves |
| 146 |
if empty? remaining then |
| 147 |
Known (mat.width buffered) |
| 148 |
else |
| 149 |
Unknown () |
| 150 |
fi, |
| 151 |
read count = |
| 152 |
(framesFor samples acc = |
| 153 |
if samples <= 0 or empty? remaining then |
| 154 |
reverse acc |
| 155 |
else |
| 156 |
this = head remaining; |
| 157 |
remaining := tail remaining; |
| 158 |
framesFor (samples - mat.width this) (this :: acc) |
| 159 |
fi; |
| 160 |
source = mat.concatHorizontal (framesFor count [buffered]); |
| 161 |
toReturn = mat.columnSlice source 0 count; |
| 162 |
position := position + mat.width toReturn; |
| 163 |
buffered := mat.columnSlice source count (mat.width source); |
| 164 |
toReturn), |
| 165 |
close = \(), |
| 166 |
}); |
| 167 |
|
| 168 |
overlapAdd overlap frames = |
| 169 |
(ola fr pending acc = |
| 170 |
case fr of |
| 171 |
first::rest: |
| 172 |
(w = mat.width pending; |
| 173 |
pre = mat.columnSlice pending 0 (w - overlap); |
| 174 |
added = mat.sum |
| 175 |
[first, |
| 176 |
(mat.resizedTo (mat.size first) |
| 177 |
(mat.columnSlice pending (w - overlap) w))]; |
| 178 |
ola rest added (pre::acc)); |
| 179 |
_: |
| 180 |
reverse (pending::acc); |
| 181 |
esac; |
| 182 |
case frames of |
| 183 |
first::rest: |
| 184 |
mat.concatHorizontal (ola rest first []); |
| 185 |
_: |
| 186 |
mat.toRowMajor (mat.zeroMatrix { rows = 0, columns = 0 });
|
| 187 |
esac); |
| 188 |
|
| 189 |
streamOverlapping rate { framesize, hop, window } frames =
|
| 190 |
(var remaining = frames; |
| 191 |
var position = 0; |
| 192 |
|
| 193 |
factor = hop / (framesize/2); |
| 194 |
w = vec.scaled factor (window framesize); |
| 195 |
channels = mat.height (head frames); // so we don't need to keep a head ptr |
| 196 |
|
| 197 |
var buffered = |
| 198 |
mat.toRowMajor (mat.zeroMatrix { rows = channels, columns = 0 });
|
| 199 |
|
| 200 |
syncd = synchronized remaining; |
| 201 |
|
| 202 |
finished' () = syncd \(empty? remaining and mat.width buffered == 0); |
| 203 |
|
| 204 |
read' count = |
| 205 |
(framesFor samples acc = |
| 206 |
if samples <= 0 or empty? remaining then |
| 207 |
reverse acc |
| 208 |
else |
| 209 |
this = mat.resizedTo { columns = framesize, rows = channels }
|
| 210 |
(mat.mapRows do v: vec.multiply [v,w] done (head remaining)); |
| 211 |
remaining := tail remaining; |
| 212 |
framesFor (samples - hop) (this::acc) |
| 213 |
fi; |
| 214 |
source = overlapAdd (framesize - hop) |
| 215 |
(framesFor count [buffered]); |
| 216 |
buffered := mat.columnSlice source count (mat.width source); |
| 217 |
mat.columnSlice source 0 count); |
| 218 |
|
| 219 |
{
|
| 220 |
get position () = syncd \(position), |
| 221 |
get channels () = channels, |
| 222 |
get sampleRate () = rate, |
| 223 |
get finished? () = finished' (), |
| 224 |
get available () = if finished' () then Known 0 else Unknown () fi, |
| 225 |
read count = syncd |
| 226 |
\(data = read' count; |
| 227 |
position := position + mat.width data; |
| 228 |
data), |
| 229 |
close = \(), |
| 230 |
}); |
| 231 |
|
| 232 |
//!!! doc: padded tells us whether the original framer was padding or |
| 233 |
//not. if not, and if the frames are overlapping, we won't reconstruct |
| 234 |
//the original stream exactly (scaling will be wrong for the first few |
| 235 |
//samples until we reach the point where each sample is being |
| 236 |
//reconstructed from the requisite number of frames) |
| 237 |
streamed rate framesize options frames = |
| 238 |
(var hop = framesize; |
| 239 |
var padded = true; |
| 240 |
var winopt = None (); |
| 241 |
for options \case of |
| 242 |
Hop h: hop := h; |
| 243 |
Padded p: padded := p; |
| 244 |
Window w: winopt := Some w; |
| 245 |
FrequencyDomain f: failWith "Cannot stream from real input with FrequencyDomain true (need to use complexStreamed)"; |
| 246 |
esac; |
| 247 |
window = |
| 248 |
case winopt of |
| 249 |
Some w: w; |
| 250 |
None (): |
| 251 |
// NB periodic, not symmetric |
| 252 |
if framesize == hop then win.boxcar else win.hann fi |
| 253 |
esac; |
| 254 |
if empty? frames then |
| 255 |
syn.empty rate 1 |
| 256 |
elif framesize == hop then |
| 257 |
streamContiguous rate framesize frames |
| 258 |
elif padded then |
| 259 |
manip.delayedBy (- (framesize - hop)) |
| 260 |
(streamOverlapping rate { framesize, hop, window } frames);
|
| 261 |
else |
| 262 |
streamOverlapping rate { framesize, hop, window } frames;
|
| 263 |
fi); |
| 264 |
|
| 265 |
//!!! todo: this is not yet tested |
| 266 |
complexStreamed rate framesize options frames = |
| 267 |
(streamOptions = array []; |
| 268 |
var rowTransform = complex.magnitudes; |
| 269 |
for options \case of |
| 270 |
FrequencyDomain f: |
| 271 |
//!!! what if both f and not-f provided in one options list? need reset |
| 272 |
if f then |
| 273 |
rowTransform := fft.realInverse framesize; |
| 274 |
fi; |
| 275 |
other: push streamOptions other; |
| 276 |
esac; |
| 277 |
transformed = map do c: |
| 278 |
mat.fromRows (map rowTransform (cm.asRows c)) |
| 279 |
done frames; |
| 280 |
streamed rate framesize streamOptions transformed); |
| 281 |
|
| 282 |
typedef opt_t = |
| 283 |
Hop number | Padded boolean | |
| 284 |
Window (number -> vec.vector_t) | FrequencyDomain boolean; |
| 285 |
|
| 286 |
{
|
| 287 |
frames is number -> list?<opt_t> -> stream_t -> list<mat.matrix_t>, |
| 288 |
complexFrames is number -> list?<opt_t> -> stream_t -> list<cm.complexmatrix_t>, |
| 289 |
|
| 290 |
framesOfFile framesize options filename = |
| 291 |
frames framesize options (af.open filename), |
| 292 |
|
| 293 |
overlapAdd, |
| 294 |
|
| 295 |
streamed is number -> number -> list?<opt_t> -> list<mat.matrix_t> -> stream_t, |
| 296 |
complexStreamed is number -> number -> list?<opt_t> -> list<cm.complexmatrix_t> -> stream_t, |
| 297 |
} |
| 298 |
|