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 ());