Chris@8: Chris@93: module yetilab.stream.framer; Chris@8: Chris@25: /** Chris@25: * Framer expresses a stream (or a file) as a lazy list of (possibly Chris@158: * overlapping) frames of data. Chris@25: */ Chris@25: Chris@273: vec = load yetilab.vector; Chris@222: bf = load yetilab.vector.blockfuncs; Chris@93: af = load yetilab.stream.audiofile; Chris@264: win = load yetilab.signal.window; Chris@93: fft = load yetilab.transform.fft; Chris@273: mat = load yetilab.matrix; Chris@158: ch = load yetilab.stream.channels; Chris@282: syn = load yetilab.stream.syntheticstream; Chris@284: filt = load yetilab.stream.filter; Chris@8: Chris@32: blockList framesize stream = Chris@14: if stream.finished? then Chris@158: stream.close (); Chris@160: [] Chris@13: else Chris@158: mat.resizedTo { rows = stream.channels, columns = framesize } Chris@158: (stream.read framesize) Chris@32: :. \(blockList framesize stream); Chris@13: fi; Chris@13: Chris@47: overlappingBlockList size hop stream valid buffer = Chris@29: ( Chris@158: m = stream.read hop; Chris@158: obtained = mat.width m; Chris@23: Chris@32: // Retain framesize - hop samples from old buffer, add hop samples Chris@29: // (zero-padded if necessary) just read Chris@158: buffer = map2 Chris@158: do buf row: Chris@218: vec.concat Chris@260: [vec.slice buf hop size, Chris@218: vec.resizedTo hop (mat.getRow row m)]; Chris@158: done buffer [0..stream.channels-1]; Chris@23: Chris@23: // Number of "valid" elements (not tail-end zero-padding) left in buffer Chris@24: remaining = valid - (hop - obtained); Chris@23: Chris@23: if remaining <= 0 then Chris@23: stream.close (); Chris@23: []; Chris@12: else Chris@163: mat.newMatrix (RowMajor ()) buffer Chris@158: :. \(overlappingBlockList size hop stream remaining buffer); Chris@23: fi); Chris@14: Chris@32: frames { framesize, hop } stream = Chris@32: if framesize == hop then Chris@32: blockList framesize stream Chris@30: else Chris@32: overlappingBlockList framesize hop stream Chris@218: framesize (map \(vec.zeros framesize) [0..stream.channels-1]); Chris@30: fi; Chris@14: Chris@284: streamContiguous rate framesize frames = Chris@284: (var remaining = frames; Chris@284: var buffered = mat.zeroSizeMatrix (); Chris@284: var position = 0; Chris@284: channels = mat.height (head frames); // so we don't need to keep a head ptr Chris@284: { Chris@284: get position () = position, Chris@284: get channels () = channels, Chris@284: get sampleRate () = rate, Chris@284: get finished? () = empty? remaining and mat.empty? buffered, Chris@284: get available () = Chris@284: // Don't take length of frames -- we don't want to lose laziness. Chris@284: // If the caller cares that much, they can measure frames themselves Chris@284: if empty? remaining then Chris@284: Known (mat.width buffered) Chris@284: else Chris@284: Unknown () Chris@284: fi, Chris@284: read count = Chris@284: (framesFor samples acc = Chris@284: if samples <= 0 or empty? remaining then Chris@284: acc Chris@284: else Chris@284: this = head remaining; Chris@284: remaining := tail remaining; Chris@284: framesFor (samples - mat.width this) (acc ++ [this]) Chris@284: fi; Chris@284: source = mat.concat (Horizontal ()) (framesFor count [buffered]); Chris@284: toReturn = mat.columnSlice source 0 count; Chris@284: position := position + mat.width toReturn; Chris@284: buffered := mat.columnSlice source count (mat.width source); Chris@284: toReturn), Chris@284: close = \(), Chris@284: }); Chris@284: Chris@285: overlapAdd channels framesize hop frames = Chris@285: (ola fr acc = Chris@285: if empty? fr then Chris@285: [acc] Chris@285: else Chris@285: frameSized = mat.resizedTo Chris@285: { columns = framesize, rows = channels }; Chris@285: extended = frameSized Chris@285: (mat.columnSlice acc hop (mat.width acc)); Chris@285: added = mat.sum (frameSized (head fr)) extended; Chris@285: (mat.columnSlice acc 0 hop) :: ola (tail fr) added; Chris@285: fi; Chris@285: mat.concat (Horizontal ()) (ola frames (mat.zeroSizeMatrix ()))); Chris@285: Chris@284: streamOverlapping rate framesize hop frames = Chris@284: (var remaining = frames; Chris@284: var buffered = mat.zeroSizeMatrix (); Chris@284: var position = 0; Chris@288: factor = hop / (framesize/2); Chris@288: w = bf.scaled factor (win.hann framesize); Chris@284: channels = mat.height (head frames); // so we don't need to keep a head ptr Chris@286: filt.delayedBy (- framesize) Chris@282: { Chris@282: get position () = position, Chris@282: get channels () = channels, Chris@282: get sampleRate () = rate, Chris@284: get finished? () = empty? remaining and mat.empty? buffered, Chris@284: get available () = Chris@284: // Don't take length of frames: we don't want to lose Chris@284: // laziness. If the caller cares that much, they can Chris@284: // measure frames themselves Chris@284: if empty? remaining then Chris@284: Known (mat.width buffered) Chris@284: else Chris@284: Unknown () Chris@284: fi, Chris@284: read count = Chris@284: (framesFor samples acc = Chris@284: if samples <= 0 or empty? remaining then Chris@284: acc Chris@284: else Chris@284: this = mat.newMatrix (RowMajor ()) Chris@284: (map (bf.multiply w) (mat.asRows (head remaining))); Chris@284: remaining := tail remaining; Chris@284: framesFor (samples - hop) (acc ++ [this]) Chris@284: fi; Chris@285: source = overlapAdd channels framesize hop Chris@285: (framesFor count [buffered]); Chris@285: // println "source = \(source)"; Chris@284: toReturn = mat.columnSlice source 0 count; Chris@286: position := position + mat.width toReturn; Chris@284: buffered := mat.columnSlice source count (mat.width source); Chris@284: toReturn), Chris@282: close = \(), Chris@284: }); Chris@284: Chris@282: //!!! doc: convert frames back to a stream Chris@282: streamed rate { framesize, hop } frames = Chris@282: if framesize == hop then Chris@282: streamContiguous rate framesize frames Chris@282: else Chris@284: streamOverlapping rate framesize hop frames Chris@282: fi; Chris@282: Chris@158: monoFrames params stream = Chris@158: map ch.mixedDown (frames params stream); Chris@158: Chris@49: windowedFrames { framesize, hop, window } stream = Chris@49: (win = window framesize; Chris@158: map (bf.multiply win) (monoFrames { framesize, hop } stream)); Chris@49: Chris@49: frequencyDomainFrames { framesize, hop } stream = Chris@49: (f = fft.realForward framesize; Chris@49: map f (windowedFrames { framesize, hop, window = win.hann } stream)); Chris@23: Chris@11: { Chris@30: frames, Chris@158: monoFrames, Chris@49: windowedFrames, Chris@49: frequencyDomainFrames, Chris@49: Chris@49: framesOfFile parameters filename = Chris@146: frames parameters (af.open filename), Chris@49: Chris@158: monoFramesOfFile parameters filename = Chris@158: monoFrames parameters (af.open filename), Chris@158: Chris@49: windowedFramesOfFile parameters filename = Chris@146: windowedFrames parameters (af.open filename), Chris@49: Chris@49: frequencyDomainFramesOfFile parameters filename = Chris@146: frequencyDomainFrames parameters (af.open filename), Chris@284: Chris@285: overlapAdd, Chris@285: Chris@284: streamed, Chris@11: } Chris@8: