view yetilab/vamp/vamp.yeti @ 218:a7f4eb1cdd72 matrix_opaque_immutable

More block -> vector
author Chris Cannam
date Sat, 11 May 2013 12:04:05 +0100
parents 26111c11d8e4
children 937f908cae52
line wrap: on
line source
module yetilab.vamp.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 yetilab.block.vector;
fr = load yetilab.stream.framer;
af = load yetilab.stream.audiofile;
mat = load yetilab.matrix.matrix;
vamprdf = load yetilab.vamp.vamprdf;
vamppost = load yetilab.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;

//!!! bring block typedef into scope -- this shouldn't be necessary,
//but see comment in processed below
load yetilab.block.blocktype;

processed { p, sampleRate, hop } frames count 
    // I don't know why this type declaration is necessary. Without
    // it, yeti records the return type as 'a and can't do anything
    // useful with it (giving IncompatibleClassChangeError when
    // e.g. accessing the values array)
    is 'a -> 'b -> 'c ->
        list<hash<number,
                  list<{ timestamp is Time number | Untimed (), 
                         duration  is Time number | Untimed (),
                         label is string,
                         values is block
                       }>>> =
    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,
}