changeset 384:20903ee53719

Use Ayr init option to preload May modules; move code from may/ to src/ to avoid Yeti compiler doing on-the-fly recompilation on load
author Chris Cannam
date Mon, 23 Sep 2013 12:35:35 +0100
parents 4b104ef8d110
children 56aecf462253
files .hgsubstate bin/may build.xml may/complex.yeti may/complex/test/test_complex.yeti may/complex/type.yeti may/feature/feature.yeti may/feature/specdiff.yeti may/feature/test/test_features.yeti may/matrix.yeti may/matrix/test/speedtest.yeti may/matrix/test/test_matrix.yeti may/matrix/type.yeti may/plot.yeti may/plot/chart.yeti may/plot/colour.yeti may/plot/test/test_plot.yeti may/signal/autocorrelation.yeti may/signal/test/test_signal.yeti may/signal/test/test_window.yeti may/signal/window.yeti may/stream/audiofile.yeti may/stream/channels.yeti may/stream/filter.yeti may/stream/format.yeti may/stream/framer.yeti may/stream/playback.yeti may/stream/record.yeti may/stream/syntheticstream.yeti may/stream/test/audiofile_reference.yeti may/stream/test/speedtest.yeti may/stream/test/test_audiofile.yeti may/stream/test/test_channels.yeti may/stream/test/test_filter.yeti may/stream/test/test_framer.yeti may/stream/test/test_syntheticstream.yeti may/stream/type.yeti may/test/all.yeti may/test/data/20samples.wav may/test/data/44100-2-16.wav may/test/data/8000-1-8.wav may/test/test.yeti may/transform/fft.yeti may/transform/test/test_fft.yeti may/vamp.yeti may/vamp/test/test_vamp.yeti may/vamp/test/test_vamppost.yeti may/vamp/vamppost.yeti may/vamp/vamprdf.yeti may/vector.yeti may/vector/blockfuncs.yeti may/vector/test/test_blockfuncs.yeti may/vector/test/test_vector.yeti may/vector/type.yeti src/may/complex.yeti src/may/complex/test/test_complex.yeti src/may/complex/type.yeti src/may/feature/feature.yeti src/may/feature/specdiff.yeti src/may/feature/test/test_features.yeti src/may/matrix.yeti src/may/matrix/test/speedtest.yeti src/may/matrix/test/test_matrix.yeti src/may/matrix/type.yeti src/may/plot.yeti src/may/plot/chart.yeti src/may/plot/colour.yeti src/may/plot/test/test_plot.yeti src/may/signal/autocorrelation.yeti src/may/signal/test/test_signal.yeti src/may/signal/test/test_window.yeti src/may/signal/window.yeti src/may/stream/audiofile.yeti src/may/stream/channels.yeti src/may/stream/filter.yeti src/may/stream/format.yeti src/may/stream/framer.yeti src/may/stream/playback.yeti src/may/stream/record.yeti src/may/stream/syntheticstream.yeti src/may/stream/test/audiofile_reference.yeti src/may/stream/test/speedtest.yeti src/may/stream/test/test_audiofile.yeti src/may/stream/test/test_channels.yeti src/may/stream/test/test_filter.yeti src/may/stream/test/test_framer.yeti src/may/stream/test/test_syntheticstream.yeti src/may/stream/type.yeti src/may/test/all.yeti src/may/test/data/20samples.wav src/may/test/data/44100-2-16.wav src/may/test/data/8000-1-8.wav src/may/test/test.yeti src/may/transform/fft.yeti src/may/transform/test/test_fft.yeti src/may/vamp.yeti src/may/vamp/test/test_vamp.yeti src/may/vamp/test/test_vamppost.yeti src/may/vamp/vamppost.yeti src/may/vamp/vamprdf.yeti src/may/vector.yeti src/may/vector/blockfuncs.yeti src/may/vector/test/test_blockfuncs.yeti src/may/vector/test/test_vector.yeti src/may/vector/type.yeti
diffstat 105 files changed, 7308 insertions(+), 7306 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsubstate	Mon Sep 16 10:56:19 2013 +0100
+++ b/.hgsubstate	Mon Sep 23 12:35:35 2013 +0100
@@ -1,1 +1,1 @@
-79aabcffd9a15ab6f74487964657fc2bc683cf4f ext
+43584910bba1d16db21b2ce6d8feafe87c9361fa ext
--- a/bin/may	Mon Sep 16 10:56:19 2013 +0100
+++ b/bin/may	Mon Sep 23 12:35:35 2013 +0100
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+MAY_INIT_MODULES="may.vector:may.matrix:may.complex:may.plot:may.vamp"
+
 ARCH=unknown
 
 case `uname -a` in
@@ -32,5 +34,5 @@
 
 YETI_MODULE_SOURCE_PATH=${YETI_LIBDIR}/modules \
     LD_LIBRARY_PATH=$SODIR:$LD_LIBRARY_PATH \
-    $JAVA_HOME/bin/java $JAVA_OPTS -classpath "$CLASSPATH" com.particularprograms.ayr "$@"
+    $JAVA_HOME/bin/java $JAVA_OPTS -classpath "$CLASSPATH" com.particularprograms.ayr -init "$MAY_INIT_MODULES" "$@"
 
--- a/build.xml	Mon Sep 16 10:56:19 2013 +0100
+++ b/build.xml	Mon Sep 23 12:35:35 2013 +0100
@@ -26,8 +26,8 @@
   </target>
 
   <target name="classes" depends="taskdef">
-    <yetic srcdir="${basedir}" destdir="${basedir}/classes"
-	   includes="may/**/*.yeti" excludes="**/test/*.yeti" preload="yeti/lang/std:yeti/lang/io"/>
+    <yetic srcdir="${basedir}/src" destdir="${basedir}/classes"
+	   includes="**/*.yeti" excludes="**/test/*.yeti" preload="yeti/lang/std:yeti/lang/io"/>
   </target>
 
   <target name="jar" depends="classes,taskdef">
@@ -41,7 +41,7 @@
 	  classname="yeti.lang.compiler.yeti"
 	  fork="true">
       <sysproperty key="java.library.path" path="${basedir}/ext/native/${archtag}"/>
-      <arg value="may/test/all.yeti"/>
+      <arg value="src/may/test/all.yeti"/>
     </java>
   </target>
 
--- a/may/complex.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-
-module may.complex;
-
-load may.vector.type;
-load may.complex.type;
-
-vec = load may.vector;
-
-import java.lang: ClassCastException;
-
-class Cplx(double real, double imag)
-    double getReal()
-        real,
-    double getImag()
-        imag,
-    double getMagnitude()
-        sqrt (real * real + imag * imag),
-    double getAngle()
-        Math#atan2(imag, real),
-    String toString()
-        if real == int real and imag == int imag then
-            if imag < 0 then
-                " \(int real) - \(int (-imag))i"
-            else 
-                " \(int real) + \(int imag)i"
-            fi
-        else
-            if imag < 0 then
-                " \(real) - \((-imag))i"
-            else 
-                " \(real) + \(imag)i"
-            fi
-        fi,
-    int hashCode()
-        Double#valueOf(real)#hashCode() + Double#valueOf(imag)#hashCode(),
-    boolean equals(Object other)
-        try
-            c = other unsafely_as ~Cplx;
-            c#getReal() == real and c#getImag() == imag
-        catch ClassCastException:
-            false
-        yrt,
-end;
-
-real c1 is ~Cplx -> number =
-    c1#getReal();
-
-imaginary c1 is ~Cplx -> number =
-    c1#getImag();
-
-complex re im is number -> number -> ~Cplx =
-    new Cplx(re, im);
-
-magnitude c is ~Cplx -> number =
-    c#getMagnitude();
-
-angle c is ~Cplx -> number =
-    c#getAngle();
-
-sum' cc is list?<~Cplx> -> ~Cplx =
-    complex (sum (map real cc)) (sum (map imaginary cc));
-
-add c1 c2 is ~Cplx -> ~Cplx -> ~Cplx =
-    complex (real c1 + real c2) (imaginary c1 + imaginary c2);
-
-multiply c1 c2 is ~Cplx -> ~Cplx -> ~Cplx =
-   (a = real c1;
-    b = imaginary c1;
-    c = real c2;
-    d = imaginary c2;
-    complex (a * c - b * d) (b * c + a * d)); //!!! need units
-
-scale r c is number -> ~Cplx -> ~Cplx =
-    complex (r * real c) (r * imaginary c);
-
-zeros n is number -> array<~Cplx> =
-    array (map \(complex 0 0) [1..n]);
-
-magnitudes cc is list?<~Cplx> -> vector =
-    vec.fromList (map magnitude cc);
-
-angles cc is list?<~Cplx> -> vector =
-    vec.fromList (map angle cc);
-
-{
-   real,
-   imaginary,
-   complex,
-   magnitude,
-   angle,
-   sum = sum',
-   add,
-   multiply,
-   scale,
-   zeros,
-   magnitudes,
-   angles,
-} as {
-   real is cplx -> number,
-   imaginary is cplx -> number,
-   complex is number -> number -> cplx,
-   magnitude is cplx -> number,
-   angle is cplx -> number,
-   sum is list?<cplx> -> cplx,
-   add is cplx -> cplx -> cplx,
-   multiply is cplx -> cplx -> cplx,
-   scale is number -> cplx -> cplx,
-   zeros is number -> array<cplx>,
-   magnitudes is list?<cplx> -> vector,
-   angles is list?<cplx> -> vector,
-}
-
--- a/may/complex/test/test_complex.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-module may.complex.test.test_complex;
-
-{ real, imaginary, complex, magnitude, angle, sum, scale, zeros, magnitudes, angles }
-   = load may.complex;
-
-{ compare } = load may.test.test;
-
-vec = load may.vector;
-
-[
-
-"complex": \( 
-    compare (complex 1 2) (complex 1 2) and
-        complex (-1) 2 != complex 1 2
-),
-
-"real": \(
-    compare (real (complex 3 2)) 3 and
-        compare (real (complex 3.3 2.2)) 3.3
-),
-
-"imaginary": \(
-    compare (imaginary (complex 3 4)) 4 and
-        compare (imaginary (complex 3 (-4.1))) (-4.1)
-),
-
-"magnitude": \(
-    compare (magnitude (complex (-3) 4)) 5
-),
-
-"angle": \(
-    compare (angle (complex 1 0)) 0 and
-        compare (angle (complex 1 1)) (pi/4) and
-        compare (angle (complex 0 1)) (pi/2) and
-        compare (angle (complex (-1) 0)) pi and
-        compare (angle (complex 0 (-1))) (-pi/2)
-),
-
-"sum": \(
-    compare (sum [complex 2 3, complex (-4) 5]) (complex (-2) 8)
-),
-
-"scale": \(
-    compare (scale 4 (complex 2 3)) (complex 8 12)
-),
-
-"zeros": \(
-    compare (zeros 0) (array []) and
-        compare (zeros 3) (array [complex 0 0, complex 0 0, complex 0 0])
-),
-
-"magnitudes": \(
-    compare (vec.list (magnitudes [ complex (-3) 4, complex 4 3, complex 0 0 ]))
-            [ 5, 5, 0 ] and
-       compare (vec.list (magnitudes (array []))) []
-),
-
-"angles": \(
-    compare (vec.list (angles [ complex 1 0, complex (-1) 0, complex 0 (-1) ]))
-            [ 0, pi, -pi/2 ] and
-       compare (vec.list (angles (array []))) []
-),
-
-
-] is hash<string, () -> boolean>;
-
-
-
--- a/may/complex/type.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-
-module may.complex.type;
-
-typedef opaque cplx = ~may.Cplx;
-
-();
-
--- a/may/feature/feature.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-
-module may.feature.feature;
-
-vec = load may.vector;
-fr = load may.stream.framer;
-
-// Utility functions for feature extractors
-
-magdiff frame1 frame2 =
-    sum (map2 do a b: abs(a - b) done (vec.list frame1) (vec.list frame2));
-
-emptyFrameFor frames =
-    vec.zeros
-        if empty? frames then 0
-        else vec.length (head frames)
-        fi;
-
-features featureFunc frames =
-   (featuresOf prev frames =
-        case frames of
-        frame::rest: featureFunc prev frame :. \(featuresOf frame rest);
-         _: [];
-        esac;
-    featuresOf (emptyFrameFor frames) frames);
-
-{ magdiff, emptyFrameFor, features };
-
-
-
--- a/may/feature/specdiff.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-
-module may.feature.specdiff;
-
-vec = load may.vector;
-fr = load may.stream.framer;
-cplx = load may.complex;
-
-load may.feature.feature;
-
-specdiff frames = 
-    features magdiff (map cplx.magnitudes frames);
-
-specdiffOfFile parameters filename =
-    specdiff (fr.frequencyDomainFramesOfFile parameters filename);
-
-{
-    specdiff,
-    specdiffOfFile,
-}
-
--- a/may/feature/test/test_features.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- a/may/matrix.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,842 +0,0 @@
-
-/**
- * Matrices. A matrix is a two-dimensional (NxM) container of
- * double-precision floating point values.
- *
- * A matrix may be dense or sparse.
- * 
- * A dense matrix (the default) is just a series of vectors, making up
- * the matrix "grid". The values may be stored in either column-major
- * order, in which case the series consists of one vector for each
- * column in the matrix, or row-major order, in which case the series
- * consists of one vector for each row. The default is column-major.
- * 
- * A sparse matrix has a more complex representation in which only the
- * non-zero values are stored. This is typically used for matrices
- * containing sparse data, that is, data in which most of the values
- * are zero: using a sparse representation is more efficient than a
- * dense one (in both time and memory) if the matrix is very large but
- * contains a relatively low proportion of non-zero values. Like dense
- * matrices, sparse ones may be column-major or row-major.
- * 
- * The choice of dense or sparse, row- or column-major is a question
- * of efficiency alone. All functions in this module should return the
- * same results regardless of how the matrices they operate on are
- * represented. However, differences in performance can be very large
- * and it is often worth converting matrices to a different storage
- * format if you know they can be more efficiently manipulated that
- * way. For example, multiplying two matrices is fastest if the first
- * is in column-major and the second in row-major order.
- * 
- * Use the isRowMajor? and isSparse? functions to query the storage
- * format of a matrix; use the flipped function to convert between
- * column-major and row-major storage; and use toSparse and toDense to
- * convert between sparse and dense storage.
- *
- * Note that the matrix representation does not take into account
- * different forms of zero-width or zero-height matrix. All matrices
- * of zero width or height are equal to each other, and all are equal
- * to the zero-sized matrix.
- */
-
-module may.matrix;
-
-vec = load may.vector;
-bf = load may.vector.blockfuncs;
-
-load may.vector.type;
-load may.matrix.type;
-
-size m =
-    case m of
-    DenseRows r:
-        major = length r;
-        { 
-            rows = major, 
-            columns = if major > 0 then vec.length r[0] else 0 fi,
-        };
-    DenseCols c:
-        major = length c;
-        { 
-            rows = if major > 0 then vec.length c[0] else 0 fi,
-            columns = major, 
-        };
-    SparseCSR { values, indices, pointers, extent }:
-        {
-            rows = (length pointers) - 1,
-            columns = extent
-        };
-    SparseCSC { values, indices, pointers, extent }:
-        {
-            rows = extent,
-            columns = (length pointers) - 1
-        };
-    esac;
-
-width m = (size m).columns;
-height m = (size m).rows;
-
-nonZeroValues m =
-   (nz d =
-        sum
-           (map do v:
-                sum (map do n: if n == 0 then 0 else 1 fi done (vec.list v))
-                done d);
-    case m of 
-    DenseRows d: nz d;
-    DenseCols d: nz d;
-    SparseCSR d: vec.length d.values;
-    SparseCSC d: vec.length d.values;
-    esac);
-
-density m =
-   ({ rows, columns } = size m;
-    cells = rows * columns;
-    (nonZeroValues m) / cells);
-
-sparseSlice n d =
-   (start = d.pointers[n];
-    end = d.pointers[n+1];
-    { 
-        values = vec.slice d.values start end,
-        indices = slice d.indices start end,
-    });
-
-nonEmptySlices d =
-   (ne = array [];
-    for [0..length d.pointers - 2] do i:
-        if d.pointers[i] != d.pointers[i+1] then
-            push ne i
-        fi
-    done;
-    ne);
-
-fromSlice n m d =
-   (slice = sparseSlice n d;
-    var v = 0;
-    for [0..length slice.indices - 1] do i:
-        if slice.indices[i] == m then
-            v := vec.at slice.values i;
-        fi
-    done;
-    v);
-
-filledSlice n d =
-   (slice = sparseSlice n d;
-    dslice = new double[d.extent];
-    for [0..length slice.indices - 1] do i:
-        dslice[slice.indices[i]] := vec.at slice.values i;
-    done;
-    vec.vector dslice);
-
-at' m row col =
-    case m of
-    DenseRows rows: r = rows[row]; vec.at r col;
-    DenseCols cols: c = cols[col]; vec.at c row;
-    SparseCSR data: fromSlice row col data;
-    SparseCSC data: fromSlice col row data;
-    esac;
-
-getColumn j m =
-    case m of
-    DenseCols cols: cols[j];
-    SparseCSC data: filledSlice j data;
-    _: vec.fromList (map do i: at' m i j done [0..height m - 1]);
-    esac;
-
-getRow i m =
-    case m of
-    DenseRows rows: rows[i];
-    SparseCSR data: filledSlice i data; 
-    _: vec.fromList (map do j: at' m i j done [0..width m - 1]);
-    esac;
-
-asRows m =
-    map do i: getRow i m done [0 .. (height m) - 1];
-
-asColumns m =
-    map do i: getColumn i m done [0 .. (width m) - 1];
-
-isRowMajor? m =
-    case m of
-    DenseRows _: true;
-    DenseCols _: false;
-    SparseCSR _: true;
-    SparseCSC _: false;
-    esac;
-
-isSparse? m =
-    case m of
-    DenseRows _: false;
-    DenseCols _: false;
-    SparseCSR _: true;
-    SparseCSC _: true;
-    esac;
-
-typeOf m =
-    if isRowMajor? m then RowMajor ()
-    else ColumnMajor ()
-    fi;
-
-flippedTypeOf m =
-    if isRowMajor? m then ColumnMajor ()
-    else RowMajor ()
-    fi;
-
-newColumnMajorStorage { rows, columns } = 
-    if rows < 1 then array []
-    else array (map \(vec.zeros rows) [1..columns])
-    fi;
-
-zeroMatrix { rows, columns } = 
-    DenseCols (newColumnMajorStorage { rows, columns });
-
-zeroMatrixWithTypeOf m { rows, columns } = 
-    if isRowMajor? m then
-        DenseRows (newColumnMajorStorage { rows = columns, columns = rows });
-    else
-        DenseCols (newColumnMajorStorage { rows, columns });
-    fi;
-
-zeroSizeMatrix () = zeroMatrix { rows = 0, columns = 0 };
-
-empty?' m = (width m == 0 or height m == 0);
-
-generate f { rows, columns } =
-    if rows < 1 or columns < 1 then zeroSizeMatrix ()
-    else
-        m = array (map \(new double[rows]) [1..columns]);
-        for [0..columns-1] do col:
-            for [0..rows-1] do row:
-                m[col][row] := f row col;
-            done;
-        done;
-        DenseCols (array (map vec.vector m))
-    fi;
-
-swapij =
-    map do { i, j, v }: { i = j, j = i, v } done;
-
-//!!! should use { row = , column = , value = } instead of i, j, v?
-enumerateSparse m =
-   (enumerate { values, indices, pointers } =
-        concat
-           (map do i:
-                start = pointers[i];
-                end = pointers[i+1];
-                map2 do j v: { i, j, v } done 
-                    (slice indices start end)
-                    (vec.list (vec.slice values start end))
-                done [0..length pointers - 2]);
-    case m of
-    SparseCSC d: swapij (enumerate d);
-    SparseCSR d: enumerate d;
-     _: [];
-    esac);
-
-enumerateDense m =
-   (enumerate d =
-        concat
-           (map do i:
-                vv = d[i];
-                map2 do j v: { i, j, v } done
-                    [0..vec.length vv - 1]
-                    (vec.list vv);
-                done [0..length d - 1]);
-    case m of
-    DenseCols c: swapij (enumerate c);
-    DenseRows r: enumerate r;
-     _: [];
-    esac);
-
-enumerate m =
-    if isSparse? m then enumerateSparse m else enumerateDense m fi;
-
-// Make a sparse matrix from entries whose i, j values are known to be
-// within range
-makeSparse type size data =
-   (isRow = case type of RowMajor (): true; ColumnMajor (): false esac;
-    ordered = 
-        sortBy do a b:
-            if a.maj == b.maj then a.min < b.min else a.maj < b.maj fi
-        done
-           (map
-                if isRow then
-                    do { i, j, v }: { maj = i, min = j, v } done;
-                else
-                    do { i, j, v }: { maj = j, min = i, v } done;
-                fi
-               (filter do d: d.v != 0 done data));
-    tagger = if isRow then SparseCSR else SparseCSC fi;
-    majorSize = if isRow then size.rows else size.columns fi;
-    minorSize = if isRow then size.columns else size.rows fi;
-    pointers = array [0];
-    setArrayCapacity pointers (size.rows + 1);
-    fillPointers n i data =
-        if n < majorSize then
-            case data of
-            d::rest:
-               (for [n..d-1] \(push pointers i);
-                fillPointers d (i+1) rest);
-             _:
-                for [n..majorSize-1] \(push pointers i);
-            esac;
-        fi;
-    fillPointers 0 0 (map (.maj) ordered);
-    tagger {
-        values = vec.fromList (map (.v) ordered),
-        indices = array (map (.min) ordered),
-        pointers,
-        extent = minorSize,
-    });
-
-// Make a sparse matrix from entries that may contain out-of-range
-// cells which need to be filtered out. This is the public API for
-// makeSparse and is also used to discard out-of-range cells from
-// resizedTo.
-newSparseMatrix type size data =
-    makeSparse type size
-       (filter
-            do { i, j, v }:
-                i == int i and i >= 0 and i < size.rows and 
-                j == int j and j >= 0 and j < size.columns
-            done data);
-
-toSparse m =
-    if isSparse? m then m
-    else
-        makeSparse (typeOf m) (size m) (enumerateDense m);
-    fi;
-
-toDense m =
-    if not (isSparse? m) then m
-    elif isRowMajor? m then
-        DenseRows (array (map do row: getRow row m done [0..height m - 1]));
-    else
-        DenseCols (array (map do col: getColumn col m done [0..width m - 1]));
-    fi;
-
-constMatrix n = generate do row col: n done;
-randomMatrix = generate do row col: Math#random() done;
-identityMatrix = constMatrix 1;
-
-transposed m =
-    case m of
-    DenseRows d: DenseCols d;
-    DenseCols d: DenseRows d;
-    SparseCSR d: SparseCSC d;
-    SparseCSC d: SparseCSR d;
-    esac;
-
-flipped m =
-    if isSparse? m then
-        makeSparse (flippedTypeOf m) (size m) (enumerateSparse m)
-    else
-        if isRowMajor? m then
-            generate do row col: at' m row col done (size m);
-        else
-            transposed
-               (generate do row col: at' m col row done
-                { rows = (width m), columns = (height m) });
-        fi
-    fi;
-
-toRowMajor m =
-    if isRowMajor? m then m else flipped m fi;
-
-toColumnMajor m =
-    if not isRowMajor? m then m else flipped m fi;
-
-equal'' comparator vecComparator m1 m2 =
-    // Prerequisite: m1 and m2 have same sparse-p and storage order
-   (compareVecLists vv1 vv2 = all id (map2 vecComparator vv1 vv2);
-    compareSparse d1 d2 =
-        d1.extent == d2.extent and
-        vecComparator d1.values d2.values and
-        d1.indices == d2.indices and
-        d1.pointers == d2.pointers;
-    case m1 of
-    DenseRows d1:
-        case m2 of DenseRows d2: compareVecLists d1 d2; _: false; esac;
-    DenseCols d1:
-        case m2 of DenseCols d2: compareVecLists d1 d2; _: false; esac;
-    SparseCSR d1:
-        case m2 of SparseCSR d2: compareSparse d1 d2; _: false; esac;
-    SparseCSC d1:
-        case m2 of SparseCSC d2: compareSparse d1 d2; _: false; esac;
-    esac);
-
-equal' comparator vecComparator m1 m2 =
-    if empty?' m1 and empty?' m2 then
-        true
-    elif size m1 != size m2 then 
-        false
-    elif isRowMajor? m1 != isRowMajor? m2 then
-        equal' comparator vecComparator (flipped m1) m2;
-    elif isSparse? m1 != isSparse? m2 then
-        if isSparse? m1 then
-            equal' comparator vecComparator m1 (toSparse m2)
-        else
-            equal' comparator vecComparator (toSparse m1) m2
-        fi
-    else
-        equal'' comparator vecComparator m1 m2
-    fi;
-
-// Compare matrices using the given comparator for individual cells.
-// Note that matrices with different storage order but the same
-// contents are equal, although comparing them is slow.
-//!!! Document the fact that sparse matrices can only be equal if they
-// have the same set of non-zero cells (regardless of comparator used)
-equalUnder comparator =
-    equal' comparator (vec.equalUnder comparator);
-
-equal =
-    equal' (==) vec.equal;
-
-newMatrix type data = //!!! NB does not copy data
-   (tagger = case type of RowMajor (): DenseRows; ColumnMajor (): DenseCols esac;
-    if empty? data or vec.empty? (head data)
-    then zeroSizeMatrix ()
-    else tagger (array data)
-    fi);
-
-newRowVector data = //!!! NB does not copy data
-    DenseRows (array [data]);
-
-newColumnVector data = //!!! NB does not copy data
-    DenseCols (array [data]);
-
-denseLinearOp op m1 m2 =
-    if isRowMajor? m1 then
-        newMatrix (typeOf m1) 
-           (map2 do c1 c2: op c1 c2 done (asRows m1) (asRows m2));
-    else
-        newMatrix (typeOf m1) 
-           (map2 do c1 c2: op c1 c2 done (asColumns m1) (asColumns m2));
-    fi;
-
-sparseSumOrDifference op m1 m2 =
-   (h = [:];
-    for (enumerate m1) do { i, j, v }:
-        if not (i in h) then h[i] := [:] fi;
-        h[i][j] := v;
-    done;
-    for (enumerate m2) do { i, j, v }:
-        if not (i in h) then h[i] := [:] fi;
-        if j in h[i] then h[i][j] := op h[i][j] v;
-        else h[i][j] := op 0 v;
-        fi;
-    done;
-    entries = concat
-       (map do i:
-            kk = keys h[i];
-            map2 do j v: { i, j, v } done kk (map (at h[i]) kk)
-            done (keys h));
-    makeSparse (typeOf m1) (size m1) entries);
-
-sum' m1 m2 =
-    if (size m1) != (size m2)
-    then failWith "Matrices are not the same size: \(size m1), \(size m2)";
-    elif isSparse? m1 and isSparse? m2 then
-        sparseSumOrDifference (+) m1 m2;
-    else
-        add2 v1 v2 = bf.add [v1,v2];
-        denseLinearOp add2 m1 m2;
-    fi;
-
-difference m1 m2 =
-    if (size m1) != (size m2)
-    then failWith "Matrices are not the same size: \(size m1), \(size m2)";
-    elif isSparse? m1 and isSparse? m2 then
-        sparseSumOrDifference (-) m1 m2;
-    else
-        denseLinearOp bf.subtract m1 m2;
-    fi;
-
-scaled factor m =
-    if isSparse? m then
-        makeSparse (typeOf m) (size m)
-           (map do { i, j, v }: { i, j, v = factor * v } done (enumerate m))
-    elif isRowMajor? m then
-        newMatrix (typeOf m) (map (bf.scaled factor) (asRows m));
-    else
-        newMatrix (typeOf m) (map (bf.scaled factor) (asColumns m));
-    fi;
-
-abs' m =
-    if isSparse? m then
-        makeSparse (typeOf m) (size m)
-           (map do { i, j, v }: { i, j, v = abs v } done (enumerate m))
-    elif isRowMajor? m then
-        newMatrix (typeOf m) (map bf.abs (asRows m));
-    else
-        newMatrix (typeOf m) (map bf.abs (asColumns m));
-    fi;
-
-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
-               (enumerate m))
-    else
-        vfilter = vec.fromList . (map do i: if f i then i else 0 fi done) . vec.list;
-        if isRowMajor? m then
-            newMatrix (typeOf m) (map vfilter (asRows m));
-        else
-            newMatrix (typeOf m) (map vfilter (asColumns m));
-        fi;
-    fi;
-
-sparseProductLeft size m1 m2 =
-   ({ values, indices, pointers } = case m1 of
-         SparseCSR d: d;
-         SparseCSC d: d;
-         _: failWith "sparseProductLeft called for non-sparse m1";
-         esac;
-    rows = isRowMajor? m1;
-    data = array (map \(new double[size.rows]) [1..size.columns]);
-    for [0..size.columns - 1] do j':
-        c = getColumn j' m2;
-        var p = 0;
-        for [0..length indices - 1] do ix:
-            ix == pointers[p+1] loop (p := p + 1);
-            i = if rows then p else indices[ix] fi;
-            j = if rows then indices[ix] else p fi;
-            data[j'][i] := data[j'][i] + (vec.at values ix) * (vec.at c j);
-        done;
-    done;
-    DenseCols (array (map vec.vector (list data))));
-
-sparseProductRight size m1 m2 =
-   ({ values, indices, pointers } = case m2 of
-         SparseCSR d: d;
-         SparseCSC d: d;
-         _: failWith "sparseProductLeft called for non-sparse m1";
-         esac;
-    rows = isRowMajor? m2;
-    data = array (map \(new double[size.columns]) [1..size.rows]);
-    for [0..size.rows - 1] do i':
-        r = getRow i' m1;
-        var p = 0;
-        for [0..length indices - 1] do ix:
-            ix == pointers[p+1] loop (p := p + 1);
-            i = if rows then p else indices[ix] fi;
-            j = if rows then indices[ix] else p fi;
-            data[i'][j] := data[i'][j] + (vec.at values ix) * (vec.at r i);
-        done;
-    done;
-    DenseRows (array (map vec.vector (list data))));
-
-sparseProduct size m1 m2 =
-    case m2 of
-    SparseCSC d:
-       ({ values, indices, pointers } = case m1 of
-            SparseCSR d1: d1;
-            SparseCSC d1: d1;
-            _: failWith "sparseProduct called for non-sparse matrices";
-            esac;
-        rows = isRowMajor? m1;
-        var p = 0;
-        pindices = new int[length indices];
-        for [0..length indices - 1] do ix:
-            ix == pointers[p+1] loop (p := p + 1);
-            pindices[ix] := p;
-        done;
-        entries =
-           (map do j':
-                cs = sparseSlice j' d;
-                hin = mapIntoHash
-                   (at cs.indices) (vec.at cs.values)
-                   [0..length cs.indices - 1];
-                hout = [:];
-                for [0..length indices - 1] do ix:
-                    i = if rows then pindices[ix] else indices[ix] fi;
-                    j = if rows then indices[ix] else pindices[ix] fi;
-                    if j in hin then
-                        p = (vec.at values ix) * hin[j];
-                        hout[i] := p + (if i in hout then hout[i] else 0 fi);
-                    fi;
-                done;
-                map do i:
-                    { i, j = j', v = hout[i] }
-                done (keys hout);
-            done (nonEmptySlices d));
-        makeSparse (ColumnMajor ()) size (concat entries));
-    SparseCSR _:
-        sparseProduct size m1 (flipped m2);
-     _: failWith "sparseProduct called for non-sparse matrices";
-    esac;
-
-denseProduct size m1 m2 =
-   (data = array (map \(new double[size.rows]) [1..size.columns]);
-    for [0..size.rows - 1] do i:
-        row = getRow i m1;
-        for [0..size.columns - 1] do j:
-            data[j][i] := bf.sum (bf.multiply row (getColumn j m2));
-        done;
-    done;
-    DenseCols (array (map vec.vector (list data))));
-
-product m1 m2 =
-    if (size m1).columns != (size m2).rows
-    then failWith "Matrix dimensions incompatible: \(size m1), \(size m2) (\((size m1).columns) != \((size m2).rows))";
-    else 
-        size = { rows = (size m1).rows, columns = (size m2).columns };
-        if isSparse? m1 then
-            if isSparse? m2 then
-                sparseProduct size m1 m2
-            else
-                sparseProductLeft size m1 m2
-            fi
-        elif isSparse? m2 then
-            sparseProductRight size m1 m2
-        else
-            denseProduct size m1 m2
-        fi;
-    fi;
-
-entryWiseProduct m1 m2 = // or element-wise, or Hadamard product
-//!!! todo: faster, sparse version, units
-    if (size m1) != (size m2)
-    then failWith "Matrices are not the same size: \(size m1), \(size m2)";
-    else generate do row col: at' m1 row col * at' m2 row col done (size m1);
-    fi;
-
-concatAgainstGrain tagger getter counter mm =
-   (n = counter (size (head mm));
-    tagger (array
-       (map do i:
-           vec.concat (map (getter i) mm)
-           done [0..n-1])));
-
-concatWithGrain tagger getter counter mm =
-    tagger (array
-       (concat
-           (map do m:
-               n = counter (size m);
-               map do i: getter i m done [0..n-1]
-               done mm)));
-
-sparseConcat direction first mm =
-   (dimension d f = if direction == d then sum (map f mm) else f first fi;
-    rows = dimension (Vertical ()) height;
-    columns = dimension (Horizontal ()) width;
-    entries ioff joff ui uj mm acc =
-        case mm of 
-        m::rest:
-            entries
-               (ioff + ui * height m)
-               (joff + uj * width m)
-                ui uj rest
-               ((map do { i, j, v }: { i = i + ioff, j = j + joff, v }
-                 done (enumerate m)) ++ acc);
-         _: acc;
-        esac;
-    makeSparse (typeOf first) { rows, columns }
-        if direction == Vertical () then entries 0 0 1 0 mm []
-        else entries 0 0 0 1 mm [] fi);
-
-checkDimensionsFor direction first mm =
-   (counter = if direction == Horizontal () then (.rows) else (.columns) fi;
-    n = counter (size first);
-    if not (all id (map do m: counter (size m) == n done mm)) then
-        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 is obviously not lazy (unlike std module)
-    case length mm of
-    0: zeroSizeMatrix ();
-    1: head mm;
-    _:
-        first = head mm;
-        checkDimensionsFor direction first mm;
-        if all isSparse? mm then
-            sparseConcat direction first mm
-        else
-            row = isRowMajor? first;
-            // horizontal, row-major: against grain with rows
-            // horizontal, col-major: with grain with cols
-            // vertical, row-major: with grain with rows
-            // vertical, col-major: against grain with cols
-            case direction of
-            Horizontal ():
-                if row then concatAgainstGrain DenseRows getRow (.rows) mm;
-                else concatWithGrain DenseCols getColumn (.columns) mm;
-                fi;
-            Vertical ():
-                if row then concatWithGrain DenseRows getRow (.rows) mm;
-                else concatAgainstGrain DenseCols getColumn (.columns) mm;
-                fi;
-            esac;
-        fi;
-    esac;
-
-//!!! doc this filter -- zero-size elts are ignored
-concat direction mm =
-    concat' direction (filter do mat: not (empty?' 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 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 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 =
-   (if newsize == (size m) then
-        m
-    elif isSparse? m then
-        // don't call makeSparse directly: want to discard
-        // out-of-range cells
-        newSparseMatrix (typeOf m) newsize (enumerateSparse m)
-    elif (height m) == 0 or (width m) == 0 then
-        zeroMatrixWithTypeOf m newsize;
-    else
-        growrows = newsize.rows - (height m);
-        growcols = newsize.columns - (width m);
-        rowm = isRowMajor? m;
-        resizedTo newsize
-            if rowm and growrows < 0 then
-                rowSlice m 0 newsize.rows
-            elif (not rowm) and growcols < 0 then 
-                columnSlice m 0 newsize.columns
-            elif growrows < 0 then 
-                rowSlice m 0 newsize.rows
-            elif growcols < 0 then 
-                columnSlice m 0 newsize.columns
-            else
-                if growrows > 0 then
-                    concat (Vertical ())
-                       [m, zeroMatrixWithTypeOf m ((size m) with { rows = growrows })]
-                else
-                    concat (Horizontal ())
-                       [m, zeroMatrixWithTypeOf m ((size m) with { columns = growcols })]
-                fi
-            fi
-    fi);
-
-{
-    size,
-    width,
-    height,
-    density,
-    nonZeroValues,
-    at = at',
-    getColumn,
-    getRow,
-    isRowMajor?,
-    isSparse?,
-    generate,
-    constMatrix,
-    randomMatrix,
-    zeroMatrix,
-    identityMatrix,
-    zeroSizeMatrix,
-    empty? = empty?',
-    equal,
-    equalUnder,
-    transposed,
-    flipped,
-    toRowMajor,
-    toColumnMajor,
-    toSparse,
-    toDense,
-    scaled,
-    resizedTo,
-    asRows,
-    asColumns,
-    sum = sum',
-    difference,
-    abs = abs',
-    filter = filter',
-    product,
-    entryWiseProduct,
-    concat,
-    rowSlice,
-    columnSlice,
-    newMatrix,
-    newRowVector,
-    newColumnVector,
-    newSparseMatrix,
-    enumerate
-}
-as
-{
-//!!! check whether these are right to be .selector rather than just selector
-
-    size is matrix -> { .rows is number, .columns is number },
-    width is matrix -> number,
-    height is matrix -> number,
-    density is matrix -> number,
-    nonZeroValues is matrix -> number,
-    at is matrix -> number -> number -> number,
-    getColumn is number -> matrix -> vector,
-    getRow is number -> matrix -> vector,
-    isRowMajor? is matrix -> boolean,
-    isSparse? is matrix -> boolean,
-    generate is (number -> number -> number) -> { .rows is number, .columns is number } -> matrix,
-    constMatrix is number -> { .rows is number, .columns is number } -> matrix,
-    randomMatrix is { .rows is number, .columns is number } -> matrix,
-    zeroMatrix is { .rows is number, .columns is number } -> matrix, 
-    identityMatrix is { .rows is number, .columns is number } -> matrix, 
-    zeroSizeMatrix is () -> matrix,
-    empty? is matrix -> boolean,
-    equal is matrix -> matrix -> boolean,
-    equalUnder is (number -> number -> boolean) -> matrix -> matrix -> boolean,
-    transposed is matrix -> matrix,
-    flipped is matrix -> matrix, 
-    toRowMajor is matrix -> matrix, 
-    toColumnMajor is matrix -> matrix,
-    toSparse is matrix -> matrix,
-    toDense is matrix -> matrix,
-    scaled is number -> matrix -> matrix,
-    thresholded is number -> matrix -> matrix,
-    resizedTo is { .rows is number, .columns is number } -> matrix -> matrix,
-    asRows is matrix -> list<vector>, 
-    asColumns is matrix -> list<vector>,
-    sum is matrix -> matrix -> matrix,
-    difference is matrix -> matrix -> matrix,
-    abs is matrix -> matrix,
-    filter is (number -> boolean) -> matrix -> matrix,
-    product is matrix -> matrix -> matrix,
-    entryWiseProduct is matrix -> matrix -> matrix,
-    concat is (Horizontal () | Vertical ()) -> list<matrix> -> matrix,
-    rowSlice is matrix -> number -> number -> matrix, 
-    columnSlice is matrix -> number -> number -> matrix,
-    newMatrix is (ColumnMajor () | RowMajor ()) -> list<vector> -> matrix, 
-    newRowVector is vector -> matrix, 
-    newColumnVector is vector -> matrix,
-    newSparseMatrix is (ColumnMajor () | RowMajor ()) -> { .rows is number, .columns is number } -> list<{ .i is number, .j is number, .v is number }> -> matrix,
-    enumerate is matrix -> list<{ .i is number, .j is number, .v is number }>
-}
-
--- a/may/matrix/test/speedtest.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
-
-program may.matrix.test.speedtest;
-
-mat = load may.matrix;
-vec = load may.vector;
-
-{ compare, compareUsing, time } = load may.test.test;
-
-norec time f = time "" f;
-
-compareMatrices = compareUsing mat.equal;
-
-makeMatrices sz sparsity =
-   (print "Making \(sz) * \(sz) random matrix...";
-    m = time \(mat.randomMatrix { rows = sz, columns = sz });
-    makeSparse () = 
-       (print "Making \(sparsity * 100)% sparse version (as dense matrix)...";
-        t = time \(mat.filter (> sparsity) m);
-        println "Reported density: \(mat.density t) (non-zero values: \(mat.nonZeroValues t))";
-        print "Converting to sparse matrix...";
-        s = time \(mat.toSparse t);
-        println "Reported density: \(mat.density s) (non-zero values: \(mat.nonZeroValues s))";
-        s);
-    s = makeSparse ();
-    println "Making types:";
-    print "Col-major dense...";
-    cmd = time \(mat.toColumnMajor m);
-    print "Row-major dense...";
-    rmd = time \(mat.toRowMajor m);
-    print "Col-major sparse...";
-    cms = time \(mat.toColumnMajor s);
-    print "Row-major sparse...";
-    rms = time \(mat.toRowMajor s);
-    println "";
-    { cmd, rmd, cms, rms });
-
-println "\nR * M multiplies:\n";
-
-sz = 2000;
-sparsity = 0.95;
-
-{ cmd, rmd, cms, rms } = makeMatrices sz sparsity;
-
-row = mat.newRowVector (vec.fromList (map \(Math#random()) [1..sz]));
-col = mat.newColumnVector (vec.fromList (map \(Math#random()) [1..sz]));
-
-print "R * CMD... ";
-a = (time \(mat.product row cmd));
-
-print "R * RMD... ";
-b = (time \(mat.product row rmd));
-
-print "R * CMS... ";
-c = (time \(mat.product row cms));
-
-print "R * RMS... ";
-d = (time \(mat.product row rms));
-
-println "\nChecking results: \(compareMatrices a b) \(compareMatrices c d)";
-
-println "\nM * C multiplies:\n";
-
-print "CMD * C... ";
-a = (time \(mat.product cmd col));
-
-print "RMD * C... ";
-b = (time \(mat.product rmd col));
-
-print "CMS * C... ";
-c = (time \(mat.product cms col));
-
-print "RMS * C... ";
-d = (time \(mat.product rms col));
-
-println "\nChecking results: \(compareMatrices a b) \(compareMatrices c d)";
-
-reportOn m = 
-   (print "                                 ";
-    println "isSparse: \(mat.isSparse? m), density \(mat.density m)");
-
-println "\nM * M multiplies (and a few sums):\n";
-
-sz = 500;
-
-{ cmd, rmd, cms, rms } = makeMatrices sz sparsity;
-
-print "CMS * CMD... ";
-reportOn (time \(mat.product cms cmd));
-
-print "CMS * RMD... ";
-reportOn (time \(mat.product cms rmd));
-
-print "RMS * CMD... ";
-reportOn (time \(mat.product rms cmd));
-
-print "RMS * RMD... ";
-reportOn (time \(mat.product rms rmd));
-
-println "";
-
-print "CMD * CMS... ";
-reportOn (time \(mat.product cmd cms));
-
-print "CMD * RMS... ";
-reportOn (time \(mat.product cmd rms));
-
-print "RMD * CMS... ";
-reportOn (time \(mat.product rmd cms));
-
-print "RMD * RMS... ";
-reportOn (time \(mat.product rmd rms));
-
-println "";
-
-print "CMS * CMS... ";
-reportOn (time \(mat.product cms cms));
-
-print "CMS * RMS... ";
-reportOn (time \(mat.product cms rms));
-
-print "RMS * CMS... ";
-reportOn (time \(mat.product rms cms));
-
-print "RMS * RMS... ";
-reportOn (time \(mat.product rms rms));
-
-println "";
-
-print "CMD + CMD... ";
-reportOn (time \(mat.sum cmd cmd));
-
-print "CMD + RMD... ";
-reportOn (time \(mat.sum cmd rmd));
-
-print "RMD + CMD... ";
-reportOn (time \(mat.sum rmd cmd));
-
-print "RMD + RMD... ";
-reportOn (time \(mat.sum rmd rmd));
-
-println "";
-
-print "CMS + CMS... ";
-reportOn (time \(mat.sum cms cms));
-
-print "CMS + RMS... ";
-reportOn (time \(mat.sum cms rms));
-
-print "RMS + CMS... ";
-reportOn (time \(mat.sum rms cms));
-
-print "RMS + RMS... ";
-reportOn (time \(mat.sum rms rms));
-
-println "";
-
-print "CMD * CMD... ";
-reportOn (time \(mat.product cmd cmd));
-
-print "CMD * RMD... ";
-reportOn (time \(mat.product cmd rmd));
-
-print "RMD * CMD... ";
-reportOn (time \(mat.product rmd cmd));
-
-print "RMD * RMD... ";
-reportOn (time \(mat.product rmd rmd));
-
-println "\nLarge sparse M * M multiplies and adds:\n";
-
-sz = 5000000;
-nnz = 10000;
-
-print "Calculating \(nnz) non-zero entry records...";
-entries = time \(e = map \({ i = int (Math#random() * sz), 
-                             j = int (Math#random() * sz),
-                             v = Math#random() }) [1..nnz];
-                 \() (length e); // make sure list non-lazy for timing purposes
-                 e);
-
-print "Making \(sz) * \(sz) random matrix with \(nnz) entries...";
-rms = time \(mat.newSparseMatrix (RowMajor ()) { rows = sz, columns = sz }
-             entries);
-println "Reported density: \(mat.density rms) (non-zero values: \(mat.nonZeroValues rms))";
-
-print "Making col-major copy...";
-cms = time \(mat.toColumnMajor rms);
-println "Reported density: \(mat.density cms) (non-zero values: \(mat.nonZeroValues cms))";
-
-println "";
-
-print "CMS * CMS... ";
-reportOn (time \(mat.product cms cms));
-
-print "CMS * RMS... ";
-reportOn (time \(mat.product cms rms));
-
-print "RMS * CMS... ";
-reportOn (time \(mat.product rms cms));
-
-print "RMS * RMS... ";
-reportOn (time \(mat.product rms rms));
-
-println "";
-
-print "CMS + CMS... ";
-reportOn (time \(mat.sum cms cms));
-
-print "CMS + RMS... ";
-reportOn (time \(mat.sum cms rms));
-
-print "RMS + CMS... ";
-reportOn (time \(mat.sum rms cms));
-
-print "RMS + RMS... ";
-reportOn (time \(mat.sum rms rms));
-
-();
--- a/may/matrix/test/test_matrix.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,575 +0,0 @@
-
-module may.matrix.test.test_matrix;
-
-mat = load may.matrix;
-vec = load may.vector;
-
-load may.vector.type;
-load may.matrix.type;
-
-import yeti.lang: FailureException;
-
-{ compare, compareUsing } = load may.test.test;
-
-compareMatrices = compareUsing mat.equal;
-
-makeTests name flipper =
-   (constMatrix n s = flipper (mat.constMatrix n s);
-    zeroMatrix s = flipper (mat.zeroMatrix s);
-    randomMatrix s = flipper (mat.randomMatrix s);
-    identityMatrix s = flipper (mat.identityMatrix s);
-    generate f s = flipper (mat.generate f s);
-    newMatrix t d = flipper (mat.newMatrix t (map vec.fromList d));
-[
-
-"constMatrixEmpty-\(name)": \(
-    m = constMatrix 2 { rows = 0, columns = 0 };
-    compare (mat.size m) { columns = 0, rows = 0 }
-),
-
-"constMatrixEmpty2-\(name)": \(
-    compare (mat.size (constMatrix 2 { rows = 0, columns = 4 })) { columns = 0, rows = 0 } and
-        compare (mat.size (constMatrix 2 { rows = 4, columns = 0 })) { columns = 0, rows = 0 }
-),
-
-"constMatrix-\(name)": \(
-    m = constMatrix 2 { rows = 3, columns = 4 };
-    compare (mat.size m) { columns = 4, rows = 3 } and
-        all id (map do row: compare (vec.list (mat.getRow row m)) [2,2,2,2] done [0..2]) and
-        all id (map do col: compare (vec.list (mat.getColumn col m)) [2,2,2] done [0..3])
-),
-
-"randomMatrixEmpty-\(name)": \(
-    m = randomMatrix { rows = 0, columns = 0 };
-    compare (mat.size m) { columns = 0, rows = 0 }
-),
-
-"randomMatrix-\(name)": \(
-    m = randomMatrix { rows = 3, columns = 4 };
-    compare (mat.size m) { columns = 4, rows = 3 }
-),
-
-"zeroMatrixEmpty-\(name)": \(
-    m = zeroMatrix { rows = 0, columns = 0 };
-    compare (mat.size m) { columns = 0, rows = 0 }
-),
-
-"zeroMatrix-\(name)": \(
-    m = zeroMatrix { rows = 3, columns = 4 };
-    compare (mat.size m) { columns = 4, rows = 3 } and
-        all id (map do row: compare (vec.list (mat.getRow row m)) [0,0,0,0] done [0..2]) and
-        all id (map do col: compare (vec.list (mat.getColumn col m)) [0,0,0] done [0..3])
-),
-
-"identityMatrixEmpty-\(name)": \(
-    m = identityMatrix { rows = 0, columns = 0 };
-    compare (mat.size m) { columns = 0, rows = 0 }
-),
-
-"identityMatrix-\(name)": \(
-    m = identityMatrix { rows = 3, columns = 4 };
-    compare (mat.size m) { columns = 4, rows = 3 } and
-        all id (map do row: compare (vec.list (mat.getRow row m)) [1,1,1,1] done [0..2]) and
-        all id (map do col: compare (vec.list (mat.getColumn col m)) [1,1,1] done [0..3])
-),
-
-"generateEmpty-\(name)": \(
-    m = generate do row col: 0 done { rows = 0, columns = 0 };
-    compare (mat.size m) { columns = 0, rows = 0 }
-),
-
-"generate-\(name)": \(
-    m = generate do row col: row * 10 + col done { rows = 2, columns = 3 };
-    compare (vec.list (mat.getRow 0 m)) [0,1,2] and
-        compare (vec.list (mat.getRow 1 m)) [10,11,12]
-),
-
-"widthAndHeight-\(name)": \(
-    m = constMatrix 2 { rows = 3, columns = 4 };
-    compare (mat.size m) { columns = mat.width m, rows = mat.height m }
-),
-
-"equal-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
-    n = m;
-    p = newMatrix (RowMajor ()) [[1,0,3],[4,5,6]];
-    q = newMatrix (ColumnMajor ()) [[1,0,3],[4,5,6]];
-    r = newMatrix (ColumnMajor ()) [[1,4],[0,5]];
-    compareMatrices m n and
-        compareMatrices m p and
-        compareMatrices n p and
-        not mat.equal m q and
-        not mat.equal m r
-),
-
-"equalUnder-\(name)": \(
-    p = newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]];
-    q = newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]];
-    r = newMatrix (ColumnMajor ()) [[4,3,1],[3,1,2]];
-    s = newMatrix (ColumnMajor ()) [[1,4,5],[6,7,8]];
-    t = newMatrix (ColumnMajor ()) [[1,4,5],[6,7,9]];
-    mat.equalUnder (==) p p and
-        mat.equalUnder (==) p q and
-        mat.equalUnder (!=) p r and
-        mat.equalUnder do a b: a % 2 == b % 2 done p s and
-        not mat.equalUnder do a b: a % 2 == b % 2 done p t
-),
-
-"at-\(name)": \(
-    generator row col = row * 10 + col;
-    m = generate generator { rows = 2, columns = 3 };
-    all id
-       (map do row: all id
-           (map do col: mat.at m row col == generator row col done [0..2])
-            done [0..1])
-),
-
-"transposedEmpty-\(name)": \(
-    compare (mat.size (mat.transposed (constMatrix 2 { rows = 0, columns = 0 }))) { columns = 0, rows = 0 } and
-        compare (mat.size (mat.transposed (constMatrix 2 { rows = 0, columns = 4 }))) { columns = 0, rows = 0 } and
-        compare (mat.size (mat.transposed (constMatrix 2 { rows = 4, columns = 0 }))) { columns = 0, rows = 0 }
-),
-
-"transposedSize-\(name)": \(
-    compare (mat.size (mat.transposed (constMatrix 2 { rows = 3, columns = 4 }))) { columns = 3, rows = 4 }
-),
-
-"transposed-\(name)": \(
-    generator row col = row * 10 + col;
-    m = generate generator { rows = 2, columns = 3 };
-    m' = mat.transposed m;
-    all id
-       (map do row: all id
-           // like at test, but with col/row flipped
-           (map do col: mat.at m' col row == generator row col done [0..2])
-            done [0..1])
-),
-
-"transposed-back-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]];
-    compareMatrices m (mat.transposed (mat.transposed m)) and
-        not mat.equal m (mat.transposed m);
-),
-
-"flipped-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
-    m' = mat.flipped m;
-    m'' = newMatrix (RowMajor ()) [[1,0,3],[4,5,6]];
-    compareMatrices m m' and compareMatrices m m'' and compareMatrices m' m'';
-),
-
-"flipped-back-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
-    compareMatrices m (mat.flipped (mat.flipped m));
-),
-
-"flipped-empty-\(name)": \(
-    m = constMatrix 2 { rows = 0, columns = 4 };
-    compareMatrices (mat.flipped m) (mat.flipped (constMatrix 0 { rows = 0, columns = 0 }));
-),
-
-"toRowMajor-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
-    m' = mat.toRowMajor m;
-    m'' = newMatrix (RowMajor ()) [[1,0,3],[4,5,6]];
-    m''' = mat.toRowMajor m'';
-    compareMatrices m m' and compareMatrices m m'' and compareMatrices m' m''
-        and compareMatrices m m''';
-),
-
-"toColumnMajor-\(name)": \(
-    m = newMatrix (RowMajor ()) [[1,4],[0,5],[3,6]];
-    m' = mat.toColumnMajor m;
-    m'' = newMatrix (ColumnMajor ()) [[1,0,3],[4,5,6]];
-    m''' = mat.toColumnMajor m'';
-    compareMatrices m m' and compareMatrices m m'' and compareMatrices m' m''
-        and compareMatrices m m''';
-),
-
-"scaled-\(name)": \(
-    compareMatrices
-       (mat.scaled 0.5 (constMatrix 2 { rows = 3, columns = 4 }))
-       (constMatrix 1 { rows = 3, columns = 4 }) and
-       compareMatrices
-          (mat.scaled 0.5 (constMatrix (-3) { rows = 3, columns = 4 }))
-          (constMatrix (-1.5) { rows = 3, columns = 4 }) and
-       compareMatrices
-          (mat.scaled 0.5 (constMatrix 2 { rows = 0, columns = 2 }))
-          (constMatrix 5 { rows = 0, columns = 0 })
-),
-
-"sum-\(name)": \(
-    compareMatrices
-       (mat.sum (constMatrix 2 { rows = 3, columns = 4 })
-                (constMatrix 1 { rows = 3, columns = 4 }))
-       (constMatrix 3 { rows = 3, columns = 4 })
-),
-
-"sumFail-\(name)": \(
-    try 
-      \() (mat.sum (constMatrix 2 { rows = 3, columns = 4 })
-                   (constMatrix 1 { rows = 3, columns = 5 }));
-        false;
-    catch FailureException e:
-        true
-    yrt
-),
-
-"sparseSum-\(name)": \(
-    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = 0, j = 0, v = 1 },
-        { i = 0, j = 2, v = 2 },
-        { i = 1, j = 1, v = 4 },
-    ];
-    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = 0, j = 1, v = 7 },
-        { i = 1, j = 0, v = 5 },
-        { i = 1, j = 1, v = -4 }, // NB this means [1,1] -> 0, sparse zero
-    ];
-    tot = mat.sum s t;
-    mat.isSparse? tot and
-        compareMatrices tot (mat.sum (mat.toDense s) t) and
-        compareMatrices tot (mat.sum (mat.toDense s) (mat.toDense t)) and
-        compareMatrices tot (mat.sum s (mat.toDense t)) and
-        compareMatrices tot 
-           (mat.newSparseMatrix (RowMajor ()) { rows = 2, columns = 3 } [
-               { i = 0, j = 0, v = 1 },
-               { i = 0, j = 1, v = 7 },
-               { i = 0, j = 2, v = 2 },
-               { i = 1, j = 0, v = 5 },
-            ]) and
-        compare (mat.density tot) (4/6)
-),
-
-"difference-\(name)": \(
-    compareMatrices
-       (mat.difference (constMatrix 2 { rows = 3, columns = 4 })
-                       (constMatrix 1 { rows = 3, columns = 4 }))
-       (constMatrix 1 { rows = 3, columns = 4 })
-),
-
-"differenceFail-\(name)": \(
-    try 
-      \() (mat.difference (constMatrix 2 { rows = 3, columns = 4 })
-                          (constMatrix 1 { rows = 3, columns = 5 }));
-        false;
-    catch FailureException e:
-        true
-    yrt
-),
-
-"sparseDifference-\(name)": \(
-    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = 0, j = 0, v = 1 },
-        { i = 0, j = 2, v = 2 },
-        { i = 1, j = 1, v = 4 },
-    ];
-    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = 0, j = 1, v = 7 },
-        { i = 1, j = 0, v = 5 },
-        { i = 1, j = 1, v = 6 },
-    ];
-    diff = mat.difference s t;
-    mat.isSparse? diff and
-        compareMatrices diff (mat.difference (mat.toDense s) t) and
-        compareMatrices diff (mat.difference (mat.toDense s) (mat.toDense t)) and
-        compareMatrices diff (mat.difference s (mat.toDense t)) and
-        compareMatrices diff 
-           (mat.newSparseMatrix (RowMajor ()) { rows = 2, columns = 3 } [
-               { i = 0, j = 0, v = 1 },
-               { i = 0, j = 1, v = -7 },
-               { i = 0, j = 2, v = 2 },
-               { i = 1, j = 0, v = -5 },
-               { i = 1, j = 1, v = -2 },
-            ])
-),
-
-"abs-\(name)": \(
-    compareMatrices
-       (mat.abs (newMatrix (ColumnMajor ()) [[-1,4],[2,-5],[-3,0]]))
-       (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,0]])
-),
-
-"product-\(name)": \(
-    compareMatrices
-       (mat.product (constMatrix 2 { rows = 4, columns = 2 })
-                    (constMatrix 3 { rows = 2, columns = 3 }))
-       (constMatrix 12 { rows = 4, columns = 3 }) and
-        compareMatrices
-           (mat.product (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]])
-                        (newMatrix (ColumnMajor ()) [[7,9,11],[8,10,12]]))
-           (newMatrix (ColumnMajor ()) [[58,139],[64,154]])
-),
-
-"sparseProduct-\(name)": \(
-    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = 0, j = 0, v = 1 },
-        { i = 0, j = 2, v = 2 },
-        { i = 1, j = 1, v = 4 },
-    ];
-    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 3, columns = 2 } [
-        { i = 0, j = 1, v = 7 },
-        { i = 1, j = 0, v = 5 },
-        { i = 2, j = 0, v = 6 },
-    ];
-    prod = mat.product s t;
-    mat.isSparse? prod and
-        compareMatrices prod (mat.product (mat.toDense s) t) and
-        compareMatrices prod (mat.product (mat.toDense s) (mat.toDense t)) and
-        compareMatrices prod (mat.product s (mat.toDense t)) and
-        compareMatrices prod 
-           (mat.newSparseMatrix (RowMajor ()) { rows = 2, columns = 2 } [
-               { i = 0, j = 0, v = 12 },
-               { i = 0, j = 1, v = 7 },
-               { i = 1, j = 0, v = 20 },
-            ])
-),
-
-"productFail-\(name)": \(
-    try
-      \() (mat.product (constMatrix 2 { rows = 4, columns = 2 })
-                       (constMatrix 3 { rows = 3, columns = 2 }));
-        false;
-    catch FailureException e:
-        true
-    yrt
-),
-
-"resizedTo-\(name)": \(
-    compareMatrices
-       (mat.resizedTo { rows = 2, columns = 2 }
-           (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]]))
-       (newMatrix (ColumnMajor ()) [[1,4],[2,5]]) and
-        compareMatrices
-           (mat.resizedTo { rows = 3, columns = 4 }
-               (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]]))
-           (newMatrix (ColumnMajor ()) [[1,4,0],[2,5,0],[3,6,0],[0,0,0]]) and
-        compareMatrices
-           (mat.resizedTo { rows = 1, columns = 1 }
-               (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]]))
-           (newMatrix (RowMajor ()) [[1]]) and
-        compareMatrices
-           (mat.resizedTo { rows = 2, columns = 3 }
-               (mat.zeroSizeMatrix ()))
-           (newMatrix (RowMajor ()) [[0,0,0],[0,0,0]]) and
-        mat.isSparse?
-           (mat.resizedTo { rows = 1, columns = 1 }
-               (mat.toSparse (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]])))
-),
-
-"zeroSizeMatrix-\(name)": \(
-    compareMatrices
-       (mat.zeroSizeMatrix ())
-       (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)": \(
-    compare
-       (map vec.list
-           (mat.asRows (newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]])))
-        [[1,0,3],[4,5,6]];
-),
-
-"asColumns-\(name)": \(
-    compare
-       (map vec.list
-           (mat.asColumns (newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]])))
-        [[1,4],[0,5],[3,6]];
-),
-
-"concat-horiz-\(name)": \(
-    compareMatrices
-       (mat.concat (Horizontal ()) 
-          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
-           (newMatrix (RowMajor ()) [[3],[6]])])
-       (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]]),
-           mat.toSparse (newMatrix (RowMajor ()) [[3],[6]])];
-    compareMatrices s (newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]]) and
-        compare (mat.isSparse? s) true and
-        compare (mat.density s) (5/6)
-),
-
-"concatFail-horiz-\(name)": \(
-    try
-        \() (mat.concat (Horizontal ()) 
-          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
-           (newMatrix (ColumnMajor ()) [[3],[6]])]);
-        false
-    catch FailureException e:
-        true
-    yrt
-),
-
-"concat-vert-\(name)": \(
-    compareMatrices
-       (mat.concat (Vertical ()) 
-          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
-           (newMatrix (RowMajor ()) [[3,6]])])
-       (newMatrix (ColumnMajor ()) [[1,4,3],[0,5,6]])
-),
-
-"sparseConcat-vert-\(name)": \(
-    s = mat.concat (Vertical ()) 
-          [mat.toSparse (newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
-           mat.toSparse (newMatrix (RowMajor ()) [[3,6]])];
-    compareMatrices s (newMatrix (ColumnMajor ()) [[1,4,3],[0,5,6]]) and
-        compare (mat.isSparse? s) true and
-        compare (mat.density s) (5/6)
-),
-
-"concatFail-vert-\(name)": \(
-    try
-        \() (mat.concat (Vertical ()) 
-          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
-           (newMatrix (RowMajor ()) [[3],[6]])]);
-        false
-    catch FailureException e:
-        true
-    yrt
-),
-
-"rowSlice-\(name)": \(
-    compareMatrices
-       (mat.rowSlice (newMatrix (RowMajor ()) [[1,0],[3,4],[0,6],[7,8]]) 1 3)
-       (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]]) and
-        compareMatrices
-           (mat.columnSlice (newMatrix (RowMajor ()) [[1,0,3,4],[0,6,7,8]]) 2 5)
-           (newMatrix (RowMajor ()) [[3,4],[7,8]])
-),
-
-"density-\(name)": \(
-    compare (mat.density (newMatrix (ColumnMajor ()) [[1,2,0],[0,5,0]])) (3/6) and
-        compare (mat.density (newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]])) (6/6) and
-        compare (mat.density (newMatrix (ColumnMajor ()) [[0,0,0],[0,0,0]])) 0
-),
-
-"nonZeroValues-\(name)": \(
-    compare (mat.nonZeroValues (newMatrix (ColumnMajor ()) [[1,2,0],[0,5,0]])) 3 and
-        compare (mat.nonZeroValues (newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]])) 6 and
-        compare (mat.nonZeroValues (newMatrix (ColumnMajor ()) [[0,0,0],[0,0,0]])) 0
-),
-
-"toSparse-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
-    compareMatrices (mat.toSparse m) m and
-        compareMatrices (mat.toDense (mat.toSparse m)) m and
-        compare (mat.density (mat.toSparse m)) (6/9)
-),
-
-"toDense-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
-    compareMatrices (mat.toDense m) m and
-        compareMatrices (mat.toSparse (mat.toDense m)) m
-),
-
-"filter-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
-    compareMatrices
-       (mat.filter (> 2) m)
-       (newMatrix (ColumnMajor ()) [[0,0,0],[0,0,6],[0,0,3]]) and
-        compare (mat.density (mat.filter (> 2) m)) (2/9)
-),
-
-"newSparseMatrix-\(name)": \(
-    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = 0, j = 0, v = 1 },
-        { i = 0, j = 2, v = 2 },
-        { i = 1, j = 1, v = 4 },
-    ];
-    // If there are zeros in the entries list, they should not end up
-    // in the sparse data
-    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = 0, j = 0, v = 1 },
-        { i = 0, j = 2, v = 0 },
-        { i = 1, j = 1, v = 4 },
-    ];
-    // Any out-of-range or non-integer i, j should be ignored too
-    u = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
-        { i = -1, j = 0, v = 1 },
-        { i = 0, j = 4, v = 3 },
-        { i = 1, j = 1.5, v = 4 },
-    ];
-    compare (mat.density s) (3/6) and
-        compare (mat.density t) (2/6) and
-        compareMatrices s (newMatrix (RowMajor ()) [[1,0,2],[0,4,0]]) and
-        compareMatrices t (newMatrix (RowMajor ()) [[1,0,0],[0,4,0]]) and
-        compareMatrices u (newMatrix (RowMajor ()) [[0,0,0],[0,0,0]])
-),
-
-"enumerate-\(name)": \(
-    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
-    all = [
-        { i = 0, j = 0, v = 1 },
-        { i = 1, j = 0, v = 2 },
-        { i = 2, j = 0, v = 0 },
-        { i = 0, j = 1, v = -1 },
-        { i = 1, j = 1, v = -4 },
-        { i = 2, j = 1, v = 6 },
-        { i = 0, j = 2, v = 0 },
-        { i = 1, j = 2, v = 0 },
-        { i = 2, j = 2, v = 3 },
-    ];
-    sortEntries = 
-        sortBy do a b:
-            if a.i == b.i then a.j < b.j else a.i < b.i fi
-        done;
-    compare
-       (sortEntries (mat.enumerate m))
-       (sortEntries 
-           (if mat.isSparse? m then filter do d: d.v != 0 done all else all fi));
-),
-
-]);
-
-colhash = makeTests "column-dense" id;
-rowhash = makeTests "row-dense" mat.flipped;
-sparsecolhash = makeTests "column-sparse" mat.toSparse;
-
-// there are two possible orders for constructing a sparse row-major
-// matrix from a dense col-major one, so test them both:
-sparserowhash1 = makeTests "row-sparse-a" (mat.toSparse . mat.flipped);
-sparserowhash2 = makeTests "row-sparse-b" (mat.flipped . mat.toSparse);
-
-all = [:];
-for [ colhash, rowhash, sparsecolhash, sparserowhash1, sparserowhash2 ] do h:
-    for (keys h) do k: all[k] := h[k] done;
-done;
-
-all is hash<string, () -> boolean>;
-
-
--- a/may/matrix/type.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-
-module may.matrix.type;
-
-load may.vector.type;
-
-typedef opaque matrix =
-    DenseRows. array<vector> | // array of rows
-    DenseCols. array<vector> | // array of columns
-    SparseCSR. {
-        .values is vector,
-        .indices is array<number>, // column index of each value
-        .pointers is array<number>, // offset of first value in each row
-        .extent is number // max possible index + 1, i.e. number of columns
-        } |
-    SparseCSC. {
-        .values is vector,
-        .indices is array<number>, // row index of each value
-        .pointers is array<number>, // offset of first value in each column
-        .extent is number // max pointers index + 1, i.e. number of rows
-        };
-
-();
-
--- a/may/plot.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-module may.plot;
-
-vec = load may.vector;
-mat = load may.matrix;
-
-{ distinctColour } = load may.plot.colour;
-
-import org.jzy3d.plot3d.builder: Mapper;
-import org.jzy3d.maths: Range, Coord3d;
-import org.jzy3d.chart: Chart, ChartLauncher;
-import org.jzy3d.plot3d.builder: Builder;
-import org.jzy3d.plot3d.builder.concrete: OrthonormalGrid;
-import org.jzy3d.colors.colormaps: ColorMapRainbow;
-import org.jzy3d.colors: ColorMapper, Color;
-import org.jzy3d.plot3d.rendering.canvas: Quality;
-import org.jzy3d.plot3d.rendering.view.modes: ViewPositionMode;
-import org.jzy3d.plot3d.primitives: FlatLine2d, Point;
-
-newMatrixMapper matrix =
-   (class MMapper extends Mapper
-        double f(double x, double y)
-            result = mat.at matrix y x;
-            println "f(\(x),\(y)) -> \(result)";
-            result
-    end;
-    new MMapper());
-
-newMatrixLogMapper matrix =
-   (class MMapper extends Mapper
-        double f(double x, double y)
-            ln (mat.at matrix y x)
-    end;
-    new MMapper());
-
-newMapper mapFunction =
-   (class FMapper extends Mapper
-        double f(double x, double y)
-            mapFunction x y
-    end;
-    new FMapper());
-
-plotMatrix chart colour matrix is ~Chart -> ~Color -> 'a -> () =
-   (mapper = newMatrixMapper matrix;
-    size = mat.size matrix;
-    //!!! doesn't work if either rows or columns is 1
-    xrange = new Range(0, size.columns - 1);
-    yrange = new Range(0, size.rows - 1);
-    grid = new OrthonormalGrid(xrange, size.columns, yrange, size.rows);
-    println "Matrix size: \(size)";
-    surface = Builder#buildOrthonormal(grid, mapper); //??? big?
-    println "Z Bounds: \(surface#getBounds()#getZmin()) -> \(surface#getBounds()#getZmax())";
-    surface#setFaceDisplayed(true);
-    surface#setWireframeDisplayed(true);
-    surface#setWireframeColor(colour);
-    chart#getScene()#getGraph()#add(surface);
-    ());
-
-plotCurve chart colour depth curve is ~Chart -> ~Color -> number -> 'a -> () =
-   (scene = chart#getScene();
-    xx = map (.time) curve;
-    yy = map (.value) curve;
-    line = new FlatLine2d(xx as ~float[], yy as ~float[], depth);
-    line#setWireframeDisplayed(true);
-    line#setWireframeColor(colour);
-    line#setWireframeWidth(2);
-    line#setFaceDisplayed(false);
-    scene#add(line);
-    chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
-/*
-    axes = chart#getAxeLayout();
-    axes#setXAxeLabelDisplayed(false);
-    axes#setYAxeLabelDisplayed(false);
-    axes#setZAxeLabelDisplayed(true);
-    axes#setZAxeLabel("unit goes here"); //!!!
-    axes#setYTickLabelDisplayed(false);
-*/
-    ());
-
-plotSeries chart colour depth { start, step, values } is ~Chart -> ~Color -> number -> 'a -> () =
-   (scene = chart#getScene();
-    xx = map do i: start + step * i done [0..length values - 1];
-    yy = list values;
-    line = new FlatLine2d(xx as ~float[], yy as ~float[], depth);
-    line#setWireframeDisplayed(true);
-    line#setWireframeColor(colour);
-    line#setWireframeWidth(2);
-    line#setFaceDisplayed(false);
-    scene#add(line);
-    chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
-/*
-    axes = chart#getAxeLayout();
-    axes#setXAxeLabelDisplayed(false);
-    axes#setYAxeLabelDisplayed(false);
-    axes#setZAxeLabelDisplayed(true);
-    axes#setZAxeLabel("unit goes here"); //!!!
-    axes#setYTickLabelDisplayed(false);
-*/
-    ());
-
-plot structures =
-   (chart = new Chart(Quality#Nicest);
-    var j = 0;
-    for structures do s:
-        colour = distinctColour j;
-        case s of
-        Grid matrix:
-            plotMatrix chart colour matrix;
-        Curve curve:
-            plotCurve chart colour j curve;
-        Series series:
-            plotSeries chart colour j series;
-        Vector vector:
-            plotSeries chart colour j
-                { start = 0, step = 1, values = vec.list vector };
-        other:
-            failWith "Unable to plot \(other)";
-        esac;
-        j := j + 1;
-    done;
-    ChartLauncher#openChart(chart);
-    chart);
-
-{
-    newMatrixMapper,
-    newMatrixLogMapper,
-    newMapper,
-    plotMatrix, 
-    plotCurve,
-    plotSeries,
-    plot,
-}
-
--- a/may/plot/chart.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-module may.plot.chart;
-
-{ distinctColour } = load may.plot.colour;
-
-import org.jzy3d.plot3d.text.drawable: DrawableTextBillboard, DrawableTextBitmap;
-import org.jzy3d.maths: Range, Coord3d;
-import org.jzy3d.plot3d.primitives: Shape, HistogramBar, FlatLine2d, Polygon, Quad, Point;
-import org.jzy3d.plot3d.primitives.axes.layout.providers: StaticTickProvider, RegularTickProvider;
-import org.jzy3d.plot3d.primitives.axes.layout.renderers: ITickRenderer, TickLabelMap, IntegerTickRenderer;
-import org.jzy3d.chart: Chart, ChartLauncher;
-import org.jzy3d.plot3d.builder: Builder;
-import org.jzy3d.colors: Color;
-import org.jzy3d.plot3d.rendering.canvas: Quality;
-import org.jzy3d.plot3d.rendering.view.modes: ViewPositionMode;
-
-import javax.imageio: ImageIO;
-
-import java.io: File;
-
-newPercentTickRenderer () =
-   (f v = " \(int (v * 100))%";
-    class PercentageTickRenderer extends ITickRenderer
-        String format(double value) f value,
-        String format(float value) f value
-    end;
-    new PercentageTickRenderer());
-
-newPaddedIntTickRenderer () =
-   (f v = "    \(int (v + 0.5))";
-    class PaddedIntTickRenderer extends ITickRenderer
-        String format(double value) f value,
-        String format(float value) f value
-    end;
-    new PaddedIntTickRenderer());
-
-parseOptions options defaultKeys defaultXKeys =
-   (parsed = {
-        var keys = array (sort defaultKeys),
-        var labels = [:],
-        var animated = false,
-        var normalised = false,
-        var unit = "",
-        var xkeys = array (sort defaultXKeys),
-        var saveTo = "",
-        var display = true,
-    };
-    for options
-       \case of
-        Keys kk: parsed.keys := array kk;
-        XKeys xk: parsed.xkeys := array xk;
-        Animated a: parsed.animated := a;
-        Normalised n: parsed.normalised := n;
-        Unit u: parsed.unit := u;
-        Labels ll: parsed.labels := ll;
-        SaveTo file: parsed.saveTo := file;
-        Display d: parsed.display := d;
-        esac;
-    if empty? parsed.labels then
-        parsed.labels := mapIntoHash id id parsed.keys
-    fi;
-    parsed);
-
-newChart opts =
-   (quality = Quality#Fastest;
-    quality#setAnimated(opts.animated);
-//    if opts.display then
-        new Chart(quality);
-//    else
-//        new Chart(quality, "offscreen,640,640");
-//    fi);
-    );
-
-showChart opts chart is 'a -> ~Chart -> () =
-   (if opts.display then
-        \() ChartLauncher#openChart(chart);
-    else
-        \() ChartLauncher#openStaticChart(chart);
-    fi;
-    if opts.saveTo != "" then
-        \() chart#screenshot(opts.saveTo);
-    fi);
-    
-plotBarChart options values =
-   (opts = parseOptions options (keys values) [];
-    chart = newChart opts;
-    var n = length opts.keys;
-    scene = chart#getScene();
-    ticks = new double[n];
-    tickLabels = new TickLabelMap();
-    var i = 0;
-    var x = n - i - 1;
-    total = sum (map do k: if k in values then values[k] else 0 fi done opts.keys);
-    for opts.keys do k:
-        bar = new HistogramBar();
-        v = if k in values then values[k] else 0 fi;
-        v = if opts.normalised and total > 0 then v / total else v fi;
-        bar#setData(new Coord3d(x, 0, 0), v, 0.45, distinctColour i);
-        bar#setWireframeDisplayed(false);
-        scene#add(bar);
-        ticks[i] := i;
-        tickLabels#register(x, opts.labels[k]);
-        i := i + 1;
-        x := x - 1;
-    done;
-    chart#getView()#setViewPoint(new Coord3d(pi/2, 0, 0));
-    axes = chart#getAxeLayout();
-    axes#setXAxeLabelDisplayed(false);
-    axes#setYAxeLabelDisplayed(false);
-    axes#setZAxeLabelDisplayed(true);
-    if opts.normalised then
-        axes#setZAxeLabel("");
-        axes#setZTickRenderer(newPercentTickRenderer ());
-    else
-        axes#setZAxeLabel(opts.unit);
-    fi;
-    axes#setXTickProvider(new StaticTickProvider(ticks));
-    axes#setXTickRenderer(tickLabels);
-    axes#setYTickLabelDisplayed(false);
-    showChart opts chart);
-
-plotLines options values =
-   (opts = parseOptions options (keys values) (keys values[head (keys values)]);
-    chart = newChart opts;
-    scene = chart#getScene();
-    n = length opts.xkeys;
-    var z = 0;
-    for opts.keys do k:
-        v = values[k];
-        x = new float[n];
-        y = new float[n];
-        var i = 0;
-        for opts.xkeys do xk:
-            x[i] := i;
-            y[i] := if xk in v then v[xk] else 0 fi;
-            i := i + 1;
-        done;
-        line = new FlatLine2d(x, y, z);
-        line#setWireframeDisplayed(true);
-        line#setWireframeColor(distinctColour z);
-        line#setWireframeWidth(2);
-        line#setFaceDisplayed(false);
-        scene#add(line);
-        z := z + 1;
-    done;
-    chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
-    axes = chart#getAxeLayout();
-    axes#setXAxeLabelDisplayed(false);
-    axes#setYAxeLabelDisplayed(false);
-    axes#setZAxeLabelDisplayed(true);
-    axes#setZAxeLabel(opts.unit);
-    axes#setYTickLabelDisplayed(false);
-    showChart opts chart);
-
-stack keys xkeys values normalised =
-   (stacked = mapIntoHash id \(mapIntoHash id \{ y0 = 0, y1 = 0 } xkeys) keys;
-    prev = mapIntoHash id \0 xkeys;
-    valueOf k xk =
-        if k in values and xk in values[k] 
-        then values[k][xk] else 0 
-        fi;
-    for xkeys do xk:
-        total = sum (map do k: valueOf k xk done keys);
-        for keys do k:
-            value =
-                if normalised and total > 0
-                then (valueOf k xk) / total
-                else (valueOf k xk)
-                fi;
-            height = prev[xk] + value;
-            stacked[k][xk] := { y0 = prev[xk], y1 = height };
-            prev[xk] := height;
-        done;
-    done;
-    stacked);
-
-newRect x y0 y1 z colour is number -> number -> number -> number -> ~Color -> 'a =
-   (poly = new Quad();
-    poly#add(new Point(new Coord3d(x + 0.5, z, y0)));
-    poly#add(new Point(new Coord3d(x + 0.5, z, y1)));
-    poly#add(new Point(new Coord3d(x - 0.5, z, y1)));
-    poly#add(new Point(new Coord3d(x - 0.5, z, y0)));
-    poly#setWireframeDisplayed(true);
-    poly#setWireframeColor(colour);
-    poly#setFaceDisplayed(true);
-    poly#setColor(colour);
-    poly);
-
-plotStacked options values =
-   (opts = parseOptions options (keys values) (keys values[head (keys values)]);
-    chart = newChart opts;
-    scene = chart#getScene();
-    stacked = stack opts.keys opts.xkeys values opts.normalised;
-    var z = 0;
-    var ty = 0;
-    nxk = length opts.xkeys;
-    xticks = new double[nxk];
-    xtickLabels = new TickLabelMap();
-    for [0..nxk - 1] do x:
-        xticks[x] := x;
-        k = opts.xkeys[x];
-        xtickLabels#register(x, if k in opts.labels then opts.labels[k] else k fi);
-    done;
-    for opts.keys do k:
-        ranges = stacked[k];
-        c = distinctColour z;
-        for [0..nxk - 1] do x:
-            xk = opts.xkeys[x];
-            rect = newRect x ranges[xk].y0 ranges[xk].y1 z c;
-            scene#add(rect);
-        done;
-        text = new DrawableTextBitmap(opts.labels[k], new Coord3d(-(nxk/5 + 0.5), z, ty), c);
-        scene#add(text);
-        z := z - 1;
-        ty := ty + 0.1;
-    done;
-    chart#getView()#setViewPoint(new Coord3d(-pi/2, 0, 0));
-    axes = chart#getAxeLayout();
-    axes#setXAxeLabelDisplayed(false);
-    if nxk < 10 then
-        axes#setXTickProvider(new StaticTickProvider(xticks));
-    fi;
-    axes#setXTickRenderer(xtickLabels);
-    axes#setYAxeLabelDisplayed(false);
-    axes#setZAxeLabelDisplayed(true);
-    if opts.normalised then 
-        axes#setZAxeLabel("");
-        axes#setZTickRenderer(newPercentTickRenderer ());
-    else
-        axes#setZAxeLabel(opts.unit);
-        axes#setZTickRenderer(newPaddedIntTickRenderer ());
-    fi;
-    axes#setYTickLabelDisplayed(false);
-    showChart opts chart);
-
-{
-    plotBarChart,
-    plotLines,
-    stack,
-    plotStacked,
-}
-
--- a/may/plot/colour.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-module may.plot.colour;
-
-import org.jzy3d.colors: Color;
-
-distinctColours = array [
-    { r = 82,  g = 126, b = 154 }, // dark steel blue
-    { r = 161, g = 54,  b = 2   }, // red
-    { r = 207, g = 228, b = 148 }, // grey-green
-    { r = 21,  g = 183, b = 197 }, // light blue
-    { r = 251, g = 116, b = 43  }, // light red
-    { r = 200, g = 125, b = 234 }, // light purple
-    { r = 126, g = 33,  b = 28  }, // dried blood!
-    { r = 188, g = 13,  b = 207 }, // mid purple
-];    
-
-distinctColour n =
-    if n < 0
-    then distinctColour (-n)
-    else
-        rgb = distinctColours[n % (length distinctColours)];
-        new Color(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0);
-    fi;
-
-{
-    distinctColour
-};
-
--- a/may/plot/test/test_plot.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-module may.plot.test.test_plot;
-
-ch = load may.plot.chart;
-
-{ compare } = load may.test.test;
-
-[
-
-"stack": \(
-    compare
-       (ch.stack
-            [ "Conrad", "Alice", "Bob" ]
-            [ "Jan", "Feb", "Mar" ]
-            [ "Alice":  [ "Jan": 3, "Mar": 2 ],
-              "Bob":    [ "Jan": 0, "Feb": 1, "Mar": 4 ],
-              "Conrad": [ "Feb": 2, "Mar": 1 ] ]
-            false)
-        [ "Conrad": [ "Jan": { y0 = 0, y1 = 0 },
-                      "Feb": { y0 = 0, y1 = 2 },
-                      "Mar": { y0 = 0, y1 = 1 } ],
-          "Alice":  [ "Jan": { y0 = 0, y1 = 3 },
-                      "Feb": { y0 = 2, y1 = 2 },
-                      "Mar": { y0 = 1, y1 = 3 } ],
-          "Bob":    [ "Jan": { y0 = 3, y1 = 3 },
-                      "Feb": { y0 = 2, y1 = 3 },
-                      "Mar": { y0 = 3, y1 = 7 } ] ]
-),
-
-"stack-normalised": \(
-    compare
-       (ch.stack
-            [ "Conrad", "Alice", "Bob" ]
-            [ "Jan", "Feb", "Mar" ]
-            [ "Alice":  [ "Jan": 3, "Mar": 2 ],
-              "Bob":    [ "Jan": 0, "Feb": 1, "Mar": 4 ],
-              "Conrad": [ "Feb": 2, "Mar": 1 ] ]
-            true)
-        [ "Conrad": [ "Jan": { y0 = 0, y1 = 0 },
-                      "Feb": { y0 = 0, y1 = 2/3 },
-                      "Mar": { y0 = 0, y1 = 1/7 } ],
-          "Alice":  [ "Jan": { y0 = 0, y1 = 1 },
-                      "Feb": { y0 = 2/3, y1 = 2/3 },
-                      "Mar": { y0 = 1/7, y1 = 3/7 } ],
-          "Bob":    [ "Jan": { y0 = 1, y1 = 1 },
-                      "Feb": { y0 = 2/3, y1 = 1 },
-                      "Mar": { y0 = 3/7, y1 = 1 } ] ]
-),
-
-] is hash<string, () -> boolean>;
-
--- a/may/signal/autocorrelation.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-
-module may.signal.autocorrelation;
-
-acf len series =
-   (a = array series;
-    map do i:
-        sum (map do j:
-                 a[j] * a[j-i]
-                 done [i..length a - 1])
-        done [0..len-1]);
-
-acfNormalised len series =
-   (n = length series;
-    map2 do v i: if n == i then 0 else v / (n - i) fi done
-       (acf len series) [0..len-1]);
-
-acfUnityNormalised len series =
-   (a = acfNormalised len series;
-    max = head (sortBy (>) a);
-    map (/ max) a);
-
-{
-    acf,
-    acfNormalised,
-    acfUnityNormalised,
-}
-
--- a/may/signal/test/test_signal.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-module may.signal.test.test_signal;
-
-{ acf, acfNormalised, acfUnityNormalised } = load may.signal.autocorrelation;
-
-{ compare } = load may.test.test;
-
-[
-
-"unnormalised": \(
-    compare (acf 12 (array [1,0,0, 1,0,0, 1,0,0, 1,0,0]))
-        [4,0,0, 3,0,0, 2,0,0, 1,0,0 ];
-),
-
-"normalised": \(
-    compare (acfNormalised 9 (array [1,0,0, 1,0,0, 1,0,0, 1,0,0]))
-        [4/12,0,0, 3/9,0,0, 2/6,0,0 ];
-),
-
-"normalisedUnity": \(
-    compare (acfUnityNormalised 9 (array [1,0,0, 1,0,0, 1,0,0, 1,0,0]))
-        [1,0,0, 1,0,0, 1,0,0 ];
-),
-
-] is hash<string, () -> boolean>;
--- a/may/signal/test/test_window.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-
-module may.signal.test.test_window;
-
-win = load may.signal.window;
-vec = load may.vector;
-
-{ compare, compareUsing } = load may.test.test;
-
-functions = [
-    Hann () : win.hann,
-    Hamming () : win.hamming,
-    Blackman () : win.blackman,
-    Nuttall () : win.nuttall,
-    BlackmanNuttall () : win.blackmanNuttall,
-    BlackmanHarris () : win.blackmanHarris,
-    Boxcar () : win.boxcar,
-    Bartlett () : win.bartlett,
-];
-
-close aa bb = all id (map2 do a b: (abs (a - b) < 0.0001) done aa bb);
-
-isSymmetric a =
-   (len = (vec.length a);
-    b = if len % 2 == 0 then
-            half = vec.slice a 0 (len/2);
-            vec.concat [half, vec.reverse half];
-        else
-            half = vec.slice a 0 (int (len/2));
-            mid = vec.slice a (int (len/2)) (int (len/2) + 1);
-            vec.concat [half, mid, vec.reverse half];
-        fi;
-    compareUsing close (vec.list a) (vec.list b));
-
-[
-
-"windowFunction": \(
-    all id (map do type:
-        f = functions[type];
-        a = f 10;
-        b = win.windowFunction type [ Symmetric false ] 10;
-        compareUsing close (vec.list a) (vec.list b)
-            or (eprintln "** failed window type: \(type)"; false)
-    done (keys functions));
-),
-
-"symmetric-even": \(
-    len = 10;
-    all id (map do type:
-        f = win.windowFunction type [ Symmetric true ];
-        v = f len;
-        (compare (vec.length v) len and isSymmetric v) or
-            (eprintln "** failed window type: \(type)"; false);
-    done (keys functions));
-),
-
-"symmetric-odd": \(
-    len = 11;
-    all id (map do type:
-        f = win.windowFunction type [ Symmetric true ];
-        v = f len;
-        (compare (vec.length v) len and isSymmetric v) or
-            (eprintln "** failed window type: \(type)"; false);
-    done (keys functions));
-),
-      
-"periodic-even": \(
-    // We can't actually test whether a function is periodic, given
-    // only one cycle of it! But we can make sure that all but the
-    // first sample is symmetric, which is what a symmetric window
-    // becomes when generated in periodic mode
-    len = 10;
-    all id (map do type:
-        f = win.windowFunction type [ Symmetric false ];
-        v = f len;
-        (compare (vec.length v) len and isSymmetric (vec.slice v 1 len)) or
-            (eprintln "** failed window type: \(type)"; false);
-    done (keys functions));
-),
-
-"periodic-odd": \(
-    len = 11;
-    all id (map do type:
-        f = win.windowFunction type [ Symmetric false ];
-        v = f len;
-        (compare (vec.length v) len and isSymmetric (vec.slice v 1 len)) or
-            (eprintln "** failed window type: \(type)"; false);
-    done (keys functions));
-),
-
-"bartlett-periodic": \(
-    compare (vec.list (win.bartlett 1)) [1] and
-       compare (vec.list (win.bartlett 2)) [0,1] and
-       compare (vec.list (win.bartlett 3)) [0,2/3,2/3] and
-       compare (vec.list (win.bartlett 4)) [0,1/2,1,1/2]
-),
-
-"bartlett-symmetric": \(
-    b = win.windowFunction (Bartlett ()) [ Symmetric true ];
-    compare (vec.list (b 1)) [1] and
-       compare (vec.list (b 2)) [0,0] and
-       compare (vec.list (b 3)) [0,1,0] and
-       compare (vec.list (b 4)) [0,2/3,2/3,0] and
-       compare (vec.list (b 5)) [0,1/2,1,1/2,0]
-),
-
-"hann": \(
-    compareUsing close (vec.list (win.hann 10)) [
-        0, 0.0955, 0.3455, 0.6545, 0.9045,
-        1.0000, 0.9045, 0.6545, 0.3455, 0.0955,
-    ] and
-    compareUsing close 
-       (vec.list (win.windowFunction (Hann ()) [ Symmetric true ] 10)) [
-        0, 0.1170, 0.4132, 0.7500, 0.9698,
-        0.9698, 0.7500, 0.4132, 0.1170, 0,
-    ]
-),
-
-"hamming": \(
-    compareUsing close (vec.list (win.hamming 10)) [
-        0.0800, 0.1679, 0.3979, 0.6821, 0.9121,
-        1.0000, 0.9121, 0.6821, 0.3979, 0.1679,
-    ] and
-    compareUsing close 
-       (vec.list (win.windowFunction (Hamming ()) [ Symmetric true ] 10)) [
-        0.0800, 0.1876, 0.4601, 0.7700, 0.9723,
-        0.9723, 0.7700, 0.4601, 0.1876, 0.0800,
-    ]
-),
-
-"blackman": \(
-    compareUsing close (vec.list (win.blackman 10)) [
-        0, 0.0402, 0.2008, 0.5098, 0.8492,
-        1.0000, 0.8492, 0.5098, 0.2008, 0.0402,
-    ] and
-    compareUsing close 
-       (vec.list (win.windowFunction (Blackman ()) [ Symmetric true ] 10)) [
-        0, 0.0509, 0.2580, 0.6300, 0.9511,
-        0.9511, 0.6300, 0.2580, 0.0509, 0,
-    ]
-),
-
-"blackmanHarris": \(
-    compareUsing close (vec.list (win.blackmanHarris 10)) [
-        0.0001, 0.0110, 0.1030, 0.3859, 0.7938,
-        1.0000, 0.7938, 0.3859, 0.1030, 0.0110,
-    ] and
-    compareUsing close 
-       (vec.list (win.windowFunction (BlackmanHarris ()) [ Symmetric true ] 10)) [
-        0.0001, 0.0151, 0.1470, 0.5206, 0.9317,
-        0.9317, 0.5206, 0.1470, 0.0151, 0.0001,
-    ]
-),
-
-"dirac": \(
-    compareUsing close (vec.list (win.dirac 1)) [ 1 ] and
-        compareUsing close (vec.list (win.dirac 5)) [ 0, 0, 1, 0, 0 ] and
-        compareUsing close (vec.list (win.dirac 6)) [ 0, 0, 0, 1, 0, 0 ]
-),
-
-"sinc": \(
-    compareUsing close (vec.list (win.sinc 1 5)) [ 0, 0, 1, 0, 0 ] and
-        compareUsing close (vec.list (win.sinc 2 5)) [ 0, 0, 1, 0, 0 ] and
-        compareUsing close (vec.list (win.sinc 4 7)) [ -0.2122, 0, 0.6366, 1, 0.6366, 0, -0.2122 ] and
-        compareUsing close (vec.list (win.sinc 4 6)) [ -0.2122, 0, 0.6366, 1, 0.6366, 0 ]
-),
-
-"kaiser": \(
-    compareUsing close (vec.list (win.kaiser 4 10)) [
-        0.0885, 0.2943, 0.5644, 0.8216, 0.9789,
-        0.9789, 0.8216, 0.5644, 0.2943, 0.0885
-    ] and
-    compareUsing close (vec.list (win.kaiser 2.5 11)) [
-        0.3040, 0.5005, 0.6929, 0.8546, 0.9622,
-        1.0000, 0.9622, 0.8546, 0.6929, 0.5005, 0.3040
-    ]
-),
-
-"degenerate": \(
-    all id (map do type:
-        f = functions[type];
-        periodic = f;
-        symmetric = win.windowFunction type [ Symmetric true ];
-       (compare (vec.list (periodic 0)) [] and
-        compare (vec.list (periodic 1)) [1] and
-        compare (vec.list (symmetric 0)) [] and
-        compare (vec.list (symmetric 1)) [1])
-            or (eprintln "** failed window type: \(type)"; false)
-    done (keys functions));
-),
-
-] is hash<string, () -> boolean>;
-
--- a/may/signal/window.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +0,0 @@
-module may.signal.window;
-
-vec = load may.vector;
-bf = load may.vector.blockfuncs;
-
-cosineWindowSymmetric a0 a1 a2 a3 n =
-   (n1 = n - 1;
-    vec.fromList
-       (map do i:
-            a0
-            - a1 * cos(2 * pi * i / n1)
-            + a2 * cos(4 * pi * i / n1)
-            - a3 * cos(6 * pi * i / n1)
-            done [0..n1]));
-
-cosineWindowPeriodic a0 a1 a2 a3 n =
-   (vec.fromList
-       (map do i:
-            a0
-            - a1 * cos(2 * pi * i / n)
-            + a2 * cos(4 * pi * i / n)
-            - a3 * cos(6 * pi * i / n)
-            done [0..n-1]));
-                  
-cosineWindow a0 a1 a2 a3 sampling n =
-    if n < 2 then vec.ones n
-    else
-        case sampling of 
-        Symmetric (): cosineWindowSymmetric;
-        Periodic (): cosineWindowPeriodic;
-        esac a0 a1 a2 a3 n;
-    fi;
-
-bartlettSymmetric n =
-    if n < 2 then vec.ones n
-    else
-        vec.fromList
-           (n1 = n - 1;
-            h = int (n1 / 2);
-            concat [
-                map do i:
-                    2 * i / n1
-                    done [0..h],
-                map do i:
-                    2 - (2 * i / n1)
-                    done [h+1..n1]
-                ]);
-    fi;
-
-bartlettPeriodic n = 
-    if n < 2 then vec.ones n
-    else
-        vec.slice (bartlettSymmetric (n+1)) 0 n;
-    fi;
-
-bartlett sampling =
-    case sampling of
-    Symmetric (): bartlettSymmetric;
-    Periodic (): bartlettPeriodic;
-    esac;
-
-hann = cosineWindow 0.5 0.5 0.0 0.0;
-hamming = cosineWindow 0.54 0.46 0.0 0.0;
-blackman = cosineWindow 0.42 0.50 0.08 0.0;
-nuttall = cosineWindow 0.355768 0.487396 0.144232 0.012604;
-blackmanNuttall = cosineWindow 0.3635819 0.4891775 0.1365995 0.0106411;
-blackmanHarris = cosineWindow 0.35875 0.48829 0.14128 0.01168;
-
-boxcar n = vec.ones n;
-
-/**
- * Vector of size n with the "middle" sample equal to 1 and all others
- * equal to 0. The middle sample is sample (n-1)/2 for odd n or n/2+1
- * for even n.
- */
-dirac n =
-    if n < 2 then vec.ones n
-    else 
-        n0 = if n % 2 == 0 then n/2 else (n-1)/2 fi;
-        vec.concat [ vec.zeros n0, vec.ones 1, vec.zeros n0 ]
-    fi;
-
-/**
- * Make a vector of size n containing the values of sinc(x) with
- * x=0 in the middle, i.e. at sample (n-1)/2 for odd n or n/2+1 for
- * even n, such that the distance from -pi to pi (the point at
- * which the sinc function first crosses zero, for negative and
- * positive arguments respectively) is p samples.  p does not have
- * to be an integer.
- */
-sinc p n =
-    if n < 2 then vec.ones n
-    else 
-        n0 = if n % 2 == 0 then n/2 else (n-1)/2 fi;
-        n1 = if n % 2 == 0 then n/2 else (n+1)/2 fi;
-        half = 1 :: map do i: x = i * 2*pi / p; sin(x) / x done [1..n/2];
-        vec.fromList ((take n0 (reverse half)) ++ (take n1 half));
-    fi;
-
-kaiser beta n =
-   (bes0 x = 
-       (fact x = fold do x y: x*y done 1 [1..x];   // x!
-        ipow a b = fold do x _: x*a done 1 [1..b]; // a^b where b∈ℕ
-        square x = x*x;
-        term x i =
-            case i of
-             0: 1;
-             _: (ipow (x/2) (i*2)) / (square (fact i));
-            esac;
-        sum (map (term x) [0..20]));
-    denominator = bes0 beta;
-    vec.fromList
-       (map do i:
-            k = 2*i / (n-1) - 1;
-            bes0 (beta * sqrt (1 - k*k)) / denominator;
-            done [0..n-1]));
-
-/** 
-  Kaiser window with sidelobe attenuation of 𝛼 dB and window length n
-*/
-kaiserForAttenuation alpha n =
-   (beta =
-        if alpha > 50 then 0.1102 * (alpha - 8.7)
-        elif alpha > 21 then 0.5842 * Math#pow(alpha - 21, 0.4) + 0.07886 * (alpha - 21)
-        else 0
-        fi;
-    kaiser beta n);
-
-/**
-  Kaiser window with sidelobe attenuation of alpha dB and transition 
-  bandwidth of (tw * samplerate) / (2 * pi)
-*/
-kaiserForTransitionLength alpha tw =
-   (m = if alpha > 21 
-        then Math#ceil((alpha - 7.95) / (2.285 * tw))
-        else Math#ceil(5.79 / tw)
-        fi;
-    kaiserForAttenuation alpha (m+1));
-
-/**
-  Kaiser window with sidelobe attenuation of alpha dB and transition
-  bandwidth of tbw Hz at the given sampleRate
-*/
-kaiserForBandwidth alpha tbw samplerate =
-    kaiserForTransitionLength alpha ((tbw * 2 * pi) / samplerate);
-
-windowFunction type options =
-   (var sampling = Periodic ();
-    var beta = 4;
-    for options \case of
-        Symmetric s: if s then sampling := Symmetric () fi;
-        Beta b: beta := b;
-        esac;
-    case type of
-    Hann (): hann sampling;
-    Hamming (): hamming sampling;
-    Blackman (): blackman sampling;
-    Nuttall (): nuttall sampling;
-    BlackmanNuttall (): blackmanNuttall sampling;
-    BlackmanHarris (): blackmanHarris sampling;
-    Boxcar (): boxcar;
-    Bartlett (): bartlett sampling;
-    Kaiser (): kaiser beta;
-    esac);
-
-//!!! should use vector. but does anyone use this function anyway? would we use it in framer if it used vector?
-windowed windowFunc frames =
-    case frames of
-        []: frames;
-         _: (first = head frames;
-             window = windowFunc (vec.length first);
-             map (bf.multiply window) frames);
-    esac;
-
-{
-cosineWindow,
-hann = hann (Periodic ()),
-hamming = hamming (Periodic ()), 
-blackman = blackman (Periodic ()), 
-nuttall = nuttall (Periodic ()), 
-blackmanNuttall = blackmanNuttall (Periodic ()), 
-blackmanHarris = blackmanHarris (Periodic ()),
-boxcar,
-bartlett = bartlett (Periodic ()), 
-dirac,
-sinc,
-kaiser, kaiserForAttenuation, kaiserForTransitionLength, kaiserForBandwidth,
-windowFunction,
-windowed
-};
-
--- a/may/stream/audiofile.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-
-module may.stream.audiofile;
-
-load may.stream.type;
-
-import javax.sound.sampled:
-     AudioSystem, AudioInputStream, AudioFormat, AudioFormat$Encoding,
-     AudioFileFormat, AudioFileFormat$Type,
-     TargetDataLine, LineListener, Line, Line$Info, Control, Control$Type,
-     UnsupportedAudioFileException;
-
-import java.io: File, IOException;
-
-import java.nio: ByteBuffer, ByteOrder;
-
-ch = load may.stream.channels;
-vec = load may.vector;
-mat = load may.matrix;
-
-{ decode } = load may.stream.format;
-
-readInterleaved' { format is ~AudioFormat, aistream is ~AudioInputStream } nframes =
-   (channels = format#getChannels();
-    bytesPerSample = format#getSampleSizeInBits() / 8;
-    bytes = new byte[nframes * channels * bytesPerSample];
-    bytesRead = aistream#read(bytes);
-    if bytesRead <= 0 then vec.zeros 0;
-    else
-        n = int(bytesRead / bytesPerSample);
-        doubles = new double[n];
-        decode { format } bytes doubles n;
-        vec.vector doubles;
-    fi;
-   );
-
-read' { format is ~AudioFormat, aistream is ~AudioInputStream } n =
-   (b = readInterleaved' { format, aistream } n;
-    channels = format#getChannels();
-    ch.deinterleaved channels b;
-   );
-
-readMono' { format is ~AudioFormat, aistream is ~AudioInputStream } n =
-   (b = readInterleaved' { format, aistream } n;
-    channels = format#getChannels();
-    ch.deinterleaved 1 (ch.mixedDownFromInterleaved channels b);
-   );
-
-// Note, all this assumes aistream is non-blocking (i.e. available()
-// is to the end of file). Our stream interface does support
-// indefinite and infinite streams, but audiofile doesn't yet.
-
-available' { format is ~AudioFormat, aistream is ~AudioInputStream } =
-    aistream#available() / ((format#getSampleSizeInBits() / 8) * format#getChannels());
-
-close' { aistream is ~AudioInputStream } =
-    aistream#close();
-
-openWithReader reader ch name is 'a -> number -> string -> stream =
-   (f = new File(name);
-    aistream = AudioSystem#getAudioInputStream(f);
-    format = aistream#getFormat();
-    len = available' { format, aistream }; // at start of stream
-    syncd = synchronized aistream;
-    {
-        get position () = syncd \(len - available' { aistream, format }),
-        get channels () = if ch == 0 then format#getChannels() else ch fi,
-        get sampleRate () = format#getSampleRate(),
-        get available () = syncd \(Known (available' { aistream, format })),
-        get finished? () = syncd \(not (aistream#available() > 0)),
-        read = syncd \(reader { aistream, format }),
-        close () = syncd \(close' { aistream }),
-    });
-
-/**
- * Open the named audio file and return a stream object for
- * reading from it. May throw UnsupportedAudioFileException
- * or IOException.
- */
-open = openWithReader read' 0;
-
-/**
- * Open the named audio file and return a stream object that
- * reads mono samples from it, using
- * may.stream.channels.mixedDown to mix channels as necessary. 
- * May throw UnsupportedAudioFileException or IOException.
- */
-openMono = openWithReader readMono' 1;
-
-/**
- * Open an audio file with the given name and write all available
- * samples to it from the given stream, returning the number of
- * sample frames written. The stream must have finite length.
- * 
- * Example:
- * : str = audiofile.open "in.wav";
- * : n = audiofile.write str "out.wav";
- */
-write str name is stream -> string -> number =
-   (var tot = 0;
-    case str.available of
-    Infinite (): failWith "Cannot write infinite stream to file";
-    _: ()
-    esac;
-    class StreamAdapter extends TargetDataLine
-        int read(byte[] bytes, int off, int len)
-           (bb = ByteBuffer#wrap(bytes, off, len);
-            bb#order(ByteOrder#LITTLE_ENDIAN);
-            m = str.read (len / (str.channels * 2));
-            tot := mat.width m;
-            for [0..mat.width m - 1] do i:
-                for [0..str.channels - 1] do c:
-                    v = int (32767 * mat.at m c i);
-                    v = if v < -32768 then -32768
-                        elif v > 32767 then 32767
-                        else v fi;
-                    \() bb#putShort((i * str.channels + c) * 2, v);
-                done
-            done;
-            str.channels * 2 * mat.width m),
-        int available()
-            case str.available of
-            Known n: str.channels * n * 2;
-            other: str.channels * 1024; //!!!???
-            esac,
-        AudioFormat getFormat()
-            new AudioFormat(str.sampleRate, 16, str.channels, true, false),
-        void open(),
-        void open(AudioFormat format),
-        void open(AudioFormat format, int bufferSize),
-        void close(),
-        void drain(),
-        void flush(),
-        boolean isOpen() true,
-        boolean isActive() true,
-        boolean isRunning() true,
-        int getFramePosition() str.position,
-        long getLongFramePosition() str.position,
-        long getMicrosecondPosition() 0,
-        int getBufferSize() 0,
-        float getLevel() 1.0,
-        void start(),
-        void stop(),
-        void addLineListener(LineListener ll),
-        void removeLineListener(LineListener ll),
-        Control getControl(Control$Type c),
-        Control[] getControls(),
-        Line$Info getLineInfo(),
-        boolean isControlSupported(Control$Type c) false,
-    end;
-    f = new File(name);
-    n = AudioSystem#write(new AudioInputStream(new StreamAdapter()),
-                          AudioFileFormat$Type#WAVE, f);
-    str.close ();
-    n); //!!! this is wrong, should be number of frames (is number of bytes?)
-
-{
-    open,
-    openMono,
-    write,
-} as {
-    open is string -> stream,
-    openMono is string -> stream,
-    write is stream -> string -> number,
-} 
-
--- a/may/stream/channels.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-
-module may.stream.channels;
-
-vec = load may.vector;
-mat = load may.matrix;
-bf = load may.vector.blockfuncs;
-
-load may.vector.type;
-
-//!!! "internal" vector function to retrieve data for read-only
-// purposes without copying
-//!!! need to have this in some internal-use module 
-raw =
-   (raw' v is ~double[] -> ~double[] = v;
-    raw' as vector -> ~double[]);
-        
-interleaved m = 
-   ({ columns, rows } = (mat.size m);
-    if rows == 1 then
-        mat.getRow 0 m
-    else
-        v = new double[columns * rows];
-        for [0..rows-1] do row:
-            for [0..columns-1] do col:
-                v[col * rows + row] := mat.at m row col;
-            done;
-        done;
-        vec.vector v;
-    fi);
-
-deinterleaved channels b =
-    if channels == 1 then
-        mat.newRowVector b
-    else
-        rows = (vec.length b) / channels;
-        vv = array (map \(new double[rows]) [0..channels-1]);
-        v = raw b;
-        for [0..rows-1] do row:
-            for [0..channels-1] do col:
-                vv[col][row] := v[channels * row + col];
-            done
-        done;
-        mat.newMatrix (RowMajor ()) (map vec.vector vv);
-    fi;
-
-mixedDown m =  //!!!doc: average, not sum
-   ({ columns, rows } = (mat.size m);
-    if rows < 1 or columns < 1 then
-        vec.zeros 0
-    elif rows == 1 then
-        mat.getRow 0 m
-    else
-        bf.divideBy rows (bf.add (mat.asRows m));
-    fi);
-
-mixedDownFromInterleaved channels b =
-    if channels == 1 then
-        b;
-    else
-        columns = ((vec.length b) / channels);
-        v = raw b;
-        v' = new double[columns];
-        for [0..channels-1] do row:
-            for [0..columns-1] do col:
-                v'[col] := v'[col] + v[col * channels + row];
-            done;
-        done;
-        bf.divideBy channels (vec.vector v');
-    fi;
-
-mixedFromInterleavedTo targetChannels channels b = 
-    if targetChannels == channels then
-        b;
-    elif targetChannels == 1 then
-        mixedDownFromInterleaved channels b;
-    else
-        columns = ((vec.length b) / channels);
-        v = raw b;
-        v' = new double[columns * targetChannels];
-        for [0..targetChannels-1] do target:
-            for [0..columns-1] do col:
-                if target < channels then
-                    v'[col * targetChannels + target] := v[col * channels + target];
-                elif channels == 1 and target == 1 then
-                    v'[col * targetChannels + target] := v[col * channels];
-                fi
-            done
-        done;
-        vec.vector v';
-    fi;
-
-mixedTo targetChannels m = 
-    if targetChannels == mat.height m then   // n -> n: pass unmodified
-        m
-    elif targetChannels == 1 then            // n -> 1: mix down
-        deinterleaved 1 (mixedDown m)
-    elif mat.height m == 1 then              // 1 -> n: copy to all channels
-        mat.newMatrix (RowMajor ()) (map \(mat.getRow 0 m) [1..targetChannels])
-    else                                     // n -> m: truncate or add zeros
-        mat.resizedTo { rows = targetChannels, columns = mat.width m } m
-    fi;
-
-mixedAndInterleavedTo targetChannels m = 
-    if targetChannels == 1 then
-        mixedDown m
-    else
-        interleaved (mixedTo targetChannels m);
-    fi;
-
-//!!! some of these names are terrible
-{
-    interleaved, deinterleaved,
-    mixedDown, mixedDownFromInterleaved,
-    mixedFromInterleavedTo, mixedTo, mixedAndInterleavedTo
-}
-
--- a/may/stream/filter.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,570 +0,0 @@
-
-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;
-
-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;
-
-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
-   (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 𝛼 dB
- */
-kaiserSincWindow zc n 𝛼 =
-   (sw = win.sinc (n*2) (n*zc*2 + 1);
-    kw = win.kaiserForAttenuation 𝛼 (vec.length sw);
-    bf.multiply sw kw);
-
-bandpassed f0 f1 attenuation bandwidth s =
-   (rate = s.sampleRate;
-    kw = win.kaiserForBandwidth attenuation bandwidth rate;
-    filterLength = vec.length kw;
-    // First arg to sinc is the complete cycle length for the cutoff frequency
-    idealFor freq =
-        bf.scaled (2 * freq / rate)
-           (win.sinc (rate / freq) filterLength);
-    idealBandpass =
-         if f1 < rate/2 then
-             if f0 > 0 then bf.subtract (idealFor f1) (idealFor f0)
-             else idealFor f1
-             fi
-         else
-             if f0 > 0 then bf.subtract (win.dirac filterLength) (idealFor f0)
-             else win.dirac filterLength;
-             fi;
-         fi;
-    filter = bf.multiply idealBandpass kw;
-    filtered = convolvedWith [Framesize 1024]
-       (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels]))
-        s;
-    delayedBy (- (int (filterLength / 2))) filtered);
-
-lowpassed f attenuation bandwidth s = 
-    bandpassed 0 f attenuation bandwidth s;
-
-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;
-        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
-resampledTo 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));
-
-{
-    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,
-    lowpassed, bandpassed, highpassed,
-    spaced, interpolated, decimated, picked,
-    resampledTo,
-}
-
--- a/may/stream/format.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-
-module may.stream.format;
-
-import java.nio: ByteBuffer, ByteOrder;
-
-import javax.sound.sampled: 
-    AudioFormat, AudioFormat$Encoding,
-    UnsupportedAudioFileException;
-
-decode8u bytes doubles n is ~byte[] -> ~double[] -> number -> () =
-   (for [0..n-1] do i:
-       doubles[i] := (bytes[i] / 128.0) - 1.0;
-    done
-   );
-
-decode16s bytes doubles n is ~byte[] -> ~double[] -> number -> () =
-   (bb = ByteBuffer#wrap(bytes, 0, n * 2);
-    bb#order(ByteOrder#LITTLE_ENDIAN);
-    for [0..n-1] do i:
-       doubles[i] := bb#getShort(i*2) / 32768.0;
-    done
-   );
-
-decode32f bytes doubles n is ~byte[] -> ~double[] -> number -> () =
-   (bb = ByteBuffer#wrap(bytes, 0, n * 4);
-    bb#order(ByteOrder#LITTLE_ENDIAN);
-    for [0..n-1] do i:
-       doubles[i] := bb#getFloat(i*4);
-    done
-   );
-
-decodeFail () = 
-    throw new UnsupportedAudioFileException("Audio format not supported. Supported formats are 8-bit unsigned PCM, 16-bit signed little-endian PCM, or IEEE float");
-
-decode { format is ~AudioFormat } bytes doubles n = 
-   (if format#isBigEndian() then
-        decodeFail()
-    else
-        enc = format#getEncoding();
-        bits = format#getSampleSizeInBits();
-        if bits == 32 then
-            decode32f bytes doubles n;
-        elif bits == 16 and enc == AudioFormat$Encoding#PCM_SIGNED then
-            decode16s bytes doubles n;
-        elif bits == 8 and enc == AudioFormat$Encoding#PCM_UNSIGNED then
-            decode8u bytes doubles n;
-        else
-            decodeFail();
-        fi
-    fi);
-
-{
-    decode
-}
-
--- a/may/stream/framer.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-
-module may.stream.framer;
-
-/**
- * Framer expresses a stream (or a file) as a lazy list of (possibly
- * overlapping) frames of data.
- */
-
-vec = load may.vector;
-bf = load may.vector.blockfuncs;
-af = load may.stream.audiofile;
-win = load may.signal.window;
-fft = load may.transform.fft;
-mat = load may.matrix;
-ch = load may.stream.channels;
-
-load may.stream.type;
-load may.matrix.type;
-load may.vector.type;
-load may.complex.type;
-
-//!!! todo: synchronized for everything with state assignment
-
-blockList framesize stream =
-    if stream.finished? then
-        stream.close ();
-        []
-    else
-        mat.resizedTo { rows = stream.channels, columns = framesize }
-           (stream.read framesize)
-            :. \(blockList framesize stream);
-    fi;
-
-overlappingBlockList size hop stream valid buffer =
-   (
-    m = stream.read hop;
-    obtained = mat.width m;
-
-    // Retain framesize - hop samples from old buffer, add hop samples
-    // (zero-padded if necessary) just read
-    buffer = map2
-        do buf row:
-            vec.concat
-               [vec.slice buf hop size,
-                vec.resizedTo hop (mat.getRow row m)];
-        done buffer [0..stream.channels-1];
-
-    // Number of "valid" elements (not tail-end zero-padding) left in buffer
-    remaining = valid - (hop - obtained);
-
-    if remaining <= 0 then
-        stream.close ();
-        [];
-    else
-        mat.newMatrix (RowMajor ()) buffer
-            :. \(overlappingBlockList size hop stream remaining buffer);
-    fi);
-
-frames { framesize, hop } stream =
-    if framesize == hop then
-        blockList framesize stream
-    else
-        overlappingBlockList framesize hop stream 
-            framesize (map \(vec.zeros framesize) [0..stream.channels-1]);
-    fi;
-
-streamContiguous rate framesize frames =
-   (var remaining = frames;
-    var buffered = mat.zeroSizeMatrix ();
-    var position = 0;
-    channels = mat.height (head frames); // so we don't need to keep a head ptr
-    {
-        get position () = position,
-        get channels () = channels,
-        get sampleRate () = rate,
-        get finished? () = empty? remaining and mat.empty? buffered,
-        get available () = 
-            // Don't take length of frames -- we don't want to lose laziness.
-            // If the caller cares that much, they can measure frames themselves
-            if empty? remaining then
-                Known (mat.width buffered) 
-            else
-                Unknown ()
-            fi,
-        read count =
-           (framesFor samples acc =
-                if samples <= 0 or empty? remaining then
-                    reverse acc
-                else
-                    this = head remaining;
-                    remaining := tail remaining;
-                    framesFor (samples - mat.width this) (this :: acc)
-                fi;
-            source = mat.concat (Horizontal ()) (framesFor count [buffered]);
-            toReturn = mat.columnSlice source 0 count;
-            position := position + mat.width toReturn;
-            buffered := mat.columnSlice source count (mat.width source);
-            toReturn),
-        close = \(),
-    });
-
-overlapAdd overlap frames =
-   (ola fr pending acc =
-        case fr of
-        first::rest:
-           (w = mat.width pending;
-            pre = mat.columnSlice pending 0 (w - overlap);
-            added = mat.sum first
-               (mat.resizedTo (mat.size first)
-               (mat.columnSlice pending (w - overlap) w));
-            ola rest added (pre::acc));
-         _:
-            reverse (pending::acc);
-        esac;
-    case frames of
-    first::rest:
-        mat.concat (Horizontal ()) (ola rest first []);
-     _: 
-        mat.zeroSizeMatrix ();
-    esac);
-
-streamOverlapping rate { framesize, hop, window } frames =
-   (var remaining = frames;
-    var buffered = mat.zeroSizeMatrix ();
-    var position = 0;
-
-    factor = hop / (framesize/2);
-    w = bf.scaled factor (window framesize);
-    channels = mat.height (head frames); // so we don't need to keep a head ptr
-
-    syncd = synchronized remaining;
-
-    finished' () = syncd \(empty? remaining and mat.empty? buffered);
-
-    read' count = 
-       (framesFor samples acc =
-            if samples <= 0 or empty? remaining then
-                reverse acc
-            else
-                this = mat.resizedTo { columns = framesize, rows = channels }
-                   (mat.newMatrix (RowMajor ())
-                       (map (bf.multiply w) (mat.asRows (head remaining))));
-                remaining := tail remaining;
-                framesFor (samples - hop) (this::acc)
-            fi;
-        source = overlapAdd (framesize - hop)
-           (framesFor count [buffered]);
-        buffered := mat.columnSlice source count (mat.width source);
-        mat.columnSlice source 0 count);
-
-    // lose initial padding
-    \() (read' (framesize - hop));
-    
-    {
-        get position () = syncd \(position),
-        get channels () = channels,
-        get sampleRate () = rate,
-        get finished? () = finished' (),
-        get available () = if finished' () then Known 0 else Unknown () fi,
-        read count = syncd
-          \(data = read' count;
-            position := position + mat.width data;
-            data),
-        close = \(),
-    });
-    
-//!!! doc: convert frames back to a stream
-streamed rate { framesize, hop } frames =
-    if framesize == hop then
-        streamContiguous rate framesize frames
-    else
-        streamOverlapping rate
-            { framesize, hop, window = win.hann } // periodic, not symmetric
-            frames
-    fi;
-
-monoFrames params stream =
-    map ch.mixedDown (frames params stream);
-
-windowedFrames { framesize, hop, window } stream =
-   (win = window framesize;
-    map (bf.multiply win) (monoFrames { framesize, hop } stream));
-
-frequencyDomainFrames parameters stream =
-   (f = fft.realForward parameters.framesize;
-    map f (windowedFrames parameters stream));
-
-typedef params = { framesize is number, hop is number };
-typedef winparams = { framesize is number, hop is number, window is number -> vector };
-
-{ 
-    frames is params -> stream -> list<matrix>,
-    monoFrames is params -> stream -> list<vector>,
-    windowedFrames is winparams -> stream -> list<vector>,
-    frequencyDomainFrames is winparams -> stream -> list<array<cplx> >,
-
-    framesOfFile parameters filename =
-        frames parameters (af.open filename),
-
-    monoFramesOfFile parameters filename =
-        monoFrames parameters (af.open filename),
-
-    windowedFramesOfFile parameters filename = 
-        windowedFrames parameters (af.open filename),
-
-    frequencyDomainFramesOfFile parameters filename = 
-        frequencyDomainFrames parameters (af.open filename),
-
-    overlapAdd is number -> list<matrix> -> matrix,
-
-    streamed is number -> params -> list<matrix> -> stream,
-    streamOverlapping is number -> winparams -> list<matrix> -> stream,
-}
-
--- a/may/stream/playback.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-
-module may.stream.playback;
-
-vec = load may.vector;
-mat = load may.matrix;
-af = load may.stream.audiofile;
-ch = load may.stream.channels;
-
-import javax.sound.sampled:
-    AudioSystem, AudioFormat, AudioFormat$Encoding, SourceDataLine;
-
-import java.nio: ByteBuffer, ByteOrder;
-
-playBlock line b is ~SourceDataLine -> 'a -> () =
-   (len = vec.length b;
-    samples = vec.primitive b;
-    nb = len * 2;
-    bytes = new byte[nb];
-    bb = ByteBuffer#wrap(bytes, 0, nb);
-    bb#order(ByteOrder#LITTLE_ENDIAN);
-    sb = bb#asShortBuffer();
-    for [0..len-1] do i: sb#put(i, samples[i] * 32767.0); () done;
-    \() line#write(bytes, 0, nb));
-
-play line blocks = for blocks (playBlock line);
-    
-open { rate, channels } = 
-   (format = new AudioFormat(AudioFormat$Encoding#PCM_SIGNED, rate, 16,
-                             channels, channels * 2, rate, false);
-    line = AudioSystem#getSourceDataLine(format);
-    line#open(format);
-    line#start();
-    {
-        line,
-        sampleRate = rate,
-        channels,
-        play = play line,
-        close () = (line#drain(); line#close()),
-    });
-
-playMatrix rate m =
-   (channels = mat.height m;
-    line = open { rate, channels };
-    line.play [ch.mixedAndInterleavedTo channels m];
-    line.close());
-
-playStream stream =
-   (line = open { rate = stream.sampleRate, channels = stream.channels };
-    blocksize = 10240;
-    not stream.finished? loop 
-        line.play [(ch.mixedAndInterleavedTo line.channels 
-                    (stream.read blocksize))];
-    line.close();
-    stream.close());
-
-playFile filename = 
-    playStream (af.open filename);
-
-{
-    open, playMatrix, playStream, playFile,
-}
-
--- a/may/stream/record.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-
-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:
-    AudioSystem, AudioFormat, AudioFormat$Encoding, 
-    TargetDataLine, DataLine, DataLine$Info;
-
-{ decode } = load may.stream.format;
-
-recordInterleaved seconds line is number -> ~TargetDataLine -> 'a =
-   (format = line#getFormat();
-    channels = format#getChannels();
-    nframes = seconds * format#getSampleRate();
-    bytesPerSample = format#getSampleSizeInBits() / 8;
-    bytes = new byte[nframes * channels * bytesPerSample];
-    bytesRead = line#read(bytes, 0, length bytes);
-    if bytesRead <= 0 then vec.zeros 0;
-    else
-        n = int(bytesRead / bytesPerSample);
-        doubles = new double[n];
-        decode { format } bytes doubles n;
-        vec.vector doubles;
-    fi;
-   );
-
-recordFor seconds line is number -> ~TargetDataLine -> 'a =
-    ch.deinterleaved line#getFormat()#getChannels() 
-       (recordInterleaved seconds line);
-
-open { rate, channels } =
-   (format = new AudioFormat(AudioFormat$Encoding#PCM_SIGNED, rate, 16,
-                             channels, channels * 2, rate, false);
-    info = new DataLine$Info
-       (Class#forName("javax.sound.sampled.TargetDataLine"), format);
-    if not AudioSystem#isLineSupported(info) then
-        failWith "Unable to open 16-bit PCM line at rate \(rate), \(channels) channels";
-    fi;
-    line = AudioSystem#getLine(info) unsafely_as ~TargetDataLine;
-    line#open(format);
-    line#start();
-    {
-        line,
-        sampleRate = rate,
-        channels,
-        norec recordFor t = recordFor t line,
-        close () = (line#drain(); line#close()),
-    });
-
-recordFor seconds =
-   (line = open { rate = 44100, channels = 2 }; //!!! or system defaults?
-    data = line.recordFor seconds;
-    line.close ();
-    {
-        sampleRate = line.sampleRate,
-        channels = line.channels,
-        data
-    });
-
-{
-    open,
-    recordFor
-}
-
-
--- a/may/stream/syntheticstream.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-
-module may.stream.syntheticstream;
-
-ch = load may.stream.channels;
-vec = load may.vector;
-mat = load may.matrix;
-
-load may.vector.type;
-load may.matrix.type;
-load may.stream.type;
-
-generated sampleRate generator =
-   (// generator takes sample number as arg, returns number in -1,+1 range
-    var position = 0;
-    {
-        get position () = position,
-        get channels () = 1, 
-        get sampleRate () = sampleRate,
-        get available () = Infinite (),
-        get finished? () = false,
-        read count = ch.deinterleaved 1
-           (result = new double[count];
-            for [0..count-1] do i:
-                result[i] := generator (position + i)
-            done;
-            position := position + count;
-            vec.vector result),
-        close = \(),
-    });
-
-sinusoid rate freq =
-    generated rate (sin . (* (2 * pi * freq / rate)));
-
-whiteNoise rate =
-    generated rate \((Math#random() * 2.0) - 1.0);
-
-silent rate =
-    generated rate \0;
-
-precalculatedMono rate data =
-   (n = vec.length data;
-    var position = 0;
-    {
-        get position () = position,
-        get channels () = 1,
-        get sampleRate () = rate,
-        get available () = Known (n - position),
-        get finished? () = not (n > position),
-        read count = ch.deinterleaved 1
-           (rc = min count (n - position);
-            result = vec.slice data position (position + rc);
-            position := position + rc;
-            result),
-        close = \(),
-    });
-
-precalculated rate data =
-   (n = mat.width data;
-    c = mat.height data;
-    var position = 0;
-    {
-        get position () = position,
-        get channels () = c,
-        get sampleRate () = rate,
-        get available () = Known (n - position),
-        get finished? () = not (n > position),
-        read count = 
-           (rc = min count (n - position);
-            result = mat.columnSlice data position (position + rc);
-            position := position + rc;
-            result),
-        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 -> matrix -> stream,
-    precalculatedMono is number -> vector -> stream,
-    sinusoid is number -> number -> stream, 
-    whiteNoise is number -> stream,
-    silent is number -> stream,
-    empty is number -> number -> stream,
-}
-
-
-
--- a/may/stream/test/audiofile_reference.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-
-module may.stream.test.audiofile_reference;
-
-syn = load may.stream.syntheticstream;
-filt = load may.stream.filter;
-
-//!!! docs from turbot
-
-pulseChannel rate =
-   (pulseFreq = 2;
-    pulseWidth = 0.01 * rate;
-    generator i =
-       (pulseNo = int ((i * pulseFreq) / rate);
-        index = (i * pulseFreq) - (rate * pulseNo);
-        if index < pulseWidth then
-	    s = 1.0 - abs(pulseWidth/2 - index) / (pulseWidth/2);
-	    if pulseNo % 2 != 0 then (-s) else s fi
-        else 0
-        fi);
-    syn.generated rate generator);
-
-referenceChannels rate =
-   (leftovers rate n =
-       (syn.generated rate \(n / 20) :. \(leftovers rate (n+1)));
-    syn.sinusoid rate 600 :: pulseChannel rate :: leftovers rate 2);
-
-afReference rate channels =
-    filt.withDuration (2 * rate)
-       (filt.multiplexed (take channels (referenceChannels rate)));
-    
-{
-    afReference
-}
-
-
--- a/may/stream/test/speedtest.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-
-program may.stream.test.speedtest;
-
-af = load may.stream.audiofile;
-vec = load may.vector;
-mat = load may.matrix;
-filt = load may.stream.filter;
-
-import java.lang: StackOverflowError;
-
-{ compare, compareUsing, time } = load may.test.test;
-
-before = System#currentTimeMillis();
-
-str = time "open audio file" \(af.openMono "shortish.wav");
-
-conv = time "prepare convolve" \(filt.convolvedWith [ Framesize 256 ] (mat.newRowVector (vec.fromList [ 1, 0.8, 0.5 ])) str);
-
-var len = 0;
-time "read convolve" \((not conv.finished?) loop len := len + mat.width (conv.read 65536));
-
-str.close ();
-
-println "Done";
-
-after = System#currentTimeMillis();
-
-println "Total time: \(after - before)ms for \(len) audio sample frames [\(int (len / ((after - before) / 1000))) fps]";
-
-
-before = System#currentTimeMillis();
-
-str = time "open audio file" \(af.openMono "shortish.wav");
-
-res = time "prepare resample" \(filt.resampledTo 44100 str);
-
-len := 0;
-time "read resampled" \((not res.finished?) loop len := len + mat.width (res.read 65536));
-
-str.close ();
-
-println "Done";
-
-after = System#currentTimeMillis();
-
-println "Total time: \(after - before)ms for \(len) audio sample frames [\(int (len / ((after - before) / 1000))) fps]";
-
--- a/may/stream/test/test_audiofile.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-
-module may.stream.test.test_audiofile;
-
-af = load may.stream.audiofile;
-vec = load may.vector;
-mat = load may.matrix;
-bf = load may.vector.blockfuncs;
-
-ref = load may.stream.test.audiofile_reference;
-
-{ compare, compareUsing } = load may.test.test;
-
-testfile name = "may/test/data/\(name).wav";
-
-float n is number -> number =
-    // round number to float precision (for comparison with floats)
-   (arr = new float[1];
-    arr[0] := n;
-    arr[0]);
-
-readAll stream =
-    case stream.available of
-    Known n: stream.read n;
-    _: failWith "Known-duration stream required";
-    esac;
-
-bitdepthComparator depth =
-   (slack = if depth == 8 then 0.015 else 0.001 fi;
-    do test ref: abs (test - ref) < slack done);
-
-maxOf m =
-    bf.max (vec.fromList (map bf.max (mat.asRows m)));
-
-testReferenceFile rate channels bitdepth =
-   (test = readAll (af.open (testfile "\(rate)-\(channels)-\(bitdepth)"));
-    ref = readAll (ref.afReference rate channels);
-    if mat.equalUnder (bitdepthComparator bitdepth) test ref then
-        true
-    else
-        println "** peak difference: \(maxOf (mat.difference ref test))";
-        for [0..mat.height test - 1] do ch:
-            if mat.equalUnder (bitdepthComparator bitdepth)
-               (mat.newRowVector (mat.getRow ch test))
-               (mat.newRowVector (mat.getRow ch ref)) then
-                println "   channel \(ch): ok";
-            else
-                println "   channel \(ch): not ok";
-// This isn't really simple enough!
-/*!!!
-                seriesFor m =
-                    Series {
-                        start = 0, 
-                        step = 1, 
-                        values = take 1000 (vec.list (mat.getRow ch m))
-                        };
-                \() (pl.plot (map seriesFor [ test, ref, mat.scaled 10000 (mat.difference test ref) ]));
-*/
-            fi;
-        done;
-        false
-    fi);
-
-[ 
-
-"20samples-open": \(
-    f = af.open (testfile "20samples");
-    compare f.position 0 and
-        compare f.channels 1 and
-        compare f.sampleRate 44100 and
-        compare f.available (Known 20) and
-        compare f.finished? false and
-        ( f.close () ; true )
-),
-
-"20samples-read": \(
-    all id (map do opener:
-        f = opener (testfile "20samples");
-        first15 = f.read 15;
-        last5 = f.read 10;
-        compare (mat.size first15) { rows = 1, columns = 15 } and
-            compare (mat.size last5) { rows = 1, columns = 5 } and
-            compare (vec.list (mat.getRow 0 first15))
-                [ float (32767/32768),0,0,0,0,0,0,0,0,0,0,0,0,0,0 ] and
-            compare (vec.list (mat.getRow 0 last5)) [ 0,0,0,0,-1 ] and
-            ( f.close () ; true )
-        done [ af.open, af.openMono ]);
-),
-
-"8000-1-8": \(
-    testReferenceFile 8000 1 8;
-),
-
-"44100-2-16": \(
-    testReferenceFile 44100 2 16;
-),
-
-] is hash<string, () -> boolean>
-
--- a/may/stream/test/test_channels.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-
-module may.stream.test.test_channels;
-
-ch = load may.stream.channels;
-mat = load may.matrix;
-vec = load may.vector;
-
-{ compare, compareUsing } = load may.test.test;
-
-newMatrix data = mat.newMatrix (ColumnMajor ()) (map vec.fromList data);
-
-compareBlocks b1 b2 =
-    compare (vec.list b1) (vec.list b2);
-
-[
-
-"interleaved": \(
-    compareBlocks (ch.interleaved (newMatrix [[1,4],[2,5],[3,6]]))
-       (vec.fromList [1,4,2,5,3,6]) and
-        compareBlocks (ch.interleaved (newMatrix [[1],[2],[3]]))
-           (vec.fromList [1,2,3])
-),
-
-"deinterleaved": \(
-    compareUsing mat.equal (ch.deinterleaved 2 (vec.fromList [1,4,2,5,3,6]))
-       (newMatrix [[1,4],[2,5],[3,6]]) and
-        compareUsing mat.equal (ch.deinterleaved 1 (vec.fromList [1,2,3]))
-           (newMatrix [[1],[2],[3]])
-),
-
-"mixedDown": \(
-    compareBlocks (ch.mixedDown (newMatrix [[1,4],[2,5],[3,6]]))
-       (vec.fromList [5/2,7/2,9/2]) and
-        compareBlocks (ch.mixedDown (newMatrix []))
-           (vec.fromList [])
-),
-
-"mixedDownFromInterleaved": \(
-    compareBlocks (ch.mixedDownFromInterleaved 2 (vec.fromList [1,4,2,5,3,6]))
-       (vec.fromList [5/2,7/2,9/2]) and
-        compareBlocks (ch.mixedDownFromInterleaved 1 (vec.fromList [1,2,3]))
-           (vec.fromList [1,2,3])
-),
-
-"mixedFromInterleavedTo": \(
-    compareBlocks (ch.mixedFromInterleavedTo 1 2 (vec.fromList [1,4,2,5,3,6]))
-       (vec.fromList [5/2,7/2,9/2]) and
-        compareBlocks (ch.mixedFromInterleavedTo 2 2 (vec.fromList [1,4,2,5,3,6]))
-           (vec.fromList [1,4,2,5,3,6]) and
-        compareBlocks (ch.mixedFromInterleavedTo 3 2 (vec.fromList [1,4,2,5,3,6]))
-           (vec.fromList [1,4,0,2,5,0,3,6,0]) and
-        compareBlocks (ch.mixedFromInterleavedTo 1 1 (vec.fromList [1,2,3]))
-           (vec.fromList [1,2,3]) and
-        compareBlocks (ch.mixedFromInterleavedTo 2 1 (vec.fromList [1,2,3]))
-           (vec.fromList [1,1,2,2,3,3]) and
-        compareBlocks (ch.mixedFromInterleavedTo 3 1 (vec.fromList [1,2,3]))
-           (vec.fromList [1,1,0,2,2,0,3,3,0])
-),
-
-"mixedAndInterleavedTo": \(
-    compareBlocks (ch.mixedAndInterleavedTo 1 (newMatrix [[1,4],[2,5],[3,6]]))
-       (vec.fromList [5/2,7/2,9/2]) and
-        compareBlocks (ch.mixedAndInterleavedTo 2 (newMatrix [[1,4],[2,5],[3,6]]))
-           (vec.fromList [1,4,2,5,3,6]) and
-        compareBlocks (ch.mixedAndInterleavedTo 3 (newMatrix [[1,4],[2,5],[3,6]]))
-           (vec.fromList [1,4,0,2,5,0,3,6,0]) and
-        compareBlocks (ch.mixedAndInterleavedTo 1 (newMatrix [[1],[2],[3]]))
-           (vec.fromList [1,2,3]) and
-        compareBlocks (ch.mixedAndInterleavedTo 2 (newMatrix [[1],[2],[3]]))
-           (vec.fromList [1,1,2,2,3,3]) and
-        compareBlocks (ch.mixedAndInterleavedTo 3 (newMatrix [[1],[2],[3]]))
-           (vec.fromList [1,1,1,2,2,2,3,3,3])
-),
-
-] is hash<string, () -> boolean>;
-
-
--- a/may/stream/test/test_filter.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,831 +0,0 @@
-
-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;
-syn = load may.stream.syntheticstream;
-filt = load may.stream.filter;
-
-//pl = load may.plot;//!!!
-
-pl = { plot things = true; };
-
-{ compare, compareUsing } = load may.test.test;
-
-compareClose = compareUsing 
-    do m1 m2:
-        all id (map2 do v1 v2:
-            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 ]
-];
-
-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;
-            compareClose (map vec.list (mat.asRows (c.read 4))) [[ 1,0,-1,0 ]] and
-               ( c.close (); true )
-        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;
-            compareClose (map vec.list (mat.asRows (c.read 4))) [[ 8,6,4,2 ]] and
-               ( c.close (); true )
-        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;
-            compareClose (map vec.list (mat.asRows (c.read 4)))
-                [[0,0,0,1],[0,8,14,10],[0,0,1,1]] and
-               ( c.close (); true )
-        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;
-            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)) and
-                ( c.close (); true )
-        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 )
-),
-
-//!!! 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);
-    array (map do v: 20 * Math#log10(v) done (vec.list spectrum)));
-
-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 2; // 2Hz sine sampled at 16Hz
-    input = filt.withDuration 64 sinusoid;
-    output = filt.decimated 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;
-),
-
-"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];
-    compareClose [a] [b];
-),
-
-"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;
-        output = filt.lowpassed cutoff attenuation bandwidth input;
-        logspec = logSpectrumFrom output n;
-        acceptances = map do bin:
-            freq = (rate / n) * bin;
-            db = logspec[bin];
-            //!!! what should these 0.01 actually be?
-            if freq < cutoff - bandwidth/2 then (db < 0.01 and db > -0.01)
-            elif freq > cutoff + bandwidth/2 then (db < -attenuation)
-            else (db < 0.01 and db > -attenuation)
-            fi;
-        done [0..n/2];
-        compare acceptances (map \true [0..n/2]));
-    all id 
-       (map test [
-            { rate = 800, cutoff = 200, attenuation = 80, bandwidth = 10, n = 1000 },
-        ]);
-),
-
-"highpassed-dirac": \(
-    test { rate, cutoff, attenuation, bandwidth, n } = 
-       (input = makeDiracStream rate n;
-        output = filt.highpassed cutoff attenuation bandwidth input;
-        logspec = logSpectrumFrom output n;
-        acceptances = map do bin:
-            freq = (rate / n) * bin;
-            db = logspec[bin];
-            //!!! what should these 0.01 actually be?
-            if freq > cutoff + bandwidth/2 then (db < 0.01 and db > -0.01)
-            elif freq < cutoff - bandwidth/2 then (db < -attenuation)
-            else (db < 0.01 and db > -attenuation)
-            fi;
-        done [0..n/2];
-        compare acceptances (map \true [0..n/2]));
-    all id 
-       (map test [
-            { rate = 800, cutoff = 200, attenuation = 80, bandwidth = 10, n = 1000 },
-        ]);
-),
-
-"bandpassed-dirac": \(
-    test { rate, f0, f1, attenuation, bandwidth, n } = 
-       (input = makeDiracStream rate n;
-        output = filt.bandpassed f0 f1 attenuation bandwidth input;
-        logspec = logSpectrumFrom output n;
-        acceptances = map do bin:
-            freq = (rate / n) * bin;
-            db = logspec[bin];
-            //!!! what should these 0.01 actually be?
-            if freq < f0 - bandwidth/2 then (db < -attenuation)
-            elif freq < f0 + bandwidth/2 then (db < 0.01 and db > -attenuation)
-            elif freq < f1 - bandwidth/2 then (db < 0.01 and db > -0.01)
-            elif freq < f1 + bandwidth/2 then (db < 0.01 and db > -attenuation)
-            else (db < -attenuation)
-            fi;
-        done [0..n/2];
-        compare acceptances (map \true [0..n/2]));
-    all id 
-       (map test [
-            { rate = 800, f0 = 200, f1 = 300, attenuation = 80, bandwidth = 10, n = 1000 },
-        ]);
-),
-
-];    
-
-all = [:];
-for [ knowns, unknowns, filtering ] do h:
-    for (keys h) do k: all[k] := h[k] done
-done;
-
-all is hash<string, () -> boolean>;
-
--- a/may/stream/test/test_framer.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-
-module may.stream.test.test_framer;
-
-fr = load may.stream.framer;
-vec = load may.vector;
-mat = load may.matrix;
-syn = load may.stream.syntheticstream;
-
-{ compare, compareUsing } = load may.test.test;
-
-rate = 10;
-
-testStream n is number -> 'a  = syn.precalculatedMono rate (vec.fromList [1..n]);
-
-compareFrames frames1 frames2 =
-    all id (map2 do f1 f2: compareUsing mat.equal f1 f2 done frames1
-       (map (mat.newRowVector . vec.fromList) frames2));
-
-testFramesWith params length expected firstChunkSize =
-   (f = fr.frames params (testStream length);
-    str = fr.streamed rate params f;
-    ts = testStream length; // newly initialised stream
-
-    compareFrames f expected and
-
-       (firstChunk = str.read firstChunkSize;
-        compareUsing mat.equal
-            firstChunk (ts.read firstChunkSize)) and
-
-        compare str.position firstChunkSize and
-        compare str.finished? false and
-
-       (restChunk = str.read (length - firstChunkSize);
-        compareUsing mat.equal
-            restChunk (ts.read (length - firstChunkSize))) and
-        compare str.position length and
-
-       (trailingZeros = str.read (params.framesize + 1);
-        compareUsing mat.equal
-            trailingZeros
-               (mat.zeroMatrix
-                { rows = str.channels, columns = mat.width trailingZeros }) and
-           (mat.width trailingZeros < params.framesize)) and
-
-       compare str.finished? true and
-       compare str.available (Known 0));
-
-testFramesInvertible params length expected =
-    all id (map (testFramesWith params length expected) [1..length]);
-
-testFrames params length expected =
-   (f = fr.frames params (testStream length);
-    compareFrames f expected);
-
-[
-
-"framecount-2x2": \( 
-    fr = fr.frames { framesize = 2, hop = 2 } (testStream 2);
-    compare (length fr) 1
-),
-
-"framecount-2x3": \( 
-    fr = fr.frames { framesize = 2, hop = 2 } (testStream 3);
-    compare (length fr) 2
-),
-
-"framecount-2x4": \( 
-    fr = fr.frames { framesize = 2, hop = 2 } (testStream 4);
-    compare (length fr) 2
-),
-
-"framecount-2.1x0": \( 
-    fr = fr.frames { framesize = 2, hop = 1 } (testStream 0);
-    compare (length fr) 1
-),
-
-"framecount-2.1x1": \( 
-    fr = fr.frames { framesize = 2, hop = 1 } (testStream 1);
-    compare (length fr) 2
-),
-
-"framecount-2.1x2": \( 
-    fr = fr.frames { framesize = 2, hop = 1 } (testStream 2);
-    compare (length fr) 3
-),
-
-"framecount-2.1x3": \( 
-    fr = fr.frames { framesize = 2, hop = 1 } (testStream 3);
-    compare (length fr) 4
-),
-
-"framecount-4.1x4": \( 
-    fr = fr.frames { framesize = 4, hop = 1 } (testStream 4);
-    compare (length fr) 7
-),
-
-"framecount-4.3x4": \( 
-    fr = fr.frames { framesize = 4, hop = 3 } (testStream 4);
-    compare (length fr) 2 
-),
-
-"framecount-4.4x4": \( 
-    fr = fr.frames { framesize = 4, hop = 4 } (testStream 4);
-    compare (length fr) 1
-),
-
-"framecount-3.2x4": \(
-    fr = fr.frames { framesize = 3, hop = 2 } (testStream 4);
-    compare (length fr) 3
-),
-
-"frames-2x5": \( 
-    testFramesInvertible { framesize = 2, hop = 2 } 5 [ [1,2], [3,4], [5,0] ];
-),
-
-"frames-4.3x4": \( 
-    testFrames { framesize = 4, hop = 3 } 4 [ [0,1,2,3], [3,4,0,0] ];
-),
-
-"frames-3.2x4": \(
-    testFrames { framesize = 3, hop = 2 } 4 [ [0,1,2], [2,3,4], [4,0,0] ];
-),
-
-"frames-3.1x6": \(
-    testFramesInvertible { framesize = 3, hop = 1 } 6
-        [ [0,0,1], [0,1,2], [1,2,3], [2,3,4],
-          [3,4,5], [4,5,6], [5,6,0], [6,0,0] ];
-),
-
-"frames-4.2x8": \(
-    testFramesInvertible { framesize = 4, hop = 2 } 8
-        [ [0,0,1,2], [1,2,3,4], [3,4,5,6], [5,6,7,8], [7,8,0,0] ];
-),
-
-"overlapAdd-3.1": \(
-    compareUsing (mat.equal)
-       (fr.overlapAdd 2 [ mat.newRowVector (vec.fromList [ 1,2,3 ]),
-                          mat.newRowVector (vec.fromList [   4,5,6 ]),
-                          mat.newRowVector (vec.fromList [     7,8,9 ]) ])
-       (mat.newRowVector (vec.fromList [ 1,6,15,14,9 ]))
-),
-
-"overlapAdd-3.2": \(
-    compareUsing (mat.equal)
-       (fr.overlapAdd 1 [ mat.newRowVector (vec.fromList [ 1,2,3 ]),
-                          mat.newRowVector (vec.fromList [     4,5,6 ]),
-                          mat.newRowVector (vec.fromList [         7,8,9 ]) ])
-       (mat.newRowVector (vec.fromList [ 1,2,7,5,13,8,9 ]))
-),
-
-"overlapAdd-4.2": \(
-    compareUsing (mat.equal)
-       (fr.overlapAdd 2 [ mat.newRowVector (vec.fromList [ 1,2,3,4 ]),
-                          mat.newRowVector (vec.fromList [     5,6,7,8 ]),
-                          mat.newRowVector (vec.fromList [         9,0,1,2 ]) ])
-       (mat.newRowVector (vec.fromList [ 1,2,8,10,16,8,1,2 ]))
-),
-
-"overlapAdd-6+4.2": \(
-    // Must work even if blocks vary in length (what if shorter than
-    // overlap though?)
-    compareUsing (mat.equal)
-       (fr.overlapAdd 2 [ mat.newRowVector (vec.fromList [ 1,2,3,4,5,6 ]),
-                          mat.newRowVector (vec.fromList [         7,8,9,0 ]),
-                          mat.newRowVector (vec.fromList [             1,2,3 ]),
-                          mat.newRowVector (vec.fromList [               4,5,6 ]) ])
-       (mat.newRowVector (vec.fromList [ 1,2,3,4,12,14,10,6,8,6 ]))
-),
-
-] is hash<string, () -> boolean>;
-
-
-
--- a/may/stream/test/test_syntheticstream.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-
-module may.stream.test.test_syntheticstream;
-
-vec = load may.vector;
-mat = load may.matrix;
-syn = load may.stream.syntheticstream;
-
-{ compare, compareUsing } = load may.test.test;
-
-compareApprox eps =
-    compareUsing do v1 v2: all id (map2 do f1 f2: abs (f1 - f2) < eps done v1 v2) done;
-
-epsilon = 0.000001;
-
-[
-
-"generated": \(
-    str = 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
-),
-
-"sinusoid": \(
-    // 2Hz sine sampled 8 times a second
-    str = syn.sinusoid 8 2;
-    compare str.position 0 and
-        compare str.channels 1 and
-        compare str.sampleRate 8 and
-        compare str.available (Infinite ()) and
-        compare str.finished? false and
-        compareApprox epsilon (vec.list (mat.getRow 0 (str.read 6))) [ 0, 1, 0, -1, 0, 1 ] and
-        compare str.position 6
-),
-
-"silent": \(
-    str = syn.silent 8;
-    compare str.position 0 and
-        compare str.channels 1 and
-        compare str.sampleRate 8 and
-        compare str.available (Infinite ()) and
-        compare str.finished? false and
-        compare (vec.list (mat.getRow 0 (str.read 3))) [ 0, 0, 0 ] and
-        compare str.position 3
-),
-
-"precalculatedMono-empty": \(
-    str = syn.precalculatedMono 2 (vec.fromList []);
-    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
-        compare (vec.list (mat.getRow 0 (str.read 3))) [] and
-        compare str.position 0
-),
-
-"precalculatedMono": \(
-    str = syn.precalculatedMono 2 (vec.fromList [ 1, 2, 3, 4 ]);
-    compare str.position 0 and
-        compare str.channels 1 and
-        compare str.sampleRate 2 and
-        compare str.available (Known 4) and
-        compare str.finished? false and
-        compare (vec.list (mat.getRow 0 (str.read 3))) [ 1, 2, 3 ] and
-        compare str.position 3 and
-        compare str.available (Known 1) and
-        compare str.finished? false and
-        compare (vec.list (mat.getRow 0 (str.read 3))) [ 4 ] and
-        compare str.position 4 and
-        compare str.available (Known 0) and
-        compare str.finished? true
-),
-
-] is hash<string, () -> boolean>
-
--- a/may/stream/type.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-
-module may.stream.type;
-
-load may.matrix.type;
-
-typedef stream =
-    {
-        position is number,
-        channels is number,
-        sampleRate is number,
-        available is Known number | Unknown () | Infinite (),
-        finished? is boolean,
-        read is number -> matrix,
-        close is () -> (),
-    };
-
-();
-
--- a/may/test/all.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-
-program may.test.all;
-
-{ runTests } = load may.test.test;
-
-tests = [
-"vector"     : load may.vector.test.test_vector,
-"blockfuncs" : load may.vector.test.test_blockfuncs,
-"complex"    : load may.complex.test.test_complex,
-"framer"     : load may.stream.test.test_framer,
-"channels"   : load may.stream.test.test_channels,
-"audiofile"  : load may.stream.test.test_audiofile,
-"synstream"  : load may.stream.test.test_syntheticstream,
-"filter"     : load may.stream.test.test_filter,
-"fft"        : load may.transform.test.test_fft,
-"vamppost"   : load may.vamp.test.test_vamppost,
-"vamp"       : load may.vamp.test.test_vamp,
-"matrix"     : load may.matrix.test.test_matrix,
-"plot"       : load may.plot.test.test_plot,
-"signal"     : load may.signal.test.test_signal,
-"window"     : load may.signal.test.test_window,
-];
-
-bad = sum (mapHash do name testHash: runTests name testHash done tests);
-
-if (bad > 0) then
-    println "\n** \(bad) test(s) failed!";
-else
-    ()
-fi
-
Binary file may/test/data/20samples.wav has changed
Binary file may/test/data/44100-2-16.wav has changed
Binary file may/test/data/8000-1-8.wav has changed
--- a/may/test/test.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-module may.test.test;
-
-import yeti.lang: FailureException;
-
-var goodCompares = 0;
-
-compareUsing comparator obtained expected =
-    if comparator obtained expected then
-        goodCompares := goodCompares + 1;
-        true;
-    else
-        println "** expected: \(expected)\n   obtained: \(obtained)";
-        false;
-    fi;
-
-compare obtained expected = compareUsing (==) obtained expected;
-
-time msg f =
-   (start = System#currentTimeMillis();
-    result = f ();
-    end = System#currentTimeMillis();
-    println "\(msg): \(end-start)ms";
-    result);
-
-select f = fold do r x: if f x then x::r else r fi done [];
-
-failedTests testHash =
-    select (!= "")
-       (mapHash do name f:
-            try
-                if f () then "" else
-                    println "Test \(name) failed";
-                    name;
-                fi 
-            catch FailureException e:
-                println "Test \(name) threw exception: \(e)";
-                name;
-            yrt;
-        done testHash);
-        
-runTests group testHash =
-   (failed = failedTests testHash;
-    good = (length testHash - length failed);
-    bad = length failed;
-    println "\(group): \(good)/\(good+bad) tests passed";
-    if not empty? failed then
-        println "\(group): Failed tests [\(bad)]: \(strJoin ' ' failed)";
-    fi;
-    bad);
-
-{
-    compare, compareUsing,
-    time,
-    runTests, 
-}
-
--- a/may/transform/fft.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-
-module may.transform.fft;
-
-import edu.emory.mathcs.jtransforms.fft: DoubleFFT_1D;
-
-vec = load may.vector;
-complex = load may.complex;
-
-load may.complex.type;
-
-packedToComplex len p is number -> ~double[] -> array<cplx> =
-   (n = len / 2;
-    array
-       (map do i:
-            re = if i == n then p[1] else p[i*2] fi;
-            im = if i == 0 or i == n then 0 else p[i*2+1] fi;
-            complex.complex re im;
-        done [0..n]));
-
-complexToPacked arr =
-   (n = length arr;
-    v = new double[n*2-2];
-    for [0..(n-1)*2-1] do i:
-        ix = int (i/2);
-        v[i] :=
-            if i == ix*2 then
-                complex.real arr[ix]
-            else 
-                complex.imaginary arr[ix] 
-            fi;
-    done;
-    v[1] := complex.real arr[n-1];
-    v);
-
-//!!! doc: n is supplied separately from the input vector to support partial evaluation
-//!!! doc: output has n/2+1 complex values
-//!!! doc: powers of two only? check with jtransforms
-realForward n = 
-   (d = new DoubleFFT_1D(n);
-    do bl:
-        v = vec.primitive bl;
-        d#realForward(v);
-        packedToComplex (vec.length bl) v;
-    done);
-
-//!!! doc: input requires n/2+1 complex values (or should test and throw?)
-//!!! doc: powers of two only? check with jtransforms
-realInverse n = 
-   (d = new DoubleFFT_1D(n);
-    do cplx:
-        v = complexToPacked (array cplx);
-        d#realInverse(v, true);
-        vec.vector v;
-    done);
-
-{
-realForward,
-realInverse,
-}
-
--- a/may/transform/test/test_fft.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-
-module may.transform.test.test_fft;
-
-{ realForward, realInverse } = load may.transform.fft;
-{ list, fromList } = load may.vector;
-{ complex } = load may.complex;
-
-{ compare } = load may.test.test;
-
-testFFT orig reals imags =
-   (out = realForward (length orig) (fromList orig);
-    back = realInverse (length orig) out;
-    compare out (array (map2 complex reals imags)) and compare (list back) orig);
-
-[
-
-"dc": \(
-    testFFT [1,1,1,1] [4,0,0] [0,0,0];
-),
-
-"sine": \(
-    testFFT [0,1,0,-1] [0,0,0] [0,-2,0];
-),
-
-"cosine": \(
-    testFFT [1,0,-1,0] [0,2,0] [0,0,0];
-),
-
-"sineCosine": \(
-    testFFT [0.5,1,-0.5,-1] [0,1,0] [0,-2,0];
-),
-
-"nyquist": \(
-    testFFT [1,-1,1,-1] [0,0,4] [0,0,0];
-),
-
-"dirac": \(
-    testFFT [1,0,0,0] [1,1,1] [0,0,0] and
-        testFFT [0,1,0,0] [1,0,-1] [0,-1,0] and
-        testFFT [0,0,1,0] [1,-1,1] [0,0,0] and
-        testFFT [0,0,0,1] [1,0,-1] [0,1,0];
-),
-
-] is hash<string, () -> boolean>;
-
-
--- a/may/vamp.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,321 +0,0 @@
-module may.vamp;
-
-import org.vamp_plugins:
-       Plugin, Plugin$InputDomain,
-       PluginLoader, PluginLoader$AdapterFlags, PluginLoader$LoadFailedException,
-       ParameterDescriptor, OutputDescriptor, OutputDescriptor$SampleType,
-       RealTime, Feature;
-
-import java.lang: UnsatisfiedLinkError;
-
-import java.util: Map, List;
-
-vec = load may.vector;
-fr = load may.stream.framer;
-af = load may.stream.audiofile;
-mat = load may.matrix;
-vamprdf = load may.vamp.vamprdf;
-vamppost = load may.vamp.vamppost;
-
-store = load yertle.store;
-
-realTime r is ~RealTime -> number = r#sec() + (r#nsec() / 1000000000);
-
-feature f is ~Feature -> 'a = {
-    timestamp = if f#hasTimestamp then Time (realTime f#timestamp) else Untimed () fi,
-    duration = if f#hasDuration then Time (realTime f#duration) else Untimed () fi,
-    values = vec.fromFloats f#values,
-    label = f#label,
-    };
-
-featureList fl is ~Object -> 'a =
-    if nullptr? fl then []
-    else
-        a = fl unsafely_as ~List;
-        result = array [];
-        itr = a#iterator();
-        itr#hasNext() loop (push result (feature (itr#next() unsafely_as ~Feature)));
-        list result
-    fi;
-
-featureSet fs is ~Map -> 'a =
-   (numberOf n is ~Object -> number = (n unsafely_as ~Integer)#intValue();
-    s = [:];
-    kk = list fs#keySet()#toArray();
-    for kk do k: s[numberOf k] := featureList fs#get(k) done;
-    s);
-
-stores = [:];
-
-getSingletonStoreFor loader =
-    synchronized stores do:
-        if loader in stores then
-            stores[loader]
-        else
-            s = store.newRdfStore ();
-            loader s;
-            stores[loader] := s;
-            s;
-        fi
-    done;
-
-getSystemStore () =
-    getSingletonStoreFor vamprdf.loadSystemVampRdf;
-
-getGlobalStore () = 
-    getSingletonStoreFor vamprdf.loadGlobalVampRdf;
-
-getPluginPath () =
-   (try
-        map string PluginLoader#getInstance()#getPluginPath();
-    catch UnsatisfiedLinkError e:
-        eprintln "Warning: Unable to obtain plugin path:\n\(e)";
-        [];
-    yrt);
-
-listPlugins () =
-   (try
-        map string PluginLoader#getInstance()#listPlugins();
-    catch UnsatisfiedLinkError e:
-        eprintln "Warning: Unable to obtain plugin list:\n\(e)";
-        [];
-    yrt);
-
-getKnownPluginKeys () =
-   (store = getGlobalStore ();
-    nodes = vamprdf.allPluginNodes store;
-    // ordering is random out of the store; might as well sort
-    sort (map do n: (vamprdf.pluginDataByNode store n).pluginKey done nodes));
-
-getDataForKnownPlugin key =
-    vamprdf.pluginDataByKey (getGlobalStore ()) key;
-   
-categoryOf key =
-    list PluginLoader#getInstance()#getPluginCategory(key);
-
-inputDomain d is ~Plugin$InputDomain -> 'a = 
-    if d == Plugin$InputDomain#FREQUENCY_DOMAIN then
-        FrequencyDomain ()
-    else
-        TimeDomain ()
-    fi;
-
-parameterDescriptor pd is ~ParameterDescriptor -> 'a = {
-    identifier = pd#identifier,
-    name = pd#name,
-    description = pd#description,
-    unit = pd#unit,
-    minValue = pd#minValue,
-    maxValue = pd#maxValue,
-    defaultValue = pd#defaultValue,
-    get quantize () = if pd#isQuantized then QuantizeStep pd#quantizeStep else Unquantized () fi,
-    valueNames = map string pd#valueNames
-    };
-
-sampleType t rate is ~OutputDescriptor$SampleType -> number -> 'a =
-    if t == OutputDescriptor$SampleType#OneSamplePerStep then
-        OneSamplePerStep ()
-    elif t == OutputDescriptor$SampleType#FixedSampleRate then
-        FixedSampleRate rate
-    else
-        VariableSampleRate rate
-    fi;
-
-structureOf rdfOutputData od is 'a -> ~OutputDescriptor -> 'b = 
-   (computes = case rdfOutputData of Some d: d.computes; None (): Unknown () esac;
-    s = getSystemStore ();
-    noteIRI = case s.expand "af:Note" of IRI iri: iri; _: "" esac;
-    if od#hasFixedBinCount and od#binCount == 0 then
-        Instants ();
-    elif od#hasDuration then
-        if computes != Unknown () then
-            if computes == Event noteIRI then Notes ();
-            else Regions ();
-            fi
-        elif od#hasFixedBinCount then
-            if od#binCount > 1 then Notes ();
-            elif od#unit == "Hz" or strIndexOf (strLower od#unit) "midi" 0 >= 0 then Notes ();
-            else Regions ();
-            fi
-        else
-            Unknown ();
-        fi
-    elif od#hasFixedBinCount and od#binCount == 1 then
-        case computes of
-        Event e:
-            if strEnds? e "Segment" 
-            then Segmentation ()
-            else Curve ()
-            fi;
-        _:
-            if od#sampleType == OutputDescriptor$SampleType#OneSamplePerStep
-            then Series ()
-            else Curve ()
-            fi;
-        esac;
-    elif od#hasFixedBinCount and
-         od#sampleType != OutputDescriptor$SampleType#VariableSampleRate then
-        Grid ();
-    else
-        Unknown ();
-    fi);
-
-outputDescriptor rdfOutputData od is 'a -> ~OutputDescriptor -> 'b = {
-    identifier = od#identifier,
-    name = od#name,
-    description = od#description,
-    get binCount () = if od#hasFixedBinCount then Fixed od#binCount else Variable () fi,
-    get valueExtents () = if od#hasKnownExtents then Known { min = od#minValue, max = od#maxValue } else Unknown () fi,
-    get valueQuantize () = if od#isQuantized then QuantizeStep od#quantizeStep else Unquantized () fi,
-    valueUnit = od#unit,
-    binNames = array (map string od#binNames),
-    sampleType = sampleType od#sampleType od#sampleRate,
-    hasDuration = od#hasDuration,
-    get computes () = case rdfOutputData of Some data: data.computes; None (): Unknown () esac,
-    get inferredStructure () = structureOf rdfOutputData od,
-    };
-
-plugin key p is string -> ~Plugin -> 'a =
-   (rdfData = vamprdf.pluginDataByKey (getSystemStore ()) key;
-    {
-    plugin = p,
-    key,
-    get apiVersion () = p#getVampApiVersion(),
-    get identifier () = p#getIdentifier(),
-    get name () = p#getName(),
-    get description () = p#getDescription(),
-    get maker () = p#getMaker(),
-    get copyright () = p#getCopyright(),
-    get version () = p#getPluginVersion(),
-    get category () = PluginLoader#getInstance()#getPluginCategory(key),
-    get hasRdfDescription () = (rdfData != None ()),
-    get infoURL () = case rdfData of Some data: data.infoURL; None (): "" esac,
-    get parameters () = array (map parameterDescriptor p#getParameterDescriptors()),
-    parameterValue identifier = p#getParameter(identifier),
-    setParameterValue identifier value = p#setParameter(identifier, value),
-    get programs () = array (map string p#getPrograms()),
-    get currentProgram () = p#getCurrentProgram(),
-    selectProgram pr = p#selectProgram(pr),
-    get inputDomain () = inputDomain p#getInputDomain(),
-    get preferredBlockSize () = p#getPreferredBlockSize(),
-    get preferredStepSize () = p#getPreferredStepSize(),
-    get minChannelCount () = p#getMinChannelCount(),
-    get maxChannelCount () = p#getMaxChannelCount(),
-    initialise { channels, hop, blockSize } = p#initialise(channels, hop, blockSize),
-    reset () = p#reset(),
-    get outputs () =
-        array case rdfData of
-        Some data: map2 outputDescriptor (map Some data.outputs) p#getOutputDescriptors();
-        None (): map (outputDescriptor (None ())) p#getOutputDescriptors();
-        esac,
-    process frame time is 'a -> ~RealTime -> 'b = 
-        featureSet p#process((map vec.floats (mat.asRows frame)) as ~float[][], 0, time),
-    getRemainingFeatures () = featureSet p#getRemainingFeatures(),
-    dispose () = p#dispose(),
-    });
-
-featuresFromSet outputNo f = if outputNo in f then f[outputNo] else [] fi;
-
-outputNumberByName p name =
-   (outputs = p.outputs;
-    case find ((== name) . (.identifier)) outputs of
-    first::rest: index first outputs;
-    _: -1;
-    esac);
-
-loadPlugin rate key =
-    try
-        OK (plugin key 
-            PluginLoader#getInstance()#loadPlugin(key, rate,
-                PluginLoader$AdapterFlags#ADAPT_INPUT_DOMAIN +
-                PluginLoader$AdapterFlags#ADAPT_CHANNEL_COUNT))
-    catch PluginLoader$LoadFailedException _:
-        Error "Failed to load Vamp plugin with key \(key)"
-    yrt;
-
-processed { p, sampleRate, hop } frames count =
-    case frames of
-    frame::rest:
-        p.process frame RealTime#frame2RealTime(count, sampleRate)
-        :.
-        \(processed { p, sampleRate, hop } rest (count + hop));
-    _: 
-       (rf = p.getRemainingFeatures ();
-        p.dispose ();
-        [rf]);
-    esac;
-
-converted { p, sampleRate, hop } outputNo fl =
-    map (featuresFromSet outputNo) fl;
-
-returnErrorFrom p stream text = (p.dispose (); stream.close (); failWith text);
-
-processWith key p outputNo stream =
-   (blockSize =
-        if p.preferredBlockSize == 0 then 2048
-        else p.preferredBlockSize fi;
-    stepSize =
-        if p.preferredStepSize == 0 then
-            if p.inputDomain == FrequencyDomain () then blockSize / 2
-            else blockSize fi;
-        else p.preferredStepSize fi;
-    channels = 1;
-    params = {
-        p, sampleRate = stream.sampleRate, channels = 1,
-        framesize = blockSize, blockSize, hop = stepSize
-    };
-    if p.initialise params then
-        {
-            key = key,
-            output = p.outputs[outputNo],
-            parameters = mapIntoHash id p.parameterValue
-               (map (.identifier) p.parameters),
-            config = {
-                channels, blockSize, stepSize,
-                sampleRate = stream.sampleRate
-            },
-            features = converted params outputNo
-               (processed params (fr.frames params stream) 0)
-        };
-        // If processing completed successfully, then p is
-        // disposed by processed and stream is closed by the
-        // framer
-    else
-        returnErrorFrom p stream "Failed to initialise plugin \(key) with channels = \(channels), blockSize = \(blockSize), stepSize = \(stepSize)";
-    fi);
-
-processStream key output stream =
-    case loadPlugin stream.sampleRate key of
-    OK p:
-        outputNo = outputNumberByName p output;
-        if outputNo >= 0 then
-            processWith key p outputNo stream
-        else
-            outputs = strJoin ", " (map (.identifier) p.outputs);
-            returnErrorFrom p stream "Plugin \(key) has no output named \(output) (outputs are: \(outputs))"
-        fi;
-    Error e: failWith e;
-    esac;
-
-processFile key output filename = 
-    processStream key output (af.open filename);
-
-processStreamStructured key output filename =
-    vamppost.postprocess (processStream key output filename);
-
-processFileStructured key output filename =
-    vamppost.postprocess (processFile key output filename);
-
-{
-get pluginPath = getPluginPath,
-get pluginKeys = listPlugins,
-loadPlugin,
-categoryOf,
-processStream,
-processFile,
-processStreamStructured,
-processFileStructured,
-getKnownPluginKeys,
-getDataForKnownPlugin,
-}
-
--- a/may/vamp/test/test_vamp.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,150 +0,0 @@
-module may.vamp.test.test_vamp;
-
-v = load may.vamp;
-synthetic = load may.stream.syntheticstream;
-filter = load may.stream.filter;
-mat = load may.matrix;
-vec = load may.vector;
-
-{ compare, compareUsing } = load may.test.test;
-
-testPluginKey = "vamp-test-plugin:vamp-test-plugin";
-
-rate = 44100;
-
-testStream () = filter.withDuration (rate * 20) (synthetic.whiteNoise rate);
-
-processTest output = 
-    v.processStreamStructured testPluginKey output (testStream ());
-
-float n is number -> number =
-    // round number to float precision (for comparison with floats)
-   (arr = new float[1];
-    arr[0] := n;
-    arr[0]);
-
-floats nn = map float nn;
-
-tests =
-[
-
-"version": \(
-    case v.loadPlugin rate testPluginKey of
-    Error e: (eprintln "version: Error: \(e)"; false);
-    OK plugin: compare plugin.version 1;
-    esac
-),
-
-"instants": \(
-    case processTest "instants" of
-    Instants ii:
-        compare (map (.time) ii) [ 0, 1.5, 3, 4.5, 6, 7.5, 9, 10.5, 12, 13.5 ];
-    other: failWith "wrong structure type: expected Instants tag, got \(other)";
-    esac
-),
-
-"curve-oss": \(
-    case processTest "curve-oss" of
-    Series s:
-        compare s.start 0 and
-        //!!! want to specify step and block size for processing
-            compare s.step (2048/rate) and
-            compare s.values (floats (map (* 0.05) [0..19]));
-    other: failWith "wrong structure type: expected Series tag, got \(other)";
-    esac
-),
-
-"curve-fsr": \(
-    case processTest "curve-fsr" of
-    Curve c:
-        compare (map (.time) c) (map (* 0.4) [0..9]) and
-           compare (map (.value) c) (floats (map (* 0.1) [0..9]));
-/*
-        compare s.start 0 and
-            compare s.step 0.4 and
-            compare s.values (floats (map (* 0.1) [0..9]));
-*/
-    other: failWith "wrong structure type: expected Series tag, got \(other)";
-    esac
-),
-    
-"curve-fsr-timed": \(
-    case processTest "curve-fsr-timed" of
-    Curve c:
-        compare (map (.time) c) [ 0, 0, 0, 0.4, 2, 2, 2, 2.4, 4, 4 ] and
-           compare (map (.value) c) (floats (map (* 0.1) [0..9]));
-    other: failWith "wrong structure type: expected Curve tag, got \(other)";
-    esac
-),
-
-"curve-vsr": \(
-    case processTest "curve-vsr" of
-    Curve c:
-        compare (map (.time) c) (map (* 0.75) [0..9]) and
-           compare (map (.value) c) (floats (map (* 0.1) [0..9]));
-    other: failWith "wrong structure type: expected Curve tag, got \(other)";
-    esac
-),
-
-"grid-oss": \(
-    case processTest "grid-oss" of //!!! test spacing?
-    Grid g:
-        compareUsing mat.equal g 
-           (mat.newMatrix (ColumnMajor ())
-               (map do x:
-                   (vec.fromList . floats) (map do y:
-                        (x + y + 2) / 30
-                        done [0..9])
-                    done [0..19]));
-    other: failWith "wrong structure type: expected Grid tag, got \(other)";
-    esac
-),
-
-"grid-fsr": \(
-    case processTest "grid-fsr" of //!!! test spacing?
-    Grid g:
-        compareUsing mat.equal g 
-           (mat.newMatrix (ColumnMajor ())
-               (map do x:
-                   (vec.fromList . floats) (map do y:
-                        (x + y + 2) / 20
-                        done [0..9])
-                    done [0..9]));
-    other: failWith "wrong structure type: expected Grid tag, got \(other)";
-    esac
-),
-
-"notes-regions": \(
-    case processTest "notes-regions" of
-    Regions rr:
-        compare (map (.time) rr) [0..9] and
-           compare (map (.duration) rr) (map do i: Time (if i % 2 == 0 then 1.75 else 0.5 fi) done [0..9]) and
-           compare (map (.values) rr) (map do i: array [float i] done (map (* 0.1) [0..9]));
-    other: failWith "wrong structure type: expected Curve tag, got \(other)";
-    esac
-),
-    
-];
-
-// Check we have the test plugin. Without it, all the tests must fail
-
-if contains? testPluginKey v.pluginKeys then 
-    tests
-else
-    eprintln
-"** Vamp test plugin not found!
-   Either the Vamp module is not working, or the test plugin is not installed.
-   Please ensure vamp-test-plugin.{so,dll,dylib} is in the path,
-   or set $VAMP_PATH to point to its location.
-
-   Current path: \(v.pluginPath)
-   Required plugin key: \"\(testPluginKey)\"
-   Plugin keys found: \(v.pluginKeys)
-
-   All of the Vamp plugin tests will fail until this is fixed.
-";
-    mapIntoHash id \\false (keys tests)
-fi is hash<string, () -> boolean>;
-
-
-
--- a/may/vamp/test/test_vamppost.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-module may.vamp.test.test_vamppost;
-
-vp = load may.vamp.vamppost;
-
-{ compare } = load may.test.test;
-
-untimed n = { timestamp = Untimed (), values = [n] };
-timed t n = { timestamp = Time t, values = [n] };
-
-testdata =  {
-    output = { sampleType = OneSamplePerStep () },
-    config = { sampleRate = 1000, stepSize = 100 },
-    features = [
-        [],
-        [ untimed 1 ],
-        [ untimed 1, untimed 2 ],
-        [],
-        [ timed 1.1 1, untimed 2, timed 4 3 ]
-    ]
-};
-
-[
-
-"fillOneSamplePerStep": \(
-    // All features returned within a single process call (i.e. within
-    // a single sub-list of the feature list) should be given the same
-    // timestamp; the timestamp increments according to the config
-    // step size between sub-lists
-    filled = vp.fillTimestamps
-       (testdata with { output = { sampleType = OneSamplePerStep () } });
-    compare filled [
-        timed 0.1 1 ,
-        timed 0.2 1, timed 0.2 2 ,
-        timed 0.4 1, timed 0.4 2, timed 0.4 3
-    ];
-),
-
-"fillFixedSampleRate": \(
-    // "If the output feature's hasTimestamp field is true, the host
-    // should read and use the output feature's timestamp. The host
-    // may round the timestamp according to the sample rate given in
-    // the output descriptor's sampleRate field [...] If
-    // [hasTimestamp] is false, its time will be implicitly calculated
-    // by incrementing the time of the previous feature according to
-    // the [output descriptor's] sample rate". Note that the time is
-    // based on that of the previous feature, not that of the previous
-    // process cycle (as is the case with OneSamplePerStep features).
-    filled = vp.fillTimestamps
-       (testdata with { output = { sampleType = FixedSampleRate 5 } });
-    compare filled [
-        timed 0 1 ,
-        timed 0.2 1, timed 0.4 2 ,
-        timed 1.2 1, timed 1.4 2, timed 4.0 3
-    ];
-),
-
-"fillFixedSampleRate2": \(
-    // As above, but with non-integer output sample rate
-    filled = vp.fillTimestamps
-       (testdata with { output = { sampleType = FixedSampleRate 2.5 } });
-    compare filled [
-        timed 0 1 ,
-        timed 0.4 1, timed 0.8 2 ,
-        timed 1.2 1, timed 1.6 2, timed 4.0 3
-    ];
-),
-
-"fillVariableSampleRate": \(
-    // For VariableSampleRate outputs, the timestamps should always
-    // be left entirely alone by fillTimestamps -- it's an error for
-    // the plugin to return any features without valid timestamps,
-    // but it isn't the job of fillTimestamps to handle that error
-    filled = vp.fillTimestamps
-       (testdata with { output = { sampleType = VariableSampleRate 5 } });
-    compare filled [
-        untimed 1 ,
-        untimed 1, untimed 2 ,
-        timed 1.1 1, untimed 2, timed 4.0 3
-    ];
-),
-
-] is hash<string, () -> boolean>;
-
--- a/may/vamp/vamppost.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-module may.vamp.vamppost;
-
-mat = load may.matrix;
-vec = load may.vector;
-
-fillOneSamplePerStep config features =
-   (fill' n pending features =
-        // For OneSamplePerStep features, the time is incremented
-        // between process blocks (a process block is one element in
-        // the features list, which is then expanded into the complete
-        // pending list)
-        case pending of
-        feature::rest:
-            stamped = feature with
-            //!!! how do we ensure feature timestamps are rationals where possible?
-               { timestamp = Time ((n * config.stepSize) / config.sampleRate) };
-            stamped :. \(fill' n rest features);
-        _:
-            if empty? features then []
-            else fill' (n+1) (head features) (tail features);
-            fi;
-        esac;
-    fill' (-1) [] features);
-
-fillFixedSampleRate config rate features =
-   (fill' n pending features =
-        // For FixedSampleRate features without explicit timestamps,
-        // the time is incremented from the previous *feature* not the
-        // previous process block (i.e. between elements in the
-        // pending list)
-        case pending of
-        feature::rest:
-            n = case feature.timestamp of
-                Untimed (): n + 1;
-                Time t: int (t * rate + 0.5);
-                esac;
-            stamped = feature with { timestamp = Time (n / rate) };
-            stamped :. \(fill' n rest features);
-        _:
-            if empty? features then []
-            else fill' n (head features) (tail features);
-            fi;
-        esac;
-    fill' (-1) [] features);
-
-fillTimestamps { output, config, features } =
-    case output.sampleType of
-    OneSamplePerStep ():
-        fillOneSamplePerStep config features;
-    FixedSampleRate rate:
-        fillFixedSampleRate config rate features;
-    VariableSampleRate _:
-        concat features;
-    esac;
-
-structureGrid binCount features =
-//!!! need to return grid resolution as well -- or will caller read that from output elsewhere if they need it?
-    if empty? features then
-        mat.zeroMatrix { rows = binCount, columns = 0 };
-    else
-        mat.newMatrix (ColumnMajor ()) (map (.values) features);
-    fi;
-
-timeOf f =
-    case f.timestamp of
-    Time t: t;
-    _: failWith "Internal error: timestamps not filled";
-    esac;
-
-structureSeries features =
-    if empty? features then { start = 0, step = 0, values = [] }
-    else 
-        t0 = timeOf (head features);
-        t1 = if empty? (tail features) then t0
-             else timeOf (head (tail features)) fi;
-        {
-            start = t0,
-            step = t1 - t0,
-            values = map do f: vec.at f.values 0 done features;
-        }
-    fi;
-
-structureCurve features =
-    map do f: {
-        time = timeOf f,
-        value = vec.at f.values 0,
-        label = f.label
-    } done features;
-
-structureInstants features =
-    map do f: {
-        time = timeOf f,
-        label = f.label
-    } done features;
-
-structureSegmentation features =
-    map do f: {
-        time = timeOf f,
-        type = vec.at f.values 0,
-        label = f.label
-    } done features;
-
-structureNotes features =
-    map do f: {
-        time = timeOf f,
-        duration = f.duration,
-        pitch = vec.at f.values 0, //!!! no, might be empty
-        otherValues = array (tail (vec.list f.values)), //!!! no, might be empty
-        label = f.label
-    } done features;
-
-structureWithDuration features =
-    map do f: {
-        time = timeOf f,
-        duration = f.duration,
-        values = array (vec.list f.values),
-        label = f.label
-    } done features;
-
-structureWithoutDuration features =
-    map do f: {
-        time = timeOf f,
-        values = array (vec.list f.values),
-        label = f.label
-    } done features;
-
-structure data =
-   (type = data.output.inferredStructure;
-    features =
-        case type of
-        Grid (): concat data.features;
-        _: fillTimestamps data;
-        esac;
-    binCount = 
-        case data.output.binCount of
-        Fixed n: n;
-        _: 0;
-        esac;
-    case type of
-    Series ():              // No duration, one value, not variable rate
-        Series (structureSeries features);
-    Grid ():                // No duration, >1 value, not variable rate
-        Grid (structureGrid binCount features);
-    Curve ():               // No duration, one value, variable rate
-        Curve (structureCurve features);
-    Instants ():            // Zero-valued features
-        Instants (structureInstants features);
-    Notes ():               // Duration, at least one value (pitch or freq)
-        Notes (structureNotes features);
-    Regions ():             // Duration, zero or more values
-        Regions (structureWithDuration features);
-    Segmentation ():        // No duration, one value, segment type in RDF
-        Segmentation (structureSegmentation features);
-    Unknown ():             // Other
-        Unknown
-           ((if data.output.hasDuration then structureWithDuration
-             else structureWithoutDuration
-             fi) features);
-    esac);
-
-{
-fillTimestamps,
-postprocess = structure
-}
-
-
--- a/may/vamp/vamprdf.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,290 +0,0 @@
-
-module may.vamp.vamprdf;
-
-read = load yertle.read;
-{ newRdfStore } = load yertle.store;
-
-import java.io: File;
-
-import org.vamp_plugins: PluginLoader;
-
-import java.lang: UnsatisfiedLinkError;
-
-getPluginPath () =
-   (try map string PluginLoader#getInstance()#getPluginPath();
-    catch UnsatisfiedLinkError e: [];
-    yrt);
-
-systemVampRdfFiles () =
-    concat
-       (map do p:
-            map ((p ^ File#separator ^) . (.name))
-               (filter do entry:
-                    entry.file? and
-                       (lc = strLower entry.name;
-                        (strEnds? lc ".ttl") or
-                        (strEnds? lc ".n3") or
-                        (strEnds? lc ".nt"))
-                    done (listDirectory false p))
-            done (getPluginPath ()));
-
-addVampPrefixes store =
-   (store.addPrefix "vamp" "http://purl.org/ontology/vamp/";
-    store.addPrefix "dc" "http://purl.org/dc/elements/1.1/";
-    store.addPrefix "foaf" "http://xmlns.com/foaf/0.1/";
-    store.addPrefix "owl" "http://www.w3.org/2002/07/owl#";
-    store.addPrefix "af" "http://purl.org/ontology/af/");
-
-loadSystemVampRdf store =
-   (addVampPrefixes store;
-    for (systemVampRdfFiles ()) do file:
-        case read.loadTurtleFile store ("file://" ^ file) file of
-        OK (): ();
-        Error e: eprintln
-            "WARNING: Failed to load Vamp plugin RDF file \"\(file)\": \(e)";
-        esac
-    done);
-
-getGlobalPluginIndex () =
-    list (strSplit "\n" (fetchURL [ Timeout 10 ] (Handle getContents)
-                         "http://www.vamp-plugins.org/rdf/plugins/index.txt"));
-
-//!!! need to cache these retrievals
-parseGlobalVampRdf () =
-   (parse urls =
-        case urls of
-        url::rest:
-           (doc = fetchURL [ Timeout 10 ] (Handle getContents) url;
-            parsed = read.parseTurtleString url doc;
-            { url, parsed } :. \(parse rest));
-         _: [];
-        esac;
-    parse (getGlobalPluginIndex ()));
-
-loadGlobalVampRdf store =
-    for (parseGlobalVampRdf ()) do { url, parsed }:
-        case read.loadParsedTriples store parsed of
-        OK (): ();
-        Error e: eprintln "WARNING: Failed to load Vamp RDF from URL \(url): \(e)";
-        esac;
-    done;
-
-subjects = map (.s);
-
-iriTypes =
-    map do t:
-        case t of
-        IRI iri: IRI iri;
-        Blank n: Blank n;
-        esac done;
-
-iriSubjects = iriTypes . subjects;
-
-allLibraryNodes store =
-    iriSubjects
-       (store.match {
-            s = Wildcard (),
-            p = Known (store.expand "a"),
-            o = Known (store.expand "vamp:PluginLibrary")
-            });
-
-allPluginNodes store =
-    iriSubjects
-       (store.match {
-            s = Wildcard (),
-            p = Known (store.expand "a"),
-            o = Known (store.expand "vamp:Plugin")
-            });
-
-pluginsWithId store id = 
-    iriTypes
-       (filter do pnode:
-        store.contains {
-            s = pnode,
-            p = store.expand "vamp:identifier",
-            o = Literal { value = id, type = "", language = "" }
-            }
-        done (allPluginNodes store));
-
-librariesWithId store id =
-    iriTypes
-       (filter do lnode:
-        store.contains {
-            s = lnode,
-            p = store.expand "vamp:identifier",
-            o = Literal { value = id, type = "", language = "" }
-            }
-        done (allLibraryNodes store));
-
-splitPluginKey key =
-   (bits = strSplit ":" key;
-    reversed = reverse bits;
-    soname = strJoin ":" (reverse (tail reversed));
-    identifier = head reversed;
-    { soname, identifier });
-
-pluginNodesByKey store key =
-   (case splitPluginKey key of { soname, identifier }:
-        candidatePlugins = pluginsWithId store identifier;
-        candidateLibraries = librariesWithId store soname;
-        filter do pnode:
-            any do lnode:
-                store.contains {
-                    s = lnode,
-                    p = store.expand "vamp:available_plugin",
-                    o = pnode
-                    }
-                done candidateLibraries
-            done candidatePlugins
-    esac);
-
-libraryNodeFor store pluginNode =
-    case store.match {
-        s = Wildcard (), p = Known (store.expand "vamp:available_plugin"), o = Known pluginNode
-        } of
-    { s = IRI iri }::others: Some (IRI iri);
-    { s = Blank n }::others: Some (Blank n);
-     _: None ();
-    esac;
-
-textProperty store subject name =
-    case store.match {
-        s = Known subject, p = Known (store.expand name), o = Wildcard ()
-        } of
-    { o = Literal { value = text } }::others: text;
-     _: "";
-    esac;
-
-iriProperty store subject name =
-    case store.match {
-        s = Known subject, p = Known (store.expand name), o = Wildcard ()
-        } of
-    { o = IRI iri }::others: IRI iri;
-     _: None ();
-    esac;
-
-nodeProperty store subject name =
-    case store.match {
-        s = Known subject, p = Known (store.expand name), o = Wildcard ()
-        } of
-    { o = IRI iri }::others: Some (IRI iri);
-    { o = Blank n }::others: Some (Blank n);
-     _: None ();
-    esac;
-
-inputDomainOf store pluginNode =
-   case store.match {
-        s = Known pluginNode, p = Known (store.expand "vamp:input_domain"), o = Wildcard ()
-        } of
-    { o = IRI iri }::others:
-        if IRI iri == store.expand "vamp:FrequencyDomain"
-        then FrequencyDomain ()
-        else TimeDomain ()
-        fi;
-     _: TimeDomain ();
-    esac;
-
-outputDescriptor store outputNode =
-   (tprop abbr = textProperty store outputNode abbr;
-    iprop abbr = iriProperty store outputNode abbr;
-    bprop abbr deflt =
-       (b = strLower (textProperty store outputNode abbr);
-        if b == "true" then true elif b == "false" then false else deflt fi);
-    nprop abbr =
-        try number (textProperty store outputNode abbr); catch Exception _: 0 yrt;
-    {
-        identifier = tprop "vamp:identifier",
-        name = tprop "dc:title",
-        description = tprop "dc:description",
-        rdfType = case iprop "a" of IRI iri: iri; _: "" esac,
-        valueUnit = tprop "vamp:unit",
-        binCount = 
-            if bprop "vamp:fixed_bin_count" false
-            then Known (nprop "vamp:bin_count")
-            else Unknown ()
-            fi,
-        computes =
-            case iprop "vamp:computes_event_type" of
-            IRI iri: Event iri;
-             _: case iprop "vamp:computes_signal_type" of
-                IRI iri: Signal iri;
-                 _: case iprop "vamp:computes_feature_type" of
-                    IRI iri: Feature iri;
-                     _: Unknown ();
-                    esac
-                esac
-            esac,
-        //!!! and some other properties
-    });
-
-pluginDataByNode store pluginNode =
-   (tprop abbr = textProperty store pluginNode abbr;
-    nprop abbr =
-        try number (textProperty store pluginNode abbr); catch Exception _: 0 yrt;
-    soname =
-        case libraryNodeFor store pluginNode of
-        None (): "";
-        Some n: textProperty store n "vamp:identifier";
-        esac;
-    {
-        pluginKey = soname ^ ":" ^ tprop "vamp:identifier",
-        soname,
-        apiVersion = nprop "vamp:vamp_API_version",
-        identifier = tprop "vamp:identifier",
-        name = tprop "dc:title",
-        description = tprop "dc:description",
-        maker =
-           (tmaker = tprop "foaf:maker";
-            if tmaker == "" then
-                case nodeProperty store pluginNode "foaf:maker" of
-                Some n: textProperty store n "foaf:name";
-                None (): "";
-                esac
-            else
-                tmaker
-            fi),
-        copyright = tprop "dc:rights",
-        version = tprop "owl:versionInfo",
-        category = tprop "vamp:category",
-        inputDomain = inputDomainOf store pluginNode,
-        infoURL =
-           (case iriProperty store pluginNode "foaf:page" of
-            IRI iri: iri;
-            None ():
-                case libraryNodeFor store pluginNode of
-                None (): "";
-                Some n:
-                    case iriProperty store n "foaf:page" of
-                    IRI iri: iri;
-                    None (): "";
-                    esac;
-                esac;
-            esac),
-        outputs = 
-           (matches = store.match { s = Known pluginNode,
-                                    p = Known (store.expand "vamp:output"), 
-                                    o = Wildcard () };
-            array (map do t:
-                       case t.o of
-                       IRI iri: outputDescriptor store (IRI iri);
-                       Blank n: outputDescriptor store (Blank n);
-                       esac
-                       done matches)),
-    });
-
-pluginDataByKey store key =
-    case pluginNodesByKey store key of
-    node::others: Some (pluginDataByNode store node);
-    _: None ()
-    esac;
-
-{
-loadSystemVampRdf,
-loadGlobalVampRdf,
-allPluginNodes,
-allLibraryNodes,
-pluginNodesByKey,
-pluginDataByNode,
-pluginDataByKey,
-}
-
--- a/may/vector.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,189 +0,0 @@
-
-/**
- * Vectors. A May vector is a typesafe, immutable wrapper around a Java
- * primitive array of doubles.
- *
- * Although not as convenient and flexible as a Yeti array<number> or
- * list<number>, a vector can be faster and more compact when dealing
- * with dense data of suitable range and precision such as sampled
- * sequences.
- */
-
-module may.vector;
-
-load may.vector.type;
-
-import java.util: Arrays;
-
-/// Return a vector of n zeros.
-zeros n =
-    new double[n];
-
-/// Return a vector of length n, containing all m.
-consts m n =
-   (a = zeros n;
-    for [0..n-1] do i:
-        a[i] := m;
-    done;
-    a);
-
-/// Return a vector of length n, containing all ones.
-ones n = consts 1.0 n;
-
-/// Return a vector of the values in the given list.
-fromList l is list?<number> -> ~double[] =
-    l as ~double[];
-
-/// Return the given vector as a list.
-list' a is ~double[] -> list<number> =
-    list a;
-
-/// Return the given vector as a Yeti array.
-array' a is ~double[] -> array<number> =
-    array a;
-
-/// Return the length of the given vector.
-length' =
-    length . list';
-
-/// Return true if the given vector is empty (has length 0).
-empty?' =
-    empty? . list';
-
-/// Return element n in the given vector v. (The function name and
-/// argument order are chosen for symmetry with the similar standard
-/// library array function.)
-at' v n is ~double[] -> number -> number =
-    v[n];
-
-/// Return the given vector as a Java primitive float array.
-floats a is ~double[] -> ~float[] =
-   (len = length' a;
-    f = new float[len];
-    for [0..len-1] do i:
-        f[i] := a[i];
-    done;
-    f);
-
-/// Return a vector of the values in the given Java primitive float array.
-fromFloats ff is ~float[] -> ~double[] =
-   (len = length (list ff);
-    a = new double[len];
-    for [0..len-1] do i:
-        a[i] := ff[i];
-    done;
-    a);
-
-/// Return true if the given vectors are equal, using the standard ==
-/// comparator on their elements.
-equal v1 v2 =
-    list' v1 == list' v2;
-
-/// Return true if the given vectors are equal, when applying the
-/// given numerical comparator to each element.
-equalUnder comparator v1 v2 =
-    length' v1 == length' v2 and
-        all id (map2 comparator (list' v1) (list' v2));
-
-/// Return another copy of the given vector.
-copyOf v is ~double[] -> ~double[] =
-    Arrays#copyOf(v, list' v |> length);
-
-/// Return a new vector containing a subset of the elements of the
-/// given vector, from index start (inclusive) to index end
-/// (exclusive). (The function name and argument order are chosen for
-/// symmetry with the standard library slice and strSlice functions.)
-slice v start end is ~double[] -> number -> number -> ~double[] =
-    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;
-
-/// Return a new vector of length n, containing the contents of the
-/// given vector v. If v is longer than n, the contents will be
-/// truncated; if shorter, they will be padded with zeros.
-resizedTo n v is number -> ~double[] -> ~double[] =
-    Arrays#copyOf(v, n);
-
-/// Return a new vector that is the reverse of the given vector.  Name
-/// chosen (in preference to passive "reversed") for symmetry with the
-/// standard library list reverse function.
-reverse v is ~double[] -> ~double[] =
-   (len = length (list v);
-    a = new double[len];
-    for [0..len-1] do i:
-        a[len-i-1] := v[i];
-    done;
-    a);
-
-/// Return a single new vector that contains the contents of all the
-/// given vectors, in order. (Unlike the standard module list concat
-/// function, this one cannot be lazy.)
-concat vv is list?<~double[]> -> ~double[] =
-   (len = sum (map length' vv);
-    vout = zeros len;
-    var base = 0;
-    for vv do v: 
-        vlen = length' v;
-        for [0..vlen-1] do i: vout[base + i] := v[i] done;
-        base := base + vlen;
-    done;
-    vout);
-
-/// Return a single new vector that contains the contents of the given
-/// vector, repeated n times. The vector will therefore have length n
-/// times the length of v.
-repeated v n is ~double[] -> number -> ~double[] =
-    concat (map \(v) [1..n]);
-
-{
-    zeros,
-    consts,
-    ones,
-    vector v = v,
-    primitive = copyOf,
-    floats,
-    fromFloats,
-    fromList,
-    list = list',
-    array = array',
-    length = length',
-    empty? = empty?',
-    at = at',
-    equal,
-    equalUnder,
-    slice,
-    resizedTo,
-    reverse,
-    repeated,
-    concat,
-} as {
-    zeros is number -> vector,
-    consts is number -> number -> vector,
-    ones is number -> vector,
-    vector is ~double[] -> vector,
-    primitive is vector -> ~double[],
-    floats is vector -> ~float[],
-    fromFloats is ~float[] -> vector,
-    fromList is list?<number> -> vector,
-    list is vector -> list<number>,
-    array is vector -> array<number>,
-    length is vector -> number,
-    empty? is vector -> boolean,
-    at is vector -> number -> number,
-    equal is vector -> vector -> boolean,
-    equalUnder is (number -> number -> boolean) -> vector -> vector -> boolean,
-    slice is vector -> number -> number -> vector,
-    resizedTo is number -> vector -> vector,
-    reverse is vector -> vector,
-    repeated is vector -> number -> vector,
-    concat is list?<vector> -> vector,
-}
-
-
-
--- a/may/vector/blockfuncs.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-
-module may.vector.blockfuncs;
-
-vec = load may.vector;
-
-load may.vector.type;
-
-//!!! "internal" vector function to retrieve data for read-only
-// purposes without copying
-raw =
-   (raw' v is ~double[] -> ~double[] = v;
-    raw' as vector -> ~double[]);
-
-sum' v =
-   (dat = raw v;
-    tot = new double[1];
-    for [0..length dat - 1] do i:
-        tot[0] := tot[0] + dat[i]
-    done;
-    tot[0]);
-
-max' v = 
-   (dat = raw v;
-    var mx = 0;
-    for [0..length dat - 1] do i:
-        if i == 0 or dat[i] > mx then
-            mx := dat[i];
-        fi
-    done;
-    mx);
-
-maxindex v =
-   (dat = raw v;
-    var mx = 0;
-    var mi = -1;
-    for [0..length dat - 1] do i:
-        if i == 0 or dat[i] > mx then
-            mx := dat[i];
-            mi := i;
-        fi
-    done;
-    mi);
-
-min' v = 
-   (dat = raw v;
-    var mn = 0;
-    for [0..length dat - 1] do i:
-        if i == 0 or dat[i] < mn then
-            mn := dat[i];
-        fi
-    done;
-    mn);
-
-minindex v =
-   (dat = raw v;
-    var mn = 0;
-    var mi = -1;
-    for [0..length dat - 1] do i:
-        if i == 0 or dat[i] < mn then
-            mn := dat[i];
-            mi := i;
-        fi
-    done;
-    mi);
-
-mean v =
-    case vec.length v of
-        0: 0;
-        len: sum' v / len
-    esac;
-
-add bb =
-   (len = head (sort (map vec.length bb));
-    vv = map raw bb;
-    out = new double[len];
-    for [0..len-1] do i:
-        for vv do v: 
-            out[i] := out[i] + v[i];
-        done;
-    done;
-    vec.vector out);
-
-subtract b1 b2 =
-   (v1 = raw b1;
-    v2 = raw b2;
-    len = if length v1 < length v2 then length v1 else length v2 fi;
-    out = new double[len];
-    for [0..len-1] do i:
-        out[i] := v1[i] - v2[i]
-    done;
-    vec.vector out);
-
-multiply b1 b2 =
-   (v1 = raw b1;
-    v2 = raw b2;
-    len = if length v1 < length v2 then length v1 else length v2 fi;
-    out = new double[len];
-    for [0..len-1] do i:
-        out[i] := v1[i] * v2[i]
-    done;
-    vec.vector out);
-
-scaled n v =
-    vec.fromList (map (* n) (vec.list v));
-
-divideBy n v =  // Not just "scaled (1/n)" -- this way we get exact rationals
-    vec.fromList (map (/ n) (vec.list v));
-
-sqr v =
-    multiply v v;
-
-rms =
-    sqrt . mean . sqr;
-
-abs' =
-    vec.fromList . (map abs) . vec.list;
-
-sqrt' =
-    vec.fromList . (map sqrt) . vec.list;
-
-unityNormalised v = 
-   (m = max' (abs' v);
-    if m != 0 then
-        divideBy m v;
-    else
-        v;
-    fi);
-
-fftshift v =
-   (len = vec.length v;
-    half = int(len/2 + 0.5); // round up for odd-length sequences
-    vec.concat [vec.slice v half len, vec.slice v 0 half]);
-
-ifftshift v =
-   (len = vec.length v;
-    half = int(len/2); // round down for odd-length sequences
-    vec.concat [vec.slice v half len, vec.slice v 0 half]);
-
-{
-sum is vector -> number = sum',
-mean is vector -> number,
-add is list?<vector> -> vector,
-subtract is vector -> vector -> vector,
-multiply is vector -> vector -> vector, 
-divideBy is number -> vector -> vector, 
-scaled is number -> vector -> vector,
-abs is vector -> vector = abs',
-sqr is vector -> vector,
-sqrt is vector -> vector = sqrt',
-rms is vector -> number,
-max is vector -> number = max',
-min is vector -> number = min',
-maxindex is vector -> number,
-minindex is vector -> number,
-unityNormalised is vector -> vector,
-fftshift is vector -> vector,
-ifftshift is vector -> vector,
-}
-
-
-        
--- a/may/vector/test/test_blockfuncs.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-
-module may.vector.test.test_blockfuncs;
-
-stdSqrt = sqrt;
-
-{ zeros, consts, ones, fromList, list } = load may.vector;
-{ sum, max, min, maxindex, minindex, mean, add, subtract, multiply, divideBy, scaled, abs, sqr, sqrt, rms, unityNormalised, fftshift, ifftshift } = load may.vector.blockfuncs;
-{ compare } = load may.test.test;
-
-[
-
-"sum": \(
-    compare ((sum . zeros) 0) 0 and
-        compare ((sum . zeros) 5) 0 and
-        compare ((sum . ones) 5) 5 and
-        compare ((sum . fromList) [1,-2,3,0]) 2
-),
-
-"max": \(
-    compare ((max . fromList) [1,-2,3,0]) 3 and
-        compare ((max . fromList) [-1,-2,-3]) (-1) and
-        compare ((max . fromList) [4,1]) 4 and
-        compare ((max . fromList) []) 0
-),
-
-"min": \(
-    compare ((min . fromList) [1,-2,3,0]) (-2) and
-        compare ((min . fromList) [-1,-2,-3]) (-3) and
-        compare ((min . fromList) [4,1]) 1 and
-        compare ((min . fromList) []) 0
-),
-
-"maxindex": \(
-    compare ((maxindex . fromList) [1,-2,3,0]) 2 and
-        compare ((maxindex . fromList) [-1,-2,-3]) 0 and
-        compare ((maxindex . fromList) [4,1]) 0 and
-        compare ((maxindex . fromList) []) (-1)
-),
-
-"minindex": \(
-    compare ((minindex . fromList) [1,-2,3,0]) 1 and
-        compare ((minindex . fromList) [-1,-2,-3]) 2 and
-        compare ((minindex . fromList) [4,1]) 1 and
-        compare ((minindex . fromList) []) (-1)
-),
-
-"mean": \(
-    compare ((mean . zeros) 0) 0 and
-        compare ((mean . zeros) 5) 0 and
-        compare ((mean . ones) 5) 1 and
-        compare ((mean . fromList) [1,-2,3,0]) 0.5
-),
-
-"add": \(
-    compare (list (add [zeros 0, ones 5])) [] and
-        compare (list (add [consts 3 4, fromList [1,2,3] ])) [4,5,6] and
-        compare (list (add [consts (-3) 4, fromList [1,2,3] ])) [-2,-1,0] 
-),
-
-"subtract": \(
-    compare (list (subtract (zeros 0) (ones 5))) [] and
-        compare (list (subtract (consts 3 4) (fromList [1,2,3]))) [2,1,0] and
-        compare (list (subtract (consts (-3) 4) (fromList [1,2,3]))) [-4,-5,-6]
-),
-
-"multiply": \(
-    compare (list (multiply (zeros 0) (ones 5))) [] and
-        compare (list (multiply (consts (-3) 4) (fromList [1,2,3]))) [-3,-6,-9]
-),
-
-"divideBy": \(
-    compare (list (divideBy 5 (ones 0))) [] and
-        compare (list (divideBy 5 (fromList [1,2,-3]))) [0.2,0.4,-0.6]
-),
-
-"scaled": \(
-    compare (list (scaled 5 (ones 0))) [] and
-        compare (list (scaled 5 (fromList [1,2,-3]))) [5,10,-15]
-),
-
-"abs": \(
-    compare (list (abs (ones 0))) [] and
-        compare (list (abs (fromList [1,2,-3]))) [1,2,3]
-),
-
-"sqr": \(
-    compare ((list . sqr . zeros) 0) [] and
-        compare ((list . sqr . ones) 5) [1,1,1,1,1] and
-        compare ((list . sqr . fromList) [0.5,-2,3,0]) [0.25,4,9,0]
-),
-
-"sqrt": \(
-    compare ((list . sqrt . zeros) 0) [] and
-        compare ((list . sqrt . ones) 5) [1,1,1,1,1] and
-        compare ((list . sqrt . fromList) [0.25,4,9,0]) [0.5,2,3,0]
-),
-
-"rms": \(
-    compare ((rms . zeros) 0) 0 and
-        compare ((rms . ones) 5) 1 and
-        compare ((rms . fromList) [-1,2,2]) (stdSqrt 3)
-),
-
-"unityNormalised": \(
-    compare ((list . unityNormalised . fromList) [1,-2,3,0]) [1/3,-2/3,1,0] and
-        compare ((list . unityNormalised . fromList) [-1,-2,-3]) [-1/3,-2/3,-1] and
-        compare ((list . unityNormalised . fromList) [4,1]) [1,1/4] and
-        compare ((list . unityNormalised . fromList) []) []
-),
-
-"fftshift": \(
-    compare ((list . fftshift . zeros) 0) [] and 
-        compare ((list . fftshift . fromList) [1,2,3,4]) [3,4,1,2] and
-        compare ((list . fftshift . fromList) [1,2,3,4,5]) [4,5,1,2,3]
-),
-
-"ifftshift": \(
-    compare ((list . ifftshift . zeros) 0) [] and 
-        compare ((list . ifftshift . fromList) [3,4,1,2]) [1,2,3,4] and
-        compare ((list . ifftshift . fromList) [4,5,1,2,3]) [1,2,3,4,5]
-),
-
-] is hash<string, () -> boolean>;
-
-
--- a/may/vector/test/test_vector.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-
-module may.vector.test.test_vector;
-
-vec = load may.vector;
-
-{ compare } = load may.test.test;
-
-[
-
-"zeros-empty": \(
-    v = vec.zeros 0;
-    compare (vec.length v) 0;
-),
-
-"zeros": \(
-    v = vec.zeros 3;
-    a = vec.array v;
-    compare (vec.length v) 3 and
-        compare a[0] 0 and
-        compare a[1] 0 and
-        compare a[2] 0;
-),
-
-"consts-empty": \(
-    v = vec.consts 4 0;
-    compare (vec.length v) 0;
-),
-
-"consts": \(
-    v = vec.consts 4 3;
-    a = vec.array v;
-    compare (vec.length v) 3 and
-        compare a[0] 4 and
-        compare a[1] 4 and
-        compare a[2] 4;
-),
-
-"ones-empty": \(
-    v = vec.ones 0;
-    compare (vec.length v) 0;
-),
-
-"ones": \(
-    v = vec.ones 3;
-    a = vec.array v;
-    compare (vec.length v) 3 and
-        compare a[0] 1 and
-        compare a[1] 1 and
-        compare a[2] 1;
-),
-
-"from-list-empty": \(
-    v = vec.fromList [];
-    compare (vec.length v) 0;
-),
-
-"from-list": \(
-    v = vec.fromList [1,2,3,4];
-    a = vec.array v;
-    compare (vec.length v) 4 and
-        compare a[0] 1 and
-        compare a[1] 2 and
-        compare a[2] 3 and
-        compare a[3] 4;
-),
-
-"equal-empty": \(
-    vec.equal (vec.fromList []) (vec.fromList [])
-),
-
-"equal": \(
-    v = vec.fromList [1,1,1,1];
-    w = vec.ones 4;
-    w' = vec.zeros 4;
-    w'' = vec.ones 3;
-    vec.equal v w and not vec.equal v w' and not vec.equal v w'';
-),
-
-"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]) 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": \(
-    vec.equal (vec.resizedTo 4 (vec.fromList [])) (vec.zeros 4) and
-        vec.equal (vec.resizedTo 2 (vec.fromList [1,2])) (vec.fromList [1,2]) and
-        vec.equal (vec.resizedTo 3 (vec.fromList [1,2])) (vec.fromList [1,2,0]) and
-        vec.equal (vec.resizedTo 2 (vec.fromList [1,2,3])) (vec.fromList [1,2]);
-),
-
-"repeated": \(
-    vec.equal (vec.repeated (vec.fromList []) 2) (vec.fromList []) and
-        vec.equal (vec.repeated (vec.fromList [1,2,3]) 0) (vec.fromList []) and
-        vec.equal (vec.repeated (vec.fromList [1,2,3]) 1) (vec.fromList [1,2,3]) and
-        vec.equal (vec.repeated (vec.fromList [1,2,3]) 2) (vec.fromList [1,2,3,1,2,3])
-),
-
-"reverse": \(
-    vec.equal (vec.reverse (vec.fromList [])) (vec.fromList []) and
-        vec.equal (vec.reverse (vec.fromList [1,2,3])) (vec.fromList [3,2,1]) and
-        vec.equal (vec.reverse (vec.fromList [1,2])) (vec.fromList [2,1])
-),
-
-"concat2": \(
-    v = vec.fromList [1,2,3];
-    w = vec.fromList [4,5,6];
-    x = vec.concat [v, w];
-    x' = vec.fromList [1,2,3,4,5,6];
-    vec.equal x x' and
-        vec.equal x' (vec.concat [x', vec.fromList []]) and
-        vec.equal x' (vec.concat [vec.fromList [], x'])
-),
-
-"concatn": \(
-    v = vec.fromList [1,2,3];
-    w = vec.fromList [4,5,6];
-    vec.equal (vec.concat []) (vec.zeros 0) and
-        vec.equal (vec.concat [v]) v and
-        vec.equal (vec.concat [v,w,v]) (vec.fromList [1,2,3,4,5,6,1,2,3])
-),
-
-] is hash<string, () -> boolean>;
-
-
-
--- a/may/vector/type.yeti	Mon Sep 16 10:56:19 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-
-module may.vector.type;
-
-typedef opaque vector = ~double[];
-
-();
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/complex.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,112 @@
+
+module may.complex;
+
+load may.vector.type;
+load may.complex.type;
+
+vec = load may.vector;
+
+import java.lang: ClassCastException;
+
+class Cplx(double real, double imag)
+    double getReal()
+        real,
+    double getImag()
+        imag,
+    double getMagnitude()
+        sqrt (real * real + imag * imag),
+    double getAngle()
+        Math#atan2(imag, real),
+    String toString()
+        if real == int real and imag == int imag then
+            if imag < 0 then
+                " \(int real) - \(int (-imag))i"
+            else 
+                " \(int real) + \(int imag)i"
+            fi
+        else
+            if imag < 0 then
+                " \(real) - \((-imag))i"
+            else 
+                " \(real) + \(imag)i"
+            fi
+        fi,
+    int hashCode()
+        Double#valueOf(real)#hashCode() + Double#valueOf(imag)#hashCode(),
+    boolean equals(Object other)
+        try
+            c = other unsafely_as ~Cplx;
+            c#getReal() == real and c#getImag() == imag
+        catch ClassCastException:
+            false
+        yrt,
+end;
+
+real c1 is ~Cplx -> number =
+    c1#getReal();
+
+imaginary c1 is ~Cplx -> number =
+    c1#getImag();
+
+complex re im is number -> number -> ~Cplx =
+    new Cplx(re, im);
+
+magnitude c is ~Cplx -> number =
+    c#getMagnitude();
+
+angle c is ~Cplx -> number =
+    c#getAngle();
+
+sum' cc is list?<~Cplx> -> ~Cplx =
+    complex (sum (map real cc)) (sum (map imaginary cc));
+
+add c1 c2 is ~Cplx -> ~Cplx -> ~Cplx =
+    complex (real c1 + real c2) (imaginary c1 + imaginary c2);
+
+multiply c1 c2 is ~Cplx -> ~Cplx -> ~Cplx =
+   (a = real c1;
+    b = imaginary c1;
+    c = real c2;
+    d = imaginary c2;
+    complex (a * c - b * d) (b * c + a * d)); //!!! need units
+
+scale r c is number -> ~Cplx -> ~Cplx =
+    complex (r * real c) (r * imaginary c);
+
+zeros n is number -> array<~Cplx> =
+    array (map \(complex 0 0) [1..n]);
+
+magnitudes cc is list?<~Cplx> -> vector =
+    vec.fromList (map magnitude cc);
+
+angles cc is list?<~Cplx> -> vector =
+    vec.fromList (map angle cc);
+
+{
+   real,
+   imaginary,
+   complex,
+   magnitude,
+   angle,
+   sum = sum',
+   add,
+   multiply,
+   scale,
+   zeros,
+   magnitudes,
+   angles,
+} as {
+   real is cplx -> number,
+   imaginary is cplx -> number,
+   complex is number -> number -> cplx,
+   magnitude is cplx -> number,
+   angle is cplx -> number,
+   sum is list?<cplx> -> cplx,
+   add is cplx -> cplx -> cplx,
+   multiply is cplx -> cplx -> cplx,
+   scale is number -> cplx -> cplx,
+   zeros is number -> array<cplx>,
+   magnitudes is list?<cplx> -> vector,
+   angles is list?<cplx> -> vector,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/complex/test/test_complex.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,68 @@
+module may.complex.test.test_complex;
+
+{ real, imaginary, complex, magnitude, angle, sum, scale, zeros, magnitudes, angles }
+   = load may.complex;
+
+{ compare } = load may.test.test;
+
+vec = load may.vector;
+
+[
+
+"complex": \( 
+    compare (complex 1 2) (complex 1 2) and
+        complex (-1) 2 != complex 1 2
+),
+
+"real": \(
+    compare (real (complex 3 2)) 3 and
+        compare (real (complex 3.3 2.2)) 3.3
+),
+
+"imaginary": \(
+    compare (imaginary (complex 3 4)) 4 and
+        compare (imaginary (complex 3 (-4.1))) (-4.1)
+),
+
+"magnitude": \(
+    compare (magnitude (complex (-3) 4)) 5
+),
+
+"angle": \(
+    compare (angle (complex 1 0)) 0 and
+        compare (angle (complex 1 1)) (pi/4) and
+        compare (angle (complex 0 1)) (pi/2) and
+        compare (angle (complex (-1) 0)) pi and
+        compare (angle (complex 0 (-1))) (-pi/2)
+),
+
+"sum": \(
+    compare (sum [complex 2 3, complex (-4) 5]) (complex (-2) 8)
+),
+
+"scale": \(
+    compare (scale 4 (complex 2 3)) (complex 8 12)
+),
+
+"zeros": \(
+    compare (zeros 0) (array []) and
+        compare (zeros 3) (array [complex 0 0, complex 0 0, complex 0 0])
+),
+
+"magnitudes": \(
+    compare (vec.list (magnitudes [ complex (-3) 4, complex 4 3, complex 0 0 ]))
+            [ 5, 5, 0 ] and
+       compare (vec.list (magnitudes (array []))) []
+),
+
+"angles": \(
+    compare (vec.list (angles [ complex 1 0, complex (-1) 0, complex 0 (-1) ]))
+            [ 0, pi, -pi/2 ] and
+       compare (vec.list (angles (array []))) []
+),
+
+
+] is hash<string, () -> boolean>;
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/complex/type.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,7 @@
+
+module may.complex.type;
+
+typedef opaque cplx = ~may.Cplx;
+
+();
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/feature/feature.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,29 @@
+
+module may.feature.feature;
+
+vec = load may.vector;
+fr = load may.stream.framer;
+
+// Utility functions for feature extractors
+
+magdiff frame1 frame2 =
+    sum (map2 do a b: abs(a - b) done (vec.list frame1) (vec.list frame2));
+
+emptyFrameFor frames =
+    vec.zeros
+        if empty? frames then 0
+        else vec.length (head frames)
+        fi;
+
+features featureFunc frames =
+   (featuresOf prev frames =
+        case frames of
+        frame::rest: featureFunc prev frame :. \(featuresOf frame rest);
+         _: [];
+        esac;
+    featuresOf (emptyFrameFor frames) frames);
+
+{ magdiff, emptyFrameFor, features };
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/feature/specdiff.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,20 @@
+
+module may.feature.specdiff;
+
+vec = load may.vector;
+fr = load may.stream.framer;
+cplx = load may.complex;
+
+load may.feature.feature;
+
+specdiff frames = 
+    features magdiff (map cplx.magnitudes frames);
+
+specdiffOfFile parameters filename =
+    specdiff (fr.frequencyDomainFramesOfFile parameters filename);
+
+{
+    specdiff,
+    specdiffOfFile,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/feature/test/test_features.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/matrix.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,842 @@
+
+/**
+ * Matrices. A matrix is a two-dimensional (NxM) container of
+ * double-precision floating point values.
+ *
+ * A matrix may be dense or sparse.
+ * 
+ * A dense matrix (the default) is just a series of vectors, making up
+ * the matrix "grid". The values may be stored in either column-major
+ * order, in which case the series consists of one vector for each
+ * column in the matrix, or row-major order, in which case the series
+ * consists of one vector for each row. The default is column-major.
+ * 
+ * A sparse matrix has a more complex representation in which only the
+ * non-zero values are stored. This is typically used for matrices
+ * containing sparse data, that is, data in which most of the values
+ * are zero: using a sparse representation is more efficient than a
+ * dense one (in both time and memory) if the matrix is very large but
+ * contains a relatively low proportion of non-zero values. Like dense
+ * matrices, sparse ones may be column-major or row-major.
+ * 
+ * The choice of dense or sparse, row- or column-major is a question
+ * of efficiency alone. All functions in this module should return the
+ * same results regardless of how the matrices they operate on are
+ * represented. However, differences in performance can be very large
+ * and it is often worth converting matrices to a different storage
+ * format if you know they can be more efficiently manipulated that
+ * way. For example, multiplying two matrices is fastest if the first
+ * is in column-major and the second in row-major order.
+ * 
+ * Use the isRowMajor? and isSparse? functions to query the storage
+ * format of a matrix; use the flipped function to convert between
+ * column-major and row-major storage; and use toSparse and toDense to
+ * convert between sparse and dense storage.
+ *
+ * Note that the matrix representation does not take into account
+ * different forms of zero-width or zero-height matrix. All matrices
+ * of zero width or height are equal to each other, and all are equal
+ * to the zero-sized matrix.
+ */
+
+module may.matrix;
+
+vec = load may.vector;
+bf = load may.vector.blockfuncs;
+
+load may.vector.type;
+load may.matrix.type;
+
+size m =
+    case m of
+    DenseRows r:
+        major = length r;
+        { 
+            rows = major, 
+            columns = if major > 0 then vec.length r[0] else 0 fi,
+        };
+    DenseCols c:
+        major = length c;
+        { 
+            rows = if major > 0 then vec.length c[0] else 0 fi,
+            columns = major, 
+        };
+    SparseCSR { values, indices, pointers, extent }:
+        {
+            rows = (length pointers) - 1,
+            columns = extent
+        };
+    SparseCSC { values, indices, pointers, extent }:
+        {
+            rows = extent,
+            columns = (length pointers) - 1
+        };
+    esac;
+
+width m = (size m).columns;
+height m = (size m).rows;
+
+nonZeroValues m =
+   (nz d =
+        sum
+           (map do v:
+                sum (map do n: if n == 0 then 0 else 1 fi done (vec.list v))
+                done d);
+    case m of 
+    DenseRows d: nz d;
+    DenseCols d: nz d;
+    SparseCSR d: vec.length d.values;
+    SparseCSC d: vec.length d.values;
+    esac);
+
+density m =
+   ({ rows, columns } = size m;
+    cells = rows * columns;
+    (nonZeroValues m) / cells);
+
+sparseSlice n d =
+   (start = d.pointers[n];
+    end = d.pointers[n+1];
+    { 
+        values = vec.slice d.values start end,
+        indices = slice d.indices start end,
+    });
+
+nonEmptySlices d =
+   (ne = array [];
+    for [0..length d.pointers - 2] do i:
+        if d.pointers[i] != d.pointers[i+1] then
+            push ne i
+        fi
+    done;
+    ne);
+
+fromSlice n m d =
+   (slice = sparseSlice n d;
+    var v = 0;
+    for [0..length slice.indices - 1] do i:
+        if slice.indices[i] == m then
+            v := vec.at slice.values i;
+        fi
+    done;
+    v);
+
+filledSlice n d =
+   (slice = sparseSlice n d;
+    dslice = new double[d.extent];
+    for [0..length slice.indices - 1] do i:
+        dslice[slice.indices[i]] := vec.at slice.values i;
+    done;
+    vec.vector dslice);
+
+at' m row col =
+    case m of
+    DenseRows rows: r = rows[row]; vec.at r col;
+    DenseCols cols: c = cols[col]; vec.at c row;
+    SparseCSR data: fromSlice row col data;
+    SparseCSC data: fromSlice col row data;
+    esac;
+
+getColumn j m =
+    case m of
+    DenseCols cols: cols[j];
+    SparseCSC data: filledSlice j data;
+    _: vec.fromList (map do i: at' m i j done [0..height m - 1]);
+    esac;
+
+getRow i m =
+    case m of
+    DenseRows rows: rows[i];
+    SparseCSR data: filledSlice i data; 
+    _: vec.fromList (map do j: at' m i j done [0..width m - 1]);
+    esac;
+
+asRows m =
+    map do i: getRow i m done [0 .. (height m) - 1];
+
+asColumns m =
+    map do i: getColumn i m done [0 .. (width m) - 1];
+
+isRowMajor? m =
+    case m of
+    DenseRows _: true;
+    DenseCols _: false;
+    SparseCSR _: true;
+    SparseCSC _: false;
+    esac;
+
+isSparse? m =
+    case m of
+    DenseRows _: false;
+    DenseCols _: false;
+    SparseCSR _: true;
+    SparseCSC _: true;
+    esac;
+
+typeOf m =
+    if isRowMajor? m then RowMajor ()
+    else ColumnMajor ()
+    fi;
+
+flippedTypeOf m =
+    if isRowMajor? m then ColumnMajor ()
+    else RowMajor ()
+    fi;
+
+newColumnMajorStorage { rows, columns } = 
+    if rows < 1 then array []
+    else array (map \(vec.zeros rows) [1..columns])
+    fi;
+
+zeroMatrix { rows, columns } = 
+    DenseCols (newColumnMajorStorage { rows, columns });
+
+zeroMatrixWithTypeOf m { rows, columns } = 
+    if isRowMajor? m then
+        DenseRows (newColumnMajorStorage { rows = columns, columns = rows });
+    else
+        DenseCols (newColumnMajorStorage { rows, columns });
+    fi;
+
+zeroSizeMatrix () = zeroMatrix { rows = 0, columns = 0 };
+
+empty?' m = (width m == 0 or height m == 0);
+
+generate f { rows, columns } =
+    if rows < 1 or columns < 1 then zeroSizeMatrix ()
+    else
+        m = array (map \(new double[rows]) [1..columns]);
+        for [0..columns-1] do col:
+            for [0..rows-1] do row:
+                m[col][row] := f row col;
+            done;
+        done;
+        DenseCols (array (map vec.vector m))
+    fi;
+
+swapij =
+    map do { i, j, v }: { i = j, j = i, v } done;
+
+//!!! should use { row = , column = , value = } instead of i, j, v?
+enumerateSparse m =
+   (enumerate { values, indices, pointers } =
+        concat
+           (map do i:
+                start = pointers[i];
+                end = pointers[i+1];
+                map2 do j v: { i, j, v } done 
+                    (slice indices start end)
+                    (vec.list (vec.slice values start end))
+                done [0..length pointers - 2]);
+    case m of
+    SparseCSC d: swapij (enumerate d);
+    SparseCSR d: enumerate d;
+     _: [];
+    esac);
+
+enumerateDense m =
+   (enumerate d =
+        concat
+           (map do i:
+                vv = d[i];
+                map2 do j v: { i, j, v } done
+                    [0..vec.length vv - 1]
+                    (vec.list vv);
+                done [0..length d - 1]);
+    case m of
+    DenseCols c: swapij (enumerate c);
+    DenseRows r: enumerate r;
+     _: [];
+    esac);
+
+enumerate m =
+    if isSparse? m then enumerateSparse m else enumerateDense m fi;
+
+// Make a sparse matrix from entries whose i, j values are known to be
+// within range
+makeSparse type size data =
+   (isRow = case type of RowMajor (): true; ColumnMajor (): false esac;
+    ordered = 
+        sortBy do a b:
+            if a.maj == b.maj then a.min < b.min else a.maj < b.maj fi
+        done
+           (map
+                if isRow then
+                    do { i, j, v }: { maj = i, min = j, v } done;
+                else
+                    do { i, j, v }: { maj = j, min = i, v } done;
+                fi
+               (filter do d: d.v != 0 done data));
+    tagger = if isRow then SparseCSR else SparseCSC fi;
+    majorSize = if isRow then size.rows else size.columns fi;
+    minorSize = if isRow then size.columns else size.rows fi;
+    pointers = array [0];
+    setArrayCapacity pointers (size.rows + 1);
+    fillPointers n i data =
+        if n < majorSize then
+            case data of
+            d::rest:
+               (for [n..d-1] \(push pointers i);
+                fillPointers d (i+1) rest);
+             _:
+                for [n..majorSize-1] \(push pointers i);
+            esac;
+        fi;
+    fillPointers 0 0 (map (.maj) ordered);
+    tagger {
+        values = vec.fromList (map (.v) ordered),
+        indices = array (map (.min) ordered),
+        pointers,
+        extent = minorSize,
+    });
+
+// Make a sparse matrix from entries that may contain out-of-range
+// cells which need to be filtered out. This is the public API for
+// makeSparse and is also used to discard out-of-range cells from
+// resizedTo.
+newSparseMatrix type size data =
+    makeSparse type size
+       (filter
+            do { i, j, v }:
+                i == int i and i >= 0 and i < size.rows and 
+                j == int j and j >= 0 and j < size.columns
+            done data);
+
+toSparse m =
+    if isSparse? m then m
+    else
+        makeSparse (typeOf m) (size m) (enumerateDense m);
+    fi;
+
+toDense m =
+    if not (isSparse? m) then m
+    elif isRowMajor? m then
+        DenseRows (array (map do row: getRow row m done [0..height m - 1]));
+    else
+        DenseCols (array (map do col: getColumn col m done [0..width m - 1]));
+    fi;
+
+constMatrix n = generate do row col: n done;
+randomMatrix = generate do row col: Math#random() done;
+identityMatrix = constMatrix 1;
+
+transposed m =
+    case m of
+    DenseRows d: DenseCols d;
+    DenseCols d: DenseRows d;
+    SparseCSR d: SparseCSC d;
+    SparseCSC d: SparseCSR d;
+    esac;
+
+flipped m =
+    if isSparse? m then
+        makeSparse (flippedTypeOf m) (size m) (enumerateSparse m)
+    else
+        if isRowMajor? m then
+            generate do row col: at' m row col done (size m);
+        else
+            transposed
+               (generate do row col: at' m col row done
+                { rows = (width m), columns = (height m) });
+        fi
+    fi;
+
+toRowMajor m =
+    if isRowMajor? m then m else flipped m fi;
+
+toColumnMajor m =
+    if not isRowMajor? m then m else flipped m fi;
+
+equal'' comparator vecComparator m1 m2 =
+    // Prerequisite: m1 and m2 have same sparse-p and storage order
+   (compareVecLists vv1 vv2 = all id (map2 vecComparator vv1 vv2);
+    compareSparse d1 d2 =
+        d1.extent == d2.extent and
+        vecComparator d1.values d2.values and
+        d1.indices == d2.indices and
+        d1.pointers == d2.pointers;
+    case m1 of
+    DenseRows d1:
+        case m2 of DenseRows d2: compareVecLists d1 d2; _: false; esac;
+    DenseCols d1:
+        case m2 of DenseCols d2: compareVecLists d1 d2; _: false; esac;
+    SparseCSR d1:
+        case m2 of SparseCSR d2: compareSparse d1 d2; _: false; esac;
+    SparseCSC d1:
+        case m2 of SparseCSC d2: compareSparse d1 d2; _: false; esac;
+    esac);
+
+equal' comparator vecComparator m1 m2 =
+    if empty?' m1 and empty?' m2 then
+        true
+    elif size m1 != size m2 then 
+        false
+    elif isRowMajor? m1 != isRowMajor? m2 then
+        equal' comparator vecComparator (flipped m1) m2;
+    elif isSparse? m1 != isSparse? m2 then
+        if isSparse? m1 then
+            equal' comparator vecComparator m1 (toSparse m2)
+        else
+            equal' comparator vecComparator (toSparse m1) m2
+        fi
+    else
+        equal'' comparator vecComparator m1 m2
+    fi;
+
+// Compare matrices using the given comparator for individual cells.
+// Note that matrices with different storage order but the same
+// contents are equal, although comparing them is slow.
+//!!! Document the fact that sparse matrices can only be equal if they
+// have the same set of non-zero cells (regardless of comparator used)
+equalUnder comparator =
+    equal' comparator (vec.equalUnder comparator);
+
+equal =
+    equal' (==) vec.equal;
+
+newMatrix type data = //!!! NB does not copy data
+   (tagger = case type of RowMajor (): DenseRows; ColumnMajor (): DenseCols esac;
+    if empty? data or vec.empty? (head data)
+    then zeroSizeMatrix ()
+    else tagger (array data)
+    fi);
+
+newRowVector data = //!!! NB does not copy data
+    DenseRows (array [data]);
+
+newColumnVector data = //!!! NB does not copy data
+    DenseCols (array [data]);
+
+denseLinearOp op m1 m2 =
+    if isRowMajor? m1 then
+        newMatrix (typeOf m1) 
+           (map2 do c1 c2: op c1 c2 done (asRows m1) (asRows m2));
+    else
+        newMatrix (typeOf m1) 
+           (map2 do c1 c2: op c1 c2 done (asColumns m1) (asColumns m2));
+    fi;
+
+sparseSumOrDifference op m1 m2 =
+   (h = [:];
+    for (enumerate m1) do { i, j, v }:
+        if not (i in h) then h[i] := [:] fi;
+        h[i][j] := v;
+    done;
+    for (enumerate m2) do { i, j, v }:
+        if not (i in h) then h[i] := [:] fi;
+        if j in h[i] then h[i][j] := op h[i][j] v;
+        else h[i][j] := op 0 v;
+        fi;
+    done;
+    entries = concat
+       (map do i:
+            kk = keys h[i];
+            map2 do j v: { i, j, v } done kk (map (at h[i]) kk)
+            done (keys h));
+    makeSparse (typeOf m1) (size m1) entries);
+
+sum' m1 m2 =
+    if (size m1) != (size m2)
+    then failWith "Matrices are not the same size: \(size m1), \(size m2)";
+    elif isSparse? m1 and isSparse? m2 then
+        sparseSumOrDifference (+) m1 m2;
+    else
+        add2 v1 v2 = bf.add [v1,v2];
+        denseLinearOp add2 m1 m2;
+    fi;
+
+difference m1 m2 =
+    if (size m1) != (size m2)
+    then failWith "Matrices are not the same size: \(size m1), \(size m2)";
+    elif isSparse? m1 and isSparse? m2 then
+        sparseSumOrDifference (-) m1 m2;
+    else
+        denseLinearOp bf.subtract m1 m2;
+    fi;
+
+scaled factor m =
+    if isSparse? m then
+        makeSparse (typeOf m) (size m)
+           (map do { i, j, v }: { i, j, v = factor * v } done (enumerate m))
+    elif isRowMajor? m then
+        newMatrix (typeOf m) (map (bf.scaled factor) (asRows m));
+    else
+        newMatrix (typeOf m) (map (bf.scaled factor) (asColumns m));
+    fi;
+
+abs' m =
+    if isSparse? m then
+        makeSparse (typeOf m) (size m)
+           (map do { i, j, v }: { i, j, v = abs v } done (enumerate m))
+    elif isRowMajor? m then
+        newMatrix (typeOf m) (map bf.abs (asRows m));
+    else
+        newMatrix (typeOf m) (map bf.abs (asColumns m));
+    fi;
+
+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
+               (enumerate m))
+    else
+        vfilter = vec.fromList . (map do i: if f i then i else 0 fi done) . vec.list;
+        if isRowMajor? m then
+            newMatrix (typeOf m) (map vfilter (asRows m));
+        else
+            newMatrix (typeOf m) (map vfilter (asColumns m));
+        fi;
+    fi;
+
+sparseProductLeft size m1 m2 =
+   ({ values, indices, pointers } = case m1 of
+         SparseCSR d: d;
+         SparseCSC d: d;
+         _: failWith "sparseProductLeft called for non-sparse m1";
+         esac;
+    rows = isRowMajor? m1;
+    data = array (map \(new double[size.rows]) [1..size.columns]);
+    for [0..size.columns - 1] do j':
+        c = getColumn j' m2;
+        var p = 0;
+        for [0..length indices - 1] do ix:
+            ix == pointers[p+1] loop (p := p + 1);
+            i = if rows then p else indices[ix] fi;
+            j = if rows then indices[ix] else p fi;
+            data[j'][i] := data[j'][i] + (vec.at values ix) * (vec.at c j);
+        done;
+    done;
+    DenseCols (array (map vec.vector (list data))));
+
+sparseProductRight size m1 m2 =
+   ({ values, indices, pointers } = case m2 of
+         SparseCSR d: d;
+         SparseCSC d: d;
+         _: failWith "sparseProductLeft called for non-sparse m1";
+         esac;
+    rows = isRowMajor? m2;
+    data = array (map \(new double[size.columns]) [1..size.rows]);
+    for [0..size.rows - 1] do i':
+        r = getRow i' m1;
+        var p = 0;
+        for [0..length indices - 1] do ix:
+            ix == pointers[p+1] loop (p := p + 1);
+            i = if rows then p else indices[ix] fi;
+            j = if rows then indices[ix] else p fi;
+            data[i'][j] := data[i'][j] + (vec.at values ix) * (vec.at r i);
+        done;
+    done;
+    DenseRows (array (map vec.vector (list data))));
+
+sparseProduct size m1 m2 =
+    case m2 of
+    SparseCSC d:
+       ({ values, indices, pointers } = case m1 of
+            SparseCSR d1: d1;
+            SparseCSC d1: d1;
+            _: failWith "sparseProduct called for non-sparse matrices";
+            esac;
+        rows = isRowMajor? m1;
+        var p = 0;
+        pindices = new int[length indices];
+        for [0..length indices - 1] do ix:
+            ix == pointers[p+1] loop (p := p + 1);
+            pindices[ix] := p;
+        done;
+        entries =
+           (map do j':
+                cs = sparseSlice j' d;
+                hin = mapIntoHash
+                   (at cs.indices) (vec.at cs.values)
+                   [0..length cs.indices - 1];
+                hout = [:];
+                for [0..length indices - 1] do ix:
+                    i = if rows then pindices[ix] else indices[ix] fi;
+                    j = if rows then indices[ix] else pindices[ix] fi;
+                    if j in hin then
+                        p = (vec.at values ix) * hin[j];
+                        hout[i] := p + (if i in hout then hout[i] else 0 fi);
+                    fi;
+                done;
+                map do i:
+                    { i, j = j', v = hout[i] }
+                done (keys hout);
+            done (nonEmptySlices d));
+        makeSparse (ColumnMajor ()) size (concat entries));
+    SparseCSR _:
+        sparseProduct size m1 (flipped m2);
+     _: failWith "sparseProduct called for non-sparse matrices";
+    esac;
+
+denseProduct size m1 m2 =
+   (data = array (map \(new double[size.rows]) [1..size.columns]);
+    for [0..size.rows - 1] do i:
+        row = getRow i m1;
+        for [0..size.columns - 1] do j:
+            data[j][i] := bf.sum (bf.multiply row (getColumn j m2));
+        done;
+    done;
+    DenseCols (array (map vec.vector (list data))));
+
+product m1 m2 =
+    if (size m1).columns != (size m2).rows
+    then failWith "Matrix dimensions incompatible: \(size m1), \(size m2) (\((size m1).columns) != \((size m2).rows))";
+    else 
+        size = { rows = (size m1).rows, columns = (size m2).columns };
+        if isSparse? m1 then
+            if isSparse? m2 then
+                sparseProduct size m1 m2
+            else
+                sparseProductLeft size m1 m2
+            fi
+        elif isSparse? m2 then
+            sparseProductRight size m1 m2
+        else
+            denseProduct size m1 m2
+        fi;
+    fi;
+
+entryWiseProduct m1 m2 = // or element-wise, or Hadamard product
+//!!! todo: faster, sparse version, units
+    if (size m1) != (size m2)
+    then failWith "Matrices are not the same size: \(size m1), \(size m2)";
+    else generate do row col: at' m1 row col * at' m2 row col done (size m1);
+    fi;
+
+concatAgainstGrain tagger getter counter mm =
+   (n = counter (size (head mm));
+    tagger (array
+       (map do i:
+           vec.concat (map (getter i) mm)
+           done [0..n-1])));
+
+concatWithGrain tagger getter counter mm =
+    tagger (array
+       (concat
+           (map do m:
+               n = counter (size m);
+               map do i: getter i m done [0..n-1]
+               done mm)));
+
+sparseConcat direction first mm =
+   (dimension d f = if direction == d then sum (map f mm) else f first fi;
+    rows = dimension (Vertical ()) height;
+    columns = dimension (Horizontal ()) width;
+    entries ioff joff ui uj mm acc =
+        case mm of 
+        m::rest:
+            entries
+               (ioff + ui * height m)
+               (joff + uj * width m)
+                ui uj rest
+               ((map do { i, j, v }: { i = i + ioff, j = j + joff, v }
+                 done (enumerate m)) ++ acc);
+         _: acc;
+        esac;
+    makeSparse (typeOf first) { rows, columns }
+        if direction == Vertical () then entries 0 0 1 0 mm []
+        else entries 0 0 0 1 mm [] fi);
+
+checkDimensionsFor direction first mm =
+   (counter = if direction == Horizontal () then (.rows) else (.columns) fi;
+    n = counter (size first);
+    if not (all id (map do m: counter (size m) == n done mm)) then
+        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 is obviously not lazy (unlike std module)
+    case length mm of
+    0: zeroSizeMatrix ();
+    1: head mm;
+    _:
+        first = head mm;
+        checkDimensionsFor direction first mm;
+        if all isSparse? mm then
+            sparseConcat direction first mm
+        else
+            row = isRowMajor? first;
+            // horizontal, row-major: against grain with rows
+            // horizontal, col-major: with grain with cols
+            // vertical, row-major: with grain with rows
+            // vertical, col-major: against grain with cols
+            case direction of
+            Horizontal ():
+                if row then concatAgainstGrain DenseRows getRow (.rows) mm;
+                else concatWithGrain DenseCols getColumn (.columns) mm;
+                fi;
+            Vertical ():
+                if row then concatWithGrain DenseRows getRow (.rows) mm;
+                else concatAgainstGrain DenseCols getColumn (.columns) mm;
+                fi;
+            esac;
+        fi;
+    esac;
+
+//!!! doc this filter -- zero-size elts are ignored
+concat direction mm =
+    concat' direction (filter do mat: not (empty?' 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 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 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 =
+   (if newsize == (size m) then
+        m
+    elif isSparse? m then
+        // don't call makeSparse directly: want to discard
+        // out-of-range cells
+        newSparseMatrix (typeOf m) newsize (enumerateSparse m)
+    elif (height m) == 0 or (width m) == 0 then
+        zeroMatrixWithTypeOf m newsize;
+    else
+        growrows = newsize.rows - (height m);
+        growcols = newsize.columns - (width m);
+        rowm = isRowMajor? m;
+        resizedTo newsize
+            if rowm and growrows < 0 then
+                rowSlice m 0 newsize.rows
+            elif (not rowm) and growcols < 0 then 
+                columnSlice m 0 newsize.columns
+            elif growrows < 0 then 
+                rowSlice m 0 newsize.rows
+            elif growcols < 0 then 
+                columnSlice m 0 newsize.columns
+            else
+                if growrows > 0 then
+                    concat (Vertical ())
+                       [m, zeroMatrixWithTypeOf m ((size m) with { rows = growrows })]
+                else
+                    concat (Horizontal ())
+                       [m, zeroMatrixWithTypeOf m ((size m) with { columns = growcols })]
+                fi
+            fi
+    fi);
+
+{
+    size,
+    width,
+    height,
+    density,
+    nonZeroValues,
+    at = at',
+    getColumn,
+    getRow,
+    isRowMajor?,
+    isSparse?,
+    generate,
+    constMatrix,
+    randomMatrix,
+    zeroMatrix,
+    identityMatrix,
+    zeroSizeMatrix,
+    empty? = empty?',
+    equal,
+    equalUnder,
+    transposed,
+    flipped,
+    toRowMajor,
+    toColumnMajor,
+    toSparse,
+    toDense,
+    scaled,
+    resizedTo,
+    asRows,
+    asColumns,
+    sum = sum',
+    difference,
+    abs = abs',
+    filter = filter',
+    product,
+    entryWiseProduct,
+    concat,
+    rowSlice,
+    columnSlice,
+    newMatrix,
+    newRowVector,
+    newColumnVector,
+    newSparseMatrix,
+    enumerate
+}
+as
+{
+//!!! check whether these are right to be .selector rather than just selector
+
+    size is matrix -> { .rows is number, .columns is number },
+    width is matrix -> number,
+    height is matrix -> number,
+    density is matrix -> number,
+    nonZeroValues is matrix -> number,
+    at is matrix -> number -> number -> number,
+    getColumn is number -> matrix -> vector,
+    getRow is number -> matrix -> vector,
+    isRowMajor? is matrix -> boolean,
+    isSparse? is matrix -> boolean,
+    generate is (number -> number -> number) -> { .rows is number, .columns is number } -> matrix,
+    constMatrix is number -> { .rows is number, .columns is number } -> matrix,
+    randomMatrix is { .rows is number, .columns is number } -> matrix,
+    zeroMatrix is { .rows is number, .columns is number } -> matrix, 
+    identityMatrix is { .rows is number, .columns is number } -> matrix, 
+    zeroSizeMatrix is () -> matrix,
+    empty? is matrix -> boolean,
+    equal is matrix -> matrix -> boolean,
+    equalUnder is (number -> number -> boolean) -> matrix -> matrix -> boolean,
+    transposed is matrix -> matrix,
+    flipped is matrix -> matrix, 
+    toRowMajor is matrix -> matrix, 
+    toColumnMajor is matrix -> matrix,
+    toSparse is matrix -> matrix,
+    toDense is matrix -> matrix,
+    scaled is number -> matrix -> matrix,
+    thresholded is number -> matrix -> matrix,
+    resizedTo is { .rows is number, .columns is number } -> matrix -> matrix,
+    asRows is matrix -> list<vector>, 
+    asColumns is matrix -> list<vector>,
+    sum is matrix -> matrix -> matrix,
+    difference is matrix -> matrix -> matrix,
+    abs is matrix -> matrix,
+    filter is (number -> boolean) -> matrix -> matrix,
+    product is matrix -> matrix -> matrix,
+    entryWiseProduct is matrix -> matrix -> matrix,
+    concat is (Horizontal () | Vertical ()) -> list<matrix> -> matrix,
+    rowSlice is matrix -> number -> number -> matrix, 
+    columnSlice is matrix -> number -> number -> matrix,
+    newMatrix is (ColumnMajor () | RowMajor ()) -> list<vector> -> matrix, 
+    newRowVector is vector -> matrix, 
+    newColumnVector is vector -> matrix,
+    newSparseMatrix is (ColumnMajor () | RowMajor ()) -> { .rows is number, .columns is number } -> list<{ .i is number, .j is number, .v is number }> -> matrix,
+    enumerate is matrix -> list<{ .i is number, .j is number, .v is number }>
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/matrix/test/speedtest.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,218 @@
+
+program may.matrix.test.speedtest;
+
+mat = load may.matrix;
+vec = load may.vector;
+
+{ compare, compareUsing, time } = load may.test.test;
+
+norec time f = time "" f;
+
+compareMatrices = compareUsing mat.equal;
+
+makeMatrices sz sparsity =
+   (print "Making \(sz) * \(sz) random matrix...";
+    m = time \(mat.randomMatrix { rows = sz, columns = sz });
+    makeSparse () = 
+       (print "Making \(sparsity * 100)% sparse version (as dense matrix)...";
+        t = time \(mat.filter (> sparsity) m);
+        println "Reported density: \(mat.density t) (non-zero values: \(mat.nonZeroValues t))";
+        print "Converting to sparse matrix...";
+        s = time \(mat.toSparse t);
+        println "Reported density: \(mat.density s) (non-zero values: \(mat.nonZeroValues s))";
+        s);
+    s = makeSparse ();
+    println "Making types:";
+    print "Col-major dense...";
+    cmd = time \(mat.toColumnMajor m);
+    print "Row-major dense...";
+    rmd = time \(mat.toRowMajor m);
+    print "Col-major sparse...";
+    cms = time \(mat.toColumnMajor s);
+    print "Row-major sparse...";
+    rms = time \(mat.toRowMajor s);
+    println "";
+    { cmd, rmd, cms, rms });
+
+println "\nR * M multiplies:\n";
+
+sz = 2000;
+sparsity = 0.95;
+
+{ cmd, rmd, cms, rms } = makeMatrices sz sparsity;
+
+row = mat.newRowVector (vec.fromList (map \(Math#random()) [1..sz]));
+col = mat.newColumnVector (vec.fromList (map \(Math#random()) [1..sz]));
+
+print "R * CMD... ";
+a = (time \(mat.product row cmd));
+
+print "R * RMD... ";
+b = (time \(mat.product row rmd));
+
+print "R * CMS... ";
+c = (time \(mat.product row cms));
+
+print "R * RMS... ";
+d = (time \(mat.product row rms));
+
+println "\nChecking results: \(compareMatrices a b) \(compareMatrices c d)";
+
+println "\nM * C multiplies:\n";
+
+print "CMD * C... ";
+a = (time \(mat.product cmd col));
+
+print "RMD * C... ";
+b = (time \(mat.product rmd col));
+
+print "CMS * C... ";
+c = (time \(mat.product cms col));
+
+print "RMS * C... ";
+d = (time \(mat.product rms col));
+
+println "\nChecking results: \(compareMatrices a b) \(compareMatrices c d)";
+
+reportOn m = 
+   (print "                                 ";
+    println "isSparse: \(mat.isSparse? m), density \(mat.density m)");
+
+println "\nM * M multiplies (and a few sums):\n";
+
+sz = 500;
+
+{ cmd, rmd, cms, rms } = makeMatrices sz sparsity;
+
+print "CMS * CMD... ";
+reportOn (time \(mat.product cms cmd));
+
+print "CMS * RMD... ";
+reportOn (time \(mat.product cms rmd));
+
+print "RMS * CMD... ";
+reportOn (time \(mat.product rms cmd));
+
+print "RMS * RMD... ";
+reportOn (time \(mat.product rms rmd));
+
+println "";
+
+print "CMD * CMS... ";
+reportOn (time \(mat.product cmd cms));
+
+print "CMD * RMS... ";
+reportOn (time \(mat.product cmd rms));
+
+print "RMD * CMS... ";
+reportOn (time \(mat.product rmd cms));
+
+print "RMD * RMS... ";
+reportOn (time \(mat.product rmd rms));
+
+println "";
+
+print "CMS * CMS... ";
+reportOn (time \(mat.product cms cms));
+
+print "CMS * RMS... ";
+reportOn (time \(mat.product cms rms));
+
+print "RMS * CMS... ";
+reportOn (time \(mat.product rms cms));
+
+print "RMS * RMS... ";
+reportOn (time \(mat.product rms rms));
+
+println "";
+
+print "CMD + CMD... ";
+reportOn (time \(mat.sum cmd cmd));
+
+print "CMD + RMD... ";
+reportOn (time \(mat.sum cmd rmd));
+
+print "RMD + CMD... ";
+reportOn (time \(mat.sum rmd cmd));
+
+print "RMD + RMD... ";
+reportOn (time \(mat.sum rmd rmd));
+
+println "";
+
+print "CMS + CMS... ";
+reportOn (time \(mat.sum cms cms));
+
+print "CMS + RMS... ";
+reportOn (time \(mat.sum cms rms));
+
+print "RMS + CMS... ";
+reportOn (time \(mat.sum rms cms));
+
+print "RMS + RMS... ";
+reportOn (time \(mat.sum rms rms));
+
+println "";
+
+print "CMD * CMD... ";
+reportOn (time \(mat.product cmd cmd));
+
+print "CMD * RMD... ";
+reportOn (time \(mat.product cmd rmd));
+
+print "RMD * CMD... ";
+reportOn (time \(mat.product rmd cmd));
+
+print "RMD * RMD... ";
+reportOn (time \(mat.product rmd rmd));
+
+println "\nLarge sparse M * M multiplies and adds:\n";
+
+sz = 5000000;
+nnz = 10000;
+
+print "Calculating \(nnz) non-zero entry records...";
+entries = time \(e = map \({ i = int (Math#random() * sz), 
+                             j = int (Math#random() * sz),
+                             v = Math#random() }) [1..nnz];
+                 \() (length e); // make sure list non-lazy for timing purposes
+                 e);
+
+print "Making \(sz) * \(sz) random matrix with \(nnz) entries...";
+rms = time \(mat.newSparseMatrix (RowMajor ()) { rows = sz, columns = sz }
+             entries);
+println "Reported density: \(mat.density rms) (non-zero values: \(mat.nonZeroValues rms))";
+
+print "Making col-major copy...";
+cms = time \(mat.toColumnMajor rms);
+println "Reported density: \(mat.density cms) (non-zero values: \(mat.nonZeroValues cms))";
+
+println "";
+
+print "CMS * CMS... ";
+reportOn (time \(mat.product cms cms));
+
+print "CMS * RMS... ";
+reportOn (time \(mat.product cms rms));
+
+print "RMS * CMS... ";
+reportOn (time \(mat.product rms cms));
+
+print "RMS * RMS... ";
+reportOn (time \(mat.product rms rms));
+
+println "";
+
+print "CMS + CMS... ";
+reportOn (time \(mat.sum cms cms));
+
+print "CMS + RMS... ";
+reportOn (time \(mat.sum cms rms));
+
+print "RMS + CMS... ";
+reportOn (time \(mat.sum rms cms));
+
+print "RMS + RMS... ";
+reportOn (time \(mat.sum rms rms));
+
+();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/matrix/test/test_matrix.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,575 @@
+
+module may.matrix.test.test_matrix;
+
+mat = load may.matrix;
+vec = load may.vector;
+
+load may.vector.type;
+load may.matrix.type;
+
+import yeti.lang: FailureException;
+
+{ compare, compareUsing } = load may.test.test;
+
+compareMatrices = compareUsing mat.equal;
+
+makeTests name flipper =
+   (constMatrix n s = flipper (mat.constMatrix n s);
+    zeroMatrix s = flipper (mat.zeroMatrix s);
+    randomMatrix s = flipper (mat.randomMatrix s);
+    identityMatrix s = flipper (mat.identityMatrix s);
+    generate f s = flipper (mat.generate f s);
+    newMatrix t d = flipper (mat.newMatrix t (map vec.fromList d));
+[
+
+"constMatrixEmpty-\(name)": \(
+    m = constMatrix 2 { rows = 0, columns = 0 };
+    compare (mat.size m) { columns = 0, rows = 0 }
+),
+
+"constMatrixEmpty2-\(name)": \(
+    compare (mat.size (constMatrix 2 { rows = 0, columns = 4 })) { columns = 0, rows = 0 } and
+        compare (mat.size (constMatrix 2 { rows = 4, columns = 0 })) { columns = 0, rows = 0 }
+),
+
+"constMatrix-\(name)": \(
+    m = constMatrix 2 { rows = 3, columns = 4 };
+    compare (mat.size m) { columns = 4, rows = 3 } and
+        all id (map do row: compare (vec.list (mat.getRow row m)) [2,2,2,2] done [0..2]) and
+        all id (map do col: compare (vec.list (mat.getColumn col m)) [2,2,2] done [0..3])
+),
+
+"randomMatrixEmpty-\(name)": \(
+    m = randomMatrix { rows = 0, columns = 0 };
+    compare (mat.size m) { columns = 0, rows = 0 }
+),
+
+"randomMatrix-\(name)": \(
+    m = randomMatrix { rows = 3, columns = 4 };
+    compare (mat.size m) { columns = 4, rows = 3 }
+),
+
+"zeroMatrixEmpty-\(name)": \(
+    m = zeroMatrix { rows = 0, columns = 0 };
+    compare (mat.size m) { columns = 0, rows = 0 }
+),
+
+"zeroMatrix-\(name)": \(
+    m = zeroMatrix { rows = 3, columns = 4 };
+    compare (mat.size m) { columns = 4, rows = 3 } and
+        all id (map do row: compare (vec.list (mat.getRow row m)) [0,0,0,0] done [0..2]) and
+        all id (map do col: compare (vec.list (mat.getColumn col m)) [0,0,0] done [0..3])
+),
+
+"identityMatrixEmpty-\(name)": \(
+    m = identityMatrix { rows = 0, columns = 0 };
+    compare (mat.size m) { columns = 0, rows = 0 }
+),
+
+"identityMatrix-\(name)": \(
+    m = identityMatrix { rows = 3, columns = 4 };
+    compare (mat.size m) { columns = 4, rows = 3 } and
+        all id (map do row: compare (vec.list (mat.getRow row m)) [1,1,1,1] done [0..2]) and
+        all id (map do col: compare (vec.list (mat.getColumn col m)) [1,1,1] done [0..3])
+),
+
+"generateEmpty-\(name)": \(
+    m = generate do row col: 0 done { rows = 0, columns = 0 };
+    compare (mat.size m) { columns = 0, rows = 0 }
+),
+
+"generate-\(name)": \(
+    m = generate do row col: row * 10 + col done { rows = 2, columns = 3 };
+    compare (vec.list (mat.getRow 0 m)) [0,1,2] and
+        compare (vec.list (mat.getRow 1 m)) [10,11,12]
+),
+
+"widthAndHeight-\(name)": \(
+    m = constMatrix 2 { rows = 3, columns = 4 };
+    compare (mat.size m) { columns = mat.width m, rows = mat.height m }
+),
+
+"equal-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
+    n = m;
+    p = newMatrix (RowMajor ()) [[1,0,3],[4,5,6]];
+    q = newMatrix (ColumnMajor ()) [[1,0,3],[4,5,6]];
+    r = newMatrix (ColumnMajor ()) [[1,4],[0,5]];
+    compareMatrices m n and
+        compareMatrices m p and
+        compareMatrices n p and
+        not mat.equal m q and
+        not mat.equal m r
+),
+
+"equalUnder-\(name)": \(
+    p = newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]];
+    q = newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]];
+    r = newMatrix (ColumnMajor ()) [[4,3,1],[3,1,2]];
+    s = newMatrix (ColumnMajor ()) [[1,4,5],[6,7,8]];
+    t = newMatrix (ColumnMajor ()) [[1,4,5],[6,7,9]];
+    mat.equalUnder (==) p p and
+        mat.equalUnder (==) p q and
+        mat.equalUnder (!=) p r and
+        mat.equalUnder do a b: a % 2 == b % 2 done p s and
+        not mat.equalUnder do a b: a % 2 == b % 2 done p t
+),
+
+"at-\(name)": \(
+    generator row col = row * 10 + col;
+    m = generate generator { rows = 2, columns = 3 };
+    all id
+       (map do row: all id
+           (map do col: mat.at m row col == generator row col done [0..2])
+            done [0..1])
+),
+
+"transposedEmpty-\(name)": \(
+    compare (mat.size (mat.transposed (constMatrix 2 { rows = 0, columns = 0 }))) { columns = 0, rows = 0 } and
+        compare (mat.size (mat.transposed (constMatrix 2 { rows = 0, columns = 4 }))) { columns = 0, rows = 0 } and
+        compare (mat.size (mat.transposed (constMatrix 2 { rows = 4, columns = 0 }))) { columns = 0, rows = 0 }
+),
+
+"transposedSize-\(name)": \(
+    compare (mat.size (mat.transposed (constMatrix 2 { rows = 3, columns = 4 }))) { columns = 3, rows = 4 }
+),
+
+"transposed-\(name)": \(
+    generator row col = row * 10 + col;
+    m = generate generator { rows = 2, columns = 3 };
+    m' = mat.transposed m;
+    all id
+       (map do row: all id
+           // like at test, but with col/row flipped
+           (map do col: mat.at m' col row == generator row col done [0..2])
+            done [0..1])
+),
+
+"transposed-back-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]];
+    compareMatrices m (mat.transposed (mat.transposed m)) and
+        not mat.equal m (mat.transposed m);
+),
+
+"flipped-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
+    m' = mat.flipped m;
+    m'' = newMatrix (RowMajor ()) [[1,0,3],[4,5,6]];
+    compareMatrices m m' and compareMatrices m m'' and compareMatrices m' m'';
+),
+
+"flipped-back-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
+    compareMatrices m (mat.flipped (mat.flipped m));
+),
+
+"flipped-empty-\(name)": \(
+    m = constMatrix 2 { rows = 0, columns = 4 };
+    compareMatrices (mat.flipped m) (mat.flipped (constMatrix 0 { rows = 0, columns = 0 }));
+),
+
+"toRowMajor-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]];
+    m' = mat.toRowMajor m;
+    m'' = newMatrix (RowMajor ()) [[1,0,3],[4,5,6]];
+    m''' = mat.toRowMajor m'';
+    compareMatrices m m' and compareMatrices m m'' and compareMatrices m' m''
+        and compareMatrices m m''';
+),
+
+"toColumnMajor-\(name)": \(
+    m = newMatrix (RowMajor ()) [[1,4],[0,5],[3,6]];
+    m' = mat.toColumnMajor m;
+    m'' = newMatrix (ColumnMajor ()) [[1,0,3],[4,5,6]];
+    m''' = mat.toColumnMajor m'';
+    compareMatrices m m' and compareMatrices m m'' and compareMatrices m' m''
+        and compareMatrices m m''';
+),
+
+"scaled-\(name)": \(
+    compareMatrices
+       (mat.scaled 0.5 (constMatrix 2 { rows = 3, columns = 4 }))
+       (constMatrix 1 { rows = 3, columns = 4 }) and
+       compareMatrices
+          (mat.scaled 0.5 (constMatrix (-3) { rows = 3, columns = 4 }))
+          (constMatrix (-1.5) { rows = 3, columns = 4 }) and
+       compareMatrices
+          (mat.scaled 0.5 (constMatrix 2 { rows = 0, columns = 2 }))
+          (constMatrix 5 { rows = 0, columns = 0 })
+),
+
+"sum-\(name)": \(
+    compareMatrices
+       (mat.sum (constMatrix 2 { rows = 3, columns = 4 })
+                (constMatrix 1 { rows = 3, columns = 4 }))
+       (constMatrix 3 { rows = 3, columns = 4 })
+),
+
+"sumFail-\(name)": \(
+    try 
+      \() (mat.sum (constMatrix 2 { rows = 3, columns = 4 })
+                   (constMatrix 1 { rows = 3, columns = 5 }));
+        false;
+    catch FailureException e:
+        true
+    yrt
+),
+
+"sparseSum-\(name)": \(
+    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = 0, j = 0, v = 1 },
+        { i = 0, j = 2, v = 2 },
+        { i = 1, j = 1, v = 4 },
+    ];
+    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = 0, j = 1, v = 7 },
+        { i = 1, j = 0, v = 5 },
+        { i = 1, j = 1, v = -4 }, // NB this means [1,1] -> 0, sparse zero
+    ];
+    tot = mat.sum s t;
+    mat.isSparse? tot and
+        compareMatrices tot (mat.sum (mat.toDense s) t) and
+        compareMatrices tot (mat.sum (mat.toDense s) (mat.toDense t)) and
+        compareMatrices tot (mat.sum s (mat.toDense t)) and
+        compareMatrices tot 
+           (mat.newSparseMatrix (RowMajor ()) { rows = 2, columns = 3 } [
+               { i = 0, j = 0, v = 1 },
+               { i = 0, j = 1, v = 7 },
+               { i = 0, j = 2, v = 2 },
+               { i = 1, j = 0, v = 5 },
+            ]) and
+        compare (mat.density tot) (4/6)
+),
+
+"difference-\(name)": \(
+    compareMatrices
+       (mat.difference (constMatrix 2 { rows = 3, columns = 4 })
+                       (constMatrix 1 { rows = 3, columns = 4 }))
+       (constMatrix 1 { rows = 3, columns = 4 })
+),
+
+"differenceFail-\(name)": \(
+    try 
+      \() (mat.difference (constMatrix 2 { rows = 3, columns = 4 })
+                          (constMatrix 1 { rows = 3, columns = 5 }));
+        false;
+    catch FailureException e:
+        true
+    yrt
+),
+
+"sparseDifference-\(name)": \(
+    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = 0, j = 0, v = 1 },
+        { i = 0, j = 2, v = 2 },
+        { i = 1, j = 1, v = 4 },
+    ];
+    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = 0, j = 1, v = 7 },
+        { i = 1, j = 0, v = 5 },
+        { i = 1, j = 1, v = 6 },
+    ];
+    diff = mat.difference s t;
+    mat.isSparse? diff and
+        compareMatrices diff (mat.difference (mat.toDense s) t) and
+        compareMatrices diff (mat.difference (mat.toDense s) (mat.toDense t)) and
+        compareMatrices diff (mat.difference s (mat.toDense t)) and
+        compareMatrices diff 
+           (mat.newSparseMatrix (RowMajor ()) { rows = 2, columns = 3 } [
+               { i = 0, j = 0, v = 1 },
+               { i = 0, j = 1, v = -7 },
+               { i = 0, j = 2, v = 2 },
+               { i = 1, j = 0, v = -5 },
+               { i = 1, j = 1, v = -2 },
+            ])
+),
+
+"abs-\(name)": \(
+    compareMatrices
+       (mat.abs (newMatrix (ColumnMajor ()) [[-1,4],[2,-5],[-3,0]]))
+       (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,0]])
+),
+
+"product-\(name)": \(
+    compareMatrices
+       (mat.product (constMatrix 2 { rows = 4, columns = 2 })
+                    (constMatrix 3 { rows = 2, columns = 3 }))
+       (constMatrix 12 { rows = 4, columns = 3 }) and
+        compareMatrices
+           (mat.product (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]])
+                        (newMatrix (ColumnMajor ()) [[7,9,11],[8,10,12]]))
+           (newMatrix (ColumnMajor ()) [[58,139],[64,154]])
+),
+
+"sparseProduct-\(name)": \(
+    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = 0, j = 0, v = 1 },
+        { i = 0, j = 2, v = 2 },
+        { i = 1, j = 1, v = 4 },
+    ];
+    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 3, columns = 2 } [
+        { i = 0, j = 1, v = 7 },
+        { i = 1, j = 0, v = 5 },
+        { i = 2, j = 0, v = 6 },
+    ];
+    prod = mat.product s t;
+    mat.isSparse? prod and
+        compareMatrices prod (mat.product (mat.toDense s) t) and
+        compareMatrices prod (mat.product (mat.toDense s) (mat.toDense t)) and
+        compareMatrices prod (mat.product s (mat.toDense t)) and
+        compareMatrices prod 
+           (mat.newSparseMatrix (RowMajor ()) { rows = 2, columns = 2 } [
+               { i = 0, j = 0, v = 12 },
+               { i = 0, j = 1, v = 7 },
+               { i = 1, j = 0, v = 20 },
+            ])
+),
+
+"productFail-\(name)": \(
+    try
+      \() (mat.product (constMatrix 2 { rows = 4, columns = 2 })
+                       (constMatrix 3 { rows = 3, columns = 2 }));
+        false;
+    catch FailureException e:
+        true
+    yrt
+),
+
+"resizedTo-\(name)": \(
+    compareMatrices
+       (mat.resizedTo { rows = 2, columns = 2 }
+           (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]]))
+       (newMatrix (ColumnMajor ()) [[1,4],[2,5]]) and
+        compareMatrices
+           (mat.resizedTo { rows = 3, columns = 4 }
+               (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]]))
+           (newMatrix (ColumnMajor ()) [[1,4,0],[2,5,0],[3,6,0],[0,0,0]]) and
+        compareMatrices
+           (mat.resizedTo { rows = 1, columns = 1 }
+               (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]]))
+           (newMatrix (RowMajor ()) [[1]]) and
+        compareMatrices
+           (mat.resizedTo { rows = 2, columns = 3 }
+               (mat.zeroSizeMatrix ()))
+           (newMatrix (RowMajor ()) [[0,0,0],[0,0,0]]) and
+        mat.isSparse?
+           (mat.resizedTo { rows = 1, columns = 1 }
+               (mat.toSparse (newMatrix (ColumnMajor ()) [[1,4],[2,5],[3,6]])))
+),
+
+"zeroSizeMatrix-\(name)": \(
+    compareMatrices
+       (mat.zeroSizeMatrix ())
+       (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)": \(
+    compare
+       (map vec.list
+           (mat.asRows (newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]])))
+        [[1,0,3],[4,5,6]];
+),
+
+"asColumns-\(name)": \(
+    compare
+       (map vec.list
+           (mat.asColumns (newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]])))
+        [[1,4],[0,5],[3,6]];
+),
+
+"concat-horiz-\(name)": \(
+    compareMatrices
+       (mat.concat (Horizontal ()) 
+          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
+           (newMatrix (RowMajor ()) [[3],[6]])])
+       (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]]),
+           mat.toSparse (newMatrix (RowMajor ()) [[3],[6]])];
+    compareMatrices s (newMatrix (ColumnMajor ()) [[1,4],[0,5],[3,6]]) and
+        compare (mat.isSparse? s) true and
+        compare (mat.density s) (5/6)
+),
+
+"concatFail-horiz-\(name)": \(
+    try
+        \() (mat.concat (Horizontal ()) 
+          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
+           (newMatrix (ColumnMajor ()) [[3],[6]])]);
+        false
+    catch FailureException e:
+        true
+    yrt
+),
+
+"concat-vert-\(name)": \(
+    compareMatrices
+       (mat.concat (Vertical ()) 
+          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
+           (newMatrix (RowMajor ()) [[3,6]])])
+       (newMatrix (ColumnMajor ()) [[1,4,3],[0,5,6]])
+),
+
+"sparseConcat-vert-\(name)": \(
+    s = mat.concat (Vertical ()) 
+          [mat.toSparse (newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
+           mat.toSparse (newMatrix (RowMajor ()) [[3,6]])];
+    compareMatrices s (newMatrix (ColumnMajor ()) [[1,4,3],[0,5,6]]) and
+        compare (mat.isSparse? s) true and
+        compare (mat.density s) (5/6)
+),
+
+"concatFail-vert-\(name)": \(
+    try
+        \() (mat.concat (Vertical ()) 
+          [(newMatrix (ColumnMajor ()) [[1,4],[0,5]]),
+           (newMatrix (RowMajor ()) [[3],[6]])]);
+        false
+    catch FailureException e:
+        true
+    yrt
+),
+
+"rowSlice-\(name)": \(
+    compareMatrices
+       (mat.rowSlice (newMatrix (RowMajor ()) [[1,0],[3,4],[0,6],[7,8]]) 1 3)
+       (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]]) and
+        compareMatrices
+           (mat.columnSlice (newMatrix (RowMajor ()) [[1,0,3,4],[0,6,7,8]]) 2 5)
+           (newMatrix (RowMajor ()) [[3,4],[7,8]])
+),
+
+"density-\(name)": \(
+    compare (mat.density (newMatrix (ColumnMajor ()) [[1,2,0],[0,5,0]])) (3/6) and
+        compare (mat.density (newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]])) (6/6) and
+        compare (mat.density (newMatrix (ColumnMajor ()) [[0,0,0],[0,0,0]])) 0
+),
+
+"nonZeroValues-\(name)": \(
+    compare (mat.nonZeroValues (newMatrix (ColumnMajor ()) [[1,2,0],[0,5,0]])) 3 and
+        compare (mat.nonZeroValues (newMatrix (ColumnMajor ()) [[1,2,3],[4,5,6]])) 6 and
+        compare (mat.nonZeroValues (newMatrix (ColumnMajor ()) [[0,0,0],[0,0,0]])) 0
+),
+
+"toSparse-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
+    compareMatrices (mat.toSparse m) m and
+        compareMatrices (mat.toDense (mat.toSparse m)) m and
+        compare (mat.density (mat.toSparse m)) (6/9)
+),
+
+"toDense-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
+    compareMatrices (mat.toDense m) m and
+        compareMatrices (mat.toSparse (mat.toDense m)) m
+),
+
+"filter-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
+    compareMatrices
+       (mat.filter (> 2) m)
+       (newMatrix (ColumnMajor ()) [[0,0,0],[0,0,6],[0,0,3]]) and
+        compare (mat.density (mat.filter (> 2) m)) (2/9)
+),
+
+"newSparseMatrix-\(name)": \(
+    s = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = 0, j = 0, v = 1 },
+        { i = 0, j = 2, v = 2 },
+        { i = 1, j = 1, v = 4 },
+    ];
+    // If there are zeros in the entries list, they should not end up
+    // in the sparse data
+    t = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = 0, j = 0, v = 1 },
+        { i = 0, j = 2, v = 0 },
+        { i = 1, j = 1, v = 4 },
+    ];
+    // Any out-of-range or non-integer i, j should be ignored too
+    u = mat.newSparseMatrix (ColumnMajor ()) { rows = 2, columns = 3 } [
+        { i = -1, j = 0, v = 1 },
+        { i = 0, j = 4, v = 3 },
+        { i = 1, j = 1.5, v = 4 },
+    ];
+    compare (mat.density s) (3/6) and
+        compare (mat.density t) (2/6) and
+        compareMatrices s (newMatrix (RowMajor ()) [[1,0,2],[0,4,0]]) and
+        compareMatrices t (newMatrix (RowMajor ()) [[1,0,0],[0,4,0]]) and
+        compareMatrices u (newMatrix (RowMajor ()) [[0,0,0],[0,0,0]])
+),
+
+"enumerate-\(name)": \(
+    m = newMatrix (ColumnMajor ()) [[1,2,0],[-1,-4,6],[0,0,3]];
+    all = [
+        { i = 0, j = 0, v = 1 },
+        { i = 1, j = 0, v = 2 },
+        { i = 2, j = 0, v = 0 },
+        { i = 0, j = 1, v = -1 },
+        { i = 1, j = 1, v = -4 },
+        { i = 2, j = 1, v = 6 },
+        { i = 0, j = 2, v = 0 },
+        { i = 1, j = 2, v = 0 },
+        { i = 2, j = 2, v = 3 },
+    ];
+    sortEntries = 
+        sortBy do a b:
+            if a.i == b.i then a.j < b.j else a.i < b.i fi
+        done;
+    compare
+       (sortEntries (mat.enumerate m))
+       (sortEntries 
+           (if mat.isSparse? m then filter do d: d.v != 0 done all else all fi));
+),
+
+]);
+
+colhash = makeTests "column-dense" id;
+rowhash = makeTests "row-dense" mat.flipped;
+sparsecolhash = makeTests "column-sparse" mat.toSparse;
+
+// there are two possible orders for constructing a sparse row-major
+// matrix from a dense col-major one, so test them both:
+sparserowhash1 = makeTests "row-sparse-a" (mat.toSparse . mat.flipped);
+sparserowhash2 = makeTests "row-sparse-b" (mat.flipped . mat.toSparse);
+
+all = [:];
+for [ colhash, rowhash, sparsecolhash, sparserowhash1, sparserowhash2 ] 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/matrix/type.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,23 @@
+
+module may.matrix.type;
+
+load may.vector.type;
+
+typedef opaque matrix =
+    DenseRows. array<vector> | // array of rows
+    DenseCols. array<vector> | // array of columns
+    SparseCSR. {
+        .values is vector,
+        .indices is array<number>, // column index of each value
+        .pointers is array<number>, // offset of first value in each row
+        .extent is number // max possible index + 1, i.e. number of columns
+        } |
+    SparseCSC. {
+        .values is vector,
+        .indices is array<number>, // row index of each value
+        .pointers is array<number>, // offset of first value in each column
+        .extent is number // max pointers index + 1, i.e. number of rows
+        };
+
+();
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/plot.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,132 @@
+module may.plot;
+
+vec = load may.vector;
+mat = load may.matrix;
+
+{ distinctColour } = load may.plot.colour;
+
+import org.jzy3d.plot3d.builder: Mapper;
+import org.jzy3d.maths: Range, Coord3d;
+import org.jzy3d.chart: Chart, ChartLauncher;
+import org.jzy3d.plot3d.builder: Builder;
+import org.jzy3d.plot3d.builder.concrete: OrthonormalGrid;
+import org.jzy3d.colors.colormaps: ColorMapRainbow;
+import org.jzy3d.colors: ColorMapper, Color;
+import org.jzy3d.plot3d.rendering.canvas: Quality;
+import org.jzy3d.plot3d.rendering.view.modes: ViewPositionMode;
+import org.jzy3d.plot3d.primitives: FlatLine2d, Point;
+
+newMatrixMapper matrix =
+   (class MMapper extends Mapper
+        double f(double x, double y)
+            result = mat.at matrix y x;
+            println "f(\(x),\(y)) -> \(result)";
+            result
+    end;
+    new MMapper());
+
+newMatrixLogMapper matrix =
+   (class MMapper extends Mapper
+        double f(double x, double y)
+            ln (mat.at matrix y x)
+    end;
+    new MMapper());
+
+newMapper mapFunction =
+   (class FMapper extends Mapper
+        double f(double x, double y)
+            mapFunction x y
+    end;
+    new FMapper());
+
+plotMatrix chart colour matrix is ~Chart -> ~Color -> 'a -> () =
+   (mapper = newMatrixMapper matrix;
+    size = mat.size matrix;
+    //!!! doesn't work if either rows or columns is 1
+    xrange = new Range(0, size.columns - 1);
+    yrange = new Range(0, size.rows - 1);
+    grid = new OrthonormalGrid(xrange, size.columns, yrange, size.rows);
+    println "Matrix size: \(size)";
+    surface = Builder#buildOrthonormal(grid, mapper); //??? big?
+    println "Z Bounds: \(surface#getBounds()#getZmin()) -> \(surface#getBounds()#getZmax())";
+    surface#setFaceDisplayed(true);
+    surface#setWireframeDisplayed(true);
+    surface#setWireframeColor(colour);
+    chart#getScene()#getGraph()#add(surface);
+    ());
+
+plotCurve chart colour depth curve is ~Chart -> ~Color -> number -> 'a -> () =
+   (scene = chart#getScene();
+    xx = map (.time) curve;
+    yy = map (.value) curve;
+    line = new FlatLine2d(xx as ~float[], yy as ~float[], depth);
+    line#setWireframeDisplayed(true);
+    line#setWireframeColor(colour);
+    line#setWireframeWidth(2);
+    line#setFaceDisplayed(false);
+    scene#add(line);
+    chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
+/*
+    axes = chart#getAxeLayout();
+    axes#setXAxeLabelDisplayed(false);
+    axes#setYAxeLabelDisplayed(false);
+    axes#setZAxeLabelDisplayed(true);
+    axes#setZAxeLabel("unit goes here"); //!!!
+    axes#setYTickLabelDisplayed(false);
+*/
+    ());
+
+plotSeries chart colour depth { start, step, values } is ~Chart -> ~Color -> number -> 'a -> () =
+   (scene = chart#getScene();
+    xx = map do i: start + step * i done [0..length values - 1];
+    yy = list values;
+    line = new FlatLine2d(xx as ~float[], yy as ~float[], depth);
+    line#setWireframeDisplayed(true);
+    line#setWireframeColor(colour);
+    line#setWireframeWidth(2);
+    line#setFaceDisplayed(false);
+    scene#add(line);
+    chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
+/*
+    axes = chart#getAxeLayout();
+    axes#setXAxeLabelDisplayed(false);
+    axes#setYAxeLabelDisplayed(false);
+    axes#setZAxeLabelDisplayed(true);
+    axes#setZAxeLabel("unit goes here"); //!!!
+    axes#setYTickLabelDisplayed(false);
+*/
+    ());
+
+plot structures =
+   (chart = new Chart(Quality#Nicest);
+    var j = 0;
+    for structures do s:
+        colour = distinctColour j;
+        case s of
+        Grid matrix:
+            plotMatrix chart colour matrix;
+        Curve curve:
+            plotCurve chart colour j curve;
+        Series series:
+            plotSeries chart colour j series;
+        Vector vector:
+            plotSeries chart colour j
+                { start = 0, step = 1, values = vec.list vector };
+        other:
+            failWith "Unable to plot \(other)";
+        esac;
+        j := j + 1;
+    done;
+    ChartLauncher#openChart(chart);
+    chart);
+
+{
+    newMatrixMapper,
+    newMatrixLogMapper,
+    newMapper,
+    plotMatrix, 
+    plotCurve,
+    plotSeries,
+    plot,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/plot/chart.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,241 @@
+module may.plot.chart;
+
+{ distinctColour } = load may.plot.colour;
+
+import org.jzy3d.plot3d.text.drawable: DrawableTextBillboard, DrawableTextBitmap;
+import org.jzy3d.maths: Range, Coord3d;
+import org.jzy3d.plot3d.primitives: Shape, HistogramBar, FlatLine2d, Polygon, Quad, Point;
+import org.jzy3d.plot3d.primitives.axes.layout.providers: StaticTickProvider, RegularTickProvider;
+import org.jzy3d.plot3d.primitives.axes.layout.renderers: ITickRenderer, TickLabelMap, IntegerTickRenderer;
+import org.jzy3d.chart: Chart, ChartLauncher;
+import org.jzy3d.plot3d.builder: Builder;
+import org.jzy3d.colors: Color;
+import org.jzy3d.plot3d.rendering.canvas: Quality;
+import org.jzy3d.plot3d.rendering.view.modes: ViewPositionMode;
+
+import javax.imageio: ImageIO;
+
+import java.io: File;
+
+newPercentTickRenderer () =
+   (f v = " \(int (v * 100))%";
+    class PercentageTickRenderer extends ITickRenderer
+        String format(double value) f value,
+        String format(float value) f value
+    end;
+    new PercentageTickRenderer());
+
+newPaddedIntTickRenderer () =
+   (f v = "    \(int (v + 0.5))";
+    class PaddedIntTickRenderer extends ITickRenderer
+        String format(double value) f value,
+        String format(float value) f value
+    end;
+    new PaddedIntTickRenderer());
+
+parseOptions options defaultKeys defaultXKeys =
+   (parsed = {
+        var keys = array (sort defaultKeys),
+        var labels = [:],
+        var animated = false,
+        var normalised = false,
+        var unit = "",
+        var xkeys = array (sort defaultXKeys),
+        var saveTo = "",
+        var display = true,
+    };
+    for options
+       \case of
+        Keys kk: parsed.keys := array kk;
+        XKeys xk: parsed.xkeys := array xk;
+        Animated a: parsed.animated := a;
+        Normalised n: parsed.normalised := n;
+        Unit u: parsed.unit := u;
+        Labels ll: parsed.labels := ll;
+        SaveTo file: parsed.saveTo := file;
+        Display d: parsed.display := d;
+        esac;
+    if empty? parsed.labels then
+        parsed.labels := mapIntoHash id id parsed.keys
+    fi;
+    parsed);
+
+newChart opts =
+   (quality = Quality#Fastest;
+    quality#setAnimated(opts.animated);
+//    if opts.display then
+        new Chart(quality);
+//    else
+//        new Chart(quality, "offscreen,640,640");
+//    fi);
+    );
+
+showChart opts chart is 'a -> ~Chart -> () =
+   (if opts.display then
+        \() ChartLauncher#openChart(chart);
+    else
+        \() ChartLauncher#openStaticChart(chart);
+    fi;
+    if opts.saveTo != "" then
+        \() chart#screenshot(opts.saveTo);
+    fi);
+    
+plotBarChart options values =
+   (opts = parseOptions options (keys values) [];
+    chart = newChart opts;
+    var n = length opts.keys;
+    scene = chart#getScene();
+    ticks = new double[n];
+    tickLabels = new TickLabelMap();
+    var i = 0;
+    var x = n - i - 1;
+    total = sum (map do k: if k in values then values[k] else 0 fi done opts.keys);
+    for opts.keys do k:
+        bar = new HistogramBar();
+        v = if k in values then values[k] else 0 fi;
+        v = if opts.normalised and total > 0 then v / total else v fi;
+        bar#setData(new Coord3d(x, 0, 0), v, 0.45, distinctColour i);
+        bar#setWireframeDisplayed(false);
+        scene#add(bar);
+        ticks[i] := i;
+        tickLabels#register(x, opts.labels[k]);
+        i := i + 1;
+        x := x - 1;
+    done;
+    chart#getView()#setViewPoint(new Coord3d(pi/2, 0, 0));
+    axes = chart#getAxeLayout();
+    axes#setXAxeLabelDisplayed(false);
+    axes#setYAxeLabelDisplayed(false);
+    axes#setZAxeLabelDisplayed(true);
+    if opts.normalised then
+        axes#setZAxeLabel("");
+        axes#setZTickRenderer(newPercentTickRenderer ());
+    else
+        axes#setZAxeLabel(opts.unit);
+    fi;
+    axes#setXTickProvider(new StaticTickProvider(ticks));
+    axes#setXTickRenderer(tickLabels);
+    axes#setYTickLabelDisplayed(false);
+    showChart opts chart);
+
+plotLines options values =
+   (opts = parseOptions options (keys values) (keys values[head (keys values)]);
+    chart = newChart opts;
+    scene = chart#getScene();
+    n = length opts.xkeys;
+    var z = 0;
+    for opts.keys do k:
+        v = values[k];
+        x = new float[n];
+        y = new float[n];
+        var i = 0;
+        for opts.xkeys do xk:
+            x[i] := i;
+            y[i] := if xk in v then v[xk] else 0 fi;
+            i := i + 1;
+        done;
+        line = new FlatLine2d(x, y, z);
+        line#setWireframeDisplayed(true);
+        line#setWireframeColor(distinctColour z);
+        line#setWireframeWidth(2);
+        line#setFaceDisplayed(false);
+        scene#add(line);
+        z := z + 1;
+    done;
+    chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
+    axes = chart#getAxeLayout();
+    axes#setXAxeLabelDisplayed(false);
+    axes#setYAxeLabelDisplayed(false);
+    axes#setZAxeLabelDisplayed(true);
+    axes#setZAxeLabel(opts.unit);
+    axes#setYTickLabelDisplayed(false);
+    showChart opts chart);
+
+stack keys xkeys values normalised =
+   (stacked = mapIntoHash id \(mapIntoHash id \{ y0 = 0, y1 = 0 } xkeys) keys;
+    prev = mapIntoHash id \0 xkeys;
+    valueOf k xk =
+        if k in values and xk in values[k] 
+        then values[k][xk] else 0 
+        fi;
+    for xkeys do xk:
+        total = sum (map do k: valueOf k xk done keys);
+        for keys do k:
+            value =
+                if normalised and total > 0
+                then (valueOf k xk) / total
+                else (valueOf k xk)
+                fi;
+            height = prev[xk] + value;
+            stacked[k][xk] := { y0 = prev[xk], y1 = height };
+            prev[xk] := height;
+        done;
+    done;
+    stacked);
+
+newRect x y0 y1 z colour is number -> number -> number -> number -> ~Color -> 'a =
+   (poly = new Quad();
+    poly#add(new Point(new Coord3d(x + 0.5, z, y0)));
+    poly#add(new Point(new Coord3d(x + 0.5, z, y1)));
+    poly#add(new Point(new Coord3d(x - 0.5, z, y1)));
+    poly#add(new Point(new Coord3d(x - 0.5, z, y0)));
+    poly#setWireframeDisplayed(true);
+    poly#setWireframeColor(colour);
+    poly#setFaceDisplayed(true);
+    poly#setColor(colour);
+    poly);
+
+plotStacked options values =
+   (opts = parseOptions options (keys values) (keys values[head (keys values)]);
+    chart = newChart opts;
+    scene = chart#getScene();
+    stacked = stack opts.keys opts.xkeys values opts.normalised;
+    var z = 0;
+    var ty = 0;
+    nxk = length opts.xkeys;
+    xticks = new double[nxk];
+    xtickLabels = new TickLabelMap();
+    for [0..nxk - 1] do x:
+        xticks[x] := x;
+        k = opts.xkeys[x];
+        xtickLabels#register(x, if k in opts.labels then opts.labels[k] else k fi);
+    done;
+    for opts.keys do k:
+        ranges = stacked[k];
+        c = distinctColour z;
+        for [0..nxk - 1] do x:
+            xk = opts.xkeys[x];
+            rect = newRect x ranges[xk].y0 ranges[xk].y1 z c;
+            scene#add(rect);
+        done;
+        text = new DrawableTextBitmap(opts.labels[k], new Coord3d(-(nxk/5 + 0.5), z, ty), c);
+        scene#add(text);
+        z := z - 1;
+        ty := ty + 0.1;
+    done;
+    chart#getView()#setViewPoint(new Coord3d(-pi/2, 0, 0));
+    axes = chart#getAxeLayout();
+    axes#setXAxeLabelDisplayed(false);
+    if nxk < 10 then
+        axes#setXTickProvider(new StaticTickProvider(xticks));
+    fi;
+    axes#setXTickRenderer(xtickLabels);
+    axes#setYAxeLabelDisplayed(false);
+    axes#setZAxeLabelDisplayed(true);
+    if opts.normalised then 
+        axes#setZAxeLabel("");
+        axes#setZTickRenderer(newPercentTickRenderer ());
+    else
+        axes#setZAxeLabel(opts.unit);
+        axes#setZTickRenderer(newPaddedIntTickRenderer ());
+    fi;
+    axes#setYTickLabelDisplayed(false);
+    showChart opts chart);
+
+{
+    plotBarChart,
+    plotLines,
+    stack,
+    plotStacked,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/plot/colour.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,27 @@
+module may.plot.colour;
+
+import org.jzy3d.colors: Color;
+
+distinctColours = array [
+    { r = 82,  g = 126, b = 154 }, // dark steel blue
+    { r = 161, g = 54,  b = 2   }, // red
+    { r = 207, g = 228, b = 148 }, // grey-green
+    { r = 21,  g = 183, b = 197 }, // light blue
+    { r = 251, g = 116, b = 43  }, // light red
+    { r = 200, g = 125, b = 234 }, // light purple
+    { r = 126, g = 33,  b = 28  }, // dried blood!
+    { r = 188, g = 13,  b = 207 }, // mid purple
+];    
+
+distinctColour n =
+    if n < 0
+    then distinctColour (-n)
+    else
+        rgb = distinctColours[n % (length distinctColours)];
+        new Color(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0);
+    fi;
+
+{
+    distinctColour
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/plot/test/test_plot.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,50 @@
+module may.plot.test.test_plot;
+
+ch = load may.plot.chart;
+
+{ compare } = load may.test.test;
+
+[
+
+"stack": \(
+    compare
+       (ch.stack
+            [ "Conrad", "Alice", "Bob" ]
+            [ "Jan", "Feb", "Mar" ]
+            [ "Alice":  [ "Jan": 3, "Mar": 2 ],
+              "Bob":    [ "Jan": 0, "Feb": 1, "Mar": 4 ],
+              "Conrad": [ "Feb": 2, "Mar": 1 ] ]
+            false)
+        [ "Conrad": [ "Jan": { y0 = 0, y1 = 0 },
+                      "Feb": { y0 = 0, y1 = 2 },
+                      "Mar": { y0 = 0, y1 = 1 } ],
+          "Alice":  [ "Jan": { y0 = 0, y1 = 3 },
+                      "Feb": { y0 = 2, y1 = 2 },
+                      "Mar": { y0 = 1, y1 = 3 } ],
+          "Bob":    [ "Jan": { y0 = 3, y1 = 3 },
+                      "Feb": { y0 = 2, y1 = 3 },
+                      "Mar": { y0 = 3, y1 = 7 } ] ]
+),
+
+"stack-normalised": \(
+    compare
+       (ch.stack
+            [ "Conrad", "Alice", "Bob" ]
+            [ "Jan", "Feb", "Mar" ]
+            [ "Alice":  [ "Jan": 3, "Mar": 2 ],
+              "Bob":    [ "Jan": 0, "Feb": 1, "Mar": 4 ],
+              "Conrad": [ "Feb": 2, "Mar": 1 ] ]
+            true)
+        [ "Conrad": [ "Jan": { y0 = 0, y1 = 0 },
+                      "Feb": { y0 = 0, y1 = 2/3 },
+                      "Mar": { y0 = 0, y1 = 1/7 } ],
+          "Alice":  [ "Jan": { y0 = 0, y1 = 1 },
+                      "Feb": { y0 = 2/3, y1 = 2/3 },
+                      "Mar": { y0 = 1/7, y1 = 3/7 } ],
+          "Bob":    [ "Jan": { y0 = 1, y1 = 1 },
+                      "Feb": { y0 = 2/3, y1 = 1 },
+                      "Mar": { y0 = 3/7, y1 = 1 } ] ]
+),
+
+] is hash<string, () -> boolean>;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/signal/autocorrelation.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,27 @@
+
+module may.signal.autocorrelation;
+
+acf len series =
+   (a = array series;
+    map do i:
+        sum (map do j:
+                 a[j] * a[j-i]
+                 done [i..length a - 1])
+        done [0..len-1]);
+
+acfNormalised len series =
+   (n = length series;
+    map2 do v i: if n == i then 0 else v / (n - i) fi done
+       (acf len series) [0..len-1]);
+
+acfUnityNormalised len series =
+   (a = acfNormalised len series;
+    max = head (sortBy (>) a);
+    map (/ max) a);
+
+{
+    acf,
+    acfNormalised,
+    acfUnityNormalised,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/signal/test/test_signal.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,24 @@
+module may.signal.test.test_signal;
+
+{ acf, acfNormalised, acfUnityNormalised } = load may.signal.autocorrelation;
+
+{ compare } = load may.test.test;
+
+[
+
+"unnormalised": \(
+    compare (acf 12 (array [1,0,0, 1,0,0, 1,0,0, 1,0,0]))
+        [4,0,0, 3,0,0, 2,0,0, 1,0,0 ];
+),
+
+"normalised": \(
+    compare (acfNormalised 9 (array [1,0,0, 1,0,0, 1,0,0, 1,0,0]))
+        [4/12,0,0, 3/9,0,0, 2/6,0,0 ];
+),
+
+"normalisedUnity": \(
+    compare (acfUnityNormalised 9 (array [1,0,0, 1,0,0, 1,0,0, 1,0,0]))
+        [1,0,0, 1,0,0, 1,0,0 ];
+),
+
+] is hash<string, () -> boolean>;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/signal/test/test_window.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,192 @@
+
+module may.signal.test.test_window;
+
+win = load may.signal.window;
+vec = load may.vector;
+
+{ compare, compareUsing } = load may.test.test;
+
+functions = [
+    Hann () : win.hann,
+    Hamming () : win.hamming,
+    Blackman () : win.blackman,
+    Nuttall () : win.nuttall,
+    BlackmanNuttall () : win.blackmanNuttall,
+    BlackmanHarris () : win.blackmanHarris,
+    Boxcar () : win.boxcar,
+    Bartlett () : win.bartlett,
+];
+
+close aa bb = all id (map2 do a b: (abs (a - b) < 0.0001) done aa bb);
+
+isSymmetric a =
+   (len = (vec.length a);
+    b = if len % 2 == 0 then
+            half = vec.slice a 0 (len/2);
+            vec.concat [half, vec.reverse half];
+        else
+            half = vec.slice a 0 (int (len/2));
+            mid = vec.slice a (int (len/2)) (int (len/2) + 1);
+            vec.concat [half, mid, vec.reverse half];
+        fi;
+    compareUsing close (vec.list a) (vec.list b));
+
+[
+
+"windowFunction": \(
+    all id (map do type:
+        f = functions[type];
+        a = f 10;
+        b = win.windowFunction type [ Symmetric false ] 10;
+        compareUsing close (vec.list a) (vec.list b)
+            or (eprintln "** failed window type: \(type)"; false)
+    done (keys functions));
+),
+
+"symmetric-even": \(
+    len = 10;
+    all id (map do type:
+        f = win.windowFunction type [ Symmetric true ];
+        v = f len;
+        (compare (vec.length v) len and isSymmetric v) or
+            (eprintln "** failed window type: \(type)"; false);
+    done (keys functions));
+),
+
+"symmetric-odd": \(
+    len = 11;
+    all id (map do type:
+        f = win.windowFunction type [ Symmetric true ];
+        v = f len;
+        (compare (vec.length v) len and isSymmetric v) or
+            (eprintln "** failed window type: \(type)"; false);
+    done (keys functions));
+),
+      
+"periodic-even": \(
+    // We can't actually test whether a function is periodic, given
+    // only one cycle of it! But we can make sure that all but the
+    // first sample is symmetric, which is what a symmetric window
+    // becomes when generated in periodic mode
+    len = 10;
+    all id (map do type:
+        f = win.windowFunction type [ Symmetric false ];
+        v = f len;
+        (compare (vec.length v) len and isSymmetric (vec.slice v 1 len)) or
+            (eprintln "** failed window type: \(type)"; false);
+    done (keys functions));
+),
+
+"periodic-odd": \(
+    len = 11;
+    all id (map do type:
+        f = win.windowFunction type [ Symmetric false ];
+        v = f len;
+        (compare (vec.length v) len and isSymmetric (vec.slice v 1 len)) or
+            (eprintln "** failed window type: \(type)"; false);
+    done (keys functions));
+),
+
+"bartlett-periodic": \(
+    compare (vec.list (win.bartlett 1)) [1] and
+       compare (vec.list (win.bartlett 2)) [0,1] and
+       compare (vec.list (win.bartlett 3)) [0,2/3,2/3] and
+       compare (vec.list (win.bartlett 4)) [0,1/2,1,1/2]
+),
+
+"bartlett-symmetric": \(
+    b = win.windowFunction (Bartlett ()) [ Symmetric true ];
+    compare (vec.list (b 1)) [1] and
+       compare (vec.list (b 2)) [0,0] and
+       compare (vec.list (b 3)) [0,1,0] and
+       compare (vec.list (b 4)) [0,2/3,2/3,0] and
+       compare (vec.list (b 5)) [0,1/2,1,1/2,0]
+),
+
+"hann": \(
+    compareUsing close (vec.list (win.hann 10)) [
+        0, 0.0955, 0.3455, 0.6545, 0.9045,
+        1.0000, 0.9045, 0.6545, 0.3455, 0.0955,
+    ] and
+    compareUsing close 
+       (vec.list (win.windowFunction (Hann ()) [ Symmetric true ] 10)) [
+        0, 0.1170, 0.4132, 0.7500, 0.9698,
+        0.9698, 0.7500, 0.4132, 0.1170, 0,
+    ]
+),
+
+"hamming": \(
+    compareUsing close (vec.list (win.hamming 10)) [
+        0.0800, 0.1679, 0.3979, 0.6821, 0.9121,
+        1.0000, 0.9121, 0.6821, 0.3979, 0.1679,
+    ] and
+    compareUsing close 
+       (vec.list (win.windowFunction (Hamming ()) [ Symmetric true ] 10)) [
+        0.0800, 0.1876, 0.4601, 0.7700, 0.9723,
+        0.9723, 0.7700, 0.4601, 0.1876, 0.0800,
+    ]
+),
+
+"blackman": \(
+    compareUsing close (vec.list (win.blackman 10)) [
+        0, 0.0402, 0.2008, 0.5098, 0.8492,
+        1.0000, 0.8492, 0.5098, 0.2008, 0.0402,
+    ] and
+    compareUsing close 
+       (vec.list (win.windowFunction (Blackman ()) [ Symmetric true ] 10)) [
+        0, 0.0509, 0.2580, 0.6300, 0.9511,
+        0.9511, 0.6300, 0.2580, 0.0509, 0,
+    ]
+),
+
+"blackmanHarris": \(
+    compareUsing close (vec.list (win.blackmanHarris 10)) [
+        0.0001, 0.0110, 0.1030, 0.3859, 0.7938,
+        1.0000, 0.7938, 0.3859, 0.1030, 0.0110,
+    ] and
+    compareUsing close 
+       (vec.list (win.windowFunction (BlackmanHarris ()) [ Symmetric true ] 10)) [
+        0.0001, 0.0151, 0.1470, 0.5206, 0.9317,
+        0.9317, 0.5206, 0.1470, 0.0151, 0.0001,
+    ]
+),
+
+"dirac": \(
+    compareUsing close (vec.list (win.dirac 1)) [ 1 ] and
+        compareUsing close (vec.list (win.dirac 5)) [ 0, 0, 1, 0, 0 ] and
+        compareUsing close (vec.list (win.dirac 6)) [ 0, 0, 0, 1, 0, 0 ]
+),
+
+"sinc": \(
+    compareUsing close (vec.list (win.sinc 1 5)) [ 0, 0, 1, 0, 0 ] and
+        compareUsing close (vec.list (win.sinc 2 5)) [ 0, 0, 1, 0, 0 ] and
+        compareUsing close (vec.list (win.sinc 4 7)) [ -0.2122, 0, 0.6366, 1, 0.6366, 0, -0.2122 ] and
+        compareUsing close (vec.list (win.sinc 4 6)) [ -0.2122, 0, 0.6366, 1, 0.6366, 0 ]
+),
+
+"kaiser": \(
+    compareUsing close (vec.list (win.kaiser 4 10)) [
+        0.0885, 0.2943, 0.5644, 0.8216, 0.9789,
+        0.9789, 0.8216, 0.5644, 0.2943, 0.0885
+    ] and
+    compareUsing close (vec.list (win.kaiser 2.5 11)) [
+        0.3040, 0.5005, 0.6929, 0.8546, 0.9622,
+        1.0000, 0.9622, 0.8546, 0.6929, 0.5005, 0.3040
+    ]
+),
+
+"degenerate": \(
+    all id (map do type:
+        f = functions[type];
+        periodic = f;
+        symmetric = win.windowFunction type [ Symmetric true ];
+       (compare (vec.list (periodic 0)) [] and
+        compare (vec.list (periodic 1)) [1] and
+        compare (vec.list (symmetric 0)) [] and
+        compare (vec.list (symmetric 1)) [1])
+            or (eprintln "** failed window type: \(type)"; false)
+    done (keys functions));
+),
+
+] is hash<string, () -> boolean>;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/signal/window.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,191 @@
+module may.signal.window;
+
+vec = load may.vector;
+bf = load may.vector.blockfuncs;
+
+cosineWindowSymmetric a0 a1 a2 a3 n =
+   (n1 = n - 1;
+    vec.fromList
+       (map do i:
+            a0
+            - a1 * cos(2 * pi * i / n1)
+            + a2 * cos(4 * pi * i / n1)
+            - a3 * cos(6 * pi * i / n1)
+            done [0..n1]));
+
+cosineWindowPeriodic a0 a1 a2 a3 n =
+   (vec.fromList
+       (map do i:
+            a0
+            - a1 * cos(2 * pi * i / n)
+            + a2 * cos(4 * pi * i / n)
+            - a3 * cos(6 * pi * i / n)
+            done [0..n-1]));
+                  
+cosineWindow a0 a1 a2 a3 sampling n =
+    if n < 2 then vec.ones n
+    else
+        case sampling of 
+        Symmetric (): cosineWindowSymmetric;
+        Periodic (): cosineWindowPeriodic;
+        esac a0 a1 a2 a3 n;
+    fi;
+
+bartlettSymmetric n =
+    if n < 2 then vec.ones n
+    else
+        vec.fromList
+           (n1 = n - 1;
+            h = int (n1 / 2);
+            concat [
+                map do i:
+                    2 * i / n1
+                    done [0..h],
+                map do i:
+                    2 - (2 * i / n1)
+                    done [h+1..n1]
+                ]);
+    fi;
+
+bartlettPeriodic n = 
+    if n < 2 then vec.ones n
+    else
+        vec.slice (bartlettSymmetric (n+1)) 0 n;
+    fi;
+
+bartlett sampling =
+    case sampling of
+    Symmetric (): bartlettSymmetric;
+    Periodic (): bartlettPeriodic;
+    esac;
+
+hann = cosineWindow 0.5 0.5 0.0 0.0;
+hamming = cosineWindow 0.54 0.46 0.0 0.0;
+blackman = cosineWindow 0.42 0.50 0.08 0.0;
+nuttall = cosineWindow 0.355768 0.487396 0.144232 0.012604;
+blackmanNuttall = cosineWindow 0.3635819 0.4891775 0.1365995 0.0106411;
+blackmanHarris = cosineWindow 0.35875 0.48829 0.14128 0.01168;
+
+boxcar n = vec.ones n;
+
+/**
+ * Vector of size n with the "middle" sample equal to 1 and all others
+ * equal to 0. The middle sample is sample (n-1)/2 for odd n or n/2+1
+ * for even n.
+ */
+dirac n =
+    if n < 2 then vec.ones n
+    else 
+        n0 = if n % 2 == 0 then n/2 else (n-1)/2 fi;
+        vec.concat [ vec.zeros n0, vec.ones 1, vec.zeros n0 ]
+    fi;
+
+/**
+ * Make a vector of size n containing the values of sinc(x) with
+ * x=0 in the middle, i.e. at sample (n-1)/2 for odd n or n/2+1 for
+ * even n, such that the distance from -pi to pi (the point at
+ * which the sinc function first crosses zero, for negative and
+ * positive arguments respectively) is p samples.  p does not have
+ * to be an integer.
+ */
+sinc p n =
+    if n < 2 then vec.ones n
+    else 
+        n0 = if n % 2 == 0 then n/2 else (n-1)/2 fi;
+        n1 = if n % 2 == 0 then n/2 else (n+1)/2 fi;
+        half = 1 :: map do i: x = i * 2*pi / p; sin(x) / x done [1..n/2];
+        vec.fromList ((take n0 (reverse half)) ++ (take n1 half));
+    fi;
+
+kaiser beta n =
+   (bes0 x = 
+       (fact x = fold do x y: x*y done 1 [1..x];   // x!
+        ipow a b = fold do x _: x*a done 1 [1..b]; // a^b where b∈ℕ
+        square x = x*x;
+        term x i =
+            case i of
+             0: 1;
+             _: (ipow (x/2) (i*2)) / (square (fact i));
+            esac;
+        sum (map (term x) [0..20]));
+    denominator = bes0 beta;
+    vec.fromList
+       (map do i:
+            k = 2*i / (n-1) - 1;
+            bes0 (beta * sqrt (1 - k*k)) / denominator;
+            done [0..n-1]));
+
+/** 
+  Kaiser window with sidelobe attenuation of 𝛼 dB and window length n
+*/
+kaiserForAttenuation alpha n =
+   (beta =
+        if alpha > 50 then 0.1102 * (alpha - 8.7)
+        elif alpha > 21 then 0.5842 * Math#pow(alpha - 21, 0.4) + 0.07886 * (alpha - 21)
+        else 0
+        fi;
+    kaiser beta n);
+
+/**
+  Kaiser window with sidelobe attenuation of alpha dB and transition 
+  bandwidth of (tw * samplerate) / (2 * pi)
+*/
+kaiserForTransitionLength alpha tw =
+   (m = if alpha > 21 
+        then Math#ceil((alpha - 7.95) / (2.285 * tw))
+        else Math#ceil(5.79 / tw)
+        fi;
+    kaiserForAttenuation alpha (m+1));
+
+/**
+  Kaiser window with sidelobe attenuation of alpha dB and transition
+  bandwidth of tbw Hz at the given sampleRate
+*/
+kaiserForBandwidth alpha tbw samplerate =
+    kaiserForTransitionLength alpha ((tbw * 2 * pi) / samplerate);
+
+windowFunction type options =
+   (var sampling = Periodic ();
+    var beta = 4;
+    for options \case of
+        Symmetric s: if s then sampling := Symmetric () fi;
+        Beta b: beta := b;
+        esac;
+    case type of
+    Hann (): hann sampling;
+    Hamming (): hamming sampling;
+    Blackman (): blackman sampling;
+    Nuttall (): nuttall sampling;
+    BlackmanNuttall (): blackmanNuttall sampling;
+    BlackmanHarris (): blackmanHarris sampling;
+    Boxcar (): boxcar;
+    Bartlett (): bartlett sampling;
+    Kaiser (): kaiser beta;
+    esac);
+
+//!!! should use vector. but does anyone use this function anyway? would we use it in framer if it used vector?
+windowed windowFunc frames =
+    case frames of
+        []: frames;
+         _: (first = head frames;
+             window = windowFunc (vec.length first);
+             map (bf.multiply window) frames);
+    esac;
+
+{
+cosineWindow,
+hann = hann (Periodic ()),
+hamming = hamming (Periodic ()), 
+blackman = blackman (Periodic ()), 
+nuttall = nuttall (Periodic ()), 
+blackmanNuttall = blackmanNuttall (Periodic ()), 
+blackmanHarris = blackmanHarris (Periodic ()),
+boxcar,
+bartlett = bartlett (Periodic ()), 
+dirac,
+sinc,
+kaiser, kaiserForAttenuation, kaiserForTransitionLength, kaiserForBandwidth,
+windowFunction,
+windowed
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/audiofile.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,165 @@
+
+module may.stream.audiofile;
+
+load may.stream.type;
+
+import javax.sound.sampled:
+     AudioSystem, AudioInputStream, AudioFormat, AudioFormat$Encoding,
+     AudioFileFormat, AudioFileFormat$Type,
+     TargetDataLine, LineListener, Line, Line$Info, Control, Control$Type,
+     UnsupportedAudioFileException;
+
+import java.io: File, IOException;
+
+import java.nio: ByteBuffer, ByteOrder;
+
+ch = load may.stream.channels;
+vec = load may.vector;
+mat = load may.matrix;
+
+{ decode } = load may.stream.format;
+
+readInterleaved' { format is ~AudioFormat, aistream is ~AudioInputStream } nframes =
+   (channels = format#getChannels();
+    bytesPerSample = format#getSampleSizeInBits() / 8;
+    bytes = new byte[nframes * channels * bytesPerSample];
+    bytesRead = aistream#read(bytes);
+    if bytesRead <= 0 then vec.zeros 0;
+    else
+        n = int(bytesRead / bytesPerSample);
+        doubles = new double[n];
+        decode { format } bytes doubles n;
+        vec.vector doubles;
+    fi;
+   );
+
+read' { format is ~AudioFormat, aistream is ~AudioInputStream } n =
+   (b = readInterleaved' { format, aistream } n;
+    channels = format#getChannels();
+    ch.deinterleaved channels b;
+   );
+
+readMono' { format is ~AudioFormat, aistream is ~AudioInputStream } n =
+   (b = readInterleaved' { format, aistream } n;
+    channels = format#getChannels();
+    ch.deinterleaved 1 (ch.mixedDownFromInterleaved channels b);
+   );
+
+// Note, all this assumes aistream is non-blocking (i.e. available()
+// is to the end of file). Our stream interface does support
+// indefinite and infinite streams, but audiofile doesn't yet.
+
+available' { format is ~AudioFormat, aistream is ~AudioInputStream } =
+    aistream#available() / ((format#getSampleSizeInBits() / 8) * format#getChannels());
+
+close' { aistream is ~AudioInputStream } =
+    aistream#close();
+
+openWithReader reader ch name is 'a -> number -> string -> stream =
+   (f = new File(name);
+    aistream = AudioSystem#getAudioInputStream(f);
+    format = aistream#getFormat();
+    len = available' { format, aistream }; // at start of stream
+    syncd = synchronized aistream;
+    {
+        get position () = syncd \(len - available' { aistream, format }),
+        get channels () = if ch == 0 then format#getChannels() else ch fi,
+        get sampleRate () = format#getSampleRate(),
+        get available () = syncd \(Known (available' { aistream, format })),
+        get finished? () = syncd \(not (aistream#available() > 0)),
+        read = syncd \(reader { aistream, format }),
+        close () = syncd \(close' { aistream }),
+    });
+
+/**
+ * Open the named audio file and return a stream object for
+ * reading from it. May throw UnsupportedAudioFileException
+ * or IOException.
+ */
+open = openWithReader read' 0;
+
+/**
+ * Open the named audio file and return a stream object that
+ * reads mono samples from it, using
+ * may.stream.channels.mixedDown to mix channels as necessary. 
+ * May throw UnsupportedAudioFileException or IOException.
+ */
+openMono = openWithReader readMono' 1;
+
+/**
+ * Open an audio file with the given name and write all available
+ * samples to it from the given stream, returning the number of
+ * sample frames written. The stream must have finite length.
+ * 
+ * Example:
+ * : str = audiofile.open "in.wav";
+ * : n = audiofile.write str "out.wav";
+ */
+write str name is stream -> string -> number =
+   (var tot = 0;
+    case str.available of
+    Infinite (): failWith "Cannot write infinite stream to file";
+    _: ()
+    esac;
+    class StreamAdapter extends TargetDataLine
+        int read(byte[] bytes, int off, int len)
+           (bb = ByteBuffer#wrap(bytes, off, len);
+            bb#order(ByteOrder#LITTLE_ENDIAN);
+            m = str.read (len / (str.channels * 2));
+            tot := mat.width m;
+            for [0..mat.width m - 1] do i:
+                for [0..str.channels - 1] do c:
+                    v = int (32767 * mat.at m c i);
+                    v = if v < -32768 then -32768
+                        elif v > 32767 then 32767
+                        else v fi;
+                    \() bb#putShort((i * str.channels + c) * 2, v);
+                done
+            done;
+            str.channels * 2 * mat.width m),
+        int available()
+            case str.available of
+            Known n: str.channels * n * 2;
+            other: str.channels * 1024; //!!!???
+            esac,
+        AudioFormat getFormat()
+            new AudioFormat(str.sampleRate, 16, str.channels, true, false),
+        void open(),
+        void open(AudioFormat format),
+        void open(AudioFormat format, int bufferSize),
+        void close(),
+        void drain(),
+        void flush(),
+        boolean isOpen() true,
+        boolean isActive() true,
+        boolean isRunning() true,
+        int getFramePosition() str.position,
+        long getLongFramePosition() str.position,
+        long getMicrosecondPosition() 0,
+        int getBufferSize() 0,
+        float getLevel() 1.0,
+        void start(),
+        void stop(),
+        void addLineListener(LineListener ll),
+        void removeLineListener(LineListener ll),
+        Control getControl(Control$Type c),
+        Control[] getControls(),
+        Line$Info getLineInfo(),
+        boolean isControlSupported(Control$Type c) false,
+    end;
+    f = new File(name);
+    n = AudioSystem#write(new AudioInputStream(new StreamAdapter()),
+                          AudioFileFormat$Type#WAVE, f);
+    str.close ();
+    n); //!!! this is wrong, should be number of frames (is number of bytes?)
+
+{
+    open,
+    openMono,
+    write,
+} as {
+    open is string -> stream,
+    openMono is string -> stream,
+    write is stream -> string -> number,
+} 
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/channels.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,116 @@
+
+module may.stream.channels;
+
+vec = load may.vector;
+mat = load may.matrix;
+bf = load may.vector.blockfuncs;
+
+load may.vector.type;
+
+//!!! "internal" vector function to retrieve data for read-only
+// purposes without copying
+//!!! need to have this in some internal-use module 
+raw =
+   (raw' v is ~double[] -> ~double[] = v;
+    raw' as vector -> ~double[]);
+        
+interleaved m = 
+   ({ columns, rows } = (mat.size m);
+    if rows == 1 then
+        mat.getRow 0 m
+    else
+        v = new double[columns * rows];
+        for [0..rows-1] do row:
+            for [0..columns-1] do col:
+                v[col * rows + row] := mat.at m row col;
+            done;
+        done;
+        vec.vector v;
+    fi);
+
+deinterleaved channels b =
+    if channels == 1 then
+        mat.newRowVector b
+    else
+        rows = (vec.length b) / channels;
+        vv = array (map \(new double[rows]) [0..channels-1]);
+        v = raw b;
+        for [0..rows-1] do row:
+            for [0..channels-1] do col:
+                vv[col][row] := v[channels * row + col];
+            done
+        done;
+        mat.newMatrix (RowMajor ()) (map vec.vector vv);
+    fi;
+
+mixedDown m =  //!!!doc: average, not sum
+   ({ columns, rows } = (mat.size m);
+    if rows < 1 or columns < 1 then
+        vec.zeros 0
+    elif rows == 1 then
+        mat.getRow 0 m
+    else
+        bf.divideBy rows (bf.add (mat.asRows m));
+    fi);
+
+mixedDownFromInterleaved channels b =
+    if channels == 1 then
+        b;
+    else
+        columns = ((vec.length b) / channels);
+        v = raw b;
+        v' = new double[columns];
+        for [0..channels-1] do row:
+            for [0..columns-1] do col:
+                v'[col] := v'[col] + v[col * channels + row];
+            done;
+        done;
+        bf.divideBy channels (vec.vector v');
+    fi;
+
+mixedFromInterleavedTo targetChannels channels b = 
+    if targetChannels == channels then
+        b;
+    elif targetChannels == 1 then
+        mixedDownFromInterleaved channels b;
+    else
+        columns = ((vec.length b) / channels);
+        v = raw b;
+        v' = new double[columns * targetChannels];
+        for [0..targetChannels-1] do target:
+            for [0..columns-1] do col:
+                if target < channels then
+                    v'[col * targetChannels + target] := v[col * channels + target];
+                elif channels == 1 and target == 1 then
+                    v'[col * targetChannels + target] := v[col * channels];
+                fi
+            done
+        done;
+        vec.vector v';
+    fi;
+
+mixedTo targetChannels m = 
+    if targetChannels == mat.height m then   // n -> n: pass unmodified
+        m
+    elif targetChannels == 1 then            // n -> 1: mix down
+        deinterleaved 1 (mixedDown m)
+    elif mat.height m == 1 then              // 1 -> n: copy to all channels
+        mat.newMatrix (RowMajor ()) (map \(mat.getRow 0 m) [1..targetChannels])
+    else                                     // n -> m: truncate or add zeros
+        mat.resizedTo { rows = targetChannels, columns = mat.width m } m
+    fi;
+
+mixedAndInterleavedTo targetChannels m = 
+    if targetChannels == 1 then
+        mixedDown m
+    else
+        interleaved (mixedTo targetChannels m);
+    fi;
+
+//!!! some of these names are terrible
+{
+    interleaved, deinterleaved,
+    mixedDown, mixedDownFromInterleaved,
+    mixedFromInterleavedTo, mixedTo, mixedAndInterleavedTo
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/filter.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,570 @@
+
+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;
+
+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;
+
+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
+   (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 𝛼 dB
+ */
+kaiserSincWindow zc n 𝛼 =
+   (sw = win.sinc (n*2) (n*zc*2 + 1);
+    kw = win.kaiserForAttenuation 𝛼 (vec.length sw);
+    bf.multiply sw kw);
+
+bandpassed f0 f1 attenuation bandwidth s =
+   (rate = s.sampleRate;
+    kw = win.kaiserForBandwidth attenuation bandwidth rate;
+    filterLength = vec.length kw;
+    // First arg to sinc is the complete cycle length for the cutoff frequency
+    idealFor freq =
+        bf.scaled (2 * freq / rate)
+           (win.sinc (rate / freq) filterLength);
+    idealBandpass =
+         if f1 < rate/2 then
+             if f0 > 0 then bf.subtract (idealFor f1) (idealFor f0)
+             else idealFor f1
+             fi
+         else
+             if f0 > 0 then bf.subtract (win.dirac filterLength) (idealFor f0)
+             else win.dirac filterLength;
+             fi;
+         fi;
+    filter = bf.multiply idealBandpass kw;
+    filtered = convolvedWith [Framesize 1024]
+       (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels]))
+        s;
+    delayedBy (- (int (filterLength / 2))) filtered);
+
+lowpassed f attenuation bandwidth s = 
+    bandpassed 0 f attenuation bandwidth s;
+
+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;
+        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
+resampledTo 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));
+
+{
+    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,
+    lowpassed, bandpassed, highpassed,
+    spaced, interpolated, decimated, picked,
+    resampledTo,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/format.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,55 @@
+
+module may.stream.format;
+
+import java.nio: ByteBuffer, ByteOrder;
+
+import javax.sound.sampled: 
+    AudioFormat, AudioFormat$Encoding,
+    UnsupportedAudioFileException;
+
+decode8u bytes doubles n is ~byte[] -> ~double[] -> number -> () =
+   (for [0..n-1] do i:
+       doubles[i] := (bytes[i] / 128.0) - 1.0;
+    done
+   );
+
+decode16s bytes doubles n is ~byte[] -> ~double[] -> number -> () =
+   (bb = ByteBuffer#wrap(bytes, 0, n * 2);
+    bb#order(ByteOrder#LITTLE_ENDIAN);
+    for [0..n-1] do i:
+       doubles[i] := bb#getShort(i*2) / 32768.0;
+    done
+   );
+
+decode32f bytes doubles n is ~byte[] -> ~double[] -> number -> () =
+   (bb = ByteBuffer#wrap(bytes, 0, n * 4);
+    bb#order(ByteOrder#LITTLE_ENDIAN);
+    for [0..n-1] do i:
+       doubles[i] := bb#getFloat(i*4);
+    done
+   );
+
+decodeFail () = 
+    throw new UnsupportedAudioFileException("Audio format not supported. Supported formats are 8-bit unsigned PCM, 16-bit signed little-endian PCM, or IEEE float");
+
+decode { format is ~AudioFormat } bytes doubles n = 
+   (if format#isBigEndian() then
+        decodeFail()
+    else
+        enc = format#getEncoding();
+        bits = format#getSampleSizeInBits();
+        if bits == 32 then
+            decode32f bytes doubles n;
+        elif bits == 16 and enc == AudioFormat$Encoding#PCM_SIGNED then
+            decode16s bytes doubles n;
+        elif bits == 8 and enc == AudioFormat$Encoding#PCM_UNSIGNED then
+            decode8u bytes doubles n;
+        else
+            decodeFail();
+        fi
+    fi);
+
+{
+    decode
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/framer.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,214 @@
+
+module may.stream.framer;
+
+/**
+ * Framer expresses a stream (or a file) as a lazy list of (possibly
+ * overlapping) frames of data.
+ */
+
+vec = load may.vector;
+bf = load may.vector.blockfuncs;
+af = load may.stream.audiofile;
+win = load may.signal.window;
+fft = load may.transform.fft;
+mat = load may.matrix;
+ch = load may.stream.channels;
+
+load may.stream.type;
+load may.matrix.type;
+load may.vector.type;
+load may.complex.type;
+
+//!!! todo: synchronized for everything with state assignment
+
+blockList framesize stream =
+    if stream.finished? then
+        stream.close ();
+        []
+    else
+        mat.resizedTo { rows = stream.channels, columns = framesize }
+           (stream.read framesize)
+            :. \(blockList framesize stream);
+    fi;
+
+overlappingBlockList size hop stream valid buffer =
+   (
+    m = stream.read hop;
+    obtained = mat.width m;
+
+    // Retain framesize - hop samples from old buffer, add hop samples
+    // (zero-padded if necessary) just read
+    buffer = map2
+        do buf row:
+            vec.concat
+               [vec.slice buf hop size,
+                vec.resizedTo hop (mat.getRow row m)];
+        done buffer [0..stream.channels-1];
+
+    // Number of "valid" elements (not tail-end zero-padding) left in buffer
+    remaining = valid - (hop - obtained);
+
+    if remaining <= 0 then
+        stream.close ();
+        [];
+    else
+        mat.newMatrix (RowMajor ()) buffer
+            :. \(overlappingBlockList size hop stream remaining buffer);
+    fi);
+
+frames { framesize, hop } stream =
+    if framesize == hop then
+        blockList framesize stream
+    else
+        overlappingBlockList framesize hop stream 
+            framesize (map \(vec.zeros framesize) [0..stream.channels-1]);
+    fi;
+
+streamContiguous rate framesize frames =
+   (var remaining = frames;
+    var buffered = mat.zeroSizeMatrix ();
+    var position = 0;
+    channels = mat.height (head frames); // so we don't need to keep a head ptr
+    {
+        get position () = position,
+        get channels () = channels,
+        get sampleRate () = rate,
+        get finished? () = empty? remaining and mat.empty? buffered,
+        get available () = 
+            // Don't take length of frames -- we don't want to lose laziness.
+            // If the caller cares that much, they can measure frames themselves
+            if empty? remaining then
+                Known (mat.width buffered) 
+            else
+                Unknown ()
+            fi,
+        read count =
+           (framesFor samples acc =
+                if samples <= 0 or empty? remaining then
+                    reverse acc
+                else
+                    this = head remaining;
+                    remaining := tail remaining;
+                    framesFor (samples - mat.width this) (this :: acc)
+                fi;
+            source = mat.concat (Horizontal ()) (framesFor count [buffered]);
+            toReturn = mat.columnSlice source 0 count;
+            position := position + mat.width toReturn;
+            buffered := mat.columnSlice source count (mat.width source);
+            toReturn),
+        close = \(),
+    });
+
+overlapAdd overlap frames =
+   (ola fr pending acc =
+        case fr of
+        first::rest:
+           (w = mat.width pending;
+            pre = mat.columnSlice pending 0 (w - overlap);
+            added = mat.sum first
+               (mat.resizedTo (mat.size first)
+               (mat.columnSlice pending (w - overlap) w));
+            ola rest added (pre::acc));
+         _:
+            reverse (pending::acc);
+        esac;
+    case frames of
+    first::rest:
+        mat.concat (Horizontal ()) (ola rest first []);
+     _: 
+        mat.zeroSizeMatrix ();
+    esac);
+
+streamOverlapping rate { framesize, hop, window } frames =
+   (var remaining = frames;
+    var buffered = mat.zeroSizeMatrix ();
+    var position = 0;
+
+    factor = hop / (framesize/2);
+    w = bf.scaled factor (window framesize);
+    channels = mat.height (head frames); // so we don't need to keep a head ptr
+
+    syncd = synchronized remaining;
+
+    finished' () = syncd \(empty? remaining and mat.empty? buffered);
+
+    read' count = 
+       (framesFor samples acc =
+            if samples <= 0 or empty? remaining then
+                reverse acc
+            else
+                this = mat.resizedTo { columns = framesize, rows = channels }
+                   (mat.newMatrix (RowMajor ())
+                       (map (bf.multiply w) (mat.asRows (head remaining))));
+                remaining := tail remaining;
+                framesFor (samples - hop) (this::acc)
+            fi;
+        source = overlapAdd (framesize - hop)
+           (framesFor count [buffered]);
+        buffered := mat.columnSlice source count (mat.width source);
+        mat.columnSlice source 0 count);
+
+    // lose initial padding
+    \() (read' (framesize - hop));
+    
+    {
+        get position () = syncd \(position),
+        get channels () = channels,
+        get sampleRate () = rate,
+        get finished? () = finished' (),
+        get available () = if finished' () then Known 0 else Unknown () fi,
+        read count = syncd
+          \(data = read' count;
+            position := position + mat.width data;
+            data),
+        close = \(),
+    });
+    
+//!!! doc: convert frames back to a stream
+streamed rate { framesize, hop } frames =
+    if framesize == hop then
+        streamContiguous rate framesize frames
+    else
+        streamOverlapping rate
+            { framesize, hop, window = win.hann } // periodic, not symmetric
+            frames
+    fi;
+
+monoFrames params stream =
+    map ch.mixedDown (frames params stream);
+
+windowedFrames { framesize, hop, window } stream =
+   (win = window framesize;
+    map (bf.multiply win) (monoFrames { framesize, hop } stream));
+
+frequencyDomainFrames parameters stream =
+   (f = fft.realForward parameters.framesize;
+    map f (windowedFrames parameters stream));
+
+typedef params = { framesize is number, hop is number };
+typedef winparams = { framesize is number, hop is number, window is number -> vector };
+
+{ 
+    frames is params -> stream -> list<matrix>,
+    monoFrames is params -> stream -> list<vector>,
+    windowedFrames is winparams -> stream -> list<vector>,
+    frequencyDomainFrames is winparams -> stream -> list<array<cplx> >,
+
+    framesOfFile parameters filename =
+        frames parameters (af.open filename),
+
+    monoFramesOfFile parameters filename =
+        monoFrames parameters (af.open filename),
+
+    windowedFramesOfFile parameters filename = 
+        windowedFrames parameters (af.open filename),
+
+    frequencyDomainFramesOfFile parameters filename = 
+        frequencyDomainFrames parameters (af.open filename),
+
+    overlapAdd is number -> list<matrix> -> matrix,
+
+    streamed is number -> params -> list<matrix> -> stream,
+    streamOverlapping is number -> winparams -> list<matrix> -> stream,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/playback.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,62 @@
+
+module may.stream.playback;
+
+vec = load may.vector;
+mat = load may.matrix;
+af = load may.stream.audiofile;
+ch = load may.stream.channels;
+
+import javax.sound.sampled:
+    AudioSystem, AudioFormat, AudioFormat$Encoding, SourceDataLine;
+
+import java.nio: ByteBuffer, ByteOrder;
+
+playBlock line b is ~SourceDataLine -> 'a -> () =
+   (len = vec.length b;
+    samples = vec.primitive b;
+    nb = len * 2;
+    bytes = new byte[nb];
+    bb = ByteBuffer#wrap(bytes, 0, nb);
+    bb#order(ByteOrder#LITTLE_ENDIAN);
+    sb = bb#asShortBuffer();
+    for [0..len-1] do i: sb#put(i, samples[i] * 32767.0); () done;
+    \() line#write(bytes, 0, nb));
+
+play line blocks = for blocks (playBlock line);
+    
+open { rate, channels } = 
+   (format = new AudioFormat(AudioFormat$Encoding#PCM_SIGNED, rate, 16,
+                             channels, channels * 2, rate, false);
+    line = AudioSystem#getSourceDataLine(format);
+    line#open(format);
+    line#start();
+    {
+        line,
+        sampleRate = rate,
+        channels,
+        play = play line,
+        close () = (line#drain(); line#close()),
+    });
+
+playMatrix rate m =
+   (channels = mat.height m;
+    line = open { rate, channels };
+    line.play [ch.mixedAndInterleavedTo channels m];
+    line.close());
+
+playStream stream =
+   (line = open { rate = stream.sampleRate, channels = stream.channels };
+    blocksize = 10240;
+    not stream.finished? loop 
+        line.play [(ch.mixedAndInterleavedTo line.channels 
+                    (stream.read blocksize))];
+    line.close();
+    stream.close());
+
+playFile filename = 
+    playStream (af.open filename);
+
+{
+    open, playMatrix, playStream, playFile,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/record.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,69 @@
+
+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:
+    AudioSystem, AudioFormat, AudioFormat$Encoding, 
+    TargetDataLine, DataLine, DataLine$Info;
+
+{ decode } = load may.stream.format;
+
+recordInterleaved seconds line is number -> ~TargetDataLine -> 'a =
+   (format = line#getFormat();
+    channels = format#getChannels();
+    nframes = seconds * format#getSampleRate();
+    bytesPerSample = format#getSampleSizeInBits() / 8;
+    bytes = new byte[nframes * channels * bytesPerSample];
+    bytesRead = line#read(bytes, 0, length bytes);
+    if bytesRead <= 0 then vec.zeros 0;
+    else
+        n = int(bytesRead / bytesPerSample);
+        doubles = new double[n];
+        decode { format } bytes doubles n;
+        vec.vector doubles;
+    fi;
+   );
+
+recordFor seconds line is number -> ~TargetDataLine -> 'a =
+    ch.deinterleaved line#getFormat()#getChannels() 
+       (recordInterleaved seconds line);
+
+open { rate, channels } =
+   (format = new AudioFormat(AudioFormat$Encoding#PCM_SIGNED, rate, 16,
+                             channels, channels * 2, rate, false);
+    info = new DataLine$Info
+       (Class#forName("javax.sound.sampled.TargetDataLine"), format);
+    if not AudioSystem#isLineSupported(info) then
+        failWith "Unable to open 16-bit PCM line at rate \(rate), \(channels) channels";
+    fi;
+    line = AudioSystem#getLine(info) unsafely_as ~TargetDataLine;
+    line#open(format);
+    line#start();
+    {
+        line,
+        sampleRate = rate,
+        channels,
+        norec recordFor t = recordFor t line,
+        close () = (line#drain(); line#close()),
+    });
+
+recordFor seconds =
+   (line = open { rate = 44100, channels = 2 }; //!!! or system defaults?
+    data = line.recordFor seconds;
+    line.close ();
+    {
+        sampleRate = line.sampleRate,
+        channels = line.channels,
+        data
+    });
+
+{
+    open,
+    recordFor
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/syntheticstream.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,97 @@
+
+module may.stream.syntheticstream;
+
+ch = load may.stream.channels;
+vec = load may.vector;
+mat = load may.matrix;
+
+load may.vector.type;
+load may.matrix.type;
+load may.stream.type;
+
+generated sampleRate generator =
+   (// generator takes sample number as arg, returns number in -1,+1 range
+    var position = 0;
+    {
+        get position () = position,
+        get channels () = 1, 
+        get sampleRate () = sampleRate,
+        get available () = Infinite (),
+        get finished? () = false,
+        read count = ch.deinterleaved 1
+           (result = new double[count];
+            for [0..count-1] do i:
+                result[i] := generator (position + i)
+            done;
+            position := position + count;
+            vec.vector result),
+        close = \(),
+    });
+
+sinusoid rate freq =
+    generated rate (sin . (* (2 * pi * freq / rate)));
+
+whiteNoise rate =
+    generated rate \((Math#random() * 2.0) - 1.0);
+
+silent rate =
+    generated rate \0;
+
+precalculatedMono rate data =
+   (n = vec.length data;
+    var position = 0;
+    {
+        get position () = position,
+        get channels () = 1,
+        get sampleRate () = rate,
+        get available () = Known (n - position),
+        get finished? () = not (n > position),
+        read count = ch.deinterleaved 1
+           (rc = min count (n - position);
+            result = vec.slice data position (position + rc);
+            position := position + rc;
+            result),
+        close = \(),
+    });
+
+precalculated rate data =
+   (n = mat.width data;
+    c = mat.height data;
+    var position = 0;
+    {
+        get position () = position,
+        get channels () = c,
+        get sampleRate () = rate,
+        get available () = Known (n - position),
+        get finished? () = not (n > position),
+        read count = 
+           (rc = min count (n - position);
+            result = mat.columnSlice data position (position + rc);
+            position := position + rc;
+            result),
+        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 -> matrix -> stream,
+    precalculatedMono is number -> vector -> stream,
+    sinusoid is number -> number -> stream, 
+    whiteNoise is number -> stream,
+    silent is number -> stream,
+    empty is number -> number -> stream,
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/test/audiofile_reference.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,35 @@
+
+module may.stream.test.audiofile_reference;
+
+syn = load may.stream.syntheticstream;
+filt = load may.stream.filter;
+
+//!!! docs from turbot
+
+pulseChannel rate =
+   (pulseFreq = 2;
+    pulseWidth = 0.01 * rate;
+    generator i =
+       (pulseNo = int ((i * pulseFreq) / rate);
+        index = (i * pulseFreq) - (rate * pulseNo);
+        if index < pulseWidth then
+	    s = 1.0 - abs(pulseWidth/2 - index) / (pulseWidth/2);
+	    if pulseNo % 2 != 0 then (-s) else s fi
+        else 0
+        fi);
+    syn.generated rate generator);
+
+referenceChannels rate =
+   (leftovers rate n =
+       (syn.generated rate \(n / 20) :. \(leftovers rate (n+1)));
+    syn.sinusoid rate 600 :: pulseChannel rate :: leftovers rate 2);
+
+afReference rate channels =
+    filt.withDuration (2 * rate)
+       (filt.multiplexed (take channels (referenceChannels rate)));
+    
+{
+    afReference
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/test/speedtest.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,47 @@
+
+program may.stream.test.speedtest;
+
+af = load may.stream.audiofile;
+vec = load may.vector;
+mat = load may.matrix;
+filt = load may.stream.filter;
+
+import java.lang: StackOverflowError;
+
+{ compare, compareUsing, time } = load may.test.test;
+
+before = System#currentTimeMillis();
+
+str = time "open audio file" \(af.openMono "shortish.wav");
+
+conv = time "prepare convolve" \(filt.convolvedWith [ Framesize 256 ] (mat.newRowVector (vec.fromList [ 1, 0.8, 0.5 ])) str);
+
+var len = 0;
+time "read convolve" \((not conv.finished?) loop len := len + mat.width (conv.read 65536));
+
+str.close ();
+
+println "Done";
+
+after = System#currentTimeMillis();
+
+println "Total time: \(after - before)ms for \(len) audio sample frames [\(int (len / ((after - before) / 1000))) fps]";
+
+
+before = System#currentTimeMillis();
+
+str = time "open audio file" \(af.openMono "shortish.wav");
+
+res = time "prepare resample" \(filt.resampledTo 44100 str);
+
+len := 0;
+time "read resampled" \((not res.finished?) loop len := len + mat.width (res.read 65536));
+
+str.close ();
+
+println "Done";
+
+after = System#currentTimeMillis();
+
+println "Total time: \(after - before)ms for \(len) audio sample frames [\(int (len / ((after - before) / 1000))) fps]";
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/test/test_audiofile.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,98 @@
+
+module may.stream.test.test_audiofile;
+
+af = load may.stream.audiofile;
+vec = load may.vector;
+mat = load may.matrix;
+bf = load may.vector.blockfuncs;
+
+ref = load may.stream.test.audiofile_reference;
+
+{ compare, compareUsing } = load may.test.test;
+
+testfile name = "may/test/data/\(name).wav";
+
+float n is number -> number =
+    // round number to float precision (for comparison with floats)
+   (arr = new float[1];
+    arr[0] := n;
+    arr[0]);
+
+readAll stream =
+    case stream.available of
+    Known n: stream.read n;
+    _: failWith "Known-duration stream required";
+    esac;
+
+bitdepthComparator depth =
+   (slack = if depth == 8 then 0.015 else 0.001 fi;
+    do test ref: abs (test - ref) < slack done);
+
+maxOf m =
+    bf.max (vec.fromList (map bf.max (mat.asRows m)));
+
+testReferenceFile rate channels bitdepth =
+   (test = readAll (af.open (testfile "\(rate)-\(channels)-\(bitdepth)"));
+    ref = readAll (ref.afReference rate channels);
+    if mat.equalUnder (bitdepthComparator bitdepth) test ref then
+        true
+    else
+        println "** peak difference: \(maxOf (mat.difference ref test))";
+        for [0..mat.height test - 1] do ch:
+            if mat.equalUnder (bitdepthComparator bitdepth)
+               (mat.newRowVector (mat.getRow ch test))
+               (mat.newRowVector (mat.getRow ch ref)) then
+                println "   channel \(ch): ok";
+            else
+                println "   channel \(ch): not ok";
+// This isn't really simple enough!
+/*!!!
+                seriesFor m =
+                    Series {
+                        start = 0, 
+                        step = 1, 
+                        values = take 1000 (vec.list (mat.getRow ch m))
+                        };
+                \() (pl.plot (map seriesFor [ test, ref, mat.scaled 10000 (mat.difference test ref) ]));
+*/
+            fi;
+        done;
+        false
+    fi);
+
+[ 
+
+"20samples-open": \(
+    f = af.open (testfile "20samples");
+    compare f.position 0 and
+        compare f.channels 1 and
+        compare f.sampleRate 44100 and
+        compare f.available (Known 20) and
+        compare f.finished? false and
+        ( f.close () ; true )
+),
+
+"20samples-read": \(
+    all id (map do opener:
+        f = opener (testfile "20samples");
+        first15 = f.read 15;
+        last5 = f.read 10;
+        compare (mat.size first15) { rows = 1, columns = 15 } and
+            compare (mat.size last5) { rows = 1, columns = 5 } and
+            compare (vec.list (mat.getRow 0 first15))
+                [ float (32767/32768),0,0,0,0,0,0,0,0,0,0,0,0,0,0 ] and
+            compare (vec.list (mat.getRow 0 last5)) [ 0,0,0,0,-1 ] and
+            ( f.close () ; true )
+        done [ af.open, af.openMono ]);
+),
+
+"8000-1-8": \(
+    testReferenceFile 8000 1 8;
+),
+
+"44100-2-16": \(
+    testReferenceFile 44100 2 16;
+),
+
+] is hash<string, () -> boolean>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/test/test_channels.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,77 @@
+
+module may.stream.test.test_channels;
+
+ch = load may.stream.channels;
+mat = load may.matrix;
+vec = load may.vector;
+
+{ compare, compareUsing } = load may.test.test;
+
+newMatrix data = mat.newMatrix (ColumnMajor ()) (map vec.fromList data);
+
+compareBlocks b1 b2 =
+    compare (vec.list b1) (vec.list b2);
+
+[
+
+"interleaved": \(
+    compareBlocks (ch.interleaved (newMatrix [[1,4],[2,5],[3,6]]))
+       (vec.fromList [1,4,2,5,3,6]) and
+        compareBlocks (ch.interleaved (newMatrix [[1],[2],[3]]))
+           (vec.fromList [1,2,3])
+),
+
+"deinterleaved": \(
+    compareUsing mat.equal (ch.deinterleaved 2 (vec.fromList [1,4,2,5,3,6]))
+       (newMatrix [[1,4],[2,5],[3,6]]) and
+        compareUsing mat.equal (ch.deinterleaved 1 (vec.fromList [1,2,3]))
+           (newMatrix [[1],[2],[3]])
+),
+
+"mixedDown": \(
+    compareBlocks (ch.mixedDown (newMatrix [[1,4],[2,5],[3,6]]))
+       (vec.fromList [5/2,7/2,9/2]) and
+        compareBlocks (ch.mixedDown (newMatrix []))
+           (vec.fromList [])
+),
+
+"mixedDownFromInterleaved": \(
+    compareBlocks (ch.mixedDownFromInterleaved 2 (vec.fromList [1,4,2,5,3,6]))
+       (vec.fromList [5/2,7/2,9/2]) and
+        compareBlocks (ch.mixedDownFromInterleaved 1 (vec.fromList [1,2,3]))
+           (vec.fromList [1,2,3])
+),
+
+"mixedFromInterleavedTo": \(
+    compareBlocks (ch.mixedFromInterleavedTo 1 2 (vec.fromList [1,4,2,5,3,6]))
+       (vec.fromList [5/2,7/2,9/2]) and
+        compareBlocks (ch.mixedFromInterleavedTo 2 2 (vec.fromList [1,4,2,5,3,6]))
+           (vec.fromList [1,4,2,5,3,6]) and
+        compareBlocks (ch.mixedFromInterleavedTo 3 2 (vec.fromList [1,4,2,5,3,6]))
+           (vec.fromList [1,4,0,2,5,0,3,6,0]) and
+        compareBlocks (ch.mixedFromInterleavedTo 1 1 (vec.fromList [1,2,3]))
+           (vec.fromList [1,2,3]) and
+        compareBlocks (ch.mixedFromInterleavedTo 2 1 (vec.fromList [1,2,3]))
+           (vec.fromList [1,1,2,2,3,3]) and
+        compareBlocks (ch.mixedFromInterleavedTo 3 1 (vec.fromList [1,2,3]))
+           (vec.fromList [1,1,0,2,2,0,3,3,0])
+),
+
+"mixedAndInterleavedTo": \(
+    compareBlocks (ch.mixedAndInterleavedTo 1 (newMatrix [[1,4],[2,5],[3,6]]))
+       (vec.fromList [5/2,7/2,9/2]) and
+        compareBlocks (ch.mixedAndInterleavedTo 2 (newMatrix [[1,4],[2,5],[3,6]]))
+           (vec.fromList [1,4,2,5,3,6]) and
+        compareBlocks (ch.mixedAndInterleavedTo 3 (newMatrix [[1,4],[2,5],[3,6]]))
+           (vec.fromList [1,4,0,2,5,0,3,6,0]) and
+        compareBlocks (ch.mixedAndInterleavedTo 1 (newMatrix [[1],[2],[3]]))
+           (vec.fromList [1,2,3]) and
+        compareBlocks (ch.mixedAndInterleavedTo 2 (newMatrix [[1],[2],[3]]))
+           (vec.fromList [1,1,2,2,3,3]) and
+        compareBlocks (ch.mixedAndInterleavedTo 3 (newMatrix [[1],[2],[3]]))
+           (vec.fromList [1,1,1,2,2,2,3,3,3])
+),
+
+] is hash<string, () -> boolean>;
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/test/test_filter.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,831 @@
+
+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;
+syn = load may.stream.syntheticstream;
+filt = load may.stream.filter;
+
+//pl = load may.plot;//!!!
+
+pl = { plot things = true; };
+
+{ compare, compareUsing } = load may.test.test;
+
+compareClose = compareUsing 
+    do m1 m2:
+        all id (map2 do v1 v2:
+            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 ]
+];
+
+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;
+            compareClose (map vec.list (mat.asRows (c.read 4))) [[ 1,0,-1,0 ]] and
+               ( c.close (); true )
+        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;
+            compareClose (map vec.list (mat.asRows (c.read 4))) [[ 8,6,4,2 ]] and
+               ( c.close (); true )
+        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;
+            compareClose (map vec.list (mat.asRows (c.read 4)))
+                [[0,0,0,1],[0,8,14,10],[0,0,1,1]] and
+               ( c.close (); true )
+        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;
+            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)) and
+                ( c.close (); true )
+        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 )
+),
+
+//!!! 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);
+    array (map do v: 20 * Math#log10(v) done (vec.list spectrum)));
+
+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 2; // 2Hz sine sampled at 16Hz
+    input = filt.withDuration 64 sinusoid;
+    output = filt.decimated 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;
+),
+
+"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];
+    compareClose [a] [b];
+),
+
+"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;
+        output = filt.lowpassed cutoff attenuation bandwidth input;
+        logspec = logSpectrumFrom output n;
+        acceptances = map do bin:
+            freq = (rate / n) * bin;
+            db = logspec[bin];
+            //!!! what should these 0.01 actually be?
+            if freq < cutoff - bandwidth/2 then (db < 0.01 and db > -0.01)
+            elif freq > cutoff + bandwidth/2 then (db < -attenuation)
+            else (db < 0.01 and db > -attenuation)
+            fi;
+        done [0..n/2];
+        compare acceptances (map \true [0..n/2]));
+    all id 
+       (map test [
+            { rate = 800, cutoff = 200, attenuation = 80, bandwidth = 10, n = 1000 },
+        ]);
+),
+
+"highpassed-dirac": \(
+    test { rate, cutoff, attenuation, bandwidth, n } = 
+       (input = makeDiracStream rate n;
+        output = filt.highpassed cutoff attenuation bandwidth input;
+        logspec = logSpectrumFrom output n;
+        acceptances = map do bin:
+            freq = (rate / n) * bin;
+            db = logspec[bin];
+            //!!! what should these 0.01 actually be?
+            if freq > cutoff + bandwidth/2 then (db < 0.01 and db > -0.01)
+            elif freq < cutoff - bandwidth/2 then (db < -attenuation)
+            else (db < 0.01 and db > -attenuation)
+            fi;
+        done [0..n/2];
+        compare acceptances (map \true [0..n/2]));
+    all id 
+       (map test [
+            { rate = 800, cutoff = 200, attenuation = 80, bandwidth = 10, n = 1000 },
+        ]);
+),
+
+"bandpassed-dirac": \(
+    test { rate, f0, f1, attenuation, bandwidth, n } = 
+       (input = makeDiracStream rate n;
+        output = filt.bandpassed f0 f1 attenuation bandwidth input;
+        logspec = logSpectrumFrom output n;
+        acceptances = map do bin:
+            freq = (rate / n) * bin;
+            db = logspec[bin];
+            //!!! what should these 0.01 actually be?
+            if freq < f0 - bandwidth/2 then (db < -attenuation)
+            elif freq < f0 + bandwidth/2 then (db < 0.01 and db > -attenuation)
+            elif freq < f1 - bandwidth/2 then (db < 0.01 and db > -0.01)
+            elif freq < f1 + bandwidth/2 then (db < 0.01 and db > -attenuation)
+            else (db < -attenuation)
+            fi;
+        done [0..n/2];
+        compare acceptances (map \true [0..n/2]));
+    all id 
+       (map test [
+            { rate = 800, f0 = 200, f1 = 300, attenuation = 80, bandwidth = 10, n = 1000 },
+        ]);
+),
+
+];    
+
+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_framer.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,173 @@
+
+module may.stream.test.test_framer;
+
+fr = load may.stream.framer;
+vec = load may.vector;
+mat = load may.matrix;
+syn = load may.stream.syntheticstream;
+
+{ compare, compareUsing } = load may.test.test;
+
+rate = 10;
+
+testStream n is number -> 'a  = syn.precalculatedMono rate (vec.fromList [1..n]);
+
+compareFrames frames1 frames2 =
+    all id (map2 do f1 f2: compareUsing mat.equal f1 f2 done frames1
+       (map (mat.newRowVector . vec.fromList) frames2));
+
+testFramesWith params length expected firstChunkSize =
+   (f = fr.frames params (testStream length);
+    str = fr.streamed rate params f;
+    ts = testStream length; // newly initialised stream
+
+    compareFrames f expected and
+
+       (firstChunk = str.read firstChunkSize;
+        compareUsing mat.equal
+            firstChunk (ts.read firstChunkSize)) and
+
+        compare str.position firstChunkSize and
+        compare str.finished? false and
+
+       (restChunk = str.read (length - firstChunkSize);
+        compareUsing mat.equal
+            restChunk (ts.read (length - firstChunkSize))) and
+        compare str.position length and
+
+       (trailingZeros = str.read (params.framesize + 1);
+        compareUsing mat.equal
+            trailingZeros
+               (mat.zeroMatrix
+                { rows = str.channels, columns = mat.width trailingZeros }) and
+           (mat.width trailingZeros < params.framesize)) and
+
+       compare str.finished? true and
+       compare str.available (Known 0));
+
+testFramesInvertible params length expected =
+    all id (map (testFramesWith params length expected) [1..length]);
+
+testFrames params length expected =
+   (f = fr.frames params (testStream length);
+    compareFrames f expected);
+
+[
+
+"framecount-2x2": \( 
+    fr = fr.frames { framesize = 2, hop = 2 } (testStream 2);
+    compare (length fr) 1
+),
+
+"framecount-2x3": \( 
+    fr = fr.frames { framesize = 2, hop = 2 } (testStream 3);
+    compare (length fr) 2
+),
+
+"framecount-2x4": \( 
+    fr = fr.frames { framesize = 2, hop = 2 } (testStream 4);
+    compare (length fr) 2
+),
+
+"framecount-2.1x0": \( 
+    fr = fr.frames { framesize = 2, hop = 1 } (testStream 0);
+    compare (length fr) 1
+),
+
+"framecount-2.1x1": \( 
+    fr = fr.frames { framesize = 2, hop = 1 } (testStream 1);
+    compare (length fr) 2
+),
+
+"framecount-2.1x2": \( 
+    fr = fr.frames { framesize = 2, hop = 1 } (testStream 2);
+    compare (length fr) 3
+),
+
+"framecount-2.1x3": \( 
+    fr = fr.frames { framesize = 2, hop = 1 } (testStream 3);
+    compare (length fr) 4
+),
+
+"framecount-4.1x4": \( 
+    fr = fr.frames { framesize = 4, hop = 1 } (testStream 4);
+    compare (length fr) 7
+),
+
+"framecount-4.3x4": \( 
+    fr = fr.frames { framesize = 4, hop = 3 } (testStream 4);
+    compare (length fr) 2 
+),
+
+"framecount-4.4x4": \( 
+    fr = fr.frames { framesize = 4, hop = 4 } (testStream 4);
+    compare (length fr) 1
+),
+
+"framecount-3.2x4": \(
+    fr = fr.frames { framesize = 3, hop = 2 } (testStream 4);
+    compare (length fr) 3
+),
+
+"frames-2x5": \( 
+    testFramesInvertible { framesize = 2, hop = 2 } 5 [ [1,2], [3,4], [5,0] ];
+),
+
+"frames-4.3x4": \( 
+    testFrames { framesize = 4, hop = 3 } 4 [ [0,1,2,3], [3,4,0,0] ];
+),
+
+"frames-3.2x4": \(
+    testFrames { framesize = 3, hop = 2 } 4 [ [0,1,2], [2,3,4], [4,0,0] ];
+),
+
+"frames-3.1x6": \(
+    testFramesInvertible { framesize = 3, hop = 1 } 6
+        [ [0,0,1], [0,1,2], [1,2,3], [2,3,4],
+          [3,4,5], [4,5,6], [5,6,0], [6,0,0] ];
+),
+
+"frames-4.2x8": \(
+    testFramesInvertible { framesize = 4, hop = 2 } 8
+        [ [0,0,1,2], [1,2,3,4], [3,4,5,6], [5,6,7,8], [7,8,0,0] ];
+),
+
+"overlapAdd-3.1": \(
+    compareUsing (mat.equal)
+       (fr.overlapAdd 2 [ mat.newRowVector (vec.fromList [ 1,2,3 ]),
+                          mat.newRowVector (vec.fromList [   4,5,6 ]),
+                          mat.newRowVector (vec.fromList [     7,8,9 ]) ])
+       (mat.newRowVector (vec.fromList [ 1,6,15,14,9 ]))
+),
+
+"overlapAdd-3.2": \(
+    compareUsing (mat.equal)
+       (fr.overlapAdd 1 [ mat.newRowVector (vec.fromList [ 1,2,3 ]),
+                          mat.newRowVector (vec.fromList [     4,5,6 ]),
+                          mat.newRowVector (vec.fromList [         7,8,9 ]) ])
+       (mat.newRowVector (vec.fromList [ 1,2,7,5,13,8,9 ]))
+),
+
+"overlapAdd-4.2": \(
+    compareUsing (mat.equal)
+       (fr.overlapAdd 2 [ mat.newRowVector (vec.fromList [ 1,2,3,4 ]),
+                          mat.newRowVector (vec.fromList [     5,6,7,8 ]),
+                          mat.newRowVector (vec.fromList [         9,0,1,2 ]) ])
+       (mat.newRowVector (vec.fromList [ 1,2,8,10,16,8,1,2 ]))
+),
+
+"overlapAdd-6+4.2": \(
+    // Must work even if blocks vary in length (what if shorter than
+    // overlap though?)
+    compareUsing (mat.equal)
+       (fr.overlapAdd 2 [ mat.newRowVector (vec.fromList [ 1,2,3,4,5,6 ]),
+                          mat.newRowVector (vec.fromList [         7,8,9,0 ]),
+                          mat.newRowVector (vec.fromList [             1,2,3 ]),
+                          mat.newRowVector (vec.fromList [               4,5,6 ]) ])
+       (mat.newRowVector (vec.fromList [ 1,2,3,4,12,14,10,6,8,6 ]))
+),
+
+] is hash<string, () -> boolean>;
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/test/test_syntheticstream.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,80 @@
+
+module may.stream.test.test_syntheticstream;
+
+vec = load may.vector;
+mat = load may.matrix;
+syn = load may.stream.syntheticstream;
+
+{ compare, compareUsing } = load may.test.test;
+
+compareApprox eps =
+    compareUsing do v1 v2: all id (map2 do f1 f2: abs (f1 - f2) < eps done v1 v2) done;
+
+epsilon = 0.000001;
+
+[
+
+"generated": \(
+    str = 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
+),
+
+"sinusoid": \(
+    // 2Hz sine sampled 8 times a second
+    str = syn.sinusoid 8 2;
+    compare str.position 0 and
+        compare str.channels 1 and
+        compare str.sampleRate 8 and
+        compare str.available (Infinite ()) and
+        compare str.finished? false and
+        compareApprox epsilon (vec.list (mat.getRow 0 (str.read 6))) [ 0, 1, 0, -1, 0, 1 ] and
+        compare str.position 6
+),
+
+"silent": \(
+    str = syn.silent 8;
+    compare str.position 0 and
+        compare str.channels 1 and
+        compare str.sampleRate 8 and
+        compare str.available (Infinite ()) and
+        compare str.finished? false and
+        compare (vec.list (mat.getRow 0 (str.read 3))) [ 0, 0, 0 ] and
+        compare str.position 3
+),
+
+"precalculatedMono-empty": \(
+    str = syn.precalculatedMono 2 (vec.fromList []);
+    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
+        compare (vec.list (mat.getRow 0 (str.read 3))) [] and
+        compare str.position 0
+),
+
+"precalculatedMono": \(
+    str = syn.precalculatedMono 2 (vec.fromList [ 1, 2, 3, 4 ]);
+    compare str.position 0 and
+        compare str.channels 1 and
+        compare str.sampleRate 2 and
+        compare str.available (Known 4) and
+        compare str.finished? false and
+        compare (vec.list (mat.getRow 0 (str.read 3))) [ 1, 2, 3 ] and
+        compare str.position 3 and
+        compare str.available (Known 1) and
+        compare str.finished? false and
+        compare (vec.list (mat.getRow 0 (str.read 3))) [ 4 ] and
+        compare str.position 4 and
+        compare str.available (Known 0) and
+        compare str.finished? true
+),
+
+] is hash<string, () -> boolean>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/stream/type.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,18 @@
+
+module may.stream.type;
+
+load may.matrix.type;
+
+typedef stream =
+    {
+        position is number,
+        channels is number,
+        sampleRate is number,
+        available is Known number | Unknown () | Infinite (),
+        finished? is boolean,
+        read is number -> matrix,
+        close is () -> (),
+    };
+
+();
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/test/all.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,31 @@
+
+program may.test.all;
+
+{ runTests } = load may.test.test;
+
+tests = [
+"vector"     : load may.vector.test.test_vector,
+"blockfuncs" : load may.vector.test.test_blockfuncs,
+"complex"    : load may.complex.test.test_complex,
+"framer"     : load may.stream.test.test_framer,
+"channels"   : load may.stream.test.test_channels,
+"audiofile"  : load may.stream.test.test_audiofile,
+"synstream"  : load may.stream.test.test_syntheticstream,
+"filter"     : load may.stream.test.test_filter,
+"fft"        : load may.transform.test.test_fft,
+"vamppost"   : load may.vamp.test.test_vamppost,
+"vamp"       : load may.vamp.test.test_vamp,
+"matrix"     : load may.matrix.test.test_matrix,
+"plot"       : load may.plot.test.test_plot,
+"signal"     : load may.signal.test.test_signal,
+"window"     : load may.signal.test.test_window,
+];
+
+bad = sum (mapHash do name testHash: runTests name testHash done tests);
+
+if (bad > 0) then
+    println "\n** \(bad) test(s) failed!";
+else
+    ()
+fi
+
Binary file src/may/test/data/20samples.wav has changed
Binary file src/may/test/data/44100-2-16.wav has changed
Binary file src/may/test/data/8000-1-8.wav has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/test/test.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,56 @@
+module may.test.test;
+
+import yeti.lang: FailureException;
+
+var goodCompares = 0;
+
+compareUsing comparator obtained expected =
+    if comparator obtained expected then
+        goodCompares := goodCompares + 1;
+        true;
+    else
+        println "** expected: \(expected)\n   obtained: \(obtained)";
+        false;
+    fi;
+
+compare obtained expected = compareUsing (==) obtained expected;
+
+time msg f =
+   (start = System#currentTimeMillis();
+    result = f ();
+    end = System#currentTimeMillis();
+    println "\(msg): \(end-start)ms";
+    result);
+
+select f = fold do r x: if f x then x::r else r fi done [];
+
+failedTests testHash =
+    select (!= "")
+       (mapHash do name f:
+            try
+                if f () then "" else
+                    println "Test \(name) failed";
+                    name;
+                fi 
+            catch FailureException e:
+                println "Test \(name) threw exception: \(e)";
+                name;
+            yrt;
+        done testHash);
+        
+runTests group testHash =
+   (failed = failedTests testHash;
+    good = (length testHash - length failed);
+    bad = length failed;
+    println "\(group): \(good)/\(good+bad) tests passed";
+    if not empty? failed then
+        println "\(group): Failed tests [\(bad)]: \(strJoin ' ' failed)";
+    fi;
+    bad);
+
+{
+    compare, compareUsing,
+    time,
+    runTests, 
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/transform/fft.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,60 @@
+
+module may.transform.fft;
+
+import edu.emory.mathcs.jtransforms.fft: DoubleFFT_1D;
+
+vec = load may.vector;
+complex = load may.complex;
+
+load may.complex.type;
+
+packedToComplex len p is number -> ~double[] -> array<cplx> =
+   (n = len / 2;
+    array
+       (map do i:
+            re = if i == n then p[1] else p[i*2] fi;
+            im = if i == 0 or i == n then 0 else p[i*2+1] fi;
+            complex.complex re im;
+        done [0..n]));
+
+complexToPacked arr =
+   (n = length arr;
+    v = new double[n*2-2];
+    for [0..(n-1)*2-1] do i:
+        ix = int (i/2);
+        v[i] :=
+            if i == ix*2 then
+                complex.real arr[ix]
+            else 
+                complex.imaginary arr[ix] 
+            fi;
+    done;
+    v[1] := complex.real arr[n-1];
+    v);
+
+//!!! doc: n is supplied separately from the input vector to support partial evaluation
+//!!! doc: output has n/2+1 complex values
+//!!! doc: powers of two only? check with jtransforms
+realForward n = 
+   (d = new DoubleFFT_1D(n);
+    do bl:
+        v = vec.primitive bl;
+        d#realForward(v);
+        packedToComplex (vec.length bl) v;
+    done);
+
+//!!! doc: input requires n/2+1 complex values (or should test and throw?)
+//!!! doc: powers of two only? check with jtransforms
+realInverse n = 
+   (d = new DoubleFFT_1D(n);
+    do cplx:
+        v = complexToPacked (array cplx);
+        d#realInverse(v, true);
+        vec.vector v;
+    done);
+
+{
+realForward,
+realInverse,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/transform/test/test_fft.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,46 @@
+
+module may.transform.test.test_fft;
+
+{ realForward, realInverse } = load may.transform.fft;
+{ list, fromList } = load may.vector;
+{ complex } = load may.complex;
+
+{ compare } = load may.test.test;
+
+testFFT orig reals imags =
+   (out = realForward (length orig) (fromList orig);
+    back = realInverse (length orig) out;
+    compare out (array (map2 complex reals imags)) and compare (list back) orig);
+
+[
+
+"dc": \(
+    testFFT [1,1,1,1] [4,0,0] [0,0,0];
+),
+
+"sine": \(
+    testFFT [0,1,0,-1] [0,0,0] [0,-2,0];
+),
+
+"cosine": \(
+    testFFT [1,0,-1,0] [0,2,0] [0,0,0];
+),
+
+"sineCosine": \(
+    testFFT [0.5,1,-0.5,-1] [0,1,0] [0,-2,0];
+),
+
+"nyquist": \(
+    testFFT [1,-1,1,-1] [0,0,4] [0,0,0];
+),
+
+"dirac": \(
+    testFFT [1,0,0,0] [1,1,1] [0,0,0] and
+        testFFT [0,1,0,0] [1,0,-1] [0,-1,0] and
+        testFFT [0,0,1,0] [1,-1,1] [0,0,0] and
+        testFFT [0,0,0,1] [1,0,-1] [0,1,0];
+),
+
+] is hash<string, () -> boolean>;
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vamp.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,321 @@
+module may.vamp;
+
+import org.vamp_plugins:
+       Plugin, Plugin$InputDomain,
+       PluginLoader, PluginLoader$AdapterFlags, PluginLoader$LoadFailedException,
+       ParameterDescriptor, OutputDescriptor, OutputDescriptor$SampleType,
+       RealTime, Feature;
+
+import java.lang: UnsatisfiedLinkError;
+
+import java.util: Map, List;
+
+vec = load may.vector;
+fr = load may.stream.framer;
+af = load may.stream.audiofile;
+mat = load may.matrix;
+vamprdf = load may.vamp.vamprdf;
+vamppost = load may.vamp.vamppost;
+
+store = load yertle.store;
+
+realTime r is ~RealTime -> number = r#sec() + (r#nsec() / 1000000000);
+
+feature f is ~Feature -> 'a = {
+    timestamp = if f#hasTimestamp then Time (realTime f#timestamp) else Untimed () fi,
+    duration = if f#hasDuration then Time (realTime f#duration) else Untimed () fi,
+    values = vec.fromFloats f#values,
+    label = f#label,
+    };
+
+featureList fl is ~Object -> 'a =
+    if nullptr? fl then []
+    else
+        a = fl unsafely_as ~List;
+        result = array [];
+        itr = a#iterator();
+        itr#hasNext() loop (push result (feature (itr#next() unsafely_as ~Feature)));
+        list result
+    fi;
+
+featureSet fs is ~Map -> 'a =
+   (numberOf n is ~Object -> number = (n unsafely_as ~Integer)#intValue();
+    s = [:];
+    kk = list fs#keySet()#toArray();
+    for kk do k: s[numberOf k] := featureList fs#get(k) done;
+    s);
+
+stores = [:];
+
+getSingletonStoreFor loader =
+    synchronized stores do:
+        if loader in stores then
+            stores[loader]
+        else
+            s = store.newRdfStore ();
+            loader s;
+            stores[loader] := s;
+            s;
+        fi
+    done;
+
+getSystemStore () =
+    getSingletonStoreFor vamprdf.loadSystemVampRdf;
+
+getGlobalStore () = 
+    getSingletonStoreFor vamprdf.loadGlobalVampRdf;
+
+getPluginPath () =
+   (try
+        map string PluginLoader#getInstance()#getPluginPath();
+    catch UnsatisfiedLinkError e:
+        eprintln "Warning: Unable to obtain plugin path:\n\(e)";
+        [];
+    yrt);
+
+listPlugins () =
+   (try
+        map string PluginLoader#getInstance()#listPlugins();
+    catch UnsatisfiedLinkError e:
+        eprintln "Warning: Unable to obtain plugin list:\n\(e)";
+        [];
+    yrt);
+
+getKnownPluginKeys () =
+   (store = getGlobalStore ();
+    nodes = vamprdf.allPluginNodes store;
+    // ordering is random out of the store; might as well sort
+    sort (map do n: (vamprdf.pluginDataByNode store n).pluginKey done nodes));
+
+getDataForKnownPlugin key =
+    vamprdf.pluginDataByKey (getGlobalStore ()) key;
+   
+categoryOf key =
+    list PluginLoader#getInstance()#getPluginCategory(key);
+
+inputDomain d is ~Plugin$InputDomain -> 'a = 
+    if d == Plugin$InputDomain#FREQUENCY_DOMAIN then
+        FrequencyDomain ()
+    else
+        TimeDomain ()
+    fi;
+
+parameterDescriptor pd is ~ParameterDescriptor -> 'a = {
+    identifier = pd#identifier,
+    name = pd#name,
+    description = pd#description,
+    unit = pd#unit,
+    minValue = pd#minValue,
+    maxValue = pd#maxValue,
+    defaultValue = pd#defaultValue,
+    get quantize () = if pd#isQuantized then QuantizeStep pd#quantizeStep else Unquantized () fi,
+    valueNames = map string pd#valueNames
+    };
+
+sampleType t rate is ~OutputDescriptor$SampleType -> number -> 'a =
+    if t == OutputDescriptor$SampleType#OneSamplePerStep then
+        OneSamplePerStep ()
+    elif t == OutputDescriptor$SampleType#FixedSampleRate then
+        FixedSampleRate rate
+    else
+        VariableSampleRate rate
+    fi;
+
+structureOf rdfOutputData od is 'a -> ~OutputDescriptor -> 'b = 
+   (computes = case rdfOutputData of Some d: d.computes; None (): Unknown () esac;
+    s = getSystemStore ();
+    noteIRI = case s.expand "af:Note" of IRI iri: iri; _: "" esac;
+    if od#hasFixedBinCount and od#binCount == 0 then
+        Instants ();
+    elif od#hasDuration then
+        if computes != Unknown () then
+            if computes == Event noteIRI then Notes ();
+            else Regions ();
+            fi
+        elif od#hasFixedBinCount then
+            if od#binCount > 1 then Notes ();
+            elif od#unit == "Hz" or strIndexOf (strLower od#unit) "midi" 0 >= 0 then Notes ();
+            else Regions ();
+            fi
+        else
+            Unknown ();
+        fi
+    elif od#hasFixedBinCount and od#binCount == 1 then
+        case computes of
+        Event e:
+            if strEnds? e "Segment" 
+            then Segmentation ()
+            else Curve ()
+            fi;
+        _:
+            if od#sampleType == OutputDescriptor$SampleType#OneSamplePerStep
+            then Series ()
+            else Curve ()
+            fi;
+        esac;
+    elif od#hasFixedBinCount and
+         od#sampleType != OutputDescriptor$SampleType#VariableSampleRate then
+        Grid ();
+    else
+        Unknown ();
+    fi);
+
+outputDescriptor rdfOutputData od is 'a -> ~OutputDescriptor -> 'b = {
+    identifier = od#identifier,
+    name = od#name,
+    description = od#description,
+    get binCount () = if od#hasFixedBinCount then Fixed od#binCount else Variable () fi,
+    get valueExtents () = if od#hasKnownExtents then Known { min = od#minValue, max = od#maxValue } else Unknown () fi,
+    get valueQuantize () = if od#isQuantized then QuantizeStep od#quantizeStep else Unquantized () fi,
+    valueUnit = od#unit,
+    binNames = array (map string od#binNames),
+    sampleType = sampleType od#sampleType od#sampleRate,
+    hasDuration = od#hasDuration,
+    get computes () = case rdfOutputData of Some data: data.computes; None (): Unknown () esac,
+    get inferredStructure () = structureOf rdfOutputData od,
+    };
+
+plugin key p is string -> ~Plugin -> 'a =
+   (rdfData = vamprdf.pluginDataByKey (getSystemStore ()) key;
+    {
+    plugin = p,
+    key,
+    get apiVersion () = p#getVampApiVersion(),
+    get identifier () = p#getIdentifier(),
+    get name () = p#getName(),
+    get description () = p#getDescription(),
+    get maker () = p#getMaker(),
+    get copyright () = p#getCopyright(),
+    get version () = p#getPluginVersion(),
+    get category () = PluginLoader#getInstance()#getPluginCategory(key),
+    get hasRdfDescription () = (rdfData != None ()),
+    get infoURL () = case rdfData of Some data: data.infoURL; None (): "" esac,
+    get parameters () = array (map parameterDescriptor p#getParameterDescriptors()),
+    parameterValue identifier = p#getParameter(identifier),
+    setParameterValue identifier value = p#setParameter(identifier, value),
+    get programs () = array (map string p#getPrograms()),
+    get currentProgram () = p#getCurrentProgram(),
+    selectProgram pr = p#selectProgram(pr),
+    get inputDomain () = inputDomain p#getInputDomain(),
+    get preferredBlockSize () = p#getPreferredBlockSize(),
+    get preferredStepSize () = p#getPreferredStepSize(),
+    get minChannelCount () = p#getMinChannelCount(),
+    get maxChannelCount () = p#getMaxChannelCount(),
+    initialise { channels, hop, blockSize } = p#initialise(channels, hop, blockSize),
+    reset () = p#reset(),
+    get outputs () =
+        array case rdfData of
+        Some data: map2 outputDescriptor (map Some data.outputs) p#getOutputDescriptors();
+        None (): map (outputDescriptor (None ())) p#getOutputDescriptors();
+        esac,
+    process frame time is 'a -> ~RealTime -> 'b = 
+        featureSet p#process((map vec.floats (mat.asRows frame)) as ~float[][], 0, time),
+    getRemainingFeatures () = featureSet p#getRemainingFeatures(),
+    dispose () = p#dispose(),
+    });
+
+featuresFromSet outputNo f = if outputNo in f then f[outputNo] else [] fi;
+
+outputNumberByName p name =
+   (outputs = p.outputs;
+    case find ((== name) . (.identifier)) outputs of
+    first::rest: index first outputs;
+    _: -1;
+    esac);
+
+loadPlugin rate key =
+    try
+        OK (plugin key 
+            PluginLoader#getInstance()#loadPlugin(key, rate,
+                PluginLoader$AdapterFlags#ADAPT_INPUT_DOMAIN +
+                PluginLoader$AdapterFlags#ADAPT_CHANNEL_COUNT))
+    catch PluginLoader$LoadFailedException _:
+        Error "Failed to load Vamp plugin with key \(key)"
+    yrt;
+
+processed { p, sampleRate, hop } frames count =
+    case frames of
+    frame::rest:
+        p.process frame RealTime#frame2RealTime(count, sampleRate)
+        :.
+        \(processed { p, sampleRate, hop } rest (count + hop));
+    _: 
+       (rf = p.getRemainingFeatures ();
+        p.dispose ();
+        [rf]);
+    esac;
+
+converted { p, sampleRate, hop } outputNo fl =
+    map (featuresFromSet outputNo) fl;
+
+returnErrorFrom p stream text = (p.dispose (); stream.close (); failWith text);
+
+processWith key p outputNo stream =
+   (blockSize =
+        if p.preferredBlockSize == 0 then 2048
+        else p.preferredBlockSize fi;
+    stepSize =
+        if p.preferredStepSize == 0 then
+            if p.inputDomain == FrequencyDomain () then blockSize / 2
+            else blockSize fi;
+        else p.preferredStepSize fi;
+    channels = 1;
+    params = {
+        p, sampleRate = stream.sampleRate, channels = 1,
+        framesize = blockSize, blockSize, hop = stepSize
+    };
+    if p.initialise params then
+        {
+            key = key,
+            output = p.outputs[outputNo],
+            parameters = mapIntoHash id p.parameterValue
+               (map (.identifier) p.parameters),
+            config = {
+                channels, blockSize, stepSize,
+                sampleRate = stream.sampleRate
+            },
+            features = converted params outputNo
+               (processed params (fr.frames params stream) 0)
+        };
+        // If processing completed successfully, then p is
+        // disposed by processed and stream is closed by the
+        // framer
+    else
+        returnErrorFrom p stream "Failed to initialise plugin \(key) with channels = \(channels), blockSize = \(blockSize), stepSize = \(stepSize)";
+    fi);
+
+processStream key output stream =
+    case loadPlugin stream.sampleRate key of
+    OK p:
+        outputNo = outputNumberByName p output;
+        if outputNo >= 0 then
+            processWith key p outputNo stream
+        else
+            outputs = strJoin ", " (map (.identifier) p.outputs);
+            returnErrorFrom p stream "Plugin \(key) has no output named \(output) (outputs are: \(outputs))"
+        fi;
+    Error e: failWith e;
+    esac;
+
+processFile key output filename = 
+    processStream key output (af.open filename);
+
+processStreamStructured key output filename =
+    vamppost.postprocess (processStream key output filename);
+
+processFileStructured key output filename =
+    vamppost.postprocess (processFile key output filename);
+
+{
+get pluginPath = getPluginPath,
+get pluginKeys = listPlugins,
+loadPlugin,
+categoryOf,
+processStream,
+processFile,
+processStreamStructured,
+processFileStructured,
+getKnownPluginKeys,
+getDataForKnownPlugin,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vamp/test/test_vamp.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,150 @@
+module may.vamp.test.test_vamp;
+
+v = load may.vamp;
+synthetic = load may.stream.syntheticstream;
+filter = load may.stream.filter;
+mat = load may.matrix;
+vec = load may.vector;
+
+{ compare, compareUsing } = load may.test.test;
+
+testPluginKey = "vamp-test-plugin:vamp-test-plugin";
+
+rate = 44100;
+
+testStream () = filter.withDuration (rate * 20) (synthetic.whiteNoise rate);
+
+processTest output = 
+    v.processStreamStructured testPluginKey output (testStream ());
+
+float n is number -> number =
+    // round number to float precision (for comparison with floats)
+   (arr = new float[1];
+    arr[0] := n;
+    arr[0]);
+
+floats nn = map float nn;
+
+tests =
+[
+
+"version": \(
+    case v.loadPlugin rate testPluginKey of
+    Error e: (eprintln "version: Error: \(e)"; false);
+    OK plugin: compare plugin.version 1;
+    esac
+),
+
+"instants": \(
+    case processTest "instants" of
+    Instants ii:
+        compare (map (.time) ii) [ 0, 1.5, 3, 4.5, 6, 7.5, 9, 10.5, 12, 13.5 ];
+    other: failWith "wrong structure type: expected Instants tag, got \(other)";
+    esac
+),
+
+"curve-oss": \(
+    case processTest "curve-oss" of
+    Series s:
+        compare s.start 0 and
+        //!!! want to specify step and block size for processing
+            compare s.step (2048/rate) and
+            compare s.values (floats (map (* 0.05) [0..19]));
+    other: failWith "wrong structure type: expected Series tag, got \(other)";
+    esac
+),
+
+"curve-fsr": \(
+    case processTest "curve-fsr" of
+    Curve c:
+        compare (map (.time) c) (map (* 0.4) [0..9]) and
+           compare (map (.value) c) (floats (map (* 0.1) [0..9]));
+/*
+        compare s.start 0 and
+            compare s.step 0.4 and
+            compare s.values (floats (map (* 0.1) [0..9]));
+*/
+    other: failWith "wrong structure type: expected Series tag, got \(other)";
+    esac
+),
+    
+"curve-fsr-timed": \(
+    case processTest "curve-fsr-timed" of
+    Curve c:
+        compare (map (.time) c) [ 0, 0, 0, 0.4, 2, 2, 2, 2.4, 4, 4 ] and
+           compare (map (.value) c) (floats (map (* 0.1) [0..9]));
+    other: failWith "wrong structure type: expected Curve tag, got \(other)";
+    esac
+),
+
+"curve-vsr": \(
+    case processTest "curve-vsr" of
+    Curve c:
+        compare (map (.time) c) (map (* 0.75) [0..9]) and
+           compare (map (.value) c) (floats (map (* 0.1) [0..9]));
+    other: failWith "wrong structure type: expected Curve tag, got \(other)";
+    esac
+),
+
+"grid-oss": \(
+    case processTest "grid-oss" of //!!! test spacing?
+    Grid g:
+        compareUsing mat.equal g 
+           (mat.newMatrix (ColumnMajor ())
+               (map do x:
+                   (vec.fromList . floats) (map do y:
+                        (x + y + 2) / 30
+                        done [0..9])
+                    done [0..19]));
+    other: failWith "wrong structure type: expected Grid tag, got \(other)";
+    esac
+),
+
+"grid-fsr": \(
+    case processTest "grid-fsr" of //!!! test spacing?
+    Grid g:
+        compareUsing mat.equal g 
+           (mat.newMatrix (ColumnMajor ())
+               (map do x:
+                   (vec.fromList . floats) (map do y:
+                        (x + y + 2) / 20
+                        done [0..9])
+                    done [0..9]));
+    other: failWith "wrong structure type: expected Grid tag, got \(other)";
+    esac
+),
+
+"notes-regions": \(
+    case processTest "notes-regions" of
+    Regions rr:
+        compare (map (.time) rr) [0..9] and
+           compare (map (.duration) rr) (map do i: Time (if i % 2 == 0 then 1.75 else 0.5 fi) done [0..9]) and
+           compare (map (.values) rr) (map do i: array [float i] done (map (* 0.1) [0..9]));
+    other: failWith "wrong structure type: expected Curve tag, got \(other)";
+    esac
+),
+    
+];
+
+// Check we have the test plugin. Without it, all the tests must fail
+
+if contains? testPluginKey v.pluginKeys then 
+    tests
+else
+    eprintln
+"** Vamp test plugin not found!
+   Either the Vamp module is not working, or the test plugin is not installed.
+   Please ensure vamp-test-plugin.{so,dll,dylib} is in the path,
+   or set $VAMP_PATH to point to its location.
+
+   Current path: \(v.pluginPath)
+   Required plugin key: \"\(testPluginKey)\"
+   Plugin keys found: \(v.pluginKeys)
+
+   All of the Vamp plugin tests will fail until this is fixed.
+";
+    mapIntoHash id \\false (keys tests)
+fi is hash<string, () -> boolean>;
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vamp/test/test_vamppost.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,83 @@
+module may.vamp.test.test_vamppost;
+
+vp = load may.vamp.vamppost;
+
+{ compare } = load may.test.test;
+
+untimed n = { timestamp = Untimed (), values = [n] };
+timed t n = { timestamp = Time t, values = [n] };
+
+testdata =  {
+    output = { sampleType = OneSamplePerStep () },
+    config = { sampleRate = 1000, stepSize = 100 },
+    features = [
+        [],
+        [ untimed 1 ],
+        [ untimed 1, untimed 2 ],
+        [],
+        [ timed 1.1 1, untimed 2, timed 4 3 ]
+    ]
+};
+
+[
+
+"fillOneSamplePerStep": \(
+    // All features returned within a single process call (i.e. within
+    // a single sub-list of the feature list) should be given the same
+    // timestamp; the timestamp increments according to the config
+    // step size between sub-lists
+    filled = vp.fillTimestamps
+       (testdata with { output = { sampleType = OneSamplePerStep () } });
+    compare filled [
+        timed 0.1 1 ,
+        timed 0.2 1, timed 0.2 2 ,
+        timed 0.4 1, timed 0.4 2, timed 0.4 3
+    ];
+),
+
+"fillFixedSampleRate": \(
+    // "If the output feature's hasTimestamp field is true, the host
+    // should read and use the output feature's timestamp. The host
+    // may round the timestamp according to the sample rate given in
+    // the output descriptor's sampleRate field [...] If
+    // [hasTimestamp] is false, its time will be implicitly calculated
+    // by incrementing the time of the previous feature according to
+    // the [output descriptor's] sample rate". Note that the time is
+    // based on that of the previous feature, not that of the previous
+    // process cycle (as is the case with OneSamplePerStep features).
+    filled = vp.fillTimestamps
+       (testdata with { output = { sampleType = FixedSampleRate 5 } });
+    compare filled [
+        timed 0 1 ,
+        timed 0.2 1, timed 0.4 2 ,
+        timed 1.2 1, timed 1.4 2, timed 4.0 3
+    ];
+),
+
+"fillFixedSampleRate2": \(
+    // As above, but with non-integer output sample rate
+    filled = vp.fillTimestamps
+       (testdata with { output = { sampleType = FixedSampleRate 2.5 } });
+    compare filled [
+        timed 0 1 ,
+        timed 0.4 1, timed 0.8 2 ,
+        timed 1.2 1, timed 1.6 2, timed 4.0 3
+    ];
+),
+
+"fillVariableSampleRate": \(
+    // For VariableSampleRate outputs, the timestamps should always
+    // be left entirely alone by fillTimestamps -- it's an error for
+    // the plugin to return any features without valid timestamps,
+    // but it isn't the job of fillTimestamps to handle that error
+    filled = vp.fillTimestamps
+       (testdata with { output = { sampleType = VariableSampleRate 5 } });
+    compare filled [
+        untimed 1 ,
+        untimed 1, untimed 2 ,
+        timed 1.1 1, untimed 2, timed 4.0 3
+    ];
+),
+
+] is hash<string, () -> boolean>;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vamp/vamppost.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,166 @@
+module may.vamp.vamppost;
+
+mat = load may.matrix;
+vec = load may.vector;
+
+fillOneSamplePerStep config features =
+   (fill' n pending features =
+        // For OneSamplePerStep features, the time is incremented
+        // between process blocks (a process block is one element in
+        // the features list, which is then expanded into the complete
+        // pending list)
+        case pending of
+        feature::rest:
+            stamped = feature with
+            //!!! how do we ensure feature timestamps are rationals where possible?
+               { timestamp = Time ((n * config.stepSize) / config.sampleRate) };
+            stamped :. \(fill' n rest features);
+        _:
+            if empty? features then []
+            else fill' (n+1) (head features) (tail features);
+            fi;
+        esac;
+    fill' (-1) [] features);
+
+fillFixedSampleRate config rate features =
+   (fill' n pending features =
+        // For FixedSampleRate features without explicit timestamps,
+        // the time is incremented from the previous *feature* not the
+        // previous process block (i.e. between elements in the
+        // pending list)
+        case pending of
+        feature::rest:
+            n = case feature.timestamp of
+                Untimed (): n + 1;
+                Time t: int (t * rate + 0.5);
+                esac;
+            stamped = feature with { timestamp = Time (n / rate) };
+            stamped :. \(fill' n rest features);
+        _:
+            if empty? features then []
+            else fill' n (head features) (tail features);
+            fi;
+        esac;
+    fill' (-1) [] features);
+
+fillTimestamps { output, config, features } =
+    case output.sampleType of
+    OneSamplePerStep ():
+        fillOneSamplePerStep config features;
+    FixedSampleRate rate:
+        fillFixedSampleRate config rate features;
+    VariableSampleRate _:
+        concat features;
+    esac;
+
+structureGrid binCount features =
+//!!! need to return grid resolution as well -- or will caller read that from output elsewhere if they need it?
+    if empty? features then
+        mat.zeroMatrix { rows = binCount, columns = 0 };
+    else
+        mat.newMatrix (ColumnMajor ()) (map (.values) features);
+    fi;
+
+timeOf f =
+    case f.timestamp of
+    Time t: t;
+    _: failWith "Internal error: timestamps not filled";
+    esac;
+
+structureSeries features =
+    if empty? features then { start = 0, step = 0, values = [] }
+    else 
+        t0 = timeOf (head features);
+        t1 = if empty? (tail features) then t0
+             else timeOf (head (tail features)) fi;
+        {
+            start = t0,
+            step = t1 - t0,
+            values = map do f: vec.at f.values 0 done features;
+        }
+    fi;
+
+structureCurve features =
+    map do f: {
+        time = timeOf f,
+        value = vec.at f.values 0,
+        label = f.label
+    } done features;
+
+structureInstants features =
+    map do f: {
+        time = timeOf f,
+        label = f.label
+    } done features;
+
+structureSegmentation features =
+    map do f: {
+        time = timeOf f,
+        type = vec.at f.values 0,
+        label = f.label
+    } done features;
+
+structureNotes features =
+    map do f: {
+        time = timeOf f,
+        duration = f.duration,
+        pitch = vec.at f.values 0, //!!! no, might be empty
+        otherValues = array (tail (vec.list f.values)), //!!! no, might be empty
+        label = f.label
+    } done features;
+
+structureWithDuration features =
+    map do f: {
+        time = timeOf f,
+        duration = f.duration,
+        values = array (vec.list f.values),
+        label = f.label
+    } done features;
+
+structureWithoutDuration features =
+    map do f: {
+        time = timeOf f,
+        values = array (vec.list f.values),
+        label = f.label
+    } done features;
+
+structure data =
+   (type = data.output.inferredStructure;
+    features =
+        case type of
+        Grid (): concat data.features;
+        _: fillTimestamps data;
+        esac;
+    binCount = 
+        case data.output.binCount of
+        Fixed n: n;
+        _: 0;
+        esac;
+    case type of
+    Series ():              // No duration, one value, not variable rate
+        Series (structureSeries features);
+    Grid ():                // No duration, >1 value, not variable rate
+        Grid (structureGrid binCount features);
+    Curve ():               // No duration, one value, variable rate
+        Curve (structureCurve features);
+    Instants ():            // Zero-valued features
+        Instants (structureInstants features);
+    Notes ():               // Duration, at least one value (pitch or freq)
+        Notes (structureNotes features);
+    Regions ():             // Duration, zero or more values
+        Regions (structureWithDuration features);
+    Segmentation ():        // No duration, one value, segment type in RDF
+        Segmentation (structureSegmentation features);
+    Unknown ():             // Other
+        Unknown
+           ((if data.output.hasDuration then structureWithDuration
+             else structureWithoutDuration
+             fi) features);
+    esac);
+
+{
+fillTimestamps,
+postprocess = structure
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vamp/vamprdf.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,290 @@
+
+module may.vamp.vamprdf;
+
+read = load yertle.read;
+{ newRdfStore } = load yertle.store;
+
+import java.io: File;
+
+import org.vamp_plugins: PluginLoader;
+
+import java.lang: UnsatisfiedLinkError;
+
+getPluginPath () =
+   (try map string PluginLoader#getInstance()#getPluginPath();
+    catch UnsatisfiedLinkError e: [];
+    yrt);
+
+systemVampRdfFiles () =
+    concat
+       (map do p:
+            map ((p ^ File#separator ^) . (.name))
+               (filter do entry:
+                    entry.file? and
+                       (lc = strLower entry.name;
+                        (strEnds? lc ".ttl") or
+                        (strEnds? lc ".n3") or
+                        (strEnds? lc ".nt"))
+                    done (listDirectory false p))
+            done (getPluginPath ()));
+
+addVampPrefixes store =
+   (store.addPrefix "vamp" "http://purl.org/ontology/vamp/";
+    store.addPrefix "dc" "http://purl.org/dc/elements/1.1/";
+    store.addPrefix "foaf" "http://xmlns.com/foaf/0.1/";
+    store.addPrefix "owl" "http://www.w3.org/2002/07/owl#";
+    store.addPrefix "af" "http://purl.org/ontology/af/");
+
+loadSystemVampRdf store =
+   (addVampPrefixes store;
+    for (systemVampRdfFiles ()) do file:
+        case read.loadTurtleFile store ("file://" ^ file) file of
+        OK (): ();
+        Error e: eprintln
+            "WARNING: Failed to load Vamp plugin RDF file \"\(file)\": \(e)";
+        esac
+    done);
+
+getGlobalPluginIndex () =
+    list (strSplit "\n" (fetchURL [ Timeout 10 ] (Handle getContents)
+                         "http://www.vamp-plugins.org/rdf/plugins/index.txt"));
+
+//!!! need to cache these retrievals
+parseGlobalVampRdf () =
+   (parse urls =
+        case urls of
+        url::rest:
+           (doc = fetchURL [ Timeout 10 ] (Handle getContents) url;
+            parsed = read.parseTurtleString url doc;
+            { url, parsed } :. \(parse rest));
+         _: [];
+        esac;
+    parse (getGlobalPluginIndex ()));
+
+loadGlobalVampRdf store =
+    for (parseGlobalVampRdf ()) do { url, parsed }:
+        case read.loadParsedTriples store parsed of
+        OK (): ();
+        Error e: eprintln "WARNING: Failed to load Vamp RDF from URL \(url): \(e)";
+        esac;
+    done;
+
+subjects = map (.s);
+
+iriTypes =
+    map do t:
+        case t of
+        IRI iri: IRI iri;
+        Blank n: Blank n;
+        esac done;
+
+iriSubjects = iriTypes . subjects;
+
+allLibraryNodes store =
+    iriSubjects
+       (store.match {
+            s = Wildcard (),
+            p = Known (store.expand "a"),
+            o = Known (store.expand "vamp:PluginLibrary")
+            });
+
+allPluginNodes store =
+    iriSubjects
+       (store.match {
+            s = Wildcard (),
+            p = Known (store.expand "a"),
+            o = Known (store.expand "vamp:Plugin")
+            });
+
+pluginsWithId store id = 
+    iriTypes
+       (filter do pnode:
+        store.contains {
+            s = pnode,
+            p = store.expand "vamp:identifier",
+            o = Literal { value = id, type = "", language = "" }
+            }
+        done (allPluginNodes store));
+
+librariesWithId store id =
+    iriTypes
+       (filter do lnode:
+        store.contains {
+            s = lnode,
+            p = store.expand "vamp:identifier",
+            o = Literal { value = id, type = "", language = "" }
+            }
+        done (allLibraryNodes store));
+
+splitPluginKey key =
+   (bits = strSplit ":" key;
+    reversed = reverse bits;
+    soname = strJoin ":" (reverse (tail reversed));
+    identifier = head reversed;
+    { soname, identifier });
+
+pluginNodesByKey store key =
+   (case splitPluginKey key of { soname, identifier }:
+        candidatePlugins = pluginsWithId store identifier;
+        candidateLibraries = librariesWithId store soname;
+        filter do pnode:
+            any do lnode:
+                store.contains {
+                    s = lnode,
+                    p = store.expand "vamp:available_plugin",
+                    o = pnode
+                    }
+                done candidateLibraries
+            done candidatePlugins
+    esac);
+
+libraryNodeFor store pluginNode =
+    case store.match {
+        s = Wildcard (), p = Known (store.expand "vamp:available_plugin"), o = Known pluginNode
+        } of
+    { s = IRI iri }::others: Some (IRI iri);
+    { s = Blank n }::others: Some (Blank n);
+     _: None ();
+    esac;
+
+textProperty store subject name =
+    case store.match {
+        s = Known subject, p = Known (store.expand name), o = Wildcard ()
+        } of
+    { o = Literal { value = text } }::others: text;
+     _: "";
+    esac;
+
+iriProperty store subject name =
+    case store.match {
+        s = Known subject, p = Known (store.expand name), o = Wildcard ()
+        } of
+    { o = IRI iri }::others: IRI iri;
+     _: None ();
+    esac;
+
+nodeProperty store subject name =
+    case store.match {
+        s = Known subject, p = Known (store.expand name), o = Wildcard ()
+        } of
+    { o = IRI iri }::others: Some (IRI iri);
+    { o = Blank n }::others: Some (Blank n);
+     _: None ();
+    esac;
+
+inputDomainOf store pluginNode =
+   case store.match {
+        s = Known pluginNode, p = Known (store.expand "vamp:input_domain"), o = Wildcard ()
+        } of
+    { o = IRI iri }::others:
+        if IRI iri == store.expand "vamp:FrequencyDomain"
+        then FrequencyDomain ()
+        else TimeDomain ()
+        fi;
+     _: TimeDomain ();
+    esac;
+
+outputDescriptor store outputNode =
+   (tprop abbr = textProperty store outputNode abbr;
+    iprop abbr = iriProperty store outputNode abbr;
+    bprop abbr deflt =
+       (b = strLower (textProperty store outputNode abbr);
+        if b == "true" then true elif b == "false" then false else deflt fi);
+    nprop abbr =
+        try number (textProperty store outputNode abbr); catch Exception _: 0 yrt;
+    {
+        identifier = tprop "vamp:identifier",
+        name = tprop "dc:title",
+        description = tprop "dc:description",
+        rdfType = case iprop "a" of IRI iri: iri; _: "" esac,
+        valueUnit = tprop "vamp:unit",
+        binCount = 
+            if bprop "vamp:fixed_bin_count" false
+            then Known (nprop "vamp:bin_count")
+            else Unknown ()
+            fi,
+        computes =
+            case iprop "vamp:computes_event_type" of
+            IRI iri: Event iri;
+             _: case iprop "vamp:computes_signal_type" of
+                IRI iri: Signal iri;
+                 _: case iprop "vamp:computes_feature_type" of
+                    IRI iri: Feature iri;
+                     _: Unknown ();
+                    esac
+                esac
+            esac,
+        //!!! and some other properties
+    });
+
+pluginDataByNode store pluginNode =
+   (tprop abbr = textProperty store pluginNode abbr;
+    nprop abbr =
+        try number (textProperty store pluginNode abbr); catch Exception _: 0 yrt;
+    soname =
+        case libraryNodeFor store pluginNode of
+        None (): "";
+        Some n: textProperty store n "vamp:identifier";
+        esac;
+    {
+        pluginKey = soname ^ ":" ^ tprop "vamp:identifier",
+        soname,
+        apiVersion = nprop "vamp:vamp_API_version",
+        identifier = tprop "vamp:identifier",
+        name = tprop "dc:title",
+        description = tprop "dc:description",
+        maker =
+           (tmaker = tprop "foaf:maker";
+            if tmaker == "" then
+                case nodeProperty store pluginNode "foaf:maker" of
+                Some n: textProperty store n "foaf:name";
+                None (): "";
+                esac
+            else
+                tmaker
+            fi),
+        copyright = tprop "dc:rights",
+        version = tprop "owl:versionInfo",
+        category = tprop "vamp:category",
+        inputDomain = inputDomainOf store pluginNode,
+        infoURL =
+           (case iriProperty store pluginNode "foaf:page" of
+            IRI iri: iri;
+            None ():
+                case libraryNodeFor store pluginNode of
+                None (): "";
+                Some n:
+                    case iriProperty store n "foaf:page" of
+                    IRI iri: iri;
+                    None (): "";
+                    esac;
+                esac;
+            esac),
+        outputs = 
+           (matches = store.match { s = Known pluginNode,
+                                    p = Known (store.expand "vamp:output"), 
+                                    o = Wildcard () };
+            array (map do t:
+                       case t.o of
+                       IRI iri: outputDescriptor store (IRI iri);
+                       Blank n: outputDescriptor store (Blank n);
+                       esac
+                       done matches)),
+    });
+
+pluginDataByKey store key =
+    case pluginNodesByKey store key of
+    node::others: Some (pluginDataByNode store node);
+    _: None ()
+    esac;
+
+{
+loadSystemVampRdf,
+loadGlobalVampRdf,
+allPluginNodes,
+allLibraryNodes,
+pluginNodesByKey,
+pluginDataByNode,
+pluginDataByKey,
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vector.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,189 @@
+
+/**
+ * Vectors. A May vector is a typesafe, immutable wrapper around a Java
+ * primitive array of doubles.
+ *
+ * Although not as convenient and flexible as a Yeti array<number> or
+ * list<number>, a vector can be faster and more compact when dealing
+ * with dense data of suitable range and precision such as sampled
+ * sequences.
+ */
+
+module may.vector;
+
+load may.vector.type;
+
+import java.util: Arrays;
+
+/// Return a vector of n zeros.
+zeros n =
+    new double[n];
+
+/// Return a vector of length n, containing all m.
+consts m n =
+   (a = zeros n;
+    for [0..n-1] do i:
+        a[i] := m;
+    done;
+    a);
+
+/// Return a vector of length n, containing all ones.
+ones n = consts 1.0 n;
+
+/// Return a vector of the values in the given list.
+fromList l is list?<number> -> ~double[] =
+    l as ~double[];
+
+/// Return the given vector as a list.
+list' a is ~double[] -> list<number> =
+    list a;
+
+/// Return the given vector as a Yeti array.
+array' a is ~double[] -> array<number> =
+    array a;
+
+/// Return the length of the given vector.
+length' =
+    length . list';
+
+/// Return true if the given vector is empty (has length 0).
+empty?' =
+    empty? . list';
+
+/// Return element n in the given vector v. (The function name and
+/// argument order are chosen for symmetry with the similar standard
+/// library array function.)
+at' v n is ~double[] -> number -> number =
+    v[n];
+
+/// Return the given vector as a Java primitive float array.
+floats a is ~double[] -> ~float[] =
+   (len = length' a;
+    f = new float[len];
+    for [0..len-1] do i:
+        f[i] := a[i];
+    done;
+    f);
+
+/// Return a vector of the values in the given Java primitive float array.
+fromFloats ff is ~float[] -> ~double[] =
+   (len = length (list ff);
+    a = new double[len];
+    for [0..len-1] do i:
+        a[i] := ff[i];
+    done;
+    a);
+
+/// Return true if the given vectors are equal, using the standard ==
+/// comparator on their elements.
+equal v1 v2 =
+    list' v1 == list' v2;
+
+/// Return true if the given vectors are equal, when applying the
+/// given numerical comparator to each element.
+equalUnder comparator v1 v2 =
+    length' v1 == length' v2 and
+        all id (map2 comparator (list' v1) (list' v2));
+
+/// Return another copy of the given vector.
+copyOf v is ~double[] -> ~double[] =
+    Arrays#copyOf(v, list' v |> length);
+
+/// Return a new vector containing a subset of the elements of the
+/// given vector, from index start (inclusive) to index end
+/// (exclusive). (The function name and argument order are chosen for
+/// symmetry with the standard library slice and strSlice functions.)
+slice v start end is ~double[] -> number -> number -> ~double[] =
+    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;
+
+/// Return a new vector of length n, containing the contents of the
+/// given vector v. If v is longer than n, the contents will be
+/// truncated; if shorter, they will be padded with zeros.
+resizedTo n v is number -> ~double[] -> ~double[] =
+    Arrays#copyOf(v, n);
+
+/// Return a new vector that is the reverse of the given vector.  Name
+/// chosen (in preference to passive "reversed") for symmetry with the
+/// standard library list reverse function.
+reverse v is ~double[] -> ~double[] =
+   (len = length (list v);
+    a = new double[len];
+    for [0..len-1] do i:
+        a[len-i-1] := v[i];
+    done;
+    a);
+
+/// Return a single new vector that contains the contents of all the
+/// given vectors, in order. (Unlike the standard module list concat
+/// function, this one cannot be lazy.)
+concat vv is list?<~double[]> -> ~double[] =
+   (len = sum (map length' vv);
+    vout = zeros len;
+    var base = 0;
+    for vv do v: 
+        vlen = length' v;
+        for [0..vlen-1] do i: vout[base + i] := v[i] done;
+        base := base + vlen;
+    done;
+    vout);
+
+/// Return a single new vector that contains the contents of the given
+/// vector, repeated n times. The vector will therefore have length n
+/// times the length of v.
+repeated v n is ~double[] -> number -> ~double[] =
+    concat (map \(v) [1..n]);
+
+{
+    zeros,
+    consts,
+    ones,
+    vector v = v,
+    primitive = copyOf,
+    floats,
+    fromFloats,
+    fromList,
+    list = list',
+    array = array',
+    length = length',
+    empty? = empty?',
+    at = at',
+    equal,
+    equalUnder,
+    slice,
+    resizedTo,
+    reverse,
+    repeated,
+    concat,
+} as {
+    zeros is number -> vector,
+    consts is number -> number -> vector,
+    ones is number -> vector,
+    vector is ~double[] -> vector,
+    primitive is vector -> ~double[],
+    floats is vector -> ~float[],
+    fromFloats is ~float[] -> vector,
+    fromList is list?<number> -> vector,
+    list is vector -> list<number>,
+    array is vector -> array<number>,
+    length is vector -> number,
+    empty? is vector -> boolean,
+    at is vector -> number -> number,
+    equal is vector -> vector -> boolean,
+    equalUnder is (number -> number -> boolean) -> vector -> vector -> boolean,
+    slice is vector -> number -> number -> vector,
+    resizedTo is number -> vector -> vector,
+    reverse is vector -> vector,
+    repeated is vector -> number -> vector,
+    concat is list?<vector> -> vector,
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vector/blockfuncs.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,161 @@
+
+module may.vector.blockfuncs;
+
+vec = load may.vector;
+
+load may.vector.type;
+
+//!!! "internal" vector function to retrieve data for read-only
+// purposes without copying
+raw =
+   (raw' v is ~double[] -> ~double[] = v;
+    raw' as vector -> ~double[]);
+
+sum' v =
+   (dat = raw v;
+    tot = new double[1];
+    for [0..length dat - 1] do i:
+        tot[0] := tot[0] + dat[i]
+    done;
+    tot[0]);
+
+max' v = 
+   (dat = raw v;
+    var mx = 0;
+    for [0..length dat - 1] do i:
+        if i == 0 or dat[i] > mx then
+            mx := dat[i];
+        fi
+    done;
+    mx);
+
+maxindex v =
+   (dat = raw v;
+    var mx = 0;
+    var mi = -1;
+    for [0..length dat - 1] do i:
+        if i == 0 or dat[i] > mx then
+            mx := dat[i];
+            mi := i;
+        fi
+    done;
+    mi);
+
+min' v = 
+   (dat = raw v;
+    var mn = 0;
+    for [0..length dat - 1] do i:
+        if i == 0 or dat[i] < mn then
+            mn := dat[i];
+        fi
+    done;
+    mn);
+
+minindex v =
+   (dat = raw v;
+    var mn = 0;
+    var mi = -1;
+    for [0..length dat - 1] do i:
+        if i == 0 or dat[i] < mn then
+            mn := dat[i];
+            mi := i;
+        fi
+    done;
+    mi);
+
+mean v =
+    case vec.length v of
+        0: 0;
+        len: sum' v / len
+    esac;
+
+add bb =
+   (len = head (sort (map vec.length bb));
+    vv = map raw bb;
+    out = new double[len];
+    for [0..len-1] do i:
+        for vv do v: 
+            out[i] := out[i] + v[i];
+        done;
+    done;
+    vec.vector out);
+
+subtract b1 b2 =
+   (v1 = raw b1;
+    v2 = raw b2;
+    len = if length v1 < length v2 then length v1 else length v2 fi;
+    out = new double[len];
+    for [0..len-1] do i:
+        out[i] := v1[i] - v2[i]
+    done;
+    vec.vector out);
+
+multiply b1 b2 =
+   (v1 = raw b1;
+    v2 = raw b2;
+    len = if length v1 < length v2 then length v1 else length v2 fi;
+    out = new double[len];
+    for [0..len-1] do i:
+        out[i] := v1[i] * v2[i]
+    done;
+    vec.vector out);
+
+scaled n v =
+    vec.fromList (map (* n) (vec.list v));
+
+divideBy n v =  // Not just "scaled (1/n)" -- this way we get exact rationals
+    vec.fromList (map (/ n) (vec.list v));
+
+sqr v =
+    multiply v v;
+
+rms =
+    sqrt . mean . sqr;
+
+abs' =
+    vec.fromList . (map abs) . vec.list;
+
+sqrt' =
+    vec.fromList . (map sqrt) . vec.list;
+
+unityNormalised v = 
+   (m = max' (abs' v);
+    if m != 0 then
+        divideBy m v;
+    else
+        v;
+    fi);
+
+fftshift v =
+   (len = vec.length v;
+    half = int(len/2 + 0.5); // round up for odd-length sequences
+    vec.concat [vec.slice v half len, vec.slice v 0 half]);
+
+ifftshift v =
+   (len = vec.length v;
+    half = int(len/2); // round down for odd-length sequences
+    vec.concat [vec.slice v half len, vec.slice v 0 half]);
+
+{
+sum is vector -> number = sum',
+mean is vector -> number,
+add is list?<vector> -> vector,
+subtract is vector -> vector -> vector,
+multiply is vector -> vector -> vector, 
+divideBy is number -> vector -> vector, 
+scaled is number -> vector -> vector,
+abs is vector -> vector = abs',
+sqr is vector -> vector,
+sqrt is vector -> vector = sqrt',
+rms is vector -> number,
+max is vector -> number = max',
+min is vector -> number = min',
+maxindex is vector -> number,
+minindex is vector -> number,
+unityNormalised is vector -> vector,
+fftshift is vector -> vector,
+ifftshift is vector -> vector,
+}
+
+
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vector/test/test_blockfuncs.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,125 @@
+
+module may.vector.test.test_blockfuncs;
+
+stdSqrt = sqrt;
+
+{ zeros, consts, ones, fromList, list } = load may.vector;
+{ sum, max, min, maxindex, minindex, mean, add, subtract, multiply, divideBy, scaled, abs, sqr, sqrt, rms, unityNormalised, fftshift, ifftshift } = load may.vector.blockfuncs;
+{ compare } = load may.test.test;
+
+[
+
+"sum": \(
+    compare ((sum . zeros) 0) 0 and
+        compare ((sum . zeros) 5) 0 and
+        compare ((sum . ones) 5) 5 and
+        compare ((sum . fromList) [1,-2,3,0]) 2
+),
+
+"max": \(
+    compare ((max . fromList) [1,-2,3,0]) 3 and
+        compare ((max . fromList) [-1,-2,-3]) (-1) and
+        compare ((max . fromList) [4,1]) 4 and
+        compare ((max . fromList) []) 0
+),
+
+"min": \(
+    compare ((min . fromList) [1,-2,3,0]) (-2) and
+        compare ((min . fromList) [-1,-2,-3]) (-3) and
+        compare ((min . fromList) [4,1]) 1 and
+        compare ((min . fromList) []) 0
+),
+
+"maxindex": \(
+    compare ((maxindex . fromList) [1,-2,3,0]) 2 and
+        compare ((maxindex . fromList) [-1,-2,-3]) 0 and
+        compare ((maxindex . fromList) [4,1]) 0 and
+        compare ((maxindex . fromList) []) (-1)
+),
+
+"minindex": \(
+    compare ((minindex . fromList) [1,-2,3,0]) 1 and
+        compare ((minindex . fromList) [-1,-2,-3]) 2 and
+        compare ((minindex . fromList) [4,1]) 1 and
+        compare ((minindex . fromList) []) (-1)
+),
+
+"mean": \(
+    compare ((mean . zeros) 0) 0 and
+        compare ((mean . zeros) 5) 0 and
+        compare ((mean . ones) 5) 1 and
+        compare ((mean . fromList) [1,-2,3,0]) 0.5
+),
+
+"add": \(
+    compare (list (add [zeros 0, ones 5])) [] and
+        compare (list (add [consts 3 4, fromList [1,2,3] ])) [4,5,6] and
+        compare (list (add [consts (-3) 4, fromList [1,2,3] ])) [-2,-1,0] 
+),
+
+"subtract": \(
+    compare (list (subtract (zeros 0) (ones 5))) [] and
+        compare (list (subtract (consts 3 4) (fromList [1,2,3]))) [2,1,0] and
+        compare (list (subtract (consts (-3) 4) (fromList [1,2,3]))) [-4,-5,-6]
+),
+
+"multiply": \(
+    compare (list (multiply (zeros 0) (ones 5))) [] and
+        compare (list (multiply (consts (-3) 4) (fromList [1,2,3]))) [-3,-6,-9]
+),
+
+"divideBy": \(
+    compare (list (divideBy 5 (ones 0))) [] and
+        compare (list (divideBy 5 (fromList [1,2,-3]))) [0.2,0.4,-0.6]
+),
+
+"scaled": \(
+    compare (list (scaled 5 (ones 0))) [] and
+        compare (list (scaled 5 (fromList [1,2,-3]))) [5,10,-15]
+),
+
+"abs": \(
+    compare (list (abs (ones 0))) [] and
+        compare (list (abs (fromList [1,2,-3]))) [1,2,3]
+),
+
+"sqr": \(
+    compare ((list . sqr . zeros) 0) [] and
+        compare ((list . sqr . ones) 5) [1,1,1,1,1] and
+        compare ((list . sqr . fromList) [0.5,-2,3,0]) [0.25,4,9,0]
+),
+
+"sqrt": \(
+    compare ((list . sqrt . zeros) 0) [] and
+        compare ((list . sqrt . ones) 5) [1,1,1,1,1] and
+        compare ((list . sqrt . fromList) [0.25,4,9,0]) [0.5,2,3,0]
+),
+
+"rms": \(
+    compare ((rms . zeros) 0) 0 and
+        compare ((rms . ones) 5) 1 and
+        compare ((rms . fromList) [-1,2,2]) (stdSqrt 3)
+),
+
+"unityNormalised": \(
+    compare ((list . unityNormalised . fromList) [1,-2,3,0]) [1/3,-2/3,1,0] and
+        compare ((list . unityNormalised . fromList) [-1,-2,-3]) [-1/3,-2/3,-1] and
+        compare ((list . unityNormalised . fromList) [4,1]) [1,1/4] and
+        compare ((list . unityNormalised . fromList) []) []
+),
+
+"fftshift": \(
+    compare ((list . fftshift . zeros) 0) [] and 
+        compare ((list . fftshift . fromList) [1,2,3,4]) [3,4,1,2] and
+        compare ((list . fftshift . fromList) [1,2,3,4,5]) [4,5,1,2,3]
+),
+
+"ifftshift": \(
+    compare ((list . ifftshift . zeros) 0) [] and 
+        compare ((list . ifftshift . fromList) [3,4,1,2]) [1,2,3,4] and
+        compare ((list . ifftshift . fromList) [4,5,1,2,3]) [1,2,3,4,5]
+),
+
+] is hash<string, () -> boolean>;
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vector/test/test_vector.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,130 @@
+
+module may.vector.test.test_vector;
+
+vec = load may.vector;
+
+{ compare } = load may.test.test;
+
+[
+
+"zeros-empty": \(
+    v = vec.zeros 0;
+    compare (vec.length v) 0;
+),
+
+"zeros": \(
+    v = vec.zeros 3;
+    a = vec.array v;
+    compare (vec.length v) 3 and
+        compare a[0] 0 and
+        compare a[1] 0 and
+        compare a[2] 0;
+),
+
+"consts-empty": \(
+    v = vec.consts 4 0;
+    compare (vec.length v) 0;
+),
+
+"consts": \(
+    v = vec.consts 4 3;
+    a = vec.array v;
+    compare (vec.length v) 3 and
+        compare a[0] 4 and
+        compare a[1] 4 and
+        compare a[2] 4;
+),
+
+"ones-empty": \(
+    v = vec.ones 0;
+    compare (vec.length v) 0;
+),
+
+"ones": \(
+    v = vec.ones 3;
+    a = vec.array v;
+    compare (vec.length v) 3 and
+        compare a[0] 1 and
+        compare a[1] 1 and
+        compare a[2] 1;
+),
+
+"from-list-empty": \(
+    v = vec.fromList [];
+    compare (vec.length v) 0;
+),
+
+"from-list": \(
+    v = vec.fromList [1,2,3,4];
+    a = vec.array v;
+    compare (vec.length v) 4 and
+        compare a[0] 1 and
+        compare a[1] 2 and
+        compare a[2] 3 and
+        compare a[3] 4;
+),
+
+"equal-empty": \(
+    vec.equal (vec.fromList []) (vec.fromList [])
+),
+
+"equal": \(
+    v = vec.fromList [1,1,1,1];
+    w = vec.ones 4;
+    w' = vec.zeros 4;
+    w'' = vec.ones 3;
+    vec.equal v w and not vec.equal v w' and not vec.equal v w'';
+),
+
+"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]) 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": \(
+    vec.equal (vec.resizedTo 4 (vec.fromList [])) (vec.zeros 4) and
+        vec.equal (vec.resizedTo 2 (vec.fromList [1,2])) (vec.fromList [1,2]) and
+        vec.equal (vec.resizedTo 3 (vec.fromList [1,2])) (vec.fromList [1,2,0]) and
+        vec.equal (vec.resizedTo 2 (vec.fromList [1,2,3])) (vec.fromList [1,2]);
+),
+
+"repeated": \(
+    vec.equal (vec.repeated (vec.fromList []) 2) (vec.fromList []) and
+        vec.equal (vec.repeated (vec.fromList [1,2,3]) 0) (vec.fromList []) and
+        vec.equal (vec.repeated (vec.fromList [1,2,3]) 1) (vec.fromList [1,2,3]) and
+        vec.equal (vec.repeated (vec.fromList [1,2,3]) 2) (vec.fromList [1,2,3,1,2,3])
+),
+
+"reverse": \(
+    vec.equal (vec.reverse (vec.fromList [])) (vec.fromList []) and
+        vec.equal (vec.reverse (vec.fromList [1,2,3])) (vec.fromList [3,2,1]) and
+        vec.equal (vec.reverse (vec.fromList [1,2])) (vec.fromList [2,1])
+),
+
+"concat2": \(
+    v = vec.fromList [1,2,3];
+    w = vec.fromList [4,5,6];
+    x = vec.concat [v, w];
+    x' = vec.fromList [1,2,3,4,5,6];
+    vec.equal x x' and
+        vec.equal x' (vec.concat [x', vec.fromList []]) and
+        vec.equal x' (vec.concat [vec.fromList [], x'])
+),
+
+"concatn": \(
+    v = vec.fromList [1,2,3];
+    w = vec.fromList [4,5,6];
+    vec.equal (vec.concat []) (vec.zeros 0) and
+        vec.equal (vec.concat [v]) v and
+        vec.equal (vec.concat [v,w,v]) (vec.fromList [1,2,3,4,5,6,1,2,3])
+),
+
+] is hash<string, () -> boolean>;
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/may/vector/type.yeti	Mon Sep 23 12:35:35 2013 +0100
@@ -0,0 +1,7 @@
+
+module may.vector.type;
+
+typedef opaque vector = ~double[];
+
+();
+