Mercurial > hg > may
changeset 403:842cd25efb3f resample
Split out filter into filter, resample, convolve, manipulate
author | Chris Cannam |
---|---|
date | Fri, 27 Sep 2013 14:07:08 +0100 |
parents | 4076b141df7b |
children | fbcdcfaf4813 |
files | src/may/feature/feature.yeti src/may/feature/specdiff.yeti src/may/stream/convolve.yeti src/may/stream/filter.yeti src/may/stream/manipulate.yeti src/may/stream/record.yeti src/may/stream/resample.yeti src/may/stream/test/audiofile_reference.yeti src/may/stream/test/test_convolve.yeti src/may/stream/test/test_filter.yeti src/may/stream/test/test_manipulate.yeti src/may/stream/test/test_resample.yeti src/may/test/all.yeti src/may/vamp/test/test_vamp.yeti |
diffstat | 14 files changed, 1722 insertions(+), 1550 deletions(-) [+] |
line wrap: on
line diff
--- a/src/may/feature/feature.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/feature/feature.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -2,7 +2,6 @@ module may.feature.feature; vec = load may.vector; -fr = load may.stream.framer; // Utility functions for feature extractors
--- a/src/may/feature/specdiff.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/feature/specdiff.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -1,7 +1,6 @@ module may.feature.specdiff; -vec = load may.vector; fr = load may.stream.framer; cplx = load may.complex;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/may/stream/convolve.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -0,0 +1,154 @@ + +module may.stream.convolve; + +mat = load may.matrix; +vec = load may.vector; +fr = load may.stream.framer; +win = load may.signal.window; +fft = load may.transform.fft; +syn = load may.stream.syntheticstream; +cplx = load may.complex; + +load may.stream.type; +load may.vector.type; +load may.matrix.type; + +//!!! todo: synchronized for everything with state assignment + +zeroPaddedFreqFrames framesize channels = + // take a multi-channel stream, convert to a series of list of + // complex frequency-domain frames calculated from input padded to + // framesize*2, in which each frame contains all channels one + // after another (not interleaved) [because we lack a complex + // matrix type] + (forwardTransform = fft.realForward (framesize * 2); + do stream: + padded = + (map (mat.resizedTo { rows = channels, columns = framesize * 2 }) + (fr.frames { framesize, hop = framesize } stream)); + map do fr: concat (map forwardTransform (mat.asRows fr)) done padded; + done); + +doFastConvolve irframes sigframes = + (var history = []; + irlen = length irframes; + n = length (head irframes); + map do sigf: + history := take irlen (sigf::history); + fold do cc1 cc2: map2 cplx.add cc1 cc2 done + (list (cplx.zeros n)) + (map2 do irf histf: + map2 cplx.multiply irf histf; + done irframes history); + done sigframes); + +splitInto n fr = + (parts = splitAt n fr; + if empty? parts.snd then [parts.fst] + else parts.fst :: splitInto n parts.snd; + fi); + +fastConvolvedWith ir framesize s = + // prerequisite: ir and s have same number of channels + (framer = zeroPaddedFreqFrames framesize (mat.height ir); + ch = s.channels; + irfr = framer (syn.precalculated 1 ir); // rate arg is irrelevant here + sigfr = framer s; + inverseTransform = fft.realInverse (framesize * 2); + extended = sigfr ++ + map do _: list (cplx.zeros (ch * (framesize + 1))) done + [1..length irfr-1]; + cframes = doFastConvolve irfr extended; + rframes = (mat.zeroMatrix { rows = ch, columns = framesize * 2}) :: + map do fr: + mat.newMatrix (RowMajor ()) + (map inverseTransform (splitInto (framesize+1) fr)) + done cframes; + fr.streamOverlapping s.sampleRate + { framesize = framesize * 2, hop = framesize, window = win.boxcar } + rframes; +); + +plainConvolvedWith ir s = + // prerequisite: ir and s have same number of channels + (var history = mat.toRowMajor + (mat.zeroMatrix { rows = s.channels, columns = mat.width ir - 1 }); + s with + { + get finished? () = + s.finished? and (mat.empty? history), + get available () = + case s.available of + Known n: Known (n + mat.width history); + other: other; + esac, + read count = + (// Example: The IR is four samples long; we have three + // samples in history; two samples are available to read + // before the stream runs out. That means we can return + // up to five samples. Caller requests 6. + signal = s.read count; // -> two samples + siglen = mat.width signal; // -> 2 + histlen = mat.width history; // -> 3 + convlen = min count (siglen + histlen); // -> 5 + input = mat.resizedTo { rows = s.channels, columns = convlen } + signal; // example input now 5 samples, of which 2 are signal + output = array (map \(new double[convlen]) [1..s.channels]); + for [0..s.channels - 1] do ch: + for [0..mat.width input - 1] do i: + for [0..mat.width ir - 1] do j: + v = + if i >= j then + mat.at input ch (i - j) + else + ix = mat.width ir + i - j - 1; + if ix >= histlen then + 0 + else + mat.at history ch ix + fi + fi; + output[ch][i] := output[ch][i] + v * (mat.at ir ch j); + done; + done; + done; + // Remove from history a number of samples equal to the + // number returned; add to it a number equal to the number + // read from source + extended = mat.concat (Horizontal ()) [history, signal]; // -> 5 + newlen = (histlen + siglen) - convlen; // -> 0 + w = mat.width extended; + history := mat.columnSlice extended (w - newlen) w; + mat.newMatrix (RowMajor ()) (map vec.vector output)), + }); + +nextPowerOfTwo n = + (nextPowerOfTwo' p n = + if p >= n then p + else nextPowerOfTwo' (p * 2) n + fi; + nextPowerOfTwo' 1 n); + +//!!! todo: partial application so as to preprocess ir (in fast convolution case) +convolvedWith options ir s = //!!! cheap mono thing here +//!!! doc note: fast convolution output is padded to next frame boundary + (if mat.height ir != s.channels then + failWith "Signal stream and IR must have same number of channels (\(s.channels) != \(mat.height ir))" + fi; + var type = Fast (); + var framesize = nextPowerOfTwo (mat.width ir); + for options \case of + Fast s: if s then type := Fast () else type := Plain () fi; + Framesize n: framesize := nextPowerOfTwo n; + esac; + case type of + Fast (): + fastConvolvedWith ir framesize s; + Plain (): + plainConvolvedWith ir s; + esac); + +{ + convolvedWith is list<Fast boolean | Framesize number> -> matrix -> stream -> stream, +} +
--- a/src/may/stream/filter.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/stream/filter.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -2,454 +2,14 @@ module may.stream.filter; mat = load may.matrix; -ch = load may.stream.channels; vec = load may.vector; bf = load may.vector.blockfuncs; -fr = load may.stream.framer; win = load may.signal.window; -fft = load may.transform.fft; -syn = load may.stream.syntheticstream; -cplx = load may.complex; - -plot = load may.plot; - -load may.stream.type; -load may.vector.type; -load may.matrix.type; +convolve = load may.stream.convolve; +manip = load may.stream.manipulate; //!!! todo: synchronized for everything with state assignment -minDurationOf d1 d2 = - case d1 of - Known a: - case d2 of - Known b: Known (min a b); - Unknown (): Unknown (); - Infinite (): Known a; - esac; - Unknown (): - case d2 of - Known b: Known b; - Unknown (): Unknown (); - Infinite (): Unknown (); - esac; - Infinite (): - d2; - esac; - -withDuration nsamples s = //!!! should nsamples be a time in seconds? (no) - (var pos = 0; - s with - { - get position () = pos, - get available () = Known (nsamples - pos), - get finished? () = not (nsamples > pos), - read count = - (n = min count (nsamples - pos); - pos := pos + n; - if not s.finished? then - mat.resizedTo { rows = s.channels, columns = n } (s.read n); - else - mat.zeroMatrix { columns = n, rows = s.channels } - fi), - }); - -delayedBy nsamples s = //!!! doc: nsamples may be -ve - (var prepos = 0; - zeros n = mat.toRowMajor - (prepos := prepos + n; - mat.zeroMatrix { rows = s.channels, columns = n }); - delay = - if nsamples < 0 then - \0 (s.read (-nsamples)); - else - nsamples - fi; - { - get position () = - if prepos < delay then prepos - elif s.position + nsamples < 0 then 0 - else s.position + nsamples - fi, - get channels () = s.channels, - get sampleRate () = s.sampleRate, - get available () = - case s.available of - Known a: Known (a + delay - prepos); - other: other - esac, - get finished? () = (prepos >= delay) and s.finished?, - read count = - if prepos >= delay then s.read count - elif prepos + count < delay then zeros count - else - nleft = delay - prepos; - left = zeros nleft; - right = s.read (count - nleft); - mat.concat (Horizontal ()) [left, right]; - fi, - close = s.close - }); - -scaledBy factor s = - s with - { - read count = mat.scaled factor (s.read count); - }; - -inverted s = //!!! todo: test - s with - { - read count = - (m = s.read count; - if mat.empty? m then m - else mat.difference (mat.zeroMatrix (mat.size m)) m - fi) - }; - -//!!! poor name, confusion with mixed, but consistent with channels.yeti -mixedTo targetChannels s = - s with - { - get channels () = targetChannels, - read count = ch.mixedTo targetChannels (s.read count), - }; - -//!!! what should happen if we mix or multiplex a finite-length and an -//infinite-length stream? or even two streams with differing finite -//lengths? write tests for this. At the moment the resulting stream -//has duration equal to the shortest source stream and finishes as -//soon as any one of them finishes - -sum' streams = - (mix m1 m2 = - (sz = { rows = max (mat.height m1) (mat.height m2), - columns = min (mat.width m1) (mat.width m2) }; - if sz.columns == 0 then - mat.zeroSizeMatrix () - else - mat.sum (mat.resizedTo sz m1) (mat.resizedTo sz m2); - fi); - { - get position () = head (sort (map (.position) streams)), - get channels () = head (sortBy (>) (map (.channels) streams)), - get sampleRate () = (head streams).sampleRate, //!!! document this - get available () = - fold do dur s: minDurationOf dur s.available done (Infinite ()) streams, - get finished? () = any id (map (.finished?) streams), - read count = - (readTo acc ss count = - case ss of - first::rest: - part = first.read count; - case acc of - Some m: - readTo (Some (mix m part)) rest count; - None (): - readTo (Some part) rest count; - esac; - _: acc; - esac; - case readTo (None ()) streams count of - None (): mat.zeroSizeMatrix (); - Some m: m; - esac), - close () = for streams do s: s.close() done, - }); - -mean streams = //!!! todo: test - scaledBy (1 / (length streams)) (sum' streams); - -difference s1 s2 = //!!! todo: test - sum' [ s1, inverted s2 ]; - -multiplexed streams = - { - get position () = head (sort (map (.position) streams)), // can differ after EOS - get channels () = sum (map (.channels) streams), - get sampleRate () = (head streams).sampleRate, //!!! document this - get available () = - fold do dur s: minDurationOf dur s.available done (Infinite ()) streams, - get finished? () = any id (map (.finished?) streams), - read count = - (outs = map do s: s.read count done streams; - minlen = head (sort (map mat.width outs)); - outs = map do m: - mat.resizedTo { rows = mat.height m, columns = minlen } m - done outs; - mat.concat (Vertical ()) outs - ), - close () = for streams do s: s.close() done, - }; - -repeated s = - // There is no way to reset a stream (as in principle, they might - // be "live") so we can't read from the same stream repeatedly -- - // we have to cache its output and then repeat that. This is a - // little tricky to do efficiently without knowing how long the - // stream is (in general) or how much is going to be requested at - // a time. - if s.available == Infinite () then s - else - var pos = 0; - var cache = mat.zeroSizeMatrix (); - chunks = array []; - cachedPartsFor count = - (start = pos % (mat.width cache); - avail = (mat.width cache) - start; - if avail >= count then - pos := pos + count; - [mat.columnSlice cache start (start + count)] - else - pos := pos + avail; - mat.columnSlice cache start (start + avail) :: - cachedPartsFor (count - avail); - fi); - readFromCache count = - (if (mat.width cache) == 0 then - cache := mat.concat (Horizontal ()) (list chunks); - clearArray chunks; - fi; - if (mat.width cache) == 0 then - cache - else - mat.concat (Horizontal ()) (cachedPartsFor count); - fi); - s with - { - get position () = pos, - get available () = Infinite (), - get finished? () = false, - read count = - if s.finished? then - readFromCache count - else - part = s.read count; - len = (mat.width part); - push chunks part; - pos := pos + len; - if len == count then part - else - mat.concat (Horizontal ()) - [part, readFromCache (count - len)]; - fi; - fi, - } - fi; - -duplicated copies s = -//!!! doc fact that original s cannot be used independently of this afterward -// (so maybe name is misleading?) - array if copies < 2 then map \s [1..copies]; - else - pos = [:]; - lowtide () = head (sort (map (at pos) (keys pos))); - var hightide = 0; - var cache = mat.zeroSizeMatrix (); - syncd = synchronized pos; - advance i n = - (formerLow = lowtide (); - pos[i] := pos[i] + n; - encroachment = lowtide () - formerLow; - if encroachment > 0 then - cache := mat.columnSlice cache encroachment (mat.width cache); - fi); - map do instance: - pos[instance] := 0; - { - get position () = syncd \(pos[instance]), - get channels () = syncd \(s.channels), - get sampleRate () = syncd \(s.sampleRate), - get available () = syncd - \(case s.available of - Known av: Known (av + (hightide - pos[instance])); - other: other; - esac), - get finished? () = syncd - \(if not s.finished? then false - else pos[instance] >= hightide - fi), - read count = syncd - \(ready = hightide - pos[instance]; - if s.finished? and ready <= 0 - then mat.zeroSizeMatrix () - else - if count > ready then - more = s.read (count - ready); - cache := mat.concat (Horizontal ()) [cache, more]; - hightide := hightide + (mat.width more); - fi; - offset = pos[instance] - (lowtide ()); - chunk = mat.columnSlice cache offset (offset + count); - advance instance (mat.width chunk); - chunk; - fi), - close () = syncd - \(delete pos instance; - if empty? pos then - s.close () - fi), - } - done [1..copies]; - fi; - -zeroPaddedFreqFrames framesize channels = - // take a multi-channel stream, convert to a series of list of - // complex frequency-domain frames calculated from input padded to - // framesize*2, in which each frame contains all channels one - // after another (not interleaved) [because we lack a complex - // matrix type] - (forwardTransform = fft.realForward (framesize * 2); - do stream: - padded = - (map (mat.resizedTo { rows = channels, columns = framesize * 2 }) - (fr.frames { framesize, hop = framesize } stream)); - map do fr: concat (map forwardTransform (mat.asRows fr)) done padded; - done); - -doFastConvolve irframes sigframes = - (var history = []; - irlen = length irframes; - n = length (head irframes); - map do sigf: - history := take irlen (sigf::history); - fold do cc1 cc2: map2 cplx.add cc1 cc2 done - (list (cplx.zeros n)) - (map2 do irf histf: - map2 cplx.multiply irf histf; - done irframes history); - done sigframes); - -splitInto n fr = - (parts = splitAt n fr; - if empty? parts.snd then [parts.fst] - else parts.fst :: splitInto n parts.snd; - fi); - -fastConvolvedWith ir framesize s = - // prerequisite: ir and s have same number of channels - (framer = zeroPaddedFreqFrames framesize (mat.height ir); - ch = s.channels; - irfr = framer (syn.precalculated 1 ir); // rate arg is irrelevant here - sigfr = framer s; - inverseTransform = fft.realInverse (framesize * 2); - extended = sigfr ++ - map do _: list (cplx.zeros (ch * (framesize + 1))) done - [1..length irfr-1]; - cframes = doFastConvolve irfr extended; - rframes = (mat.zeroMatrix { rows = ch, columns = framesize * 2}) :: - map do fr: - mat.newMatrix (RowMajor ()) - (map inverseTransform (splitInto (framesize+1) fr)) - done cframes; - fr.streamOverlapping s.sampleRate - { framesize = framesize * 2, hop = framesize, window = win.boxcar } - rframes; -); - -plainConvolvedWith ir s = - // prerequisite: ir and s have same number of channels - (var history = mat.toRowMajor - (mat.zeroMatrix { rows = s.channels, columns = mat.width ir - 1 }); - s with - { - get finished? () = - s.finished? and (mat.empty? history), - get available () = - case s.available of - Known n: Known (n + mat.width history); - other: other; - esac, - read count = - (// Example: The IR is four samples long; we have three - // samples in history; two samples are available to read - // before the stream runs out. That means we can return - // up to five samples. Caller requests 6. - signal = s.read count; // -> two samples - siglen = mat.width signal; // -> 2 - histlen = mat.width history; // -> 3 - convlen = min count (siglen + histlen); // -> 5 - input = mat.resizedTo { rows = s.channels, columns = convlen } - signal; // example input now 5 samples, of which 2 are signal - output = array (map \(new double[convlen]) [1..s.channels]); - for [0..s.channels - 1] do ch: - for [0..mat.width input - 1] do i: - for [0..mat.width ir - 1] do j: - v = - if i >= j then - mat.at input ch (i - j) - else - ix = mat.width ir + i - j - 1; - if ix >= histlen then - 0 - else - mat.at history ch ix - fi - fi; - output[ch][i] := output[ch][i] + v * (mat.at ir ch j); - done; - done; - done; - // Remove from history a number of samples equal to the - // number returned; add to it a number equal to the number - // read from source - extended = mat.concat (Horizontal ()) [history, signal]; // -> 5 - newlen = (histlen + siglen) - convlen; // -> 0 - w = mat.width extended; - history := mat.columnSlice extended (w - newlen) w; - mat.newMatrix (RowMajor ()) (map vec.vector output)), - }); - -nextPowerOfTwo n = - (nextPowerOfTwo' p n = - if p >= n then p - else nextPowerOfTwo' (p * 2) n - fi; - nextPowerOfTwo' 1 n); - -//!!! todo: partial application so as to preprocess ir (in fast convolution case) -convolvedWith options ir s = //!!! cheap mono thing here -//!!! doc note: fast convolution output is padded to next frame boundary - (if mat.height ir != s.channels then - failWith "Signal stream and IR must have same number of channels (\(s.channels) != \(mat.height ir))" - fi; - var type = Fast (); - var framesize = nextPowerOfTwo (mat.width ir); - for options \case of - Fast s: if s then type := Fast () else type := Plain () fi; - Framesize n: framesize := nextPowerOfTwo n; - esac; - case type of - Fast (): - fastConvolvedWith ir framesize s; - Plain (): - plainConvolvedWith ir s; - esac); - -/** - Produce a sinc window of width equal to zc zero crossings per half, - with n samples from peak to first zero crossing, multiplied by a - Kaiser window with attenuation alpha dB - */ -kaiserSincWindow zc n alpha = - (sw = win.sinc (n*2) (n*zc*2 + 1); - kw = win.kaiserForAttenuation alpha (vec.length sw); - bf.multiply sw kw); - -/** - * Produce a sinc window with n samples from peak to first zero - * crossing, multiplied by a Kaiser window with attenuation alpha dB - * and transition bandwidth Hz at the given sampleRate. The filter - * will contain an odd number of samples. - */ -kaiserSincFilterFor { n, attenuation, bandwidth, samplerate } = - (pp = win.kaiserParameters (ByFrequency { attenuation, bandwidth, samplerate }); - length = if pp.length % 2 == 0 then pp.length + 1 else pp.length fi; - kw = win.kaiser (pp with { length }); - sw = win.sinc (n*2) length; - println "kaiserSincFilterFor: filter length is \(vec.length sw)"; - bf.multiply sw kw); - bandpassed f0 f1 attenuation bandwidth s = (rate = s.sampleRate; kw = win.kaiserForBandwidth attenuation bandwidth rate; @@ -469,10 +29,10 @@ fi; fi; filter = bf.multiply idealBandpass kw; - filtered = convolvedWith [Framesize 1024] + filtered = convolve.convolvedWith [Framesize 1024] (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels])) s; - delayedBy (- (int (filterLength / 2))) filtered); + manip.delayedBy (- (int (filterLength / 2))) filtered); lowpassed f attenuation bandwidth s = bandpassed 0 f attenuation bandwidth s; @@ -480,275 +40,7 @@ highpassed f attenuation bandwidth s = bandpassed f (s.sampleRate/2) attenuation bandwidth s; -spaced mult s = //!!! mult must be an integer [how to enforce this??] - (spaceToNext pos = case (pos % mult) of 0: 0; n: (mult - n) esac; - readWithoutPadding n = - (toRead = int Math#ceil(n / mult); - source = s.read toRead; - targetWidth = min n (mult * mat.width source); - mat.newMatrix (ColumnMajor ()) - (map do i: - if i % mult == 0 then - mat.getColumn (int (i / mult)) source - else - vec.zeros s.channels - fi - done [0..targetWidth-1])); - var pos = 0; - s with - { - get position () = pos, - get available () = - case s.available of - Known n: Known ((spaceToNext pos) + (n * mult)); - other: other - esac, - read n = - (sp = spaceToNext pos; - result = mat.toRowMajor - (mat.concat (Horizontal ()) - [mat.zeroMatrix { rows = s.channels, columns = min sp n }, - readWithoutPadding (max (n - sp) 0)]); - pos := pos + mat.width result; - result), - }); - -interpolated factor s = //!!! factor must be an integer [how to enforce this??] - if factor == 1 then s - else - nzc = 13; - attenuation = 80; - filter = kaiserSincWindow nzc factor attenuation; -// println "interpolated: filter length \(vec.length filter)"; - out = delayedBy (- (nzc * factor)) - (convolvedWith [Framesize 1024] - (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels])) - (spaced factor s)); - out with { get sampleRate () = s.sampleRate * factor }; - fi; - -picked frac s = //!!! frac must be an integer [how to enforce this??] - s with - { - get position () = int Math#ceil(s.position / frac), - get available () = - case s.available of - Known n: Known (int Math#ceil(n / frac)); - other: other - esac, - read n = - (m = s.read (n*frac); - obtained = int Math#ceil((mat.width m) / frac); - mat.flipped - (mat.newMatrix (ColumnMajor ()) - (map do c: mat.getColumn (c*frac) m done [0..obtained-1]))) - }; - -decimated factor s = //!!! factor must be an integer [how to enforce this??] - if factor == 1 then s - else - nzc = 13; - attenuation = 80; - filter = kaiserSincWindow nzc factor attenuation; - filtered = - (convolvedWith [Framesize 1024] - (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels])) s); - out = scaledBy (1/factor) - (delayedBy (- nzc) - (picked factor filtered)); - out with { get sampleRate () = s.sampleRate / factor }; - fi; - -gcd a b = (c = a % b; if c == 0 then b else gcd b c fi); - -//!!! slow -- can't in this form be used for e.g. 44100 -> 48000 conversion -resampledSlowlyTo rate s = - (g = gcd rate s.sampleRate; - println "orig = \(s.sampleRate), target = \(rate), g = \(g), up = \(rate/g), down = \(s.sampleRate/g)"; - decimated (s.sampleRate / g) (interpolated (rate/g) s)); - -resampledSplendidlyTo targetRate s = - ( - // We need a low-pass filter with a cutoff of the lower of the two - // (nyquist frequencies of the) sample rates. This filter needs to - // be sampled at a sufficient resolution that we can get values - // from it at every sample point in the source rate and in the - // target. - - sourceRate = s.sampleRate; - higher = max sourceRate targetRate; - lower = min sourceRate targetRate; - g = gcd higher lower; - - initialAvailable = s.available; // only so as to distinguish Known etc - var remaining = - case initialAvailable of - Known n: int ((n * targetRate) / sourceRate + 0.5); - other: 0; - esac; - - // Example: ratio of 3:4 in sample periods, corresponding to - // e.g. resampling from 48kHz to 36kHz. The lower rate has the - // longer sample period. We need a filter with n=4 values from - // peak to first pole -- this is the longer sample period relative - // to the shorter and is obtained by dividing the *higher* rate by - // the gcd (because the ratio of sample periods is the reciprocal - // of the ratio of sample rates). - - peakToPole = higher / g; // would be 4 in the above example - - println "for target rate \(targetRate) and source rate \(sourceRate), gcd = \(g) and peakToPole = \(peakToPole)"; - - // Our filter is a sinc function with peak-to-pole length - // peakToPole, multiplied by a Kaiser window of transition - // bandwidth narrow enough at the effective sample rate n to - // exclude... what? - - //!!! ponder - - filt = kaiserSincFilterFor { - n = peakToPole, - attenuation = 140, -// bandwidth = 10, - bandwidth = lower / 100, - samplerate = lower - }; - -/* - filt = kaiserSincFilterFor { - n = peakToPole, - attenuation = 80, - bandwidth = 1, - samplerate = higher - }; -*/ - // Now we have a filter of (odd) length flen in which the lower - // sample rate corresponds to every n'th point and the higher rate - // to every m'th where n and m are higher and lower rates divided - // by their gcd respectively. So if x coordinates are on the same - // scale as our filter resolution, then source sample i is at i * - // (targetRate / gcd) and target sample j is at j * (sourceRate / - // gcd). - - // To reconstruct a single target sample, we want a buffer (real - // or virtual) of flen values formed of source samples spaced at - // intervals of (targetRate / gcd), in our example case 3. This - // is initially formed with the first sample at the filter peak. - // - // 0 0 0 0 a 0 0 b 0 - // - // and of course we have our filter - // - // f1 f2 f3 f4 f5 f6 f7 f8 f9 - // - // We take the sum of products of non-zero values from this buffer - // with corresponding values in the filter - // - // a * f5 + b * f8 - // - // Then we drop (sourceRate / gcd) values, in our example case 4, - // from the start of the buffer and fill until it has flen values - // again - // - // a 0 0 b 0 0 c 0 0 - // - // repeat to reconstruct the next target sample - // - // a * f1 + b * f4 + c * f7 - // - // and so on. - - flen = vec.length filt; - halflen = int (flen/2); // actual filter length is halflen + halflen + 1 - spacedInput = spaced (targetRate / g) s; - - initialFill = spacedInput.read halflen; - - println "initialFill = \(initialFill) (requested \(halflen))"; - - var buffer = mat.toRowMajor - (mat.concat (Horizontal ()) - [mat.zeroMatrix { rows = s.channels, columns = halflen + 1 }, - initialFill]); - var pos = 0; - - expired? () = - case initialAvailable of - Known _: remaining <= 0; - _: mat.width buffer <= halflen; - esac; - - reconstructOne ch = - (var out = 0; - var series = mat.getRow ch buffer; //!!! check this has length flen - for [0..(vec.length series)-1] do i: - x = vec.at series i; - if x != 0 then - out := out + x * (vec.at filt i); - fi; - done; - out); - - readOne () = - if expired? () then - mat.zeroSizeMatrix () - else - result = mat.newColumnVector - (vec.fromList (map reconstructOne [0 .. s.channels-1])); -// println "result = \(result)"; -print "."; - m = sourceRate / g; - next = spacedInput.read m; - buffer := mat.concat (Horizontal ()) - [mat.columnSlice buffer m (flen - m), next]; -//println "dropped \(m), read \(mat.width next), buffer now \(mat.width buffer)"; - remaining := if remaining > 0 then remaining - 1 else 0 fi; - pos := pos + 1; -//println "returning \(result)"; - result - fi; - - s with - { - get sampleRate () = targetRate, - get position () = pos, - get available () = - case initialAvailable of - Known n: Known remaining; - other: other; - esac, - get finished? () = expired? (), - read n = - (result = - mat.toRowMajor - (mat.concat (Horizontal ()) (map do _: readOne () done [1..n])); - println "returning from read: \(result)"; - result) - }); - -//resampledTo = resampledSplendidlyTo; - -//interpolated factor s = resampledTo (s.sampleRate * factor) s; -//decimated factor s = resampledTo (s.sampleRate / factor) s; - -resampledTo = resampledSlowlyTo; - { - withDuration is number -> stream -> stream, - delayedBy is number -> stream -> stream, - scaledBy is number -> stream -> stream, - inverted is stream -> stream, - mixedTo is number -> stream -> stream, - sum is list<stream> -> stream = sum', - mean is list<stream> -> stream, - difference is stream -> stream -> stream, - multiplexed is list<stream> -> stream, - repeated is stream -> stream, - duplicated is number -> stream -> array<stream>, - convolvedWith is list<Fast boolean | Framesize number> -> matrix -> stream -> stream, - kaiserSincWindow, kaiserSincFilterFor, lowpassed, bandpassed, highpassed, - spaced, interpolated, decimated, picked, - resampledTo, }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/may/stream/manipulate.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -0,0 +1,353 @@ + +module may.stream.manipulate; + +mat = load may.matrix; +ch = load may.stream.channels; +vec = load may.vector; + +load may.stream.type; +load may.vector.type; +load may.matrix.type; + +//!!! todo: synchronized for everything with state assignment + +minDurationOf d1 d2 = + case d1 of + Known a: + case d2 of + Known b: Known (min a b); + Unknown (): Unknown (); + Infinite (): Known a; + esac; + Unknown (): + case d2 of + Known b: Known b; + Unknown (): Unknown (); + Infinite (): Unknown (); + esac; + Infinite (): + d2; + esac; + +withDuration nsamples s = //!!! should nsamples be a time in seconds? (no) + (var pos = 0; + s with + { + get position () = pos, + get available () = Known (nsamples - pos), + get finished? () = not (nsamples > pos), + read count = + (n = min count (nsamples - pos); + pos := pos + n; + if not s.finished? then + mat.resizedTo { rows = s.channels, columns = n } (s.read n); + else + mat.zeroMatrix { columns = n, rows = s.channels } + fi), + }); + +delayedBy nsamples s = //!!! doc: nsamples may be -ve + (var prepos = 0; + zeros n = mat.toRowMajor + (prepos := prepos + n; + mat.zeroMatrix { rows = s.channels, columns = n }); + delay = + if nsamples < 0 then + \0 (s.read (-nsamples)); + else + nsamples + fi; + { + get position () = + if prepos < delay then prepos + elif s.position + nsamples < 0 then 0 + else s.position + nsamples + fi, + get channels () = s.channels, + get sampleRate () = s.sampleRate, + get available () = + case s.available of + Known a: Known (a + delay - prepos); + other: other + esac, + get finished? () = (prepos >= delay) and s.finished?, + read count = + if prepos >= delay then s.read count + elif prepos + count < delay then zeros count + else + nleft = delay - prepos; + left = zeros nleft; + right = s.read (count - nleft); + mat.concat (Horizontal ()) [left, right]; + fi, + close = s.close + }); + +scaledBy factor s = + s with + { + read count = mat.scaled factor (s.read count); + }; + +inverted s = //!!! todo: test + s with + { + read count = + (m = s.read count; + if mat.empty? m then m + else mat.difference (mat.zeroMatrix (mat.size m)) m + fi) + }; + +//!!! poor name, confusion with mixed, but consistent with channels.yeti +mixedTo targetChannels s = + s with + { + get channels () = targetChannels, + read count = ch.mixedTo targetChannels (s.read count), + }; + +//!!! what should happen if we mix or multiplex a finite-length and an +//infinite-length stream? or even two streams with differing finite +//lengths? write tests for this. At the moment the resulting stream +//has duration equal to the shortest source stream and finishes as +//soon as any one of them finishes + +sum' streams = + (mix m1 m2 = + (sz = { rows = max (mat.height m1) (mat.height m2), + columns = min (mat.width m1) (mat.width m2) }; + if sz.columns == 0 then + mat.zeroSizeMatrix () + else + mat.sum (mat.resizedTo sz m1) (mat.resizedTo sz m2); + fi); + { + get position () = head (sort (map (.position) streams)), + get channels () = head (sortBy (>) (map (.channels) streams)), + get sampleRate () = (head streams).sampleRate, //!!! document this + get available () = + fold do dur s: minDurationOf dur s.available done (Infinite ()) streams, + get finished? () = any id (map (.finished?) streams), + read count = + (readTo acc ss count = + case ss of + first::rest: + part = first.read count; + case acc of + Some m: + readTo (Some (mix m part)) rest count; + None (): + readTo (Some part) rest count; + esac; + _: acc; + esac; + case readTo (None ()) streams count of + None (): mat.zeroSizeMatrix (); + Some m: m; + esac), + close () = for streams do s: s.close() done, + }); + +mean streams = //!!! todo: test + scaledBy (1 / (length streams)) (sum' streams); + +difference s1 s2 = //!!! todo: test + sum' [ s1, inverted s2 ]; + +multiplexed streams = + { + get position () = head (sort (map (.position) streams)), // can differ after EOS + get channels () = sum (map (.channels) streams), + get sampleRate () = (head streams).sampleRate, //!!! document this + get available () = + fold do dur s: minDurationOf dur s.available done (Infinite ()) streams, + get finished? () = any id (map (.finished?) streams), + read count = + (outs = map do s: s.read count done streams; + minlen = head (sort (map mat.width outs)); + outs = map do m: + mat.resizedTo { rows = mat.height m, columns = minlen } m + done outs; + mat.concat (Vertical ()) outs + ), + close () = for streams do s: s.close() done, + }; + +repeated s = + // There is no way to reset a stream (as in principle, they might + // be "live") so we can't read from the same stream repeatedly -- + // we have to cache its output and then repeat that. This is a + // little tricky to do efficiently without knowing how long the + // stream is (in general) or how much is going to be requested at + // a time. + if s.available == Infinite () then s + else + var pos = 0; + var cache = mat.zeroSizeMatrix (); + chunks = array []; + cachedPartsFor count = + (start = pos % (mat.width cache); + avail = (mat.width cache) - start; + if avail >= count then + pos := pos + count; + [mat.columnSlice cache start (start + count)] + else + pos := pos + avail; + mat.columnSlice cache start (start + avail) :: + cachedPartsFor (count - avail); + fi); + readFromCache count = + (if (mat.width cache) == 0 then + cache := mat.concat (Horizontal ()) (list chunks); + clearArray chunks; + fi; + if (mat.width cache) == 0 then + cache + else + mat.concat (Horizontal ()) (cachedPartsFor count); + fi); + s with + { + get position () = pos, + get available () = Infinite (), + get finished? () = false, + read count = + if s.finished? then + readFromCache count + else + part = s.read count; + len = (mat.width part); + push chunks part; + pos := pos + len; + if len == count then part + else + mat.concat (Horizontal ()) + [part, readFromCache (count - len)]; + fi; + fi, + } + fi; + +duplicated copies s = +//!!! doc fact that original s cannot be used independently of this afterward +// (so maybe name is misleading?) + array if copies < 2 then map \s [1..copies]; + else + pos = [:]; + lowtide () = head (sort (map (at pos) (keys pos))); + var hightide = 0; + var cache = mat.zeroSizeMatrix (); + syncd = synchronized pos; + advance i n = + (formerLow = lowtide (); + pos[i] := pos[i] + n; + encroachment = lowtide () - formerLow; + if encroachment > 0 then + cache := mat.columnSlice cache encroachment (mat.width cache); + fi); + map do instance: + pos[instance] := 0; + { + get position () = syncd \(pos[instance]), + get channels () = syncd \(s.channels), + get sampleRate () = syncd \(s.sampleRate), + get available () = syncd + \(case s.available of + Known av: Known (av + (hightide - pos[instance])); + other: other; + esac), + get finished? () = syncd + \(if not s.finished? then false + else pos[instance] >= hightide + fi), + read count = syncd + \(ready = hightide - pos[instance]; + if s.finished? and ready <= 0 + then mat.zeroSizeMatrix () + else + if count > ready then + more = s.read (count - ready); + cache := mat.concat (Horizontal ()) [cache, more]; + hightide := hightide + (mat.width more); + fi; + offset = pos[instance] - (lowtide ()); + chunk = mat.columnSlice cache offset (offset + count); + advance instance (mat.width chunk); + chunk; + fi), + close () = syncd + \(delete pos instance; + if empty? pos then + s.close () + fi), + } + done [1..copies]; + fi; + +spaced mult s = //!!! mult must be an integer [how to enforce this??] + (spaceToNext pos = case (pos % mult) of 0: 0; n: (mult - n) esac; + readWithoutPadding n = + (toRead = int Math#ceil(n / mult); + source = s.read toRead; + targetWidth = min n (mult * mat.width source); + mat.newMatrix (ColumnMajor ()) + (map do i: + if i % mult == 0 then + mat.getColumn (int (i / mult)) source + else + vec.zeros s.channels + fi + done [0..targetWidth-1])); + var pos = 0; + s with + { + get position () = pos, + get available () = + case s.available of + Known n: Known ((spaceToNext pos) + (n * mult)); + other: other + esac, + read n = + (sp = spaceToNext pos; + result = mat.toRowMajor + (mat.concat (Horizontal ()) + [mat.zeroMatrix { rows = s.channels, columns = min sp n }, + readWithoutPadding (max (n - sp) 0)]); + pos := pos + mat.width result; + result), + }); + +picked frac s = //!!! frac must be an integer [how to enforce this??] + s with + { + get position () = int Math#ceil(s.position / frac), + get available () = + case s.available of + Known n: Known (int Math#ceil(n / frac)); + other: other + esac, + read n = + (m = s.read (n*frac); + obtained = int Math#ceil((mat.width m) / frac); + mat.flipped + (mat.newMatrix (ColumnMajor ()) + (map do c: mat.getColumn (c*frac) m done [0..obtained-1]))) + }; + +{ + withDuration is number -> stream -> stream, + delayedBy is number -> stream -> stream, + scaledBy is number -> stream -> stream, + inverted is stream -> stream, + mixedTo is number -> stream -> stream, + sum is list<stream> -> stream = sum', + mean is list<stream> -> stream, + difference is stream -> stream -> stream, + multiplexed is list<stream> -> stream, + repeated is stream -> stream, + duplicated is number -> stream -> array<stream>, + spaced is number -> stream -> stream, + picked is number -> stream -> stream, +} +
--- a/src/may/stream/record.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/stream/record.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -2,8 +2,6 @@ module may.stream.record; vec = load may.vector; -mat = load may.matrix; -af = load may.stream.audiofile; ch = load may.stream.channels; import javax.sound.sampled:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/may/stream/resample.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -0,0 +1,250 @@ + +module may.stream.resample; + +mat = load may.matrix; +vec = load may.vector; +bf = load may.vector.blockfuncs; +win = load may.signal.window; +manip = load may.stream.manipulate; +convolve = load may.stream.convolve; + +load may.stream.type; +load may.vector.type; +load may.matrix.type; + +//!!! todo: synchronized for everything with state assignment + +/** + Produce a sinc window of width equal to zc zero crossings per half, + with n samples from peak to first zero crossing, multiplied by a + Kaiser window with attenuation alpha dB + */ +kaiserSincWindow zc n alpha = + (sw = win.sinc (n*2) (n*zc*2 + 1); + kw = win.kaiserForAttenuation alpha (vec.length sw); + bf.multiply sw kw); + +/** + * Produce a sinc window with n samples from peak to first zero + * crossing, multiplied by a Kaiser window with attenuation alpha dB + * and transition bandwidth Hz at the given sampleRate. The filter + * will contain an odd number of samples. + */ +kaiserSincFilterFor { n, attenuation, bandwidth, samplerate } = + (pp = win.kaiserParameters (ByFrequency { attenuation, bandwidth, samplerate }); + length = if pp.length % 2 == 0 then pp.length + 1 else pp.length fi; + kw = win.kaiser (pp with { length }); + sw = win.sinc (n*2) length; + println "kaiserSincFilterFor: filter length is \(vec.length sw)"; + bf.multiply sw kw); + + +interpolated factor s = //!!! factor must be an integer [how to enforce this??] + if factor == 1 then s + else + nzc = 13; + attenuation = 80; + filter = kaiserSincWindow nzc factor attenuation; +// println "interpolated: filter length \(vec.length filter)"; + out = manip.delayedBy (- (nzc * factor)) + (convolve.convolvedWith [Framesize 1024] + (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels])) + (manip.spaced factor s)); + out with { get sampleRate () = s.sampleRate * factor }; + fi; + +decimated factor s = //!!! factor must be an integer [how to enforce this??] + if factor == 1 then s + else + nzc = 13; + attenuation = 80; + filter = kaiserSincWindow nzc factor attenuation; + filtered = + (convolve.convolvedWith [Framesize 1024] + (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels])) s); + out = manip.scaledBy (1/factor) + (manip.delayedBy (- nzc) + (manip.picked factor filtered)); + out with { get sampleRate () = s.sampleRate / factor }; + fi; + +gcd a b = (c = a % b; if c == 0 then b else gcd b c fi); + +//!!! slow -- can't in this form be used for e.g. 44100 -> 48000 conversion +resampledSlowlyTo rate s = + (g = gcd rate s.sampleRate; + println "orig = \(s.sampleRate), target = \(rate), g = \(g), up = \(rate/g), down = \(s.sampleRate/g)"; + decimated (s.sampleRate / g) (interpolated (rate/g) s)); + +resampledSplendidlyTo targetRate s = + ( + // We need a low-pass filter with a cutoff of the lower of the two + // (nyquist frequencies of the) sample rates. This filter needs to + // be sampled at a sufficient resolution that we can get values + // from it at every sample point in the source rate and in the + // target. + + sourceRate = s.sampleRate; + higher = max sourceRate targetRate; + lower = min sourceRate targetRate; + g = gcd higher lower; + + initialAvailable = s.available; // only so as to distinguish Known etc + var remaining = + case initialAvailable of + Known n: int ((n * targetRate) / sourceRate + 0.5); + other: 0; + esac; + + // Example: ratio of 3:4 in sample periods, corresponding to + // e.g. resampling from 48kHz to 36kHz. The lower rate has the + // longer sample period. We need a filter with n=4 values from + // peak to first pole -- this is the longer sample period relative + // to the shorter and is obtained by dividing the *higher* rate by + // the gcd (because the ratio of sample periods is the reciprocal + // of the ratio of sample rates). + + peakToPole = higher / g; // would be 4 in the above example + + println "for target rate \(targetRate) and source rate \(sourceRate), gcd = \(g) and peakToPole = \(peakToPole)"; + + // Our filter is a sinc function with peak-to-pole length + // peakToPole, multiplied by a Kaiser window of transition + // bandwidth narrow enough at the effective sample rate n to + // exclude... what? + + //!!! ponder + + filt = kaiserSincFilterFor { + n = peakToPole, + attenuation = 140, +// bandwidth = 10, + bandwidth = lower / 100, + samplerate = lower + }; + +/* + filt = kaiserSincFilterFor { + n = peakToPole, + attenuation = 80, + bandwidth = 1, + samplerate = higher + }; +*/ + // Now we have a filter of (odd) length flen in which the lower + // sample rate corresponds to every n'th point and the higher rate + // to every m'th where n and m are higher and lower rates divided + // by their gcd respectively. So if x coordinates are on the same + // scale as our filter resolution, then source sample i is at i * + // (targetRate / gcd) and target sample j is at j * (sourceRate / + // gcd). + + // To reconstruct a single target sample, we want a buffer (real + // or virtual) of flen values formed of source samples spaced at + // intervals of (targetRate / gcd), in our example case 3. This + // is initially formed with the first sample at the filter peak. + // + // 0 0 0 0 a 0 0 b 0 + // + // and of course we have our filter + // + // f1 f2 f3 f4 f5 f6 f7 f8 f9 + // + // We take the sum of products of non-zero values from this buffer + // with corresponding values in the filter + // + // a * f5 + b * f8 + // + // Then we drop (sourceRate / gcd) values, in our example case 4, + // from the start of the buffer and fill until it has flen values + // again + // + // a 0 0 b 0 0 c 0 0 + // + // repeat to reconstruct the next target sample + // + // a * f1 + b * f4 + c * f7 + // + // and so on. + + flen = vec.length filt; + halflen = int (flen/2); // actual filter length is halflen + halflen + 1 + spacedInput = manip.spaced (targetRate / g) s; + + initialFill = spacedInput.read halflen; + + println "initialFill = \(initialFill) (requested \(halflen))"; + + var buffer = mat.toRowMajor + (mat.concat (Horizontal ()) + [mat.zeroMatrix { rows = s.channels, columns = halflen + 1 }, + initialFill]); + var pos = 0; + + expired? () = + case initialAvailable of + Known _: remaining <= 0; + _: mat.width buffer <= halflen; + esac; + + reconstructOne ch = + (var out = 0; + var series = mat.getRow ch buffer; //!!! check this has length flen + for [0..(vec.length series)-1] do i: + x = vec.at series i; + if x != 0 then + out := out + x * (vec.at filt i); + fi; + done; + out); + + readOne () = + if expired? () then + mat.zeroSizeMatrix () + else + result = mat.newColumnVector + (vec.fromList (map reconstructOne [0 .. s.channels-1])); +// println "result = \(result)"; +print "."; + m = sourceRate / g; + next = spacedInput.read m; + buffer := mat.concat (Horizontal ()) + [mat.columnSlice buffer m (flen - m), next]; +//println "dropped \(m), read \(mat.width next), buffer now \(mat.width buffer)"; + remaining := if remaining > 0 then remaining - 1 else 0 fi; + pos := pos + 1; +//println "returning \(result)"; + result + fi; + + s with + { + get sampleRate () = targetRate, + get position () = pos, + get available () = + case initialAvailable of + Known n: Known remaining; + other: other; + esac, + get finished? () = expired? (), + read n = + (result = + mat.toRowMajor + (mat.concat (Horizontal ()) (map do _: readOne () done [1..n])); + println "returning from read: \(result)"; + result) + }); + +//resampledTo = resampledSplendidlyTo; + +//interpolated factor s = resampledTo (s.sampleRate * factor) s; +//decimated factor s = resampledTo (s.sampleRate / factor) s; + +resampledTo = resampledSlowlyTo; + +{ + kaiserSincWindow, kaiserSincFilterFor, + interpolated, decimated, + resampledTo, +} +
--- a/src/may/stream/test/audiofile_reference.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/stream/test/audiofile_reference.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -2,7 +2,7 @@ module may.stream.test.audiofile_reference; syn = load may.stream.syntheticstream; -filt = load may.stream.filter; +manip = load may.stream.manipulate; //!!! docs from turbot @@ -25,8 +25,8 @@ syn.sinusoid rate 600 :: pulseChannel rate :: leftovers rate 2); afReference rate channels = - filt.withDuration (2 * rate) - (filt.multiplexed (take channels (referenceChannels rate))); + manip.withDuration (2 * rate) + (manip.multiplexed (take channels (referenceChannels rate))); { afReference
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/may/stream/test/test_convolve.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -0,0 +1,150 @@ + +module may.stream.test.test_convolve; + +vec = load may.vector; +mat = load may.matrix; +syn = load may.stream.syntheticstream; +manip = load may.stream.manipulate; +convolve = load may.stream.convolve; + +//pl = load may.plot;//!!! + +pl = { plot things = true; }; + +{ compare, compareUsing, assert } = load may.test.test; + +compareClose = compareUsing + do m1 m2: + length m1 == length m2 and + all id (map2 do v1 v2: + length v1 == length v2 and + all id (map2 do a b: abs(a - b) < 1e-10 done v1 v2) + done m1 m2); + done; + +convolutionOptions = [ + [ Fast false ], + [ Fast true ], + [ Fast true, Framesize 1 ], + [ Fast true, Framesize 2 ], + [ Fast true, Framesize 16 ], + [], +]; + +hasInExactOutputDuration opt = opt != [ Fast false ]; + +makeTests name withUnknown = + (maybeDuration n str = + // Truncate a stream, but if withUnknown is true, return it + // with availability Unknown -- so as to test that other + // filter functions behave correctly even if availability is + // not known on their underlying streams + (ts = manip.withDuration n str; + if withUnknown then + ts with + { + get available () = if ts.finished? then Known 0 else Unknown () fi; + } + else + ts + fi); + maybeKnown n = + if withUnknown then + Unknown () + else + Known n + fi; +[ + +"convolvedImpulse-\(name)": \( + all id + (map do opts: + ir = mat.newRowVector (vec.fromList [1,0,-1,0]); + signal = maybeDuration 4 + (syn.precalculatedMono 2 (vec.fromList [1,0,0,0])); + c = convolve.convolvedWith opts ir signal; + if compareClose (map vec.list (mat.asRows (c.read 7))) [[ 1,0,-1,0,0,0,0 ]] and + (hasInExactOutputDuration opts or assert c.finished? "c.finished?") + then c.close (); true; + else println " with convolution options: \(opts)"; false; + fi + done convolutionOptions); +), + +"convolvedImpulse2-\(name)": \( + all id + (map do opts: + ir = mat.newRowVector (vec.fromList [8,6,4,2]); + signal = maybeDuration 4 + (syn.precalculatedMono 2 (vec.fromList [1,0,0,0])); + c = convolve.convolvedWith opts ir signal; + if compareClose (map vec.list (mat.asRows (c.read 7))) [[ 8,6,4,2,0,0,0 ]] and + (hasInExactOutputDuration opts or assert c.finished? "c.finished?") + then c.close (); true + else println " with convolution options: \(opts)"; false; + fi + done convolutionOptions); +), + +"convolvedImpulse-multichannel-\(name)": \( + all id + (map do opts: + ir = mat.newMatrix (RowMajor ()) + (map vec.fromList [[0,0,0,1],[8,6,4,2],[1,0,-1,0]]); + signal = maybeDuration 4 + (syn.precalculated 2 + (mat.newMatrix (RowMajor ()) + (map vec.fromList [[1,1,0,0],[0,1,1,0],[0,0,1,1]]))); + c = convolve.convolvedWith opts ir signal; + if compareClose (map vec.list (mat.asRows (c.read 7))) + [[0,0,0,1,1,0,0],[0,8,14,10,6,2,0],[0,0,1,1,-1,-1,0]] and + (hasInExactOutputDuration opts or assert c.finished? "c.finished?") + then c.close (); true; + else println " with convolution options: \(opts)"; false; + fi + done convolutionOptions); +), + +"convolvedWith-\(name)": \( + all id + (map do opts: + fast = not (opts == [ Fast false ]); + ir = mat.newRowVector (vec.fromList [1,2,3,4,5]); + signal = maybeDuration 3 + (syn.precalculatedMono 2 (vec.fromList [10,20,30])); + c = convolve.convolvedWith opts ir signal; + if compare c.position 0 and + compare c.channels 1 and + compare c.sampleRate 2 and + (fast or compare c.available (maybeKnown 7)) and + compareClose (map vec.list (mat.asRows (c.read 3))) + [[ 10*1, + 20*1 + 10*2, + 30*1 + 20*2 + 10*3 ]] and + (fast or compare c.available (Known 4)) and + compare c.finished? false and + compareClose (map vec.list (mat.asRows (c.read 4))) + [[ 30*2 + 20*3 + 10*4, + 30*3 + 20*4 + 10*5, + 30*4 + 20*5, + 30*5 ]] and + (fast or (compare c.available (Known 0) and + compare c.finished? true)) + then c.close (); true; + else println " with convolution options: \(opts)"; false; + fi + done convolutionOptions); +), + +]); + +knowns = makeTests "known" false; +unknowns = makeTests "unknown" true; + +all = [:]; +for [ knowns, unknowns ] do h: + for (keys h) do k: all[k] := h[k] done +done; + +all is hash<string, () -> boolean>; +
--- a/src/may/stream/test/test_filter.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/stream/test/test_filter.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -2,7 +2,6 @@ module may.stream.test.test_filter; vec = load may.vector; -bf = load may.vector.blockfuncs; mat = load may.matrix; cplx = load may.complex; fft = load may.transform.fft; @@ -15,745 +14,6 @@ { compare, compareUsing, assert } = load may.test.test; -compareClose = compareUsing - do m1 m2: - length m1 == length m2 and - all id (map2 do v1 v2: - length v1 == length v2 and - all id (map2 do a b: abs(a - b) < 1e-10 done v1 v2) - done m1 m2); - done; - -convolutionOptions = [ - [ Fast false ], - [ Fast true ], - [ Fast true, Framesize 1 ], - [ Fast true, Framesize 2 ], - [ Fast true, Framesize 16 ], - [], -]; - -hasInExactOutputDuration opt = opt != [ Fast false ]; - -makeTests name withUnknown = - (maybeDuration n str = - // Truncate a stream, but if withUnknown is true, return it - // with availability Unknown -- so as to test that other - // filter functions behave correctly even if availability is - // not known on their underlying streams - (ts = filt.withDuration n str; - if withUnknown then - ts with - { - get available () = if ts.finished? then Known 0 else Unknown () fi; - } - else - ts - fi); - maybeKnown n = - if withUnknown then - Unknown () - else - Known n - fi; -[ - -"truncatedTo-\(name)": \( - // not using withDuration wrapper above, because we're actually - // testing filt.withDuration here rather than just generating a - // stream for use in another test - str = filt.withDuration 3 (syn.generated 2 id); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Known 3) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2 ] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"truncatedTo-b-\(name)": \( - // as above - str = filt.withDuration 3 (syn.generated 2 id); - compare str.position 0 and - compare (vec.list (mat.getRow 0 (str.read 2))) [ 0,1 ] and - compare str.position 2 and - compare str.available (Known 1) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 2))) [ 2 ] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"extendedTo-\(name)": \( - // not using withDuration wrapper above for the outer call, because - // we're actually testing filt.withDuration here rather than just - // generating a stream for use in another test. The inner call - // does use the wrapper. - str = filt.withDuration 5 (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Known 5) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2,0 ] and - compare str.position 4 and - compare str.available (Known 1) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 2))) [ 0 ] and - compare str.position 5 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"delayedBy-0-3-\(name)": \( - str = filt.delayedBy 0 (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2 ] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"delayedBy-0-inf-\(name)": \( - str = filt.delayedBy 0 (syn.generated 2 id); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Infinite ()) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2,3 ] and - compare str.position 4 and - compare str.available (Infinite ()) and - compare str.finished? false and - ( str.close (); true ) -), - -"delayedBy-2-3-\(name)": \( - str = filt.delayedBy 2 (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 5) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,0,0,1 ] and - compare str.position 4 and - compare str.available (maybeKnown 1) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 2 ] and - compare str.position 5 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"delayedBy-m2-3-\(name)": \( - str = filt.delayedBy (-2) (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 1) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 2 ] and - compare str.position 1 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"delayedBy-m4-3-\(name)": \( - str = filt.delayedBy (-4) (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Known 0) and - compare str.finished? true and - //!!! with this and others, need to check that we read an empty matrix after finished (perhaps have a helper function that checks finished properties such as available count as well) - ( str.close (); true ) -), - -"delayedBy-2-3b-\(name)": \( - str = filt.delayedBy 2 (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 5) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 1))) [ 0 ] and - compare str.position 1 and - compare str.available (maybeKnown 4) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,0,1,2 ] and - compare str.position 5 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"delayedBy-2-3c-\(name)": \( - str = filt.delayedBy 2 (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 5) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 7))) [ 0,0,0,1,2 ] and - compare str.position 5 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"delayedBy-2-inf-\(name)": \( - str = filt.delayedBy 2 (syn.generated 2 id); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Infinite ()) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 2))) [ 0,0 ] and - compare str.position 2 and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 2))) [ 0,1 ] and - compare str.position 4 and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 2))) [ 2,3 ] and - compare str.position 6 and - compare str.finished? false and - ( str.close (); true ) -), - -"delayedBy-m2-inf-\(name)": \( - str = filt.delayedBy (-2) (syn.generated 2 id); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Infinite ()) and - compare str.finished? false and - compare (vec.list (mat.getRow 0 (str.read 2))) [ 2,3 ] and - compare str.position 2 and - compare str.finished? false and - ( str.close (); true ) -), - -"mixedTo-1-2-\(name)": \( - str = filt.mixedTo 2 (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 2 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2],[0,1,2]] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"mixedTo-2-1-\(name)": \( - str = filt.mixedTo 1 - (filt.multiplexed - [maybeDuration 3 (syn.generated 2 id), - maybeDuration 3 (syn.generated 2 id)]); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2]] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"mixedTo-2-3-\(name)": \( - str = filt.mixedTo 3 - (filt.multiplexed - [maybeDuration 3 (syn.generated 2 id), - maybeDuration 3 (syn.generated 2 (+1))]); - compare str.position 0 and - compare str.channels 3 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2],[1,2,3],[0,0,0]] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"mixedTo-1-3-\(name)": \( - str = filt.mixedTo 3 (maybeDuration 3 (syn.generated 2 id)); - compare str.position 0 and - compare str.channels 3 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2],[0,1,2],[0,1,2]] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"sum-inf-inf-\(name)": \( - str = filt.sum [syn.generated 2 (2*), syn.generated 2 (0-)]; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Infinite ()) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2,3]] and - compare str.available (Infinite ()) and - compare str.position 4 and - ( str.close (); true ) -), - -"sum-inf-trunc-\(name)": \( - str = filt.sum [syn.generated 2 (2*), maybeDuration 3 (syn.generated 2 (0-))]; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 3 and - ( str.close (); true ) -), - -"sum-precalc-trunc-\(name)": \( - str = filt.sum - [syn.precalculatedMono 2 (vec.fromList [1,2]), - maybeDuration 3 (syn.generated 2 (0-))]; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 2) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[1,1]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 2 and - ( str.close (); true ) -), - -"sum-2-1-\(name)": \( - str = filt.sum - [syn.precalculatedMono 2 (vec.fromList [1,2]), - filt.multiplexed [syn.precalculatedMono 2 (vec.fromList [3,4]), - maybeDuration 3 (syn.generated 2 (0-))]]; - compare str.position 0 and - compare str.channels 2 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 2) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[4,6], [0,-1]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 2 and - ( str.close (); true ) -), - -"sum-3-\(name)": \( - str = filt.sum - [syn.precalculatedMono 2 (vec.fromList [1,2]), - syn.precalculatedMono 2 (vec.fromList [3,4]), - maybeDuration 3 (syn.generated 2 (0-))]; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 2) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[4,5]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 2 and - ( str.close (); true ) -), - -"multiplexed-inf-inf-\(name)": \( - str = filt.multiplexed [syn.generated 2 id, syn.generated 2 (0-)]; - compare str.position 0 and - compare str.channels 2 and - compare str.sampleRate 2 and - compare str.available (Infinite ()) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) - [[0,1,2,3], [0,-1,-2,-3]] and - compare str.available (Infinite ()) and - compare str.position 4 and - ( str.close (); true ) -), - -"multiplexed-inf-trunc-\(name)": \( - str = filt.multiplexed [syn.generated 2 id, maybeDuration 3 (syn.generated 2 (0-))]; - compare str.position 0 and - compare str.channels 2 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2], [0,-1,-2]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 3 and - ( str.close (); true ) -), - -"multiplexed-precalc-trunc-\(name)": \( - str = filt.multiplexed - [syn.precalculatedMono 2 (vec.fromList [1,2]), - maybeDuration 3 (syn.generated 2 (0-))]; - compare str.position 0 and - compare str.channels 2 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 2) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[1,2], [0,-1]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 2 and - ( str.close (); true ) -), - -"multiplexed-2-1-\(name)": \( - str = filt.multiplexed - [syn.precalculatedMono 2 (vec.fromList [1,2]), - filt.multiplexed [syn.precalculatedMono 2 (vec.fromList [3,4]), - maybeDuration 3 (syn.generated 2 (0-))]]; - compare str.position 0 and - compare str.channels 3 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 2) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[1,2], [3,4], [0,-1]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 2 and - ( str.close (); true ) -), - -"multiplexed-2-1b-\(name)": \( - str = filt.multiplexed - [syn.precalculatedMono 2 (vec.fromList [1,2]), - syn.precalculatedMono 2 (vec.fromList [3,4]), - maybeDuration 3 (syn.generated 2 (0-))]; - compare str.position 0 and - compare str.channels 3 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 2) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[1,2], [3,4], [0,-1]] and - compare str.available (Known 0) and - compare str.finished? true and - compare str.position 2 and - ( str.close (); true ) -), - -"repeated-2-\(name)": \( - str = filt.repeated - (syn.precalculatedMono 2 (vec.fromList [1,2,3])); - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (Infinite ()) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 1))) [[1]] and - compare str.position 1 and - compare (map vec.list (mat.asRows (str.read 2))) [[2,3]] and - compare str.position 3 and - compare (map vec.list (mat.asRows (str.read 3))) [[1,2,3]] and - compare str.position 6 and - compare (map vec.list (mat.asRows (str.read 5))) [[1,2,3,1,2]] and - compare (map vec.list (mat.asRows (str.read 9))) [[3,1,2,3,1,2,3,1,2]] and - compare (map vec.list (mat.asRows (str.read 2))) [[3,1]] and - compare str.available (Infinite ()) and - compare str.finished? false and - compare str.position 22 and - ( str.close (); true ) -), - -"duplicated-1-\(name)": \( - original = maybeDuration 3 (syn.precalculatedMono 2 (vec.fromList [1,2,3])); - sn = filt.duplicated 1 original; - str = (head sn); - compare (length sn) 1 and - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 1))) [[1]] and - compare str.position 1 and - compare str.available (maybeKnown 2) and - compare (map vec.list (mat.asRows (str.read 3))) [[2,3]] and - compare str.position 3 and - compare str.finished? true and - compare str.available (Known 0) and - ( str.close (); true ) -), - -"duplicated-2-\(name)": \( - original = maybeDuration 3 (syn.precalculatedMono 2 (vec.fromList [1,2,3])); - sn = filt.duplicated 2 original; - s1 = (head sn); - s2 = (head (tail sn)); - - compare (length sn) 2 and - - compare s1.position 0 and - compare s1.channels 1 and - compare s1.sampleRate 2 and - compare s1.available (maybeKnown 3) and - compare s1.finished? false and - - compare s2.position 0 and - compare s2.channels 1 and - compare s2.sampleRate 2 and - compare s2.available (maybeKnown 3) and - compare s2.finished? false and - - compare (map vec.list (mat.asRows (s1.read 1))) [[1]] and - compare s1.position 1 and - compare s1.available (maybeKnown 2) and - compare s2.position 0 and - compare s2.available (maybeKnown 3) and - - compare (map vec.list (mat.asRows (s2.read 2))) [[1,2]] and - compare s1.position 1 and - compare s1.available (maybeKnown 2) and - compare s2.position 2 and - compare s2.available (maybeKnown 1) and - - compare (map vec.list (mat.asRows (s1.read 3))) [[2,3]] and - compare s1.position 3 and - compare s1.finished? true and - compare s1.available (Known 0) and - compare s2.position 2 and - compare s2.finished? false and - compare s2.available (Known 1) and // when one is known, so is the other - - compare (map vec.list (mat.asRows (s1.read 3))) [] and - - compare (map vec.list (mat.asRows (s2.read 1))) [[3]] and - compare s1.position 3 and - compare s1.finished? true and - compare s2.position 3 and - compare s2.finished? true and - - ( s1.close (); s2.close() ; true ) -), - -"convolvedImpulse-\(name)": \( - all id - (map do opts: - ir = mat.newRowVector (vec.fromList [1,0,-1,0]); - signal = maybeDuration 4 - (syn.precalculatedMono 2 (vec.fromList [1,0,0,0])); - c = filt.convolvedWith opts ir signal; - if compareClose (map vec.list (mat.asRows (c.read 7))) [[ 1,0,-1,0,0,0,0 ]] and - (hasInExactOutputDuration opts or assert c.finished? "c.finished?") - then c.close (); true; - else println " with convolution options: \(opts)"; false; - fi - done convolutionOptions); -), - -"convolvedImpulse2-\(name)": \( - all id - (map do opts: - ir = mat.newRowVector (vec.fromList [8,6,4,2]); - signal = maybeDuration 4 - (syn.precalculatedMono 2 (vec.fromList [1,0,0,0])); - c = filt.convolvedWith opts ir signal; - if compareClose (map vec.list (mat.asRows (c.read 7))) [[ 8,6,4,2,0,0,0 ]] and - (hasInExactOutputDuration opts or assert c.finished? "c.finished?") - then c.close (); true - else println " with convolution options: \(opts)"; false; - fi - done convolutionOptions); -), - -"convolvedImpulse-multichannel-\(name)": \( - all id - (map do opts: - ir = mat.newMatrix (RowMajor ()) - (map vec.fromList [[0,0,0,1],[8,6,4,2],[1,0,-1,0]]); - signal = maybeDuration 4 - (syn.precalculated 2 - (mat.newMatrix (RowMajor ()) - (map vec.fromList [[1,1,0,0],[0,1,1,0],[0,0,1,1]]))); - c = filt.convolvedWith opts ir signal; - if compareClose (map vec.list (mat.asRows (c.read 7))) - [[0,0,0,1,1,0,0],[0,8,14,10,6,2,0],[0,0,1,1,-1,-1,0]] and - (hasInExactOutputDuration opts or assert c.finished? "c.finished?") - then c.close (); true; - else println " with convolution options: \(opts)"; false; - fi - done convolutionOptions); -), - -"convolvedWith-\(name)": \( - all id - (map do opts: - fast = not (opts == [ Fast false ]); - ir = mat.newRowVector (vec.fromList [1,2,3,4,5]); - signal = maybeDuration 3 - (syn.precalculatedMono 2 (vec.fromList [10,20,30])); - c = filt.convolvedWith opts ir signal; - if compare c.position 0 and - compare c.channels 1 and - compare c.sampleRate 2 and - (fast or compare c.available (maybeKnown 7)) and - compareClose (map vec.list (mat.asRows (c.read 3))) - [[ 10*1, - 20*1 + 10*2, - 30*1 + 20*2 + 10*3 ]] and - (fast or compare c.available (Known 4)) and - compare c.finished? false and - compareClose (map vec.list (mat.asRows (c.read 4))) - [[ 30*2 + 20*3 + 10*4, - 30*3 + 20*4 + 10*5, - 30*4 + 20*5, - 30*5 ]] and - (fast or (compare c.available (Known 0) and - compare c.finished? true)) - then c.close (); true; - else println " with convolution options: \(opts)"; false; - fi - done convolutionOptions); -), - -"spaced-\(name)": \( - original = maybeDuration 3 (syn.precalculatedMono 2 (vec.fromList [1,2,3])); - str = filt.spaced 3 original; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 9) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 4))) [[1,0,0,2]] and - compare str.position 4 and - compare str.available (maybeKnown 5) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 1))) [[0]] and - compare str.position 5 and - compare str.available (maybeKnown 4) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 10))) [[0,3,0,0]] and - compare str.position 9 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -"picked-\(name)": \( - original = maybeDuration 8 (syn.precalculatedMono 2 (vec.fromList [1,2,3,4,5,6,7,8])); - str = filt.picked 3 original; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 1))) [[1]] and - compare str.position 1 and - compare str.available (maybeKnown 2) and - compare str.finished? false and - compare (map vec.list (mat.asRows (str.read 3))) [[4,7]] and - compare str.position 3 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -// Test for duration of decimated stream (does not test contents, that -// happens in the filters tests below) -"dec-duration-\(name)": \( - original = maybeDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); - str = filt.decimated 2 original; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 2 and - compare str.available (maybeKnown 4) and - compare str.finished? false and - (r = str.read 3; - println "r = \(r)"; - compare (mat.size r) { rows = 1, columns = 3 }) and - compare str.position 3 and - compare str.available (maybeKnown 1) and - compare str.finished? false and - (r = str.read 3; - println "r = \(r)"; - compare (mat.size r) { rows = 1, columns = 1 }) and - compare str.position 4 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -// Test for duration of interpolated stream (does not test contents, -// that happens in the filters tests below) -"int-duration-\(name)": \( - original = maybeDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); - str = filt.interpolated 2 original; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 8 and - compare str.available (maybeKnown 16) and - compare str.finished? false and - compare (mat.size (str.read 12)) { rows = 1, columns = 12 } and - compare str.position 12 and - compare str.available (maybeKnown 1) and - compare str.finished? false and - compare (mat.size (str.read 12)) { rows = 1, columns = 4 } and - compare str.position 16 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -// Test for duration of resampled stream (does not test contents, -// that happens in the filters tests below) -"resamp-duration-\(name)": \( - original = maybeDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); - str = filt.resampledTo 6 original; - compare str.position 0 and - compare str.channels 1 and - compare str.sampleRate 6 and - compare str.available (maybeKnown 12) and - compare str.finished? false and - compare (mat.size (str.read 9)) { rows = 1, columns = 9 } and - compare str.position 9 and - compare str.available (maybeKnown 3) and - compare str.finished? false and - compare (mat.size (str.read 9)) { rows = 1, columns = 3 } and - compare str.position 12 and - compare str.available (Known 0) and - compare str.finished? true and - ( str.close (); true ) -), - -//!!! still no tests for filters with multi-channel inputs - -]); - -knowns = makeTests "known" false; -unknowns = makeTests "unknown" true; - logSpectrumFrom output n = (outdata = mat.getRow 0 (output.read n); spectrum = cplx.magnitudes (fft.realForward n outdata); @@ -762,88 +22,8 @@ makeDiracStream rate n = syn.generated rate do x: if x == int(n/2) then 1 else 0 fi done; -filtering = [ -/*!!! -"interpolated-sine": \( - // Interpolating a sinusoid should give us a sinusoid - //!!! only beyond half the filter length - sinusoid = syn.sinusoid 8 2; // 2Hz sine sampled at 8Hz: [ 0, 1, 0, -1 ] etc - input = filt.withDuration 16 sinusoid; - output = filt.interpolated 2 input; - result = output.read 32; - reference = syn.sinusoid 16 2; - expected = reference.read 32; - compareOutputs a b = compareClose - (map vec.list (mat.asRows a)) (map vec.list (mat.asRows b)); - compareOutputs result expected; -), +[ -"decimated-sine": \( - // Decimating a sinusoid should give us a sinusoid - //!!! only beyond half the filter length - sinusoid = syn.sinusoid 32 1; // 1Hz sine sampled at 32Hz - input = filt.withDuration 400 sinusoid; - output = filt.decimated 2 input; - result = mat.columnSlice (output.read 200) 50 150; - reference = syn.sinusoid 16 1; - expected = mat.columnSlice (reference.read 200) 50 150; - compareOutputs a b = compareClose - (map vec.list (mat.asRows a)) (map vec.list (mat.asRows b)); - if not compareOutputs result expected then - println "diff: \(mat.difference result expected)"; - false - else true fi -), -*/ -"interpolated-misc": \( - // Interpolating any signal by N should give a signal in which - // every Nth sample is the original signal - data = vec.fromList [ 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 ]; - data = vec.concat [ data, bf.scaled (5/4) data, bf.scaled (3/4) data, data ]; - data = vec.concat [ data, data ]; - input = filt.withDuration (vec.length data) (syn.precalculatedMono 4 data); - factor = 3; - up = filt.interpolated factor input; - result = mat.getRow 0 (up.read (factor * vec.length data)); - phase = 0; - a = vec.list data; - b = map do i: vec.at result (i*factor + phase) done [0..vec.length data - 1]; -\() ( pl.plot [ Vector data, Vector (vec.fromList b) ] ); -\() ( pl.plot [ Vector result ] ); -println "a = \(a)"; -println "b = \(b)"; - compareClose [b] [a]; -), -/*!!! -"int-dec": \( - // Interpolating any signal then decimating by the same factor - // should get us the original back again - //!!! no, this is phase dependent -// data = vec.fromList [ 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 ]; -// data = vec.concat [ data, bf.scaled (5/4) data, bf.scaled (3/4) data, data ]; - data = vec.fromList [ 0, 1, 2, 3 ]; - data = vec.concat [ data, data ]; - - factor = 3; - - updown prepad = - (input = filt.withDuration (vec.length data) (syn.precalculatedMono 4 data); - intermediate = filt.interpolated factor input; - output = filt.decimated factor (filt.delayedBy prepad intermediate); - output.read (vec.length data)); - - result = updown 0; - if not compareClose [vec.list (mat.getRow 0 result)] [vec.list data] then - \() (pl.plot [Vector data, - Vector (mat.getRow 0 result), - Vector (mat.getRow 0 (updown 1)), - Vector (mat.getRow 0 (updown 2))]); - up = (filt.interpolated 2 (syn.precalculatedMono 4 data)).read 80; - \() (pl.plot [Vector (mat.getRow 0 up)]); - false - else true fi; -), -*/ "lowpassed-dirac": \( test { rate, cutoff, attenuation, bandwidth, n } = (input = makeDiracStream rate n; @@ -909,12 +89,5 @@ ]); ), -]; +] is hash<string, () -> boolean>; -all = [:]; -for [ knowns, unknowns, filtering ] do h: - for (keys h) do k: all[k] := h[k] done -done; - -all is hash<string, () -> boolean>; -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/may/stream/test/test_manipulate.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -0,0 +1,589 @@ + +module may.stream.test.test_manipulate; + +vec = load may.vector; +mat = load may.matrix; +syn = load may.stream.syntheticstream; +manip = load may.stream.manipulate; + +{ compare, compareUsing, assert } = load may.test.test; + +makeTests name withUnknown = + (maybeDuration n str = + // Truncate a stream, but if withUnknown is true, return it + // with availability Unknown -- so as to test that other + // filter functions behave correctly even if availability is + // not known on their underlying streams + (ts = manip.withDuration n str; + if withUnknown then + ts with + { + get available () = if ts.finished? then Known 0 else Unknown () fi; + } + else + ts + fi); + maybeKnown n = + if withUnknown then + Unknown () + else + Known n + fi; +[ + +"truncatedTo-\(name)": \( + // not using withDuration wrapper above, because we're actually + // testing manip.withDuration here rather than just generating a + // stream for use in another test + str = manip.withDuration 3 (syn.generated 2 id); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Known 3) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2 ] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"truncatedTo-b-\(name)": \( + // as above + str = manip.withDuration 3 (syn.generated 2 id); + compare str.position 0 and + compare (vec.list (mat.getRow 0 (str.read 2))) [ 0,1 ] and + compare str.position 2 and + compare str.available (Known 1) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 2))) [ 2 ] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"extendedTo-\(name)": \( + // not using withDuration wrapper above for the outer call, because + // we're actually testing manip.withDuration here rather than just + // generating a stream for use in another test. The inner call + // does use the wrapper. + str = manip.withDuration 5 (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Known 5) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2,0 ] and + compare str.position 4 and + compare str.available (Known 1) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 2))) [ 0 ] and + compare str.position 5 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"delayedBy-0-3-\(name)": \( + str = manip.delayedBy 0 (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2 ] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"delayedBy-0-inf-\(name)": \( + str = manip.delayedBy 0 (syn.generated 2 id); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Infinite ()) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,1,2,3 ] and + compare str.position 4 and + compare str.available (Infinite ()) and + compare str.finished? false and + ( str.close (); true ) +), + +"delayedBy-2-3-\(name)": \( + str = manip.delayedBy 2 (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 5) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,0,0,1 ] and + compare str.position 4 and + compare str.available (maybeKnown 1) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 2 ] and + compare str.position 5 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"delayedBy-m2-3-\(name)": \( + str = manip.delayedBy (-2) (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 1) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 2 ] and + compare str.position 1 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"delayedBy-m4-3-\(name)": \( + str = manip.delayedBy (-4) (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Known 0) and + compare str.finished? true and + //!!! with this and others, need to check that we read an empty matrix after finished (perhaps have a helper function that checks finished properties such as available count as well) + ( str.close (); true ) +), + +"delayedBy-2-3b-\(name)": \( + str = manip.delayedBy 2 (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 5) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 1))) [ 0 ] and + compare str.position 1 and + compare str.available (maybeKnown 4) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 4))) [ 0,0,1,2 ] and + compare str.position 5 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"delayedBy-2-3c-\(name)": \( + str = manip.delayedBy 2 (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 5) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 7))) [ 0,0,0,1,2 ] and + compare str.position 5 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"delayedBy-2-inf-\(name)": \( + str = manip.delayedBy 2 (syn.generated 2 id); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Infinite ()) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 2))) [ 0,0 ] and + compare str.position 2 and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 2))) [ 0,1 ] and + compare str.position 4 and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 2))) [ 2,3 ] and + compare str.position 6 and + compare str.finished? false and + ( str.close (); true ) +), + +"delayedBy-m2-inf-\(name)": \( + str = manip.delayedBy (-2) (syn.generated 2 id); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Infinite ()) and + compare str.finished? false and + compare (vec.list (mat.getRow 0 (str.read 2))) [ 2,3 ] and + compare str.position 2 and + compare str.finished? false and + ( str.close (); true ) +), + +"mixedTo-1-2-\(name)": \( + str = manip.mixedTo 2 (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 2 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2],[0,1,2]] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"mixedTo-2-1-\(name)": \( + str = manip.mixedTo 1 + (manip.multiplexed + [maybeDuration 3 (syn.generated 2 id), + maybeDuration 3 (syn.generated 2 id)]); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2]] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"mixedTo-2-3-\(name)": \( + str = manip.mixedTo 3 + (manip.multiplexed + [maybeDuration 3 (syn.generated 2 id), + maybeDuration 3 (syn.generated 2 (+1))]); + compare str.position 0 and + compare str.channels 3 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2],[1,2,3],[0,0,0]] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"mixedTo-1-3-\(name)": \( + str = manip.mixedTo 3 (maybeDuration 3 (syn.generated 2 id)); + compare str.position 0 and + compare str.channels 3 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2],[0,1,2],[0,1,2]] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"sum-inf-inf-\(name)": \( + str = manip.sum [syn.generated 2 (2*), syn.generated 2 (0-)]; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Infinite ()) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2,3]] and + compare str.available (Infinite ()) and + compare str.position 4 and + ( str.close (); true ) +), + +"sum-inf-trunc-\(name)": \( + str = manip.sum [syn.generated 2 (2*), maybeDuration 3 (syn.generated 2 (0-))]; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 3 and + ( str.close (); true ) +), + +"sum-precalc-trunc-\(name)": \( + str = manip.sum + [syn.precalculatedMono 2 (vec.fromList [1,2]), + maybeDuration 3 (syn.generated 2 (0-))]; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 2) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[1,1]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 2 and + ( str.close (); true ) +), + +"sum-2-1-\(name)": \( + str = manip.sum + [syn.precalculatedMono 2 (vec.fromList [1,2]), + manip.multiplexed [syn.precalculatedMono 2 (vec.fromList [3,4]), + maybeDuration 3 (syn.generated 2 (0-))]]; + compare str.position 0 and + compare str.channels 2 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 2) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[4,6], [0,-1]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 2 and + ( str.close (); true ) +), + +"sum-3-\(name)": \( + str = manip.sum + [syn.precalculatedMono 2 (vec.fromList [1,2]), + syn.precalculatedMono 2 (vec.fromList [3,4]), + maybeDuration 3 (syn.generated 2 (0-))]; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 2) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[4,5]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 2 and + ( str.close (); true ) +), + +"multiplexed-inf-inf-\(name)": \( + str = manip.multiplexed [syn.generated 2 id, syn.generated 2 (0-)]; + compare str.position 0 and + compare str.channels 2 and + compare str.sampleRate 2 and + compare str.available (Infinite ()) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) + [[0,1,2,3], [0,-1,-2,-3]] and + compare str.available (Infinite ()) and + compare str.position 4 and + ( str.close (); true ) +), + +"multiplexed-inf-trunc-\(name)": \( + str = manip.multiplexed [syn.generated 2 id, maybeDuration 3 (syn.generated 2 (0-))]; + compare str.position 0 and + compare str.channels 2 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[0,1,2], [0,-1,-2]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 3 and + ( str.close (); true ) +), + +"multiplexed-precalc-trunc-\(name)": \( + str = manip.multiplexed + [syn.precalculatedMono 2 (vec.fromList [1,2]), + maybeDuration 3 (syn.generated 2 (0-))]; + compare str.position 0 and + compare str.channels 2 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 2) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[1,2], [0,-1]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 2 and + ( str.close (); true ) +), + +"multiplexed-2-1-\(name)": \( + str = manip.multiplexed + [syn.precalculatedMono 2 (vec.fromList [1,2]), + manip.multiplexed [syn.precalculatedMono 2 (vec.fromList [3,4]), + maybeDuration 3 (syn.generated 2 (0-))]]; + compare str.position 0 and + compare str.channels 3 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 2) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[1,2], [3,4], [0,-1]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 2 and + ( str.close (); true ) +), + +"multiplexed-2-1b-\(name)": \( + str = manip.multiplexed + [syn.precalculatedMono 2 (vec.fromList [1,2]), + syn.precalculatedMono 2 (vec.fromList [3,4]), + maybeDuration 3 (syn.generated 2 (0-))]; + compare str.position 0 and + compare str.channels 3 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 2) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[1,2], [3,4], [0,-1]] and + compare str.available (Known 0) and + compare str.finished? true and + compare str.position 2 and + ( str.close (); true ) +), + +"repeated-2-\(name)": \( + str = manip.repeated + (syn.precalculatedMono 2 (vec.fromList [1,2,3])); + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (Infinite ()) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 1))) [[1]] and + compare str.position 1 and + compare (map vec.list (mat.asRows (str.read 2))) [[2,3]] and + compare str.position 3 and + compare (map vec.list (mat.asRows (str.read 3))) [[1,2,3]] and + compare str.position 6 and + compare (map vec.list (mat.asRows (str.read 5))) [[1,2,3,1,2]] and + compare (map vec.list (mat.asRows (str.read 9))) [[3,1,2,3,1,2,3,1,2]] and + compare (map vec.list (mat.asRows (str.read 2))) [[3,1]] and + compare str.available (Infinite ()) and + compare str.finished? false and + compare str.position 22 and + ( str.close (); true ) +), + +"duplicated-1-\(name)": \( + original = maybeDuration 3 (syn.precalculatedMono 2 (vec.fromList [1,2,3])); + sn = manip.duplicated 1 original; + str = (head sn); + compare (length sn) 1 and + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 1))) [[1]] and + compare str.position 1 and + compare str.available (maybeKnown 2) and + compare (map vec.list (mat.asRows (str.read 3))) [[2,3]] and + compare str.position 3 and + compare str.finished? true and + compare str.available (Known 0) and + ( str.close (); true ) +), + +"duplicated-2-\(name)": \( + original = maybeDuration 3 (syn.precalculatedMono 2 (vec.fromList [1,2,3])); + sn = manip.duplicated 2 original; + s1 = (head sn); + s2 = (head (tail sn)); + + compare (length sn) 2 and + + compare s1.position 0 and + compare s1.channels 1 and + compare s1.sampleRate 2 and + compare s1.available (maybeKnown 3) and + compare s1.finished? false and + + compare s2.position 0 and + compare s2.channels 1 and + compare s2.sampleRate 2 and + compare s2.available (maybeKnown 3) and + compare s2.finished? false and + + compare (map vec.list (mat.asRows (s1.read 1))) [[1]] and + compare s1.position 1 and + compare s1.available (maybeKnown 2) and + compare s2.position 0 and + compare s2.available (maybeKnown 3) and + + compare (map vec.list (mat.asRows (s2.read 2))) [[1,2]] and + compare s1.position 1 and + compare s1.available (maybeKnown 2) and + compare s2.position 2 and + compare s2.available (maybeKnown 1) and + + compare (map vec.list (mat.asRows (s1.read 3))) [[2,3]] and + compare s1.position 3 and + compare s1.finished? true and + compare s1.available (Known 0) and + compare s2.position 2 and + compare s2.finished? false and + compare s2.available (Known 1) and // when one is known, so is the other + + compare (map vec.list (mat.asRows (s1.read 3))) [] and + + compare (map vec.list (mat.asRows (s2.read 1))) [[3]] and + compare s1.position 3 and + compare s1.finished? true and + compare s2.position 3 and + compare s2.finished? true and + + ( s1.close (); s2.close() ; true ) +), + +"spaced-\(name)": \( + original = maybeDuration 3 (syn.precalculatedMono 2 (vec.fromList [1,2,3])); + str = manip.spaced 3 original; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 9) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 4))) [[1,0,0,2]] and + compare str.position 4 and + compare str.available (maybeKnown 5) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 1))) [[0]] and + compare str.position 5 and + compare str.available (maybeKnown 4) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 10))) [[0,3,0,0]] and + compare str.position 9 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +"picked-\(name)": \( + original = maybeDuration 8 (syn.precalculatedMono 2 (vec.fromList [1,2,3,4,5,6,7,8])); + str = manip.picked 3 original; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 1))) [[1]] and + compare str.position 1 and + compare str.available (maybeKnown 2) and + compare str.finished? false and + compare (map vec.list (mat.asRows (str.read 3))) [[4,7]] and + compare str.position 3 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +//!!! still no tests for multi-channel inputs + +]); + +knowns = makeTests "known" false; +unknowns = makeTests "unknown" true; + +all = [:]; +for [ knowns, unknowns ] do h: + for (keys h) do k: all[k] := h[k] done +done; + +all is hash<string, () -> boolean>; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/may/stream/test/test_resample.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -0,0 +1,212 @@ + +module may.stream.test.test_resample; + +vec = load may.vector; +bf = load may.vector.blockfuncs; +mat = load may.matrix; +syn = load may.stream.syntheticstream; +manip = load may.stream.manipulate; +resample = load may.stream.resample; + +//pl = load may.plot;//!!! + +pl = { plot things = true; }; + +{ compare, compareUsing, assert } = load may.test.test; + +compareClose = compareUsing + do m1 m2: + length m1 == length m2 and + all id (map2 do v1 v2: + length v1 == length v2 and + all id (map2 do a b: abs(a - b) < 1e-10 done v1 v2) + done m1 m2); + done; + +makeTests name withUnknown = + (maybeDuration n str = + // Truncate a stream, but if withUnknown is true, return it + // with availability Unknown -- so as to test that other + // filter functions behave correctly even if availability is + // not known on their underlying streams + (ts = manip.withDuration n str; + if withUnknown then + ts with + { + get available () = if ts.finished? then Known 0 else Unknown () fi; + } + else + ts + fi); + maybeKnown n = + if withUnknown then + Unknown () + else + Known n + fi; +[ + +// Test for duration of decimated stream (does not test contents, that +// happens in the filters tests below) +"dec-duration-\(name)": \( + original = maybeDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); + str = resample.decimated 2 original; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 2 and + compare str.available (maybeKnown 4) and + compare str.finished? false and + (r = str.read 3; + println "r = \(r)"; + compare (mat.size r) { rows = 1, columns = 3 }) and + compare str.position 3 and + compare str.available (maybeKnown 1) and + compare str.finished? false and + (r = str.read 3; + println "r = \(r)"; + compare (mat.size r) { rows = 1, columns = 1 }) and + compare str.position 4 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +// Test for duration of interpolated stream (does not test contents, +// that happens in the filters tests below) +"int-duration-\(name)": \( + original = maybeDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); + str = resample.interpolated 2 original; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 8 and + compare str.available (maybeKnown 16) and + compare str.finished? false and + compare (mat.size (str.read 12)) { rows = 1, columns = 12 } and + compare str.position 12 and + compare str.available (maybeKnown 1) and + compare str.finished? false and + compare (mat.size (str.read 12)) { rows = 1, columns = 4 } and + compare str.position 16 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +// Test for duration of resampled stream (does not test contents, +// that happens in the filters tests below) +"resamp-duration-\(name)": \( + original = maybeDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); + str = resample.resampledTo 6 original; + compare str.position 0 and + compare str.channels 1 and + compare str.sampleRate 6 and + compare str.available (maybeKnown 12) and + compare str.finished? false and + compare (mat.size (str.read 9)) { rows = 1, columns = 9 } and + compare str.position 9 and + compare str.available (maybeKnown 3) and + compare str.finished? false and + compare (mat.size (str.read 9)) { rows = 1, columns = 3 } and + compare str.position 12 and + compare str.available (Known 0) and + compare str.finished? true and + ( str.close (); true ) +), + +]); + +knowns = makeTests "known" false; +unknowns = makeTests "unknown" true; + +filtering = [ +/*!!! +"interpolated-sine": \( + // Interpolating a sinusoid should give us a sinusoid + //!!! only beyond half the filter length + sinusoid = syn.sinusoid 8 2; // 2Hz sine sampled at 8Hz: [ 0, 1, 0, -1 ] etc + input = resample.withDuration 16 sinusoid; + output = resample.interpolated 2 input; + result = output.read 32; + reference = syn.sinusoid 16 2; + expected = reference.read 32; + compareOutputs a b = compareClose + (map vec.list (mat.asRows a)) (map vec.list (mat.asRows b)); + compareOutputs result expected; +), + +"decimated-sine": \( + // Decimating a sinusoid should give us a sinusoid + //!!! only beyond half the filter length + sinusoid = syn.sinusoid 32 1; // 1Hz sine sampled at 32Hz + input = resample.withDuration 400 sinusoid; + output = resample.decimated 2 input; + result = mat.columnSlice (output.read 200) 50 150; + reference = syn.sinusoid 16 1; + expected = mat.columnSlice (reference.read 200) 50 150; + compareOutputs a b = compareClose + (map vec.list (mat.asRows a)) (map vec.list (mat.asRows b)); + if not compareOutputs result expected then + println "diff: \(mat.difference result expected)"; + false + else true fi +), +*/ +"interpolated-misc": \( + // Interpolating any signal by N should give a signal in which + // every Nth sample is the original signal + data = vec.fromList [ 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 ]; + data = vec.concat [ data, bf.scaled (5/4) data, bf.scaled (3/4) data, data ]; + data = vec.concat [ data, data ]; + input = manip.withDuration (vec.length data) (syn.precalculatedMono 4 data); + factor = 3; + up = resample.interpolated factor input; + result = mat.getRow 0 (up.read (factor * vec.length data)); + phase = 0; + a = vec.list data; + b = map do i: vec.at result (i*factor + phase) done [0..vec.length data - 1]; +\() ( pl.plot [ Vector data, Vector (vec.fromList b) ] ); +\() ( pl.plot [ Vector result ] ); +println "a = \(a)"; +println "b = \(b)"; + compareClose [b] [a]; +), +/*!!! +"int-dec": \( + // Interpolating any signal then decimating by the same factor + // should get us the original back again + //!!! no, this is phase dependent +// data = vec.fromList [ 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 ]; +// data = vec.concat [ data, bf.scaled (5/4) data, bf.scaled (3/4) data, data ]; + data = vec.fromList [ 0, 1, 2, 3 ]; + data = vec.concat [ data, data ]; + + factor = 3; + + updown prepad = + (input = manip.withDuration (vec.length data) (syn.precalculatedMono 4 data); + intermediate = resample.interpolated factor input; + output = resample.decimated factor (resample.delayedBy prepad intermediate); + output.read (vec.length data)); + + result = updown 0; + if not compareClose [vec.list (mat.getRow 0 result)] [vec.list data] then + \() (pl.plot [Vector data, + Vector (mat.getRow 0 result), + Vector (mat.getRow 0 (updown 1)), + Vector (mat.getRow 0 (updown 2))]); + up = (resample.interpolated 2 (syn.precalculatedMono 4 data)).read 80; + \() (pl.plot [Vector (mat.getRow 0 up)]); + false + else true fi; +), +*/ + +]; + +all = [:]; +for [ knowns, unknowns, filtering ] do h: + for (keys h) do k: all[k] := h[k] done +done; + +all is hash<string, () -> boolean>; +
--- a/src/may/test/all.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/test/all.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -11,7 +11,10 @@ "channels" : load may.stream.test.test_channels, "audiofile" : load may.stream.test.test_audiofile, "synstream" : load may.stream.test.test_syntheticstream, +"manipulate" : load may.stream.test.test_manipulate, +"convolve" : load may.stream.test.test_convolve, "filter" : load may.stream.test.test_filter, +"resample" : load may.stream.test.test_resample, "fft" : load may.transform.test.test_fft, "vamppost" : load may.vamp.test.test_vamppost, "vamp" : load may.vamp.test.test_vamp,
--- a/src/may/vamp/test/test_vamp.yeti Fri Sep 27 12:33:34 2013 +0100 +++ b/src/may/vamp/test/test_vamp.yeti Fri Sep 27 14:07:08 2013 +0100 @@ -2,7 +2,7 @@ v = load may.vamp; synthetic = load may.stream.syntheticstream; -filter = load may.stream.filter; +manip = load may.stream.manipulate; mat = load may.matrix; vec = load may.vector; @@ -12,7 +12,7 @@ rate = 44100; -testStream () = filter.withDuration (rate * 20) (synthetic.whiteNoise rate); +testStream () = manip.withDuration (rate * 20) (synthetic.whiteNoise rate); processTest output = v.processStreamStructured testPluginKey output (testStream ());