cannam@5: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@5: cannam@5: /* cannam@5: Vamp Plugin Tester cannam@5: Chris Cannam, cannam@all-day-breakfast.com cannam@5: Centre for Digital Music, Queen Mary, University of London. Chris@42: Copyright 2009-2014 QMUL. cannam@5: cannam@5: This program loads a Vamp plugin and tests its susceptibility to a cannam@5: number of common pitfalls, including handling of extremes of input cannam@5: data. If you can think of any additional useful tests that are cannam@5: easily added, please send them to me. cannam@5: cannam@5: Permission is hereby granted, free of charge, to any person cannam@5: obtaining a copy of this software and associated documentation cannam@5: files (the "Software"), to deal in the Software without cannam@5: restriction, including without limitation the rights to use, copy, cannam@5: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@5: of the Software, and to permit persons to whom the Software is cannam@5: furnished to do so, subject to the following conditions: cannam@5: cannam@5: The above copyright notice and this permission notice shall be cannam@5: included in all copies or substantial portions of the Software. cannam@5: cannam@5: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@5: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@5: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@5: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@5: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@5: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@5: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@5: cannam@5: Except as contained in this notice, the names of the Centre for cannam@5: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@5: shall not be used in advertising or otherwise to promote the sale, cannam@5: use or other dealings in this Software without prior written cannam@5: authorization. cannam@5: */ cannam@5: cannam@5: #include "TestDefaults.h" cannam@5: cannam@5: #include cannam@5: using namespace Vamp; cannam@5: cannam@5: #include cannam@5: using namespace std; cannam@5: cannam@5: #include cannam@6: #include cannam@5: cannam@5: Tester::TestRegistrar Chris@39: TestDefaultProgram::m_registrar("E1", "Inconsistent default program"); cannam@5: cannam@5: Tester::TestRegistrar Chris@39: TestDefaultParameters::m_registrar("E2", "Inconsistent default parameters"); cannam@5: Chris@34: Tester::TestRegistrar Chris@39: TestParametersOnReset::m_registrar("E3", "Parameter retention through reset"); Chris@34: cannam@5: static const size_t _step = 1000; cannam@5: cannam@5: Test::Results cannam@8: TestDefaultProgram::test(string key, Options options) cannam@5: { cannam@5: Plugin::FeatureSet f[2]; cannam@5: int rate = 44100; cannam@5: Results r; cannam@5: float **data = 0; cannam@5: size_t channels = 0; cannam@5: size_t count = 100; cannam@5: cannam@5: for (int run = 0; run < 2; ++run) { cannam@5: auto_ptr p(load(key, rate)); cannam@5: if (p->getPrograms().empty()) return r; cannam@5: if (run == 1) { cannam@5: p->selectProgram(p->getCurrentProgram()); cannam@5: } cannam@5: if (!initAdapted(p.get(), channels, _step, _step, r)) return r; cannam@5: if (!data) data = createTestAudio(channels, _step, count); cannam@5: for (size_t i = 0; i < count; ++i) { Chris@67: float **ptr = new float *[channels]; cannam@5: size_t idx = i * _step; cannam@5: for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; cannam@5: RealTime timestamp = RealTime::frame2RealTime(idx, rate); cannam@5: Plugin::FeatureSet fs = p->process(ptr, timestamp); Chris@67: delete[] ptr; cannam@5: appendFeatures(f[run], fs); cannam@5: } cannam@5: Plugin::FeatureSet fs = p->getRemainingFeatures(); cannam@5: appendFeatures(f[run], fs); cannam@5: } cannam@5: if (data) destroyTestAudio(data, channels); cannam@5: cannam@5: if (!(f[0] == f[1])) { cannam@8: string message = "Explicitly setting current program to its supposed current value changes the results"; cannam@8: Result res; cannam@8: if (options & NonDeterministic) res = note(message); cannam@8: else res = error(message); Chris@52: if (options & Verbose) dumpDiff(res, f[0], f[1]); cannam@7: r.push_back(res); cannam@5: } else { cannam@5: r.push_back(success()); cannam@5: } cannam@5: cannam@5: return r; cannam@5: } cannam@5: cannam@5: Test::Results cannam@8: TestDefaultParameters::test(string key, Options options) cannam@5: { cannam@5: Plugin::FeatureSet f[2]; cannam@5: int rate = 44100; cannam@5: Results r; cannam@5: float **data = 0; cannam@5: size_t channels = 0; cannam@5: size_t count = 100; cannam@5: cannam@5: for (int run = 0; run < 2; ++run) { cannam@5: auto_ptr p(load(key, rate)); cannam@5: if (p->getParameterDescriptors().empty()) return r; cannam@5: if (run == 1) { cannam@5: Plugin::ParameterList pl = p->getParameterDescriptors(); cannam@5: for (int i = 0; i < (int)pl.size(); ++i) { cannam@5: if (p->getParameter(pl[i].identifier) != pl[i].defaultValue) { cannam@23: if (options & Verbose) { cannam@23: cout << "Parameter: " << pl[i].identifier << endl; cannam@23: cout << "Expected: " << pl[i].defaultValue << endl; cannam@23: cout << "Actual: " << p->getParameter(pl[i].identifier) << endl; cannam@23: } cannam@5: r.push_back(error("Not all parameters have their default values when queried directly after construction")); cannam@5: } cannam@5: p->setParameter(pl[i].identifier, pl[i].defaultValue); cannam@5: } cannam@5: } cannam@5: if (!initAdapted(p.get(), channels, _step, _step, r)) return r; cannam@5: if (!data) data = createTestAudio(channels, _step, count); cannam@5: for (size_t i = 0; i < count; ++i) { Chris@67: float **ptr = new float *[channels]; cannam@5: size_t idx = i * _step; cannam@5: for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; cannam@5: RealTime timestamp = RealTime::frame2RealTime(idx, rate); cannam@5: Plugin::FeatureSet fs = p->process(ptr, timestamp); Chris@67: delete[] ptr; cannam@5: appendFeatures(f[run], fs); cannam@5: } cannam@5: Plugin::FeatureSet fs = p->getRemainingFeatures(); cannam@5: appendFeatures(f[run], fs); cannam@5: } cannam@5: if (data) destroyTestAudio(data, channels); cannam@5: cannam@5: if (!(f[0] == f[1])) { cannam@8: string message = "Explicitly setting parameters to their supposed default values changes the results"; cannam@8: Result res; cannam@8: if (options & NonDeterministic) res = note(message); cannam@8: else res = error(message); Chris@52: if (options & Verbose) dumpDiff(res, f[0], f[1]); cannam@7: r.push_back(res); cannam@5: } else { cannam@5: r.push_back(success()); cannam@5: } cannam@5: cannam@5: return r; cannam@5: } Chris@34: Chris@34: Test::Results Chris@34: TestParametersOnReset::test(string key, Options options) Chris@34: { Chris@34: Plugin::FeatureSet f[2]; Chris@34: int rate = 44100; Chris@34: Results r; Chris@34: float **data = 0; Chris@34: size_t channels = 0; Chris@34: size_t count = 100; Chris@34: Chris@34: for (int run = 0; run < 2; ++run) { Chris@34: auto_ptr p(load(key, rate)); Chris@34: if (p->getParameterDescriptors().empty()) return r; Chris@34: Chris@34: // Set all parameters to non-default values Chris@36: Chris@34: Plugin::ParameterList pl = p->getParameterDescriptors(); Chris@36: Chris@34: for (int i = 0; i < (int)pl.size(); ++i) { Chris@36: Chris@36: // Half-way between default and max value, seems a Chris@36: // reasonable guess for something to set it to. We want to Chris@36: // avoid the real extremes because they can sometimes be Chris@36: // very slow, and we want to avoid setting everything to Chris@36: // the same values (e.g. min) because plugins will Chris@36: // sometimes legitimately reject that. Chris@36: Chris@36: // Remember to take into account quantization Chris@36: Chris@36: float value = (pl[i].defaultValue + pl[i].maxValue) / 2; Chris@36: Chris@36: if (pl[i].isQuantized) { Chris@36: value = round(value / pl[i].quantizeStep) * pl[i].quantizeStep; Chris@34: } Chris@36: Chris@36: if (value > pl[i].maxValue) { Chris@36: value = pl[i].maxValue; Chris@36: } Chris@36: if (value < pl[i].minValue) { Chris@36: value = pl[i].minValue; Chris@36: } Chris@36: if (value == pl[i].defaultValue) { Chris@36: if (pl[i].defaultValue == pl[i].minValue) { Chris@36: value = pl[i].maxValue; Chris@36: } else { Chris@36: value = pl[i].minValue; Chris@36: } Chris@36: } Chris@36: Chris@36: p->setParameter(pl[i].identifier, value); Chris@34: } Chris@34: Chris@52: if (!initAdapted(p.get(), channels, _step, _step, r)) { Chris@52: Chris@52: // OK, plugin didn't like that. Let's try a different tack Chris@52: // -- set everything to min except those parameters whose Chris@52: // default is min, and set those to half way instead Chris@52: Chris@52: for (int i = 0; i < (int)pl.size(); ++i) { Chris@52: float value = pl[i].minValue; Chris@52: if (value == pl[i].defaultValue) { Chris@52: value = (pl[i].maxValue + pl[i].minValue) / 2; Chris@52: value = ceil(value / pl[i].quantizeStep) * pl[i].quantizeStep; Chris@52: if (value > pl[i].maxValue) { Chris@52: value = pl[i].maxValue; Chris@52: } Chris@52: } Chris@52: p->setParameter(pl[i].identifier, value); Chris@52: } Chris@52: Chris@52: r = Results(); Chris@52: if (!initAdapted(p.get(), channels, _step, _step, r)) { Chris@52: // Still didn't work, give up Chris@52: return r; Chris@52: } Chris@52: } Chris@34: Chris@34: // First run: construct, set params, init, process Chris@34: // Second run: construct, set params, init, reset, process Chris@34: // We expect these to produce the same results Chris@34: if (run == 1) p->reset(); Chris@34: Chris@34: if (!data) data = createTestAudio(channels, _step, count); Chris@34: for (size_t i = 0; i < count; ++i) { Chris@67: float **ptr = new float *[channels]; Chris@34: size_t idx = i * _step; Chris@34: for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; Chris@34: RealTime timestamp = RealTime::frame2RealTime(idx, rate); Chris@34: Plugin::FeatureSet fs = p->process(ptr, timestamp); Chris@67: delete[] ptr; Chris@34: appendFeatures(f[run], fs); Chris@34: } Chris@34: Plugin::FeatureSet fs = p->getRemainingFeatures(); Chris@34: appendFeatures(f[run], fs); Chris@34: } Chris@34: if (data) destroyTestAudio(data, channels); Chris@34: Chris@34: if (!(f[0] == f[1])) { Chris@34: string message = "Call to reset after setting parameters, but before processing, changes the results (parameter values not retained through reset?)"; Chris@34: Result res; Chris@34: if (options & NonDeterministic) res = note(message); Chris@34: else res = error(message); Chris@52: if (options & Verbose) dumpDiff(res, f[0], f[1]); Chris@34: r.push_back(res); Chris@34: } else { Chris@34: r.push_back(success()); Chris@34: } Chris@34: Chris@34: return r; Chris@34: }