cannam@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
cannam@0
|
2
|
cannam@0
|
3 /*
|
cannam@0
|
4 Vamp Plugin Tester
|
cannam@0
|
5 Chris Cannam, cannam@all-day-breakfast.com
|
cannam@0
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@42
|
7 Copyright 2009-2014 QMUL.
|
cannam@0
|
8
|
cannam@0
|
9 This program loads a Vamp plugin and tests its susceptibility to a
|
cannam@0
|
10 number of common pitfalls, including handling of extremes of input
|
cannam@0
|
11 data. If you can think of any additional useful tests that are
|
cannam@0
|
12 easily added, please send them to me.
|
cannam@0
|
13
|
cannam@0
|
14 Permission is hereby granted, free of charge, to any person
|
cannam@0
|
15 obtaining a copy of this software and associated documentation
|
cannam@0
|
16 files (the "Software"), to deal in the Software without
|
cannam@0
|
17 restriction, including without limitation the rights to use, copy,
|
cannam@0
|
18 modify, merge, publish, distribute, sublicense, and/or sell copies
|
cannam@0
|
19 of the Software, and to permit persons to whom the Software is
|
cannam@0
|
20 furnished to do so, subject to the following conditions:
|
cannam@0
|
21
|
cannam@0
|
22 The above copyright notice and this permission notice shall be
|
cannam@0
|
23 included in all copies or substantial portions of the Software.
|
cannam@0
|
24
|
cannam@0
|
25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
cannam@0
|
26 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
cannam@0
|
27 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
cannam@0
|
28 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
cannam@0
|
29 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
cannam@0
|
30 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
cannam@0
|
31 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
cannam@0
|
32
|
cannam@0
|
33 Except as contained in this notice, the names of the Centre for
|
cannam@0
|
34 Digital Music; Queen Mary, University of London; and Chris Cannam
|
cannam@0
|
35 shall not be used in advertising or otherwise to promote the sale,
|
cannam@0
|
36 use or other dealings in this Software without prior written
|
cannam@0
|
37 authorization.
|
cannam@0
|
38 */
|
cannam@0
|
39
|
cannam@0
|
40 #include "TestStaticData.h"
|
cannam@0
|
41
|
cannam@0
|
42 #include <vamp-hostsdk/Plugin.h>
|
Chris@35
|
43 #include <vamp-hostsdk/PluginLoader.h>
|
cannam@0
|
44 using namespace Vamp;
|
Chris@35
|
45 using namespace Vamp::HostExt;
|
cannam@0
|
46
|
cannam@0
|
47 #include <memory>
|
cannam@0
|
48 using namespace std;
|
cannam@0
|
49
|
cannam@0
|
50 #include <cmath>
|
cannam@0
|
51
|
cannam@0
|
52 Tester::TestRegistrar<TestIdentifiers>
|
Chris@39
|
53 TestIdentifiers::m_registrar("A1", "Invalid identifiers");
|
cannam@0
|
54
|
cannam@0
|
55 Tester::TestRegistrar<TestEmptyFields>
|
Chris@39
|
56 TestEmptyFields::m_registrar("A2", "Empty metadata fields");
|
cannam@0
|
57
|
cannam@0
|
58 Tester::TestRegistrar<TestValueRanges>
|
Chris@39
|
59 TestValueRanges::m_registrar("A3", "Inappropriate value extents");
|
cannam@0
|
60
|
Chris@35
|
61 Tester::TestRegistrar<TestCategory>
|
Chris@79
|
62 TestCategory::m_registrar("A4", "Missing category");
|
Chris@35
|
63
|
cannam@0
|
64 Test::Results
|
cannam@14
|
65 TestIdentifiers::test(string key, Options)
|
cannam@0
|
66 {
|
cannam@0
|
67 auto_ptr<Plugin> p(load(key));
|
cannam@0
|
68
|
cannam@0
|
69 Results r;
|
cannam@17
|
70 r.push_back(testIdentifier(p->getIdentifier(), "Plugin identifier"));
|
cannam@0
|
71
|
cannam@0
|
72 Plugin::ParameterList params = p->getParameterDescriptors();
|
cannam@0
|
73 for (int i = 0; i < (int)params.size(); ++i) {
|
cannam@17
|
74 r.push_back(testIdentifier(params[i].identifier, "Parameter identifier"));
|
cannam@0
|
75 }
|
cannam@0
|
76
|
cannam@0
|
77 Plugin::OutputList outputs = p->getOutputDescriptors();
|
cannam@0
|
78 for (int i = 0; i < (int)outputs.size(); ++i) {
|
cannam@17
|
79 r.push_back(testIdentifier(outputs[i].identifier, "Output identifier"));
|
cannam@0
|
80 }
|
cannam@0
|
81
|
cannam@0
|
82 return r;
|
cannam@0
|
83 }
|
cannam@0
|
84
|
cannam@0
|
85 Test::Result
|
cannam@0
|
86 TestIdentifiers::testIdentifier(string identifier, string desc)
|
cannam@0
|
87 {
|
cannam@0
|
88 for (int i = 0; i < (int)identifier.length(); ++i) {
|
cannam@0
|
89 char c = identifier[i];
|
cannam@0
|
90 if (c >= 'a' && c <= 'z') continue;
|
cannam@0
|
91 if (c >= 'A' && c <= 'Z') continue;
|
cannam@0
|
92 if (c >= '0' && c <= '9') continue;
|
cannam@0
|
93 if (c == '_' || c == '-') continue;
|
cannam@0
|
94 return error
|
cannam@0
|
95 (desc + " \"" + identifier +
|
cannam@0
|
96 "\" contains invalid character(s); permitted are: [a-zA-Z0-9_-]");
|
cannam@0
|
97 }
|
cannam@0
|
98 return success();
|
cannam@0
|
99 }
|
cannam@0
|
100
|
cannam@0
|
101 Test::Results
|
cannam@14
|
102 TestEmptyFields::test(string key, Options)
|
cannam@0
|
103 {
|
cannam@0
|
104 auto_ptr<Plugin> p(load(key));
|
cannam@0
|
105
|
cannam@0
|
106 Results r;
|
cannam@0
|
107
|
cannam@17
|
108 r.push_back(testMandatory(p->getName(), "Plugin name"));
|
cannam@17
|
109 r.push_back(testRecommended(p->getDescription(), "Plugin description"));
|
cannam@17
|
110 r.push_back(testRecommended(p->getMaker(), "Plugin maker"));
|
cannam@17
|
111 r.push_back(testRecommended(p->getCopyright(), "Plugin copyright"));
|
cannam@0
|
112
|
cannam@0
|
113 Plugin::ParameterList params = p->getParameterDescriptors();
|
cannam@0
|
114 for (int i = 0; i < (int)params.size(); ++i) {
|
cannam@0
|
115 r.push_back(testMandatory
|
cannam@0
|
116 (params[i].name,
|
cannam@17
|
117 "Plugin parameter \"" + params[i].identifier + "\" name"));
|
cannam@0
|
118 r.push_back(testRecommended
|
cannam@0
|
119 (params[i].description,
|
cannam@17
|
120 "Plugin parameter \"" + params[i].identifier + "\" description"));
|
cannam@0
|
121 }
|
cannam@0
|
122
|
cannam@0
|
123 Plugin::OutputList outputs = p->getOutputDescriptors();
|
cannam@0
|
124 for (int i = 0; i < (int)outputs.size(); ++i) {
|
cannam@0
|
125 r.push_back(testMandatory
|
cannam@0
|
126 (outputs[i].name,
|
cannam@17
|
127 "Plugin output \"" + outputs[i].identifier + "\" name"));
|
cannam@0
|
128 r.push_back(testRecommended
|
cannam@0
|
129 (outputs[i].description,
|
cannam@17
|
130 "Plugin output \"" + outputs[i].identifier + "\" description"));
|
cannam@0
|
131 }
|
cannam@0
|
132
|
cannam@0
|
133 return r;
|
cannam@0
|
134 }
|
cannam@0
|
135
|
cannam@0
|
136 Test::Result
|
cannam@0
|
137 TestEmptyFields::testMandatory(string text, string desc)
|
cannam@0
|
138 {
|
cannam@0
|
139 if (text == "") {
|
cannam@0
|
140 return error(desc + " is empty");
|
cannam@0
|
141 }
|
cannam@0
|
142 return success();
|
cannam@0
|
143 }
|
cannam@0
|
144
|
cannam@0
|
145 Test::Result
|
cannam@0
|
146 TestEmptyFields::testRecommended(string text, string desc)
|
cannam@0
|
147 {
|
cannam@0
|
148 if (text == "") {
|
cannam@0
|
149 return warning(desc + " is empty");
|
Chris@54
|
150 } else if (text == "Licence information not available." ||
|
Chris@54
|
151 text == "VamPy Plugin." ||
|
Chris@54
|
152 text == "Not given. (Hint: Implement getDescription method.)" ||
|
Chris@54
|
153 text == "VamPy Plugin (Noname)") {
|
Chris@54
|
154 return warning(desc + " is missing (returns VamPy boilerplate text)");
|
cannam@0
|
155 }
|
cannam@0
|
156 return success();
|
cannam@0
|
157 }
|
cannam@0
|
158
|
cannam@0
|
159 Test::Results
|
cannam@14
|
160 TestValueRanges::test(string key, Options)
|
cannam@0
|
161 {
|
cannam@0
|
162 auto_ptr<Plugin> p(load(key));
|
cannam@0
|
163
|
cannam@0
|
164 Results r;
|
cannam@0
|
165
|
cannam@0
|
166 Plugin::ParameterList params = p->getParameterDescriptors();
|
cannam@0
|
167 for (int i = 0; i < (int)params.size(); ++i) {
|
cannam@0
|
168 Plugin::ParameterDescriptor &pd(params[i]);
|
cannam@17
|
169 string pfx("Plugin parameter \"" + pd.identifier + "\"");
|
cannam@0
|
170 float min = pd.minValue;
|
cannam@0
|
171 float max = pd.maxValue;
|
cannam@0
|
172 float deft = pd.defaultValue;
|
cannam@0
|
173 if (max <= min) {
|
cannam@0
|
174 r.push_back(error(pfx + " maxValue <= minValue"));
|
cannam@0
|
175 }
|
cannam@0
|
176 if (deft < min || deft > max) {
|
cannam@0
|
177 r.push_back(error(pfx + " defaultValue out of range"));
|
cannam@0
|
178 }
|
cannam@0
|
179 if (pd.isQuantized) {
|
cannam@0
|
180 if (pd.quantizeStep == 0.f) {
|
cannam@0
|
181 r.push_back(error(pfx + " is quantized, but quantize step is zero"));
|
cannam@0
|
182 } else {
|
cannam@0
|
183
|
cannam@0
|
184 float epsilon = 0.00001f;
|
cannam@0
|
185 int qty = int((max - min) / pd.quantizeStep + 0.5);
|
cannam@0
|
186 float target = min + pd.quantizeStep * qty;
|
cannam@0
|
187 if (fabsf(max - target) > epsilon) {
|
cannam@0
|
188 r.push_back(warning(pfx + " value range is not a multiple of quantize step"));
|
cannam@0
|
189 }
|
cannam@0
|
190
|
cannam@0
|
191 if (!pd.valueNames.empty()) {
|
cannam@0
|
192 if ((int)pd.valueNames.size() < qty+1) {
|
cannam@0
|
193 r.push_back(warning(pfx + " has fewer value names than quantize steps"));
|
cannam@0
|
194 } else if ((int)pd.valueNames.size() > qty+1) {
|
cannam@0
|
195 r.push_back(warning(pfx + " has more value names than quantize steps"));
|
cannam@0
|
196 }
|
cannam@0
|
197 }
|
cannam@0
|
198
|
cannam@0
|
199 qty = int((deft - min) / pd.quantizeStep + 0.5);
|
cannam@0
|
200 target = min + pd.quantizeStep * qty;
|
cannam@0
|
201 if (fabsf(deft - target) > epsilon) {
|
cannam@0
|
202 r.push_back(warning(pfx + " default value is not a multiple of quantize step beyond minimum"));
|
cannam@0
|
203 }
|
cannam@0
|
204 }
|
cannam@0
|
205 }
|
cannam@0
|
206 }
|
cannam@0
|
207
|
cannam@0
|
208 return r;
|
cannam@0
|
209 }
|
cannam@0
|
210
|
Chris@35
|
211 Test::Results
|
Chris@35
|
212 TestCategory::test(string key, Options)
|
Chris@35
|
213 {
|
Chris@35
|
214 PluginLoader::PluginCategoryHierarchy hierarchy =
|
Chris@35
|
215 PluginLoader::getInstance()->getPluginCategory(key);
|
Chris@35
|
216
|
Chris@35
|
217 Results r;
|
cannam@0
|
218
|
Chris@35
|
219 if (hierarchy.empty()) {
|
Chris@35
|
220 r.push_back(warning("Plugin category missing or cannot be loaded (no .cat file?)"));
|
Chris@35
|
221 }
|
Chris@35
|
222
|
Chris@35
|
223 return r;
|
Chris@35
|
224 }
|
Chris@35
|
225
|