To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Revision:

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