changeset 283:e330ac62703b

Merge
author Chris Cannam
date Wed, 29 May 2013 17:32:58 +0100
parents f3784641245f (diff) b432db0b2529 (current diff)
children 7932bbb7bacb
files
diffstat 8 files changed, 362 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/yetilab/matrix.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/matrix.yeti	Wed May 29 17:32:58 2013 +0100
@@ -9,6 +9,11 @@
 // result regardless of storage order.  (The transpose function just
 // switches the row/column order without moving the elements.)
 
+// The matrix representation does not take into account different
+// forms of zero-width or zero-height matrix. That is, all matrices of
+// zero width or height are equal to each other and to the zero-size
+// matrix -- they can't be distinguished.
+
 vec = load yetilab.vector;
 bf = load yetilab.vector.blockfuncs;
 
@@ -168,6 +173,8 @@
 
 zeroSizeMatrix () = zeroMatrix { rows = 0, columns = 0 };
 
+isZeroSize? m = (width m == 0 or height m == 0);
+
 generate f { rows, columns } =
     if rows < 1 or columns < 1 then zeroSizeMatrix ()
     else
@@ -333,7 +340,9 @@
     esac);
 
 equal' comparator vecComparator m1 m2 =
-    if size m1 != size m2 then 
+    if isZeroSize? m1 and isZeroSize? m2 then
+        true
+    elif size m1 != size m2 then 
         false
     elif isRowMajor? m1 != isRowMajor? m2 then
         equal' comparator vecComparator (flipped m1) m2;
@@ -437,7 +446,7 @@
         newMatrix (typeOf m) (map bf.abs (asColumns m));
     fi;
 
-filter f m =
+filter' f m =
     if isSparse? m then
         makeSparse (typeOf m) (size m)
            (map do { i, j, v }: { i, j, v = if f v then v else 0 fi } done
@@ -600,7 +609,7 @@
         failWith "Matrix dimensions incompatible for concat (found \(map do m: counter (size m) done mm) not all of which are \(n))";
     fi);
 
-concat direction mm = //!!! doc: storage order is taken from first matrix in sequence
+concat' direction mm = //!!! doc: storage order is taken from first matrix in sequence
     case length mm of
     0: zeroSizeMatrix ();
     1: head mm;
@@ -628,20 +637,44 @@
         fi;
     esac;
 
+//!!! doc this filter -- zero-size elts are ignored
+concat direction mm =
+    concat' direction (filter do mat: not (isZeroSize? mat) done mm);
+
+//!!! next two v. clumsy
+
 //!!! doc note: argument order chosen for consistency with std module slice
+//!!! NB always returns dense matrix, should have sparse version
 rowSlice m start end = //!!! doc: storage order same as input
-    if isRowMajor? m then
-        DenseRows (array (map ((flip getRow) m) [start .. end - 1]))
-    else 
-        DenseCols (array (map do v: vec.slice v start end done (asColumns m)))
+    if start < 0 then rowSlice m 0 end
+    elif start > height m then rowSlice m (height m) end
+    else
+        if end < start then rowSlice m start start
+        elif end > height m then rowSlice m start (height m)
+        else
+            if isRowMajor? m then
+                DenseRows (array (map ((flip getRow) m) [start .. end - 1]))
+            else 
+                DenseCols (array (map do v: vec.slice v start end done (asColumns m)))
+            fi;
+        fi;
     fi;
 
 //!!! doc note: argument order chosen for consistency with std module slice
+//!!! NB always returns dense matrix, should have sparse version
 columnSlice m start end = //!!! doc: storage order same as input
-    if not isRowMajor? m then
-        DenseCols (array (map ((flip getColumn) m) [start .. end - 1]))
-    else 
-        DenseRows (array (map do v: vec.slice v start end done (asRows m)))
+    if start < 0 then columnSlice m 0 end
+    elif start > width m then columnSlice m (width m) end
+    else
+        if end < start then columnSlice m start start
+        elif end > width m then columnSlice m start (width m)
+        else
+            if not isRowMajor? m then
+                DenseCols (array (map ((flip getColumn) m) [start .. end - 1]))
+            else 
+                DenseRows (array (map do v: vec.slice v start end done (asRows m)))
+            fi;
+        fi;
     fi;
 
 resizedTo newsize m =
@@ -694,6 +727,7 @@
     zeroMatrix,
     identityMatrix,
     zeroSizeMatrix,
+    isZeroSize?,
     equal,
     equalUnder,
     transposed,
@@ -709,7 +743,7 @@
     sum = sum',
     difference,
     abs = abs',
-    filter,
+    filter = filter',
     product,
     concat,
     rowSlice,
@@ -740,6 +774,7 @@
     zeroMatrix is { .rows is number, .columns is number } -> matrix, 
     identityMatrix is { .rows is number, .columns is number } -> matrix, 
     zeroSizeMatrix is () -> matrix,
+    isZeroSize? is matrix -> boolean,
     equal is matrix -> matrix -> boolean,
     equalUnder is (number -> number -> boolean) -> matrix -> matrix -> boolean,
     transposed is matrix -> matrix,
--- a/yetilab/matrix/test/test_matrix.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/matrix/test/test_matrix.yeti	Wed May 29 17:32:58 2013 +0100
@@ -360,7 +360,19 @@
 "zeroSizeMatrix-\(name)": \(
     compareMatrices
        (mat.zeroSizeMatrix ())
-       (newMatrix (ColumnMajor ()) [])
+       (newMatrix (ColumnMajor ()) []) and
+        compareMatrices
+           (mat.zeroSizeMatrix ())
+           (newMatrix (ColumnMajor ()) [[]]) and
+        compareMatrices
+           (newMatrix (ColumnMajor ()) [[]])
+           (newMatrix (RowMajor ()) [[]]) and
+        compareMatrices
+           (mat.zeroSizeMatrix ())
+           (mat.newSparseMatrix (ColumnMajor ()) { rows = 0, columns = 1 } []) and
+        compareMatrices
+           (mat.zeroSizeMatrix ())
+           (mat.newSparseMatrix (ColumnMajor ()) { rows = 1, columns = 0 } [])
 ),
 
 "asRows-\(name)": \(
@@ -385,6 +397,15 @@
        (newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]])
 ),
 
+"concatEmpty-horiz-\(name)": \(
+    compareMatrices
+       (mat.concat (Horizontal ()) 
+          [(newMatrix (ColumnMajor ()) [[]]),
+           (newMatrix (RowMajor ()) [[]]),
+           (mat.zeroSizeMatrix ())])
+       (mat.zeroSizeMatrix ());
+),
+
 "sparseConcat-horiz-\(name)": \(
     s = mat.concat (Horizontal ()) 
           [mat.toSparse (newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
@@ -436,13 +457,19 @@
 "rowSlice-\(name)": \(
     compareMatrices
        (mat.rowSlice (newMatrix (RowMajor ()) [[1,0],[3,4],[0,6],[7,8]]) 1 3)
-       (newMatrix (RowMajor ()) [[3,4],[0,6]])
+       (newMatrix (RowMajor ()) [[3,4],[0,6]]) and
+        compareMatrices
+           (mat.rowSlice (newMatrix (RowMajor ()) [[1,0],[3,4],[0,6],[7,8]]) 3 6)
+           (newMatrix (RowMajor ()) [[7,8]])
 ),
 
 "columnSlice-\(name)": \(
     compareMatrices
        (mat.columnSlice (newMatrix (RowMajor ()) [[1,0,3,4],[0,6,7,8]]) 1 3)
-       (newMatrix (RowMajor ()) [[0,3],[6,7]])
+       (newMatrix (RowMajor ()) [[0,3],[6,7]]) and
+        compareMatrices
+           (mat.columnSlice (newMatrix (RowMajor ()) [[1,0,3,4],[0,6,7,8]]) 2 5)
+           (newMatrix (RowMajor ()) [[3,4],[7,8]])
 ),
 
 "density-\(name)": \(
--- a/yetilab/stream/filter.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/stream/filter.yeti	Wed May 29 17:32:58 2013 +0100
@@ -3,8 +3,11 @@
 
 mat = load yetilab.matrix;
 ch = load yetilab.stream.channels;
+vec = load yetilab.vector;
 
 load yetilab.stream.type;
+load yetilab.vector.type;
+load yetilab.matrix.type;
 
 minDurationOf d1 d2 =
     case d1 of 
@@ -202,6 +205,117 @@
         }
     fi;
 
+duplicated copies s = 
+//!!! doc fact that original s cannot be used independently of this afterward
+// (so maybe name is misleading?)
+    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;
+
+convolvedWith ir s =
+   (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 history = mat.toRowMajor
+       (mat.zeroMatrix { rows = s.channels, columns = mat.width ir - 1 });
+    s with 
+    {
+        get finished? () =
+            s.finished? and (mat.isZeroSize? 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)),
+    });
+
 {
     withDuration is number -> stream -> stream,
     delayedBy is number -> stream -> stream,
@@ -209,7 +323,7 @@
     mixed is list<stream> -> stream,
     multiplexed is list<stream> -> stream,
     repeated is stream -> stream,
+    duplicated is number -> stream -> list<stream>,
+    convolvedWith is matrix -> stream -> stream,
 }
 
-
-
--- a/yetilab/stream/framer.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/stream/framer.yeti	Wed May 29 17:32:58 2013 +0100
@@ -13,6 +13,7 @@
 fft = load yetilab.transform.fft;
 mat = load yetilab.matrix;
 ch = load yetilab.stream.channels;
+syn = load yetilab.stream.syntheticstream;
 
 blockList framesize stream =
     if stream.finished? then
@@ -57,6 +58,35 @@
             framesize (map \(vec.zeros framesize) [0..stream.channels-1]);
     fi;
 
+streamContiguous rate framesize fr =
+    if empty? frames then
+        syn.empty rate 1
+    else
+        var position = 0;
+        var frames = fr;
+        var fini = false;
+        channels = mat.height (head frames); // so we don't need to keep head ptr
+        {
+            get position () = position,
+            get channels () = channels,
+            get sampleRate () = rate,
+            get available () = if fini then Known 0 else Unknown () fi,
+            get finished? () = fini,
+                            
+                            
+            close = \(),
+        }
+    fi;
+
+//!!! doc: convert frames back to a stream
+streamed rate { framesize, hop } frames =
+    if framesize == hop then
+        streamContiguous rate framesize frames
+    else
+        //!!! OLA
+        syn.empty rate 1;
+    fi;
+
 monoFrames params stream =
     map ch.mixedDown (frames params stream);
 
--- a/yetilab/stream/syntheticstream.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/stream/syntheticstream.yeti	Wed May 29 17:32:58 2013 +0100
@@ -52,12 +52,24 @@
         close = \(),
     });
 
+empty rate channels = // degenerate stream with no data in it, occasionally useful
+    {
+        get position () = 0,
+        get channels () = channels,
+        get sampleRate () = rate,
+        get available () = Known 0,
+        get finished? () = true,
+        read count = mat.zeroSizeMatrix (),
+        close = \(),
+    };
+
 {
     generated is number -> (number -> number) -> stream, 
     precalculated is number -> vector -> stream,
     sinusoid is number -> number -> stream, 
     whiteNoise is number -> stream,
     silent is number -> stream,
+    empty = number -> number -> stream,
 }
 
 
--- a/yetilab/stream/test/test_filter.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/stream/test/test_filter.yeti	Wed May 29 17:32:58 2013 +0100
@@ -9,7 +9,7 @@
 { compare, compareUsing } = load yetilab.test.test;
 
 makeTests name withUnknown =
-   (withDuration n str =
+   (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
@@ -68,7 +68,7 @@
     // 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 (withDuration 3 (syn.generated 2 id));
+    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
@@ -86,7 +86,7 @@
 ),
 
 "delayedBy-0-3-\(name)": \(
-    str = filt.delayedBy 0 (withDuration 3 (syn.generated 2 id));
+    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
@@ -114,7 +114,7 @@
 ),
 
 "delayedBy-2-3-\(name)": \(
-    str = filt.delayedBy 2 (withDuration 3 (syn.generated 2 id));
+    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
@@ -132,7 +132,7 @@
 ),
 
 "delayedBy-m2-3-\(name)": \(
-    str = filt.delayedBy (-2) (withDuration 3 (syn.generated 2 id));
+    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
@@ -146,7 +146,7 @@
 ),
 
 "delayedBy-m4-3-\(name)": \(
-    str = filt.delayedBy (-4) (withDuration 3 (syn.generated 2 id));
+    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
@@ -157,7 +157,7 @@
 ),
 
 "delayedBy-2-3b-\(name)": \(
-    str = filt.delayedBy 2 (withDuration 3 (syn.generated 2 id));
+    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
@@ -175,7 +175,7 @@
 ),
 
 "delayedBy-2-3c-\(name)": \(
-    str = filt.delayedBy 2 (withDuration 3 (syn.generated 2 id));
+    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
@@ -221,7 +221,7 @@
 ),
 
 "mixedTo-1-2-\(name)": \(
-    str = filt.mixedTo 2 (withDuration 3 (syn.generated 2 id));
+    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
@@ -237,8 +237,8 @@
 "mixedTo-2-1-\(name)": \(
     str = filt.mixedTo 1
        (filt.multiplexed
-          [withDuration 3 (syn.generated 2 id),
-           withDuration 3 (syn.generated 2 id)]);
+          [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
@@ -254,8 +254,8 @@
 "mixedTo-2-3-\(name)": \(
     str = filt.mixedTo 3
        (filt.multiplexed
-          [withDuration 3 (syn.generated 2 id),
-           withDuration 3 (syn.generated 2 (+1))]);
+          [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
@@ -269,7 +269,7 @@
 ),
 
 "mixedTo-1-3-\(name)": \(
-    str = filt.mixedTo 3 (withDuration 3 (syn.generated 2 id));
+    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
@@ -296,7 +296,7 @@
 ),
 
 "mixed-inf-trunc-\(name)": \(
-    str = filt.mixed [syn.generated 2 (2*), withDuration 3 (syn.generated 2 (0-))];
+    str = filt.mixed [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
@@ -312,7 +312,7 @@
 "mixed-precalc-trunc-\(name)": \(
     str = filt.mixed
        [syn.precalculated 2 (vec.fromList [1,2]),
-        withDuration 3 (syn.generated 2 (0-))];
+        maybeDuration 3 (syn.generated 2 (0-))];
     compare str.position 0 and
         compare str.channels 1 and
         compare str.sampleRate 2 and
@@ -329,7 +329,7 @@
     str = filt.mixed
        [syn.precalculated 2 (vec.fromList [1,2]),
         filt.multiplexed [syn.precalculated 2 (vec.fromList [3,4]),
-                          withDuration 3 (syn.generated 2 (0-))]];
+                          maybeDuration 3 (syn.generated 2 (0-))]];
     compare str.position 0 and
         compare str.channels 2 and
         compare str.sampleRate 2 and
@@ -346,7 +346,7 @@
     str = filt.mixed
        [syn.precalculated 2 (vec.fromList [1,2]),
         syn.precalculated 2 (vec.fromList [3,4]),
-        withDuration 3 (syn.generated 2 (0-))];
+        maybeDuration 3 (syn.generated 2 (0-))];
     compare str.position 0 and
         compare str.channels 1 and
         compare str.sampleRate 2 and
@@ -374,7 +374,7 @@
 ),
 
 "multiplexed-inf-trunc-\(name)": \(
-    str = filt.multiplexed [syn.generated 2 id, withDuration 3 (syn.generated 2 (0-))];
+    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
@@ -390,7 +390,7 @@
 "multiplexed-precalc-trunc-\(name)": \(
     str = filt.multiplexed
        [syn.precalculated 2 (vec.fromList [1,2]),
-        withDuration 3 (syn.generated 2 (0-))];
+        maybeDuration 3 (syn.generated 2 (0-))];
     compare str.position 0 and
         compare str.channels 2 and
         compare str.sampleRate 2 and
@@ -407,7 +407,7 @@
     str = filt.multiplexed
        [syn.precalculated 2 (vec.fromList [1,2]),
         filt.multiplexed [syn.precalculated 2 (vec.fromList [3,4]),
-                          withDuration 3 (syn.generated 2 (0-))]];
+                          maybeDuration 3 (syn.generated 2 (0-))]];
     compare str.position 0 and
         compare str.channels 3 and
         compare str.sampleRate 2 and
@@ -424,7 +424,7 @@
     str = filt.multiplexed
        [syn.precalculated 2 (vec.fromList [1,2]),
         syn.precalculated 2 (vec.fromList [3,4]),
-        withDuration 3 (syn.generated 2 (0-))];
+        maybeDuration 3 (syn.generated 2 (0-))];
     compare str.position 0 and
         compare str.channels 3 and
         compare str.sampleRate 2 and
@@ -460,6 +460,96 @@
         ( str.close (); true )
 ),
 
+"duplicated-1-\(name)": \(
+    original = maybeDuration 3 (syn.precalculated 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.precalculated 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 )
+),
+
+"convolvedWith-\(name)": \(
+    ir = mat.newRowVector (vec.fromList [1,2,3]);
+    signal = maybeDuration 2 (syn.precalculated 2 (vec.fromList [1,2]));
+    c = filt.convolvedWith ir signal;
+    compare c.position 0 and
+        compare c.channels 1 and
+        compare c.sampleRate 2 and
+        compare c.available (maybeKnown 4) and
+        compare (map vec.list (mat.asRows (c.read 3)))
+            [[ 1*1, 2*1 + 1*2, 0*1 + 2*2 + 1*3 ]] and
+        compare c.available (Known 1) and
+        compare c.finished? false and
+        compare (map vec.list (mat.asRows (c.read 4)))
+            [[ 0*1 + 0*2 + 2*3 ]] and
+        compare c.available (Known 0) and
+        compare c.finished? true and
+        ( c.close (); true )
+),
+
 //!!! still no tests for filters with multi-channel inputs
 
 ]);
--- a/yetilab/vector.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/vector.yeti	Wed May 29 17:32:58 2013 +0100
@@ -72,7 +72,15 @@
 
 //!!! doc note: argument order chosen for consistency with std module function
 slice v start end is ~double[] -> number -> number -> ~double[] =
-    Arrays#copyOfRange(v, start, end);
+    if start < 0 then slice v 0 end
+    elif start > length' v then slice v (length' v) end
+    else
+        if end < start then slice v start start
+        elif end > length' v then slice v start (length' v)
+        else
+            Arrays#copyOfRange(v, start, end);
+        fi
+    fi;
 
 resizedTo n v is number -> ~double[] -> ~double[] =
     Arrays#copyOf(v, n);
--- a/yetilab/vector/test/test_vector.yeti	Fri May 24 12:41:41 2013 +0100
+++ b/yetilab/vector/test/test_vector.yeti	Wed May 29 17:32:58 2013 +0100
@@ -78,9 +78,12 @@
 
 "slice": \(
     v = vec.fromList [1,2,3,4];
-    vec.equal (vec.slice v 0 4) v and (
-        vec.equal (vec.slice v 2 4) (vec.fromList [3,4])
-    )
+    vec.equal (vec.slice v 0 4) v and
+        vec.equal (vec.slice v 2 4) (vec.fromList [3,4]) and
+        vec.equal (vec.slice v (-1) 2) (vec.fromList [1,2]) and
+        vec.equal (vec.slice v 3 5) (vec.fromList [4]) and
+        vec.equal (vec.slice v 5 7) (vec.fromList []) and
+        vec.equal (vec.slice v 3 2) (vec.fromList [])
 ),
 
 "resizedTo": \(