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@8: Chris@291: //!!! todo: synchronized for everything with state assignment Chris@291: 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@291: overlapAdd overlap frames = Chris@285: (ola fr acc = Chris@291: case fr of Chris@291: first::rest: Chris@291: (w = mat.width acc; Chris@291: pre = mat.columnSlice acc 0 (w - overlap); Chris@291: added = mat.sum first Chris@291: (mat.resizedTo (mat.size first) Chris@291: (mat.columnSlice acc (w - overlap) w)); Chris@291: pre :: ola rest added); Chris@291: _: Chris@291: [acc]; Chris@291: esac; Chris@291: case frames of Chris@291: first::rest: Chris@291: mat.concat (Horizontal ()) (ola rest first); Chris@291: _: Chris@291: mat.zeroSizeMatrix (); Chris@291: esac); 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@294: Chris@288: factor = hop / (framesize/2); Chris@290: w = bf.scaled factor (win.hann framesize); // periodic window, not symmetric Chris@284: channels = mat.height (head frames); // so we don't need to keep a head ptr Chris@294: Chris@291: syncd = synchronized remaining; Chris@294: Chris@294: finished' () = syncd \(empty? remaining and mat.empty? buffered); Chris@294: Chris@294: read' count = Chris@294: (framesFor samples acc = Chris@294: if samples <= 0 or empty? remaining then Chris@294: acc Chris@294: else Chris@294: this = mat.resizedTo { columns = framesize, rows = channels } Chris@294: (mat.newMatrix (RowMajor ()) Chris@294: (map (bf.multiply w) (mat.asRows (head remaining)))); Chris@294: remaining := tail remaining; Chris@294: framesFor (samples - hop) (acc ++ [this]) Chris@294: fi; Chris@294: source = overlapAdd (framesize - hop) Chris@294: (framesFor count [buffered]); Chris@294: buffered := mat.columnSlice source count (mat.width source); Chris@294: mat.columnSlice source 0 count); Chris@294: Chris@294: // lose initial padding Chris@294: \() (read' (framesize - hop)); Chris@294: Chris@294: { Chris@294: get position () = syncd \(position), Chris@294: get channels () = channels, Chris@294: get sampleRate () = rate, Chris@294: get finished? () = finished' (), Chris@294: get available () = if finished' () then Known 0 else Unknown () fi, Chris@294: read count = syncd Chris@294: \(data = read' count; Chris@294: position := position + mat.width data; Chris@294: data), Chris@294: close = \(), Chris@294: }); 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@294: frequencyDomainFrames parameters stream = Chris@294: (f = fft.realForward parameters.framesize; Chris@294: map f (windowedFrames parameters 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: