Mercurial > hg > may
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
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 -
--- 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 +
--- /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>; + + +