Chris@117: #!/usr/bin/env python Chris@117: Chris@117: # Python Vamp Host Chris@117: # Copyright (c) 2008-2015 Queen Mary, University of London Chris@117: # Chris@117: # Permission is hereby granted, free of charge, to any person Chris@117: # obtaining a copy of this software and associated documentation Chris@117: # files (the "Software"), to deal in the Software without Chris@117: # restriction, including without limitation the rights to use, copy, Chris@117: # modify, merge, publish, distribute, sublicense, and/or sell copies Chris@117: # of the Software, and to permit persons to whom the Software is Chris@117: # furnished to do so, subject to the following conditions: Chris@117: # Chris@117: # The above copyright notice and this permission notice shall be Chris@117: # included in all copies or substantial portions of the Software. Chris@117: # Chris@117: # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@117: # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@117: # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Chris@117: # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY Chris@117: # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@117: # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@117: # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@117: # Chris@117: # Except as contained in this notice, the names of the Centre for Chris@117: # Digital Music and Queen Mary, University of London shall not be Chris@117: # used in advertising or otherwise to promote the sale, use or other Chris@117: # dealings in this Software without prior written authorization. Chris@117: Chris@56: '''A high-level interface to the vampyhost extension module, for quickly and easily running Vamp audio analysis plugins on audio files and buffers.''' Chris@56: Chris@56: import vampyhost Chris@112: import vamp.load Chris@112: import vamp.process Chris@112: import vamp.frames Chris@89: Chris@93: import numpy as np Chris@93: Chris@94: def get_feature_step_time(sample_rate, step_size, output_desc): Chris@111: if output_desc["sampleType"] == vampyhost.ONE_SAMPLE_PER_STEP: Chris@94: return vampyhost.frame_to_realtime(step_size, sample_rate) Chris@111: elif output_desc["sampleType"] == vampyhost.FIXED_SAMPLE_RATE: Chris@111: return vampyhost.RealTime('seconds', 1.0 / output_desc["sampleRate"]) Chris@94: else: Chris@94: return 1 Chris@72: Chris@85: def timestamp_features(sample_rate, step_size, output_desc, features): Chris@85: n = -1 Chris@111: if output_desc["sampleType"] == vampyhost.ONE_SAMPLE_PER_STEP: Chris@85: for f in features: Chris@85: n = n + 1 Chris@85: t = vampyhost.frame_to_realtime(n * step_size, sample_rate) Chris@85: f["timestamp"] = t Chris@85: yield f Chris@111: elif output_desc["sampleType"] == vampyhost.FIXED_SAMPLE_RATE: Chris@111: output_rate = output_desc["sampleRate"] Chris@85: for f in features: Chris@85: if "has_timestamp" in f: Chris@85: n = int(f["timestamp"].to_float() * output_rate + 0.5) Chris@85: else: Chris@85: n = n + 1 Chris@85: f["timestamp"] = vampyhost.RealTime('seconds', float(n) / output_rate) Chris@85: yield f Chris@85: else: Chris@85: for f in features: Chris@85: yield f Chris@72: Chris@93: def fill_timestamps(results, sample_rate, step_size, output_desc): Chris@93: Chris@93: output = output_desc["identifier"] Chris@93: Chris@93: selected = [ r[output] for r in results ] Chris@93: Chris@93: stamped = timestamp_features(sample_rate, step_size, output_desc, selected) Chris@93: Chris@93: for s in stamped: Chris@93: yield s Chris@93: Chris@93: def deduce_shape(output_desc): Chris@111: if output_desc["hasDuration"]: Chris@117: return "list" Chris@111: if output_desc["sampleType"] == vampyhost.VARIABLE_SAMPLE_RATE: Chris@117: return "list" Chris@111: if not output_desc["hasFixedBinCount"]: Chris@117: return "list" Chris@111: if output_desc["binCount"] == 0: Chris@117: return "list" Chris@111: if output_desc["binCount"] == 1: Chris@94: return "vector" Chris@94: return "matrix" Chris@93: Chris@88: Chris@117: def reshape(results, sample_rate, step_size, output_desc, shape): Chris@71: Chris@95: output = output_desc["identifier"] Chris@94: out_step = get_feature_step_time(sample_rate, step_size, output_desc) Chris@89: Chris@93: if shape == "vector": Chris@94: rv = ( out_step, Chris@96: np.array([r[output]["values"][0] for r in results], np.float32) ) Chris@93: elif shape == "matrix": Chris@97: #!!! todo: check that each feature has the right number of bins? Chris@97: outseq = [r[output]["values"] for r in results] Chris@97: rv = ( out_step, np.array(outseq, np.float32) ) Chris@93: else: Chris@93: rv = list(fill_timestamps(results, sample_rate, step_size, output_desc)) Chris@89: Chris@95: return rv Chris@95: Chris@95: Chris@96: def collect(data, sample_rate, key, output = "", parameters = {}): Chris@99: """Process audio data with a Vamp plugin, and make the results from a Chris@99: single plugin output available as a single structure. Chris@95: Chris@99: The provided data should be a 1- or 2-dimensional list or NumPy Chris@99: array of floats. If it is 2-dimensional, the first dimension is Chris@99: taken to be the channel count. Chris@99: Chris@99: The returned results will be those calculated by the plugin with Chris@99: the given key and returned through its output with the given Chris@99: output identifier. If the requested output is the empty string, Chris@99: the first output provided by the plugin will be used. Chris@99: Chris@99: If the parameters dict is non-empty, the plugin will be configured Chris@99: by setting its parameters according to the (string) key and Chris@99: (float) value data found in the dict. Chris@99: Chris@117: The results are returned in a dictionary which will always contain Chris@117: exactly one element, whose key is one of the strings "vector", Chris@117: "matrix", or "list". Which one is used depends on the structure of Chris@117: features set out in the output descriptor for the requested plugin Chris@117: output: Chris@99: Chris@99: * If the plugin output emits single-valued features at a fixed Chris@117: sample-rate, then the "vector" element will be used. It will Chris@117: contain a tuple of step time (the time in seconds between Chris@117: consecutive feature values) and a one-dimensional NumPy array of Chris@117: feature values. An example of such a feature might be a loudness Chris@117: curve against time. Chris@99: Chris@99: * If the plugin output emits multiple-valued features, with an Chris@99: equal number of bins per feature, at a fixed sample-rate, then Chris@117: the "matrix" element will be used. It will contain a tuple of Chris@117: step time (the time in seconds between consecutive feature Chris@117: values) and a two-dimensional NumPy array of feature values. An Chris@117: example of such a feature might be a spectrogram. Chris@99: Chris@117: * Otherwise, the "list" element will be used, and will contain a Chris@117: list of features, where each feature is represented as a Chris@117: dictionary containing a timestamp (always) and a duration Chris@117: (optionally), a label (string), and a 1-dimensional array of Chris@117: float values. Chris@99: Chris@99: If you would prefer to obtain features as they are calculated Chris@99: (where the plugin supports this) and with the format in which the Chris@99: plugin returns them, via an asynchronous generator function, use Chris@99: vamp.process() instead. Chris@117: Chris@99: """ Chris@100: Chris@112: plugin, step_size, block_size = vamp.load.load_and_configure(data, sample_rate, key, parameters) Chris@95: Chris@95: if output == "": Chris@95: output_desc = plugin.get_output(0) Chris@95: output = output_desc["identifier"] Chris@95: else: Chris@95: output_desc = plugin.get_output(output) Chris@95: Chris@112: ff = vamp.frames.frames_from_array(data, step_size, block_size) Chris@95: Chris@112: results = vamp.process.process_with_initialised_plugin(ff, sample_rate, step_size, plugin, [output]) Chris@95: Chris@117: shape = deduce_shape(output_desc) Chris@117: rv = reshape(results, sample_rate, step_size, output_desc, shape) Chris@95: Chris@89: plugin.unload() Chris@117: return { shape : rv } Chris@93: