cannam@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@0: cannam@0: /* cannam@0: Vamp Plugin Tester cannam@0: Chris Cannam, cannam@all-day-breakfast.com cannam@0: Centre for Digital Music, Queen Mary, University of London. cannam@0: Copyright 2009 QMUL. cannam@0: cannam@0: This program loads a Vamp plugin and tests its susceptibility to a cannam@0: number of common pitfalls, including handling of extremes of input cannam@0: data. If you can think of any additional useful tests that are cannam@0: easily added, please send them to me. cannam@0: cannam@0: Permission is hereby granted, free of charge, to any person cannam@0: obtaining a copy of this software and associated documentation cannam@0: files (the "Software"), to deal in the Software without cannam@0: restriction, including without limitation the rights to use, copy, cannam@0: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@0: of the Software, and to permit persons to whom the Software is cannam@0: furnished to do so, subject to the following conditions: cannam@0: cannam@0: The above copyright notice and this permission notice shall be cannam@0: included in all copies or substantial portions of the Software. cannam@0: cannam@0: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@0: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@0: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@0: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@0: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@0: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@0: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@0: cannam@0: Except as contained in this notice, the names of the Centre for cannam@0: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@0: shall not be used in advertising or otherwise to promote the sale, cannam@0: use or other dealings in this Software without prior written cannam@0: authorization. cannam@0: */ cannam@0: cannam@0: #include "TestStaticData.h" cannam@0: cannam@0: #include cannam@0: using namespace Vamp; cannam@0: cannam@0: #include cannam@0: using namespace std; cannam@0: cannam@0: #include cannam@0: cannam@0: Tester::TestRegistrar cannam@4: TestIdentifiers::m_registrar("A1 Invalid identifiers"); cannam@0: cannam@0: Tester::TestRegistrar cannam@4: TestEmptyFields::m_registrar("A2 Empty metadata fields"); cannam@0: cannam@0: Tester::TestRegistrar cannam@4: TestValueRanges::m_registrar("A3 Inappropriate value extents"); cannam@0: cannam@0: Test::Results cannam@14: TestIdentifiers::test(string key, Options) cannam@0: { cannam@0: auto_ptr p(load(key)); cannam@0: cannam@0: Results r; cannam@17: r.push_back(testIdentifier(p->getIdentifier(), "Plugin identifier")); cannam@0: cannam@0: Plugin::ParameterList params = p->getParameterDescriptors(); cannam@0: for (int i = 0; i < (int)params.size(); ++i) { cannam@17: r.push_back(testIdentifier(params[i].identifier, "Parameter identifier")); cannam@0: } cannam@0: cannam@0: Plugin::OutputList outputs = p->getOutputDescriptors(); cannam@0: for (int i = 0; i < (int)outputs.size(); ++i) { cannam@17: r.push_back(testIdentifier(outputs[i].identifier, "Output identifier")); cannam@0: } cannam@0: cannam@0: return r; cannam@0: } cannam@0: cannam@0: Test::Result cannam@0: TestIdentifiers::testIdentifier(string identifier, string desc) cannam@0: { cannam@0: for (int i = 0; i < (int)identifier.length(); ++i) { cannam@0: char c = identifier[i]; cannam@0: if (c >= 'a' && c <= 'z') continue; cannam@0: if (c >= 'A' && c <= 'Z') continue; cannam@0: if (c >= '0' && c <= '9') continue; cannam@0: if (c == '_' || c == '-') continue; cannam@0: return error cannam@0: (desc + " \"" + identifier + cannam@0: "\" contains invalid character(s); permitted are: [a-zA-Z0-9_-]"); cannam@0: } cannam@0: return success(); cannam@0: } cannam@0: cannam@0: Test::Results cannam@14: TestEmptyFields::test(string key, Options) cannam@0: { cannam@0: auto_ptr p(load(key)); cannam@0: cannam@0: Results r; cannam@0: cannam@17: r.push_back(testMandatory(p->getName(), "Plugin name")); cannam@17: r.push_back(testRecommended(p->getDescription(), "Plugin description")); cannam@17: r.push_back(testRecommended(p->getMaker(), "Plugin maker")); cannam@17: r.push_back(testRecommended(p->getCopyright(), "Plugin copyright")); cannam@0: cannam@0: Plugin::ParameterList params = p->getParameterDescriptors(); cannam@0: for (int i = 0; i < (int)params.size(); ++i) { cannam@0: r.push_back(testMandatory cannam@0: (params[i].name, cannam@17: "Plugin parameter \"" + params[i].identifier + "\" name")); cannam@0: r.push_back(testRecommended cannam@0: (params[i].description, cannam@17: "Plugin parameter \"" + params[i].identifier + "\" description")); cannam@0: } cannam@0: cannam@0: Plugin::OutputList outputs = p->getOutputDescriptors(); cannam@0: for (int i = 0; i < (int)outputs.size(); ++i) { cannam@0: r.push_back(testMandatory cannam@0: (outputs[i].name, cannam@17: "Plugin output \"" + outputs[i].identifier + "\" name")); cannam@0: r.push_back(testRecommended cannam@0: (outputs[i].description, cannam@17: "Plugin output \"" + outputs[i].identifier + "\" description")); cannam@0: } cannam@0: cannam@0: return r; cannam@0: } cannam@0: cannam@0: Test::Result cannam@0: TestEmptyFields::testMandatory(string text, string desc) cannam@0: { cannam@0: if (text == "") { cannam@0: return error(desc + " is empty"); cannam@0: } cannam@0: return success(); cannam@0: } cannam@0: cannam@0: Test::Result cannam@0: TestEmptyFields::testRecommended(string text, string desc) cannam@0: { cannam@0: if (text == "") { cannam@0: return warning(desc + " is empty"); cannam@0: } cannam@0: return success(); cannam@0: } cannam@0: cannam@0: Test::Results cannam@14: TestValueRanges::test(string key, Options) cannam@0: { cannam@0: auto_ptr p(load(key)); cannam@0: cannam@0: Results r; cannam@0: cannam@0: Plugin::ParameterList params = p->getParameterDescriptors(); cannam@0: for (int i = 0; i < (int)params.size(); ++i) { cannam@0: Plugin::ParameterDescriptor &pd(params[i]); cannam@17: string pfx("Plugin parameter \"" + pd.identifier + "\""); cannam@0: float min = pd.minValue; cannam@0: float max = pd.maxValue; cannam@0: float deft = pd.defaultValue; cannam@0: if (max <= min) { cannam@0: r.push_back(error(pfx + " maxValue <= minValue")); cannam@0: } cannam@0: if (deft < min || deft > max) { cannam@0: r.push_back(error(pfx + " defaultValue out of range")); cannam@0: } cannam@0: if (pd.isQuantized) { cannam@0: if (pd.quantizeStep == 0.f) { cannam@0: r.push_back(error(pfx + " is quantized, but quantize step is zero")); cannam@0: } else { cannam@0: cannam@0: float epsilon = 0.00001f; cannam@0: int qty = int((max - min) / pd.quantizeStep + 0.5); cannam@0: float target = min + pd.quantizeStep * qty; cannam@0: if (fabsf(max - target) > epsilon) { cannam@0: r.push_back(warning(pfx + " value range is not a multiple of quantize step")); cannam@0: } cannam@0: cannam@0: if (!pd.valueNames.empty()) { cannam@0: if ((int)pd.valueNames.size() < qty+1) { cannam@0: r.push_back(warning(pfx + " has fewer value names than quantize steps")); cannam@0: } else if ((int)pd.valueNames.size() > qty+1) { cannam@0: r.push_back(warning(pfx + " has more value names than quantize steps")); cannam@0: } cannam@0: } cannam@0: cannam@0: qty = int((deft - min) / pd.quantizeStep + 0.5); cannam@0: target = min + pd.quantizeStep * qty; cannam@0: if (fabsf(deft - target) > epsilon) { cannam@0: r.push_back(warning(pfx + " default value is not a multiple of quantize step beyond minimum")); cannam@0: } cannam@0: } cannam@0: } cannam@0: } cannam@0: cannam@0: return r; cannam@0: } cannam@0: cannam@0: