changeset 85:718e0e6a9ca3

Move Vamp structured feature post-processing out into its own module, add tests for it
author Chris Cannam
date Mon, 18 Mar 2013 09:48:34 +0000
parents c1c99f26d1fc
children 4e93d8ea1e70
files test/all.yeti test/test_vamppost.yeti vamp.yeti vamppost.yeti
diffstat 4 files changed, 146 insertions(+), 77 deletions(-) [+]
line wrap: on
line diff
--- a/test/all.yeti	Sun Mar 17 22:29:19 2013 +0000
+++ b/test/all.yeti	Mon Mar 18 09:48:34 2013 +0000
@@ -9,6 +9,7 @@
 "blockfuncs" : load test.test_blockfuncs,
 "complex"    : load test.test_complex,
 "fft"        : load test.test_fft,
+"vamppost"   : load test.test_vamppost,
 ];
 
 bad = sum (mapHash do name testHash: runTests name testHash done tests);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test_vamppost.yeti	Mon Mar 18 09:48:34 2013 +0000
@@ -0,0 +1,79 @@
+module test.test_vamppost;
+
+vp = load vamppost;
+
+{ compare } = load test.test;
+
+untimed n = { timestamp = Untimed (), values = [n] };
+timed t n = { timestamp = Time t, values = [n] };
+
+[
+
+"fillOneSamplePerStep": \(
+    filled = vp.fillTimestamps {
+        output = { sampleType = OneSamplePerStep () },
+        config = { sampleRate = 1000, stepSize = 100 },
+        features = [
+            [],
+            [ untimed 1 ],
+            [ untimed 1, untimed 2 ],
+            [],
+            [ timed 1 1, untimed 2, timed 4 3 ]
+        ]
+    };
+    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 [...] 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"
+    filled = vp.fillTimestamps {
+        output = { sampleType = FixedSampleRate 500 },
+        config = { sampleRate = 1000, stepSize = 100 },
+        features = [
+            [],
+            [ untimed 1 ],
+            [ untimed 1, untimed 2 ],
+            [],
+            [ timed 1 1, untimed 2, timed 4 3 ]
+        ]
+    };
+    compare filled [
+        timed 0 1 ,
+        timed 0.2 1, timed 0.4 2 ,
+        timed 1.0 1, timed 1.2 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 {
+        output = { sampleType = VariableSampleRate 500 },
+        config = { sampleRate = 1000, stepSize = 100 },
+        features = [
+            [],
+            [ untimed 1 ],
+            [ untimed 1, untimed 2 ],
+            [],
+            [ timed 1 1, untimed 2, timed 4 3 ]
+        ]
+    };
+    compare filled [
+        untimed 1 ,
+        untimed 1, untimed 2 ,
+        timed 1.0 1, untimed 2, timed 4.0 3
+    ];
+),
+
+] is hash<string, () -> boolean>;
+
--- a/vamp.yeti	Sun Mar 17 22:29:19 2013 +0000
+++ b/vamp.yeti	Mon Mar 18 09:48:34 2013 +0000
@@ -250,8 +250,6 @@
         returnErrorFrom p stream "Failed to initialise plugin \(key) with channels = \(channels), blockSize = \(blockSize), stepSize = \(stepSize)";
     fi);
 
-outputsOf p = strJoin ", " (map (.identifier) p.outputs);
-
 process key output stream =
     case loadPlugin stream.sampleRate key of
     OK p:
@@ -259,73 +257,8 @@
         if outputNo >= 0 then
             processWith key p outputNo stream
         else
-            returnErrorFrom p stream "Plugin \(key) has no output named \(output) (outputs are \(outputsOf p))"
-        fi;
-    Error e: Error e;
-    esac;
-
-flattenTimestamps { output, config, features } =
-   (stepTime n =
-        case output.sampleType of
-        OneSamplePerStep ():
-           (n * config.stepSize) / config.sampleRate;
-        //!!! Not right: this will advance the FixedSampleRate counter for each input step (not each feature actually found) -- unit tests would help here
-        FixedSampleRate rate:
-           (n * config.stepSize) / rate;
-        VariableSampleRate rate:
-           0; //!!!
-        esac;
-    flattenTimestamps' n pending features =
-        case pending of
-        feature::rest:
-            case feature.timestamp of
-            Time t:
-                feature;
-            Untimed ():
-               (feature with { timestamp = Time (stepTime n) });
-            esac :. \(flattenTimestamps' n rest features);
-         _:
-            case features of
-            here::rest:
-                flattenTimestamps' (n+1) here rest;
-             _:
-                [];
-            esac;
-        esac;
-    flattenTimestamps' (-1) [] features);
-
-structure results =
-    case results of
-    OK data:
-       (flattened = flattenTimestamps data;
-        case data.output.inferredStructure of
-        Curve ():               // No duration, one value
-            Curve flattened;
-        Grid ():                // No duration, >1 value, not variable rate
-            Grid flattened;
-        Instants ():            // Zero-valued features
-            Instants flattened;
-        Notes ():               // Duration, at least one value (pitch or freq)
-            Notes flattened;
-        Regions ():             // Duration, zero or more values
-            Regions flattened;
-        Segmentation ():        // No duration, one value, segment type in RDF
-            Segmentation flattened;
-        Unknown ():             // Other
-            Unknown flattened;
-        esac);
-    Error e:
-        Error e;
-    esac;
-
-processStructured key output stream =
-    case loadPlugin stream.sampleRate key of
-    OK p:
-        outputNo = outputNumberByName p output;
-        if outputNo >= 0 then
-            structure (processWith key p outputNo stream);
-        else
-            returnErrorFrom p stream "Plugin \(key) has no output named \(output) (outputs are \(outputsOf p))"
+            outputs = strJoin ", " (map (.identifier) p.outputs);
+            returnErrorFrom p stream "Plugin \(key) has no output named \(output) (outputs are \(outputs))"
         fi;
     Error e: Error e;
     esac;
@@ -336,12 +269,6 @@
     Error e: Error e;
     esac;
 
-processFileStructured key output filename = 
-    case af.open filename of
-    Stream s: processStructured key output s;
-    Error e: Error e;
-    esac;
-
 {
 get pluginPath = getPluginPath,
 get pluginKeys = listPlugins,
@@ -349,7 +276,5 @@
 categoryOf,
 process,
 processFile,
-processStructured,
-processFileStructured,
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamppost.yeti	Mon Mar 18 09:48:34 2013 +0000
@@ -0,0 +1,64 @@
+module vamppost;
+
+fillOneSamplePerStep config features =
+   (stepTime n = (n * config.stepSize) / config.sampleRate;
+    fillTimestamps' n pending features =
+        case pending of
+        feature::rest:
+           (feature with { timestamp = Time (stepTime n) })
+                :. \(fillTimestamps' n rest features);
+         _:
+            case features of
+            here::rest:
+                fillTimestamps' (n+1) here rest;
+             _:
+                [];
+            esac;
+        esac;
+    fillTimestamps' (-1) [] features);
+
+fillFixedSampleRate config rate features = concat features;
+
+fillTimestamps { output, config, features } =
+    case output.sampleType of
+    OneSamplePerStep ():
+        fillOneSamplePerStep config features;
+    FixedSampleRate rate:
+        fillFixedSampleRate config rate features;
+    VariableSampleRate _:
+        concat features;
+    esac;
+
+structure type features =
+    case type of
+    Curve ():               // No duration, one value
+        Curve features;
+    Grid ():                // No duration, >1 value, not variable rate
+        Grid features;
+    Instants ():            // Zero-valued features
+        Instants features;
+    Notes ():               // Duration, at least one value (pitch or freq)
+        Notes features;
+    Regions ():             // Duration, zero or more values
+        Regions features;
+    Segmentation ():        // No duration, one value, segment type in RDF
+        Segmentation features;
+    Unknown ():             // Other
+        Unknown features;
+    esac;
+
+postprocess data =
+    case data of
+    OK data:
+        filled = fillTimestamps data;
+        structure data.output.inferredStructure filled;
+    Error e:
+        Error e;
+    esac;
+
+{
+fillTimestamps,
+postprocess
+}
+
+