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
|