| cannam@1 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| cannam@1 | 2 | 
| cannam@1 | 3 /* | 
| cannam@1 | 4     Vamp | 
| cannam@1 | 5 | 
| cannam@1 | 6     An API for audio analysis and feature extraction plugins. | 
| cannam@1 | 7 | 
| cannam@1 | 8     Centre for Digital Music, Queen Mary, University of London. | 
| cannam@1 | 9     Copyright 2006 Chris Cannam. | 
| cannam@1 | 10 | 
| cannam@1 | 11     Permission is hereby granted, free of charge, to any person | 
| cannam@1 | 12     obtaining a copy of this software and associated documentation | 
| cannam@1 | 13     files (the "Software"), to deal in the Software without | 
| cannam@1 | 14     restriction, including without limitation the rights to use, copy, | 
| cannam@1 | 15     modify, merge, publish, distribute, sublicense, and/or sell copies | 
| cannam@1 | 16     of the Software, and to permit persons to whom the Software is | 
| cannam@1 | 17     furnished to do so, subject to the following conditions: | 
| cannam@1 | 18 | 
| cannam@1 | 19     The above copyright notice and this permission notice shall be | 
| cannam@1 | 20     included in all copies or substantial portions of the Software. | 
| cannam@1 | 21 | 
| cannam@1 | 22     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | 
| cannam@1 | 23     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | 
| cannam@1 | 24     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | 
| cannam@6 | 25     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR | 
| cannam@1 | 26     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | 
| cannam@1 | 27     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | 
| cannam@1 | 28     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 
| cannam@1 | 29 | 
| cannam@1 | 30     Except as contained in this notice, the names of the Centre for | 
| cannam@1 | 31     Digital Music; Queen Mary, University of London; and Chris Cannam | 
| cannam@1 | 32     shall not be used in advertising or otherwise to promote the sale, | 
| cannam@1 | 33     use or other dealings in this Software without prior written | 
| cannam@1 | 34     authorization. | 
| cannam@1 | 35 */ | 
| cannam@1 | 36 | 
| cannam@240 | 37 | 
| cannam@240 | 38 /* | 
| cannam@240 | 39  * This "simple" Vamp plugin host is no longer as simple as it was; it | 
| cannam@240 | 40  * now has a lot of options and includes a lot of code to handle the | 
| cannam@240 | 41  * various useful list options it supports. | 
| cannam@240 | 42  * | 
| cannam@240 | 43  * However, the runPlugin function still contains a reasonable | 
| cannam@240 | 44  * implementation of a fairly generic Vamp plugin host capable of | 
| cannam@240 | 45  * evaluating a given output on a given plugin for a sound file read | 
| cannam@240 | 46  * via libsndfile. | 
| cannam@240 | 47  */ | 
| cannam@240 | 48 | 
| cannam@230 | 49 #include <vamp-hostsdk/PluginHostAdapter.h> | 
| cannam@233 | 50 #include <vamp-hostsdk/PluginInputDomainAdapter.h> | 
| cannam@233 | 51 #include <vamp-hostsdk/PluginLoader.h> | 
| cannam@1 | 52 | 
| cannam@16 | 53 #include <iostream> | 
| cannam@88 | 54 #include <fstream> | 
| cannam@99 | 55 #include <set> | 
| cannam@16 | 56 #include <sndfile.h> | 
| cannam@1 | 57 | 
| cannam@130 | 58 #include <cstring> | 
| cannam@130 | 59 #include <cstdlib> | 
| cannam@130 | 60 | 
| cannam@1 | 61 #include "system.h" | 
| cannam@1 | 62 | 
| cannam@19 | 63 #include <cmath> | 
| cannam@19 | 64 | 
| cannam@99 | 65 using namespace std; | 
| cannam@16 | 66 | 
| cannam@99 | 67 using Vamp::Plugin; | 
| cannam@99 | 68 using Vamp::PluginHostAdapter; | 
| cannam@99 | 69 using Vamp::RealTime; | 
| cannam@64 | 70 using Vamp::HostExt::PluginLoader; | 
| cannam@190 | 71 using Vamp::HostExt::PluginWrapper; | 
| cannam@190 | 72 using Vamp::HostExt::PluginInputDomainAdapter; | 
| cannam@64 | 73 | 
| cannam@240 | 74 #define HOST_VERSION "1.4" | 
| cannam@40 | 75 | 
| cannam@95 | 76 enum Verbosity { | 
| cannam@95 | 77     PluginIds, | 
| cannam@95 | 78     PluginOutputIds, | 
| cannam@240 | 79     PluginInformation, | 
| cannam@240 | 80     PluginInformationDetailed | 
| cannam@95 | 81 }; | 
| cannam@95 | 82 | 
| cannam@109 | 83 void printFeatures(int, int, int, Plugin::FeatureSet, ofstream *, bool frames); | 
| cannam@16 | 84 void transformInput(float *, size_t); | 
| cannam@16 | 85 void fft(unsigned int, bool, double *, double *, double *, double *); | 
| cannam@64 | 86 void printPluginPath(bool verbose); | 
| cannam@99 | 87 void printPluginCategoryList(); | 
| cannam@95 | 88 void enumeratePlugins(Verbosity); | 
| cannam@64 | 89 void listPluginsInLibrary(string soname); | 
| cannam@64 | 90 int runPlugin(string myname, string soname, string id, string output, | 
| cannam@109 | 91               int outputNo, string inputFile, string outfilename, bool frames); | 
| cannam@40 | 92 | 
| cannam@64 | 93 void usage(const char *name) | 
| cannam@64 | 94 { | 
| cannam@64 | 95     cerr << "\n" | 
| cannam@240 | 96          << name << ": A command-line host for Vamp audio analysis plugins.\n\n" | 
| cannam@64 | 97         "Centre for Digital Music, Queen Mary, University of London.\n" | 
| cannam@64 | 98         "Copyright 2006-2007 Chris Cannam and QMUL.\n" | 
| cannam@64 | 99         "Freely redistributable; published under a BSD-style license.\n\n" | 
| cannam@64 | 100         "Usage:\n\n" | 
| cannam@109 | 101         "  " << name << " [-s] pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin[:output] file.wav [-o out.txt]\n" | 
| cannam@109 | 102         "  " << name << " [-s] pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin file.wav [outputno] [-o out.txt]\n\n" | 
| cannam@64 | 103         "    -- Load plugin id \"plugin\" from \"pluginlibrary\" and run it on the\n" | 
| cannam@73 | 104         "       audio data in \"file.wav\", retrieving the named \"output\", or output\n" | 
| cannam@73 | 105         "       number \"outputno\" (the first output by default) and dumping it to\n" | 
| cannam@95 | 106         "       standard output, or to \"out.txt\" if the -o option is given.\n\n" | 
| cannam@73 | 107         "       \"pluginlibrary\" should be a library name, not a file path; the\n" | 
| cannam@73 | 108         "       standard Vamp library search path will be used to locate it.  If\n" | 
| cannam@73 | 109         "       a file path is supplied, the directory part(s) will be ignored.\n\n" | 
| cannam@109 | 110         "       If the -s option is given, results will be labelled with the audio\n" | 
| cannam@109 | 111         "       sample frame at which they occur. Otherwise, they will be labelled\n" | 
| cannam@109 | 112         "       with time in seconds.\n\n" | 
| cannam@240 | 113         "  " << name << " -l\n" | 
| cannam@240 | 114         "  " << name << " --list\n\n" | 
| cannam@95 | 115         "    -- List the plugin libraries and Vamp plugins in the library search path\n" | 
| cannam@95 | 116         "       in a verbose human-readable format.\n\n" | 
| cannam@240 | 117         "  " << name << " --list-full\n\n" | 
| cannam@240 | 118         "    -- List all data reported by all the Vamp plugins in the library search\n" | 
| cannam@240 | 119         "       path in a very verbose human-readable format.\n\n" | 
| cannam@95 | 120         "  " << name << " --list-ids\n\n" | 
| cannam@95 | 121         "    -- List the plugins in the search path in a terse machine-readable format,\n" | 
| cannam@95 | 122         "       in the form vamp:soname:identifier.\n\n" | 
| cannam@95 | 123         "  " << name << " --list-outputs\n\n" | 
| cannam@95 | 124         "    -- List the outputs for plugins in the search path in a machine-readable\n" | 
| cannam@95 | 125         "       format, in the form vamp:soname:identifier:output.\n\n" | 
| cannam@99 | 126         "  " << name << " --list-by-category\n\n" | 
| cannam@99 | 127         "    -- List the plugins as a plugin index by category, in a machine-readable\n" | 
| cannam@99 | 128         "       format.  The format may change in future releases.\n\n" | 
| cannam@64 | 129         "  " << name << " -p\n\n" | 
| cannam@73 | 130         "    -- Print out the Vamp library search path.\n\n" | 
| cannam@64 | 131         "  " << name << " -v\n\n" | 
| cannam@95 | 132         "    -- Display version information only.\n" | 
| cannam@64 | 133          << endl; | 
| cannam@64 | 134     exit(2); | 
| cannam@64 | 135 } | 
| cannam@1 | 136 | 
| cannam@1 | 137 int main(int argc, char **argv) | 
| cannam@1 | 138 { | 
| cannam@64 | 139     char *scooter = argv[0]; | 
| cannam@64 | 140     char *name = 0; | 
| cannam@64 | 141     while (scooter && *scooter) { | 
| cannam@64 | 142         if (*scooter == '/' || *scooter == '\\') name = ++scooter; | 
| cannam@64 | 143         else ++scooter; | 
| cannam@1 | 144     } | 
| cannam@64 | 145     if (!name || !*name) name = argv[0]; | 
| cannam@43 | 146 | 
| cannam@88 | 147     if (argc < 2) usage(name); | 
| cannam@64 | 148 | 
| cannam@88 | 149     if (argc == 2) { | 
| cannam@88 | 150 | 
| cannam@88 | 151         if (!strcmp(argv[1], "-v")) { | 
| cannam@88 | 152 | 
| cannam@88 | 153             cout << "Simple Vamp plugin host version: " << HOST_VERSION << endl | 
| cannam@88 | 154                  << "Vamp API version: " << VAMP_API_VERSION << endl | 
| cannam@88 | 155                  << "Vamp SDK version: " << VAMP_SDK_VERSION << endl; | 
| cannam@88 | 156             return 0; | 
| cannam@88 | 157 | 
| cannam@240 | 158         } else if (!strcmp(argv[1], "-l") || !strcmp(argv[1], "--list")) { | 
| cannam@88 | 159 | 
| cannam@88 | 160             printPluginPath(true); | 
| cannam@95 | 161             enumeratePlugins(PluginInformation); | 
| cannam@88 | 162             return 0; | 
| cannam@88 | 163 | 
| cannam@240 | 164         } else if (!strcmp(argv[1], "--list-full")) { | 
| cannam@240 | 165 | 
| cannam@240 | 166             enumeratePlugins(PluginInformationDetailed); | 
| cannam@240 | 167             return 0; | 
| cannam@240 | 168 | 
| cannam@88 | 169         } else if (!strcmp(argv[1], "-p")) { | 
| cannam@88 | 170 | 
| cannam@88 | 171             printPluginPath(false); | 
| cannam@88 | 172             return 0; | 
| cannam@88 | 173 | 
| cannam@95 | 174         } else if (!strcmp(argv[1], "--list-ids")) { | 
| cannam@95 | 175 | 
| cannam@95 | 176             enumeratePlugins(PluginIds); | 
| cannam@95 | 177             return 0; | 
| cannam@95 | 178 | 
| cannam@95 | 179         } else if (!strcmp(argv[1], "--list-outputs")) { | 
| cannam@95 | 180 | 
| cannam@95 | 181             enumeratePlugins(PluginOutputIds); | 
| cannam@95 | 182             return 0; | 
| cannam@95 | 183 | 
| cannam@99 | 184         } else if (!strcmp(argv[1], "--list-by-category")) { | 
| cannam@99 | 185 | 
| cannam@99 | 186             printPluginCategoryList(); | 
| cannam@99 | 187             return 0; | 
| cannam@99 | 188 | 
| cannam@88 | 189         } else usage(name); | 
| cannam@64 | 190     } | 
| cannam@64 | 191 | 
| cannam@88 | 192     if (argc < 3) usage(name); | 
| cannam@88 | 193 | 
| cannam@109 | 194     bool useFrames = false; | 
| cannam@109 | 195 | 
| cannam@109 | 196     int base = 1; | 
| cannam@109 | 197     if (!strcmp(argv[1], "-s")) { | 
| cannam@109 | 198         useFrames = true; | 
| cannam@109 | 199         base = 2; | 
| cannam@109 | 200     } | 
| cannam@109 | 201 | 
| cannam@109 | 202     string soname = argv[base]; | 
| cannam@109 | 203     string wavname = argv[base+1]; | 
| cannam@88 | 204     string plugid = ""; | 
| cannam@88 | 205     string output = ""; | 
| cannam@88 | 206     int outputNo = -1; | 
| cannam@88 | 207     string outfilename; | 
| cannam@88 | 208 | 
| cannam@109 | 209     if (argc >= base+3) { | 
| cannam@88 | 210 | 
| cannam@109 | 211         int idx = base+2; | 
| cannam@88 | 212 | 
| cannam@88 | 213         if (isdigit(*argv[idx])) { | 
| cannam@88 | 214             outputNo = atoi(argv[idx++]); | 
| cannam@88 | 215         } | 
| cannam@88 | 216 | 
| cannam@88 | 217         if (argc == idx + 2) { | 
| cannam@88 | 218             if (!strcmp(argv[idx], "-o")) { | 
| cannam@88 | 219                 outfilename = argv[idx+1]; | 
| cannam@88 | 220             } else usage(name); | 
| cannam@88 | 221         } else if (argc != idx) { | 
| cannam@88 | 222             (usage(name)); | 
| cannam@88 | 223         } | 
| cannam@40 | 224     } | 
| cannam@40 | 225 | 
| cannam@64 | 226     cerr << endl << name << ": Running..." << endl; | 
| cannam@1 | 227 | 
| cannam@88 | 228     cerr << "Reading file: \"" << wavname << "\", writing to "; | 
| cannam@88 | 229     if (outfilename == "") { | 
| cannam@88 | 230         cerr << "standard output" << endl; | 
| cannam@88 | 231     } else { | 
| cannam@88 | 232         cerr << "\"" << outfilename << "\"" << endl; | 
| cannam@88 | 233     } | 
| cannam@16 | 234 | 
| cannam@64 | 235     string::size_type sep = soname.find(':'); | 
| cannam@64 | 236 | 
| cannam@64 | 237     if (sep != string::npos) { | 
| cannam@49 | 238         plugid = soname.substr(sep + 1); | 
| cannam@20 | 239         soname = soname.substr(0, sep); | 
| cannam@1 | 240 | 
| cannam@64 | 241         sep = plugid.find(':'); | 
| cannam@64 | 242         if (sep != string::npos) { | 
| cannam@64 | 243             output = plugid.substr(sep + 1); | 
| cannam@64 | 244             plugid = plugid.substr(0, sep); | 
| cannam@16 | 245         } | 
| cannam@16 | 246     } | 
| cannam@16 | 247 | 
| cannam@64 | 248     if (plugid == "") { | 
| cannam@64 | 249         usage(name); | 
| cannam@16 | 250     } | 
| cannam@64 | 251 | 
| cannam@64 | 252     if (output != "" && outputNo != -1) { | 
| cannam@64 | 253         usage(name); | 
| cannam@64 | 254     } | 
| cannam@64 | 255 | 
| cannam@84 | 256     if (output == "" && outputNo == -1) { | 
| cannam@84 | 257         outputNo = 0; | 
| cannam@84 | 258     } | 
| cannam@84 | 259 | 
| cannam@88 | 260     return runPlugin(name, soname, plugid, output, outputNo, | 
| cannam@109 | 261                      wavname, outfilename, useFrames); | 
| cannam@64 | 262 } | 
| cannam@64 | 263 | 
| cannam@64 | 264 | 
| cannam@64 | 265 int runPlugin(string myname, string soname, string id, | 
| cannam@88 | 266               string output, int outputNo, string wavname, | 
| cannam@109 | 267               string outfilename, bool useFrames) | 
| cannam@64 | 268 { | 
| cannam@64 | 269     PluginLoader *loader = PluginLoader::getInstance(); | 
| cannam@64 | 270 | 
| cannam@64 | 271     PluginLoader::PluginKey key = loader->composePluginKey(soname, id); | 
| cannam@16 | 272 | 
| cannam@16 | 273     SNDFILE *sndfile; | 
| cannam@16 | 274     SF_INFO sfinfo; | 
| cannam@16 | 275     memset(&sfinfo, 0, sizeof(SF_INFO)); | 
| cannam@16 | 276 | 
| cannam@16 | 277     sndfile = sf_open(wavname.c_str(), SFM_READ, &sfinfo); | 
| cannam@16 | 278     if (!sndfile) { | 
| cannam@64 | 279 	cerr << myname << ": ERROR: Failed to open input file \"" | 
| cannam@64 | 280              << wavname << "\": " << sf_strerror(sndfile) << endl; | 
| cannam@16 | 281 	return 1; | 
| cannam@16 | 282     } | 
| cannam@16 | 283 | 
| cannam@88 | 284     ofstream *out = 0; | 
| cannam@88 | 285     if (outfilename != "") { | 
| cannam@88 | 286         out = new ofstream(outfilename.c_str(), ios::out); | 
| cannam@88 | 287         if (!*out) { | 
| cannam@88 | 288             cerr << myname << ": ERROR: Failed to open output file \"" | 
| cannam@88 | 289                  << outfilename << "\" for writing" << endl; | 
| cannam@88 | 290             delete out; | 
| cannam@88 | 291             return 1; | 
| cannam@88 | 292         } | 
| cannam@88 | 293     } | 
| cannam@88 | 294 | 
| cannam@99 | 295     Plugin *plugin = loader->loadPlugin | 
| cannam@92 | 296         (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE); | 
| cannam@64 | 297     if (!plugin) { | 
| cannam@64 | 298         cerr << myname << ": ERROR: Failed to load plugin \"" << id | 
| cannam@64 | 299              << "\" from library \"" << soname << "\"" << endl; | 
| cannam@64 | 300         sf_close(sndfile); | 
| cannam@88 | 301         if (out) { | 
| cannam@88 | 302             out->close(); | 
| cannam@88 | 303             delete out; | 
| cannam@88 | 304         } | 
| cannam@64 | 305         return 1; | 
| cannam@64 | 306     } | 
| cannam@16 | 307 | 
| cannam@64 | 308     cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl; | 
| cannam@16 | 309 | 
| cannam@240 | 310     // Note that the following would be much simpler if we used a | 
| cannam@240 | 311     // PluginBufferingAdapter as well -- i.e. if we had passed | 
| cannam@240 | 312     // PluginLoader::ADAPT_ALL to loader->loadPlugin() above, instead | 
| cannam@240 | 313     // of ADAPT_ALL_SAFE.  Then we could simply specify our own block | 
| cannam@240 | 314     // size, keep the step size equal to the block size, and ignore | 
| cannam@240 | 315     // the plugin's bleatings.  However, there are some issues with | 
| cannam@240 | 316     // using a PluginBufferingAdapter that make the results sometimes | 
| cannam@240 | 317     // technically different from (if effectively the same as) the | 
| cannam@240 | 318     // un-adapted plugin, so we aren't doing that here.  See the | 
| cannam@240 | 319     // PluginBufferingAdapter documentation for details. | 
| cannam@240 | 320 | 
| cannam@16 | 321     int blockSize = plugin->getPreferredBlockSize(); | 
| cannam@16 | 322     int stepSize = plugin->getPreferredStepSize(); | 
| cannam@16 | 323 | 
| cannam@91 | 324     if (blockSize == 0) { | 
| cannam@91 | 325         blockSize = 1024; | 
| cannam@91 | 326     } | 
| cannam@83 | 327     if (stepSize == 0) { | 
| cannam@99 | 328         if (plugin->getInputDomain() == Plugin::FrequencyDomain) { | 
| cannam@83 | 329             stepSize = blockSize/2; | 
| cannam@83 | 330         } else { | 
| cannam@83 | 331             stepSize = blockSize; | 
| cannam@83 | 332         } | 
| cannam@91 | 333     } else if (stepSize > blockSize) { | 
| cannam@91 | 334         cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to "; | 
| cannam@99 | 335         if (plugin->getInputDomain() == Plugin::FrequencyDomain) { | 
| cannam@91 | 336             blockSize = stepSize * 2; | 
| cannam@91 | 337         } else { | 
| cannam@91 | 338             blockSize = stepSize; | 
| cannam@91 | 339         } | 
| cannam@91 | 340         cerr << blockSize << endl; | 
| cannam@83 | 341     } | 
| cannam@83 | 342 | 
| cannam@16 | 343     int channels = sfinfo.channels; | 
| cannam@16 | 344 | 
| cannam@16 | 345     float *filebuf = new float[blockSize * channels]; | 
| cannam@16 | 346     float **plugbuf = new float*[channels]; | 
| cannam@47 | 347     for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2]; | 
| cannam@16 | 348 | 
| cannam@16 | 349     cerr << "Using block size = " << blockSize << ", step size = " | 
| cannam@16 | 350               << stepSize << endl; | 
| cannam@16 | 351 | 
| cannam@240 | 352     // The channel queries here are for informational purposes only -- | 
| cannam@240 | 353     // a PluginChannelAdapter is being used automatically behind the | 
| cannam@240 | 354     // scenes, and it will take case of any channel mismatch | 
| cannam@240 | 355 | 
| cannam@16 | 356     int minch = plugin->getMinChannelCount(); | 
| cannam@16 | 357     int maxch = plugin->getMaxChannelCount(); | 
| cannam@16 | 358     cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl; | 
| cannam@64 | 359     cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl; | 
| cannam@16 | 360 | 
| cannam@99 | 361     Plugin::OutputList outputs = plugin->getOutputDescriptors(); | 
| cannam@99 | 362     Plugin::OutputDescriptor od; | 
| cannam@16 | 363 | 
| cannam@29 | 364     int returnValue = 1; | 
| cannam@88 | 365     int progress = 0; | 
| cannam@29 | 366 | 
| cannam@190 | 367     RealTime rt; | 
| cannam@190 | 368     PluginWrapper *wrapper = 0; | 
| cannam@190 | 369     RealTime adjustment = RealTime::zeroTime; | 
| cannam@190 | 370 | 
| cannam@16 | 371     if (outputs.empty()) { | 
| cannam@64 | 372 	cerr << "ERROR: Plugin has no outputs!" << endl; | 
| cannam@16 | 373         goto done; | 
| cannam@16 | 374     } | 
| cannam@16 | 375 | 
| cannam@64 | 376     if (outputNo < 0) { | 
| cannam@16 | 377 | 
| cannam@64 | 378         for (size_t oi = 0; oi < outputs.size(); ++oi) { | 
| cannam@64 | 379             if (outputs[oi].identifier == output) { | 
| cannam@64 | 380                 outputNo = oi; | 
| cannam@64 | 381                 break; | 
| cannam@64 | 382             } | 
| cannam@64 | 383         } | 
| cannam@64 | 384 | 
| cannam@64 | 385         if (outputNo < 0) { | 
| cannam@64 | 386             cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl; | 
| cannam@64 | 387             goto done; | 
| cannam@64 | 388         } | 
| cannam@64 | 389 | 
| cannam@64 | 390     } else { | 
| cannam@64 | 391 | 
| cannam@64 | 392         if (int(outputs.size()) <= outputNo) { | 
| cannam@64 | 393             cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; | 
| cannam@64 | 394             goto done; | 
| cannam@64 | 395         } | 
| cannam@64 | 396     } | 
| cannam@64 | 397 | 
| cannam@64 | 398     od = outputs[outputNo]; | 
| cannam@64 | 399     cerr << "Output is: \"" << od.identifier << "\"" << endl; | 
| cannam@16 | 400 | 
| cannam@29 | 401     if (!plugin->initialise(channels, stepSize, blockSize)) { | 
| cannam@29 | 402         cerr << "ERROR: Plugin initialise (channels = " << channels | 
| cannam@29 | 403              << ", stepSize = " << stepSize << ", blockSize = " | 
| cannam@29 | 404              << blockSize << ") failed." << endl; | 
| cannam@29 | 405         goto done; | 
| cannam@29 | 406     } | 
| cannam@16 | 407 | 
| cannam@190 | 408     wrapper = dynamic_cast<PluginWrapper *>(plugin); | 
| cannam@190 | 409     if (wrapper) { | 
| cannam@240 | 410         // See documentation for | 
| cannam@240 | 411         // PluginInputDomainAdapter::getTimestampAdjustment | 
| cannam@190 | 412         PluginInputDomainAdapter *ida = | 
| cannam@190 | 413             wrapper->getWrapper<PluginInputDomainAdapter>(); | 
| cannam@190 | 414         if (ida) adjustment = ida->getTimestampAdjustment(); | 
| cannam@190 | 415     } | 
| cannam@190 | 416 | 
| cannam@16 | 417     for (size_t i = 0; i < sfinfo.frames; i += stepSize) { | 
| cannam@16 | 418 | 
| cannam@16 | 419         int count; | 
| cannam@16 | 420 | 
| cannam@16 | 421         if (sf_seek(sndfile, i, SEEK_SET) < 0) { | 
| cannam@16 | 422             cerr << "ERROR: sf_seek failed: " << sf_strerror(sndfile) << endl; | 
| cannam@16 | 423             break; | 
| cannam@16 | 424         } | 
| cannam@16 | 425 | 
| cannam@16 | 426         if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) { | 
| cannam@16 | 427             cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; | 
| cannam@16 | 428             break; | 
| cannam@16 | 429         } | 
| cannam@16 | 430 | 
| cannam@16 | 431         for (int c = 0; c < channels; ++c) { | 
| cannam@64 | 432             int j = 0; | 
| cannam@64 | 433             while (j < count) { | 
| cannam@64 | 434                 plugbuf[c][j] = filebuf[j * sfinfo.channels + c]; | 
| cannam@64 | 435                 ++j; | 
| cannam@64 | 436             } | 
| cannam@64 | 437             while (j < blockSize) { | 
| cannam@16 | 438                 plugbuf[c][j] = 0.0f; | 
| cannam@64 | 439                 ++j; | 
| cannam@16 | 440             } | 
| cannam@16 | 441         } | 
| cannam@16 | 442 | 
| cannam@190 | 443         rt = RealTime::frame2RealTime(i, sfinfo.samplerate); | 
| cannam@190 | 444 | 
| cannam@16 | 445         printFeatures | 
| cannam@190 | 446             (RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate), | 
| cannam@190 | 447              sfinfo.samplerate, outputNo, plugin->process(plugbuf, rt), | 
| cannam@109 | 448              out, useFrames); | 
| cannam@88 | 449 | 
| cannam@88 | 450         int pp = progress; | 
| cannam@88 | 451         progress = lrintf((float(i) / sfinfo.frames) * 100.f); | 
| cannam@88 | 452         if (progress != pp && out) { | 
| cannam@88 | 453             cerr << "\r" << progress << "%"; | 
| cannam@88 | 454         } | 
| cannam@16 | 455     } | 
| cannam@88 | 456     if (out) cerr << "\rDone" << endl; | 
| cannam@16 | 457 | 
| cannam@190 | 458     rt = RealTime::frame2RealTime(sfinfo.frames, sfinfo.samplerate); | 
| cannam@190 | 459 | 
| cannam@190 | 460     printFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate), | 
| cannam@190 | 461                   sfinfo.samplerate, outputNo, | 
| cannam@109 | 462                   plugin->getRemainingFeatures(), out, useFrames); | 
| cannam@16 | 463 | 
| cannam@29 | 464     returnValue = 0; | 
| cannam@29 | 465 | 
| cannam@16 | 466 done: | 
| cannam@16 | 467     delete plugin; | 
| cannam@88 | 468     if (out) { | 
| cannam@88 | 469         out->close(); | 
| cannam@88 | 470         delete out; | 
| cannam@88 | 471     } | 
| cannam@16 | 472     sf_close(sndfile); | 
| cannam@29 | 473     return returnValue; | 
| cannam@1 | 474 } | 
| cannam@1 | 475 | 
| cannam@16 | 476 void | 
| cannam@99 | 477 printFeatures(int frame, int sr, int output, | 
| cannam@109 | 478               Plugin::FeatureSet features, ofstream *out, bool useFrames) | 
| cannam@99 | 479 { | 
| cannam@99 | 480     for (unsigned int i = 0; i < features[output].size(); ++i) { | 
| cannam@99 | 481 | 
| cannam@109 | 482         if (useFrames) { | 
| cannam@99 | 483 | 
| cannam@109 | 484             int displayFrame = frame; | 
| cannam@109 | 485 | 
| cannam@109 | 486             if (features[output][i].hasTimestamp) { | 
| cannam@109 | 487                 displayFrame = RealTime::realTime2Frame | 
| cannam@109 | 488                     (features[output][i].timestamp, sr); | 
| cannam@109 | 489             } | 
| cannam@109 | 490 | 
| cannam@167 | 491             (out ? *out : cout) << displayFrame; | 
| cannam@167 | 492 | 
| cannam@167 | 493             if (features[output][i].hasDuration) { | 
| cannam@167 | 494                 displayFrame = RealTime::realTime2Frame | 
| cannam@167 | 495                     (features[output][i].duration, sr); | 
| cannam@167 | 496                 (out ? *out : cout) << "," << displayFrame; | 
| cannam@167 | 497             } | 
| cannam@167 | 498 | 
| cannam@167 | 499             (out ? *out : cout)  << ":"; | 
| cannam@109 | 500 | 
| cannam@109 | 501         } else { | 
| cannam@109 | 502 | 
| cannam@109 | 503             RealTime rt = RealTime::frame2RealTime(frame, sr); | 
| cannam@109 | 504 | 
| cannam@109 | 505             if (features[output][i].hasTimestamp) { | 
| cannam@109 | 506                 rt = features[output][i].timestamp; | 
| cannam@109 | 507             } | 
| cannam@109 | 508 | 
| cannam@167 | 509             (out ? *out : cout) << rt.toString(); | 
| cannam@167 | 510 | 
| cannam@167 | 511             if (features[output][i].hasDuration) { | 
| cannam@167 | 512                 rt = features[output][i].duration; | 
| cannam@167 | 513                 (out ? *out : cout) << "," << rt.toString(); | 
| cannam@167 | 514             } | 
| cannam@167 | 515 | 
| cannam@167 | 516             (out ? *out : cout) << ":"; | 
| cannam@99 | 517         } | 
| cannam@99 | 518 | 
| cannam@99 | 519         for (unsigned int j = 0; j < features[output][i].values.size(); ++j) { | 
| cannam@99 | 520             (out ? *out : cout) << " " << features[output][i].values[j]; | 
| cannam@99 | 521         } | 
| cannam@99 | 522 | 
| cannam@99 | 523         (out ? *out : cout) << endl; | 
| cannam@99 | 524     } | 
| cannam@99 | 525 } | 
| cannam@99 | 526 | 
| cannam@99 | 527 void | 
| cannam@64 | 528 printPluginPath(bool verbose) | 
| cannam@40 | 529 { | 
| cannam@64 | 530     if (verbose) { | 
| cannam@64 | 531         cout << "\nVamp plugin search path: "; | 
| cannam@64 | 532     } | 
| cannam@64 | 533 | 
| cannam@99 | 534     vector<string> path = PluginHostAdapter::getPluginPath(); | 
| cannam@40 | 535     for (size_t i = 0; i < path.size(); ++i) { | 
| cannam@64 | 536         if (verbose) { | 
| cannam@64 | 537             cout << "[" << path[i] << "]"; | 
| cannam@64 | 538         } else { | 
| cannam@64 | 539             cout << path[i] << endl; | 
| cannam@64 | 540         } | 
| cannam@40 | 541     } | 
| cannam@64 | 542 | 
| cannam@64 | 543     if (verbose) cout << endl; | 
| cannam@40 | 544 } | 
| cannam@40 | 545 | 
| cannam@240 | 546 static | 
| cannam@240 | 547 string | 
| cannam@240 | 548 header(string text, int level) | 
| cannam@240 | 549 { | 
| cannam@240 | 550     string out = '\n' + text + '\n'; | 
| cannam@240 | 551     for (size_t i = 0; i < text.length(); ++i) { | 
| cannam@240 | 552         out += (level == 1 ? '=' : level == 2 ? '-' : '~'); | 
| cannam@240 | 553     } | 
| cannam@240 | 554     out += '\n'; | 
| cannam@240 | 555     return out; | 
| cannam@240 | 556 } | 
| cannam@240 | 557 | 
| cannam@40 | 558 void | 
| cannam@95 | 559 enumeratePlugins(Verbosity verbosity) | 
| cannam@40 | 560 { | 
| cannam@64 | 561     PluginLoader *loader = PluginLoader::getInstance(); | 
| cannam@64 | 562 | 
| cannam@95 | 563     if (verbosity == PluginInformation) { | 
| cannam@95 | 564         cout << "\nVamp plugin libraries found in search path:" << endl; | 
| cannam@95 | 565     } | 
| cannam@64 | 566 | 
| cannam@99 | 567     vector<PluginLoader::PluginKey> plugins = loader->listPlugins(); | 
| cannam@99 | 568     typedef multimap<string, PluginLoader::PluginKey> | 
| cannam@64 | 569         LibraryMap; | 
| cannam@64 | 570     LibraryMap libraryMap; | 
| cannam@64 | 571 | 
| cannam@64 | 572     for (size_t i = 0; i < plugins.size(); ++i) { | 
| cannam@99 | 573         string path = loader->getLibraryPathForPlugin(plugins[i]); | 
| cannam@64 | 574         libraryMap.insert(LibraryMap::value_type(path, plugins[i])); | 
| cannam@64 | 575     } | 
| cannam@64 | 576 | 
| cannam@99 | 577     string prevPath = ""; | 
| cannam@64 | 578     int index = 0; | 
| cannam@64 | 579 | 
| cannam@64 | 580     for (LibraryMap::iterator i = libraryMap.begin(); | 
| cannam@64 | 581          i != libraryMap.end(); ++i) { | 
| cannam@64 | 582 | 
| cannam@99 | 583         string path = i->first; | 
| cannam@64 | 584         PluginLoader::PluginKey key = i->second; | 
| cannam@64 | 585 | 
| cannam@64 | 586         if (path != prevPath) { | 
| cannam@64 | 587             prevPath = path; | 
| cannam@64 | 588             index = 0; | 
| cannam@95 | 589             if (verbosity == PluginInformation) { | 
| cannam@95 | 590                 cout << "\n  " << path << ":" << endl; | 
| cannam@240 | 591             } else if (verbosity == PluginInformationDetailed) { | 
| cannam@240 | 592                 string::size_type ki = i->second.find(':'); | 
| cannam@240 | 593                 string text = "Library \"" + i->second.substr(0, ki) + "\""; | 
| cannam@240 | 594                 cout << "\n" << header(text, 1); | 
| cannam@95 | 595             } | 
| cannam@40 | 596         } | 
| cannam@64 | 597 | 
| cannam@99 | 598         Plugin *plugin = loader->loadPlugin(key, 48000); | 
| cannam@64 | 599         if (plugin) { | 
| cannam@64 | 600 | 
| cannam@64 | 601             char c = char('A' + index); | 
| cannam@64 | 602             if (c > 'Z') c = char('a' + (index - 26)); | 
| cannam@64 | 603 | 
| cannam@240 | 604             PluginLoader::PluginCategoryHierarchy category = | 
| cannam@240 | 605                 loader->getPluginCategory(key); | 
| cannam@240 | 606             string catstr; | 
| cannam@240 | 607             if (!category.empty()) { | 
| cannam@240 | 608                 for (size_t ci = 0; ci < category.size(); ++ci) { | 
| cannam@240 | 609                     if (ci > 0) catstr += " > "; | 
| cannam@240 | 610                         catstr += category[ci]; | 
| cannam@240 | 611                 } | 
| cannam@240 | 612             } | 
| cannam@240 | 613 | 
| cannam@95 | 614             if (verbosity == PluginInformation) { | 
| cannam@64 | 615 | 
| cannam@95 | 616                 cout << "    [" << c << "] [v" | 
| cannam@95 | 617                      << plugin->getVampApiVersion() << "] " | 
| cannam@95 | 618                      << plugin->getName() << ", \"" | 
| cannam@95 | 619                      << plugin->getIdentifier() << "\"" << " [" | 
| cannam@95 | 620                      << plugin->getMaker() << "]" << endl; | 
| cannam@240 | 621 | 
| cannam@240 | 622                 if (catstr != "") { | 
| cannam@240 | 623                     cout << "       > " << catstr << endl; | 
| cannam@64 | 624                 } | 
| cannam@95 | 625 | 
| cannam@95 | 626                 if (plugin->getDescription() != "") { | 
| cannam@95 | 627                     cout << "        - " << plugin->getDescription() << endl; | 
| cannam@95 | 628                 } | 
| cannam@95 | 629 | 
| cannam@240 | 630             } else if (verbosity == PluginInformationDetailed) { | 
| cannam@240 | 631 | 
| cannam@240 | 632                 cout << header(plugin->getName(), 2); | 
| cannam@240 | 633                 cout << "- Identifier:         " | 
| cannam@240 | 634                      << key << endl; | 
| cannam@240 | 635                 cout << "- Plugin Version:     " | 
| cannam@240 | 636                      << plugin->getPluginVersion() << endl; | 
| cannam@240 | 637                 cout << "- Vamp API Version:   " | 
| cannam@240 | 638                      << plugin->getVampApiVersion() << endl; | 
| cannam@240 | 639                 cout << "- Maker:              \"" | 
| cannam@240 | 640                      << plugin->getMaker() << "\"" << endl; | 
| cannam@240 | 641                 cout << "- Copyright:          \"" | 
| cannam@240 | 642                      << plugin->getCopyright() << "\"" << endl; | 
| cannam@240 | 643                 cout << "- Description:        \"" | 
| cannam@240 | 644                      << plugin->getDescription() << "\"" << endl; | 
| cannam@240 | 645                 cout << "- Input Domain:       " | 
| cannam@240 | 646                      << (plugin->getInputDomain() == Vamp::Plugin::TimeDomain ? | 
| cannam@240 | 647                          "Time Domain" : "Frequency Domain") << endl; | 
| cannam@240 | 648                 cout << "- Default Step Size:  " | 
| cannam@240 | 649                      << plugin->getPreferredStepSize() << endl; | 
| cannam@240 | 650                 cout << "- Default Block Size: " | 
| cannam@240 | 651                      << plugin->getPreferredBlockSize() << endl; | 
| cannam@240 | 652                 cout << "- Minimum Channels:   " | 
| cannam@240 | 653                      << plugin->getMinChannelCount() << endl; | 
| cannam@240 | 654                 cout << "- Maximum Channels:   " | 
| cannam@240 | 655                      << plugin->getMaxChannelCount() << endl; | 
| cannam@240 | 656 | 
| cannam@95 | 657             } else if (verbosity == PluginIds) { | 
| cannam@95 | 658                 cout << "vamp:" << key << endl; | 
| cannam@47 | 659             } | 
| cannam@95 | 660 | 
| cannam@99 | 661             Plugin::OutputList outputs = | 
| cannam@64 | 662                 plugin->getOutputDescriptors(); | 
| cannam@64 | 663 | 
| cannam@240 | 664             if (verbosity == PluginInformationDetailed) { | 
| cannam@240 | 665 | 
| cannam@240 | 666                 Plugin::ParameterList params = plugin->getParameterDescriptors(); | 
| cannam@240 | 667                 for (size_t j = 0; j < params.size(); ++j) { | 
| cannam@240 | 668                     Plugin::ParameterDescriptor &pd(params[j]); | 
| cannam@240 | 669                     cout << "\nParameter " << j+1 << ": \"" << pd.name << "\"" << endl; | 
| cannam@240 | 670                     cout << "- Identifier:         " << pd.identifier << endl; | 
| cannam@240 | 671                     cout << "- Description:        \"" << pd.description << "\"" << endl; | 
| cannam@240 | 672                     if (pd.unit != "") { | 
| cannam@240 | 673                         cout << "- Unit:               " << pd.unit << endl; | 
| cannam@240 | 674                     } | 
| cannam@240 | 675                     cout << "- Range:              "; | 
| cannam@240 | 676                     cout << pd.minValue << " -> " << pd.maxValue << endl; | 
| cannam@240 | 677                     cout << "- Default:            "; | 
| cannam@240 | 678                     cout << pd.defaultValue << endl; | 
| cannam@240 | 679                     if (pd.isQuantized) { | 
| cannam@240 | 680                         cout << "- Quantize Step:      " | 
| cannam@240 | 681                              << pd.quantizeStep << endl; | 
| cannam@240 | 682                     } | 
| cannam@240 | 683                     if (!pd.valueNames.empty()) { | 
| cannam@240 | 684                         cout << "- Value Names:        "; | 
| cannam@240 | 685                         for (size_t k = 0; k < pd.valueNames.size(); ++k) { | 
| cannam@240 | 686                             if (k > 0) cout << ", "; | 
| cannam@240 | 687                             cout << "\"" << pd.valueNames[k] << "\""; | 
| cannam@240 | 688                         } | 
| cannam@240 | 689                         cout << endl; | 
| cannam@240 | 690                     } | 
| cannam@240 | 691                 } | 
| cannam@240 | 692 | 
| cannam@240 | 693                 if (outputs.empty()) { | 
| cannam@240 | 694                     cout << "\n** Note: This plugin reports no outputs!" << endl; | 
| cannam@240 | 695                 } | 
| cannam@240 | 696                 for (size_t j = 0; j < outputs.size(); ++j) { | 
| cannam@240 | 697                     Plugin::OutputDescriptor &od(outputs[j]); | 
| cannam@240 | 698                     cout << "\nOutput " << j+1 << ": \"" << od.name << "\"" << endl; | 
| cannam@240 | 699                     cout << "- Identifier:         " << od.identifier << endl; | 
| cannam@240 | 700                     cout << "- Description:        \"" << od.description << "\"" << endl; | 
| cannam@240 | 701                     if (od.unit != "") { | 
| cannam@240 | 702                         cout << "- Unit:               " << od.unit << endl; | 
| cannam@240 | 703                     } | 
| cannam@240 | 704                     if (od.hasFixedBinCount) { | 
| cannam@240 | 705                         cout << "- Default Bin Count:  " << od.binCount << endl; | 
| cannam@240 | 706                     } | 
| cannam@240 | 707                     if (!od.binNames.empty()) { | 
| cannam@240 | 708                         cout << "- Bin Names:          "; | 
| cannam@240 | 709                         for (size_t k = 0; k < od.binNames.size(); ++k) { | 
| cannam@240 | 710                             if (k > 0) cout << ", "; | 
| cannam@240 | 711                             cout << "\"" << od.binNames[k] << "\""; | 
| cannam@240 | 712                         } | 
| cannam@240 | 713                         cout << endl; | 
| cannam@240 | 714                     } | 
| cannam@240 | 715                     if (od.hasKnownExtents) { | 
| cannam@240 | 716                         cout << "- Default Extents:    "; | 
| cannam@240 | 717                         cout << od.minValue << " -> " << od.maxValue << endl; | 
| cannam@240 | 718                     } | 
| cannam@240 | 719                     if (od.isQuantized) { | 
| cannam@240 | 720                         cout << "- Quantize Step:      " | 
| cannam@240 | 721                              << od.quantizeStep << endl; | 
| cannam@240 | 722                     } | 
| cannam@240 | 723                     cout << "- Sample Type:        " | 
| cannam@240 | 724                          << (od.sampleType == | 
| cannam@240 | 725                              Plugin::OutputDescriptor::OneSamplePerStep ? | 
| cannam@240 | 726                              "One Sample Per Step" : | 
| cannam@240 | 727                              od.sampleType == | 
| cannam@240 | 728                              Plugin::OutputDescriptor::FixedSampleRate ? | 
| cannam@240 | 729                              "Fixed Sample Rate" : | 
| cannam@240 | 730                              "Variable Sample Rate") << endl; | 
| cannam@240 | 731                     if (od.sampleType != | 
| cannam@240 | 732                         Plugin::OutputDescriptor::OneSamplePerStep) { | 
| cannam@240 | 733                         cout << "- Default Rate:       " | 
| cannam@240 | 734                              << od.sampleRate << endl; | 
| cannam@240 | 735                     } | 
| cannam@240 | 736                     cout << "- Has Duration:       " | 
| cannam@240 | 737                          << (od.hasDuration ? "Yes" : "No") << endl; | 
| cannam@240 | 738                 } | 
| cannam@240 | 739             } | 
| cannam@240 | 740 | 
| cannam@95 | 741             if (outputs.size() > 1 || verbosity == PluginOutputIds) { | 
| cannam@64 | 742                 for (size_t j = 0; j < outputs.size(); ++j) { | 
| cannam@95 | 743                     if (verbosity == PluginInformation) { | 
| cannam@95 | 744                         cout << "         (" << j << ") " | 
| cannam@95 | 745                              << outputs[j].name << ", \"" | 
| cannam@95 | 746                              << outputs[j].identifier << "\"" << endl; | 
| cannam@95 | 747                         if (outputs[j].description != "") { | 
| cannam@95 | 748                             cout << "             - " | 
| cannam@95 | 749                                  << outputs[j].description << endl; | 
| cannam@95 | 750                         } | 
| cannam@95 | 751                     } else if (verbosity == PluginOutputIds) { | 
| cannam@95 | 752                         cout << "vamp:" << key << ":" << outputs[j].identifier << endl; | 
| cannam@40 | 753                     } | 
| cannam@40 | 754                 } | 
| cannam@64 | 755             } | 
| cannam@64 | 756 | 
| cannam@64 | 757             ++index; | 
| cannam@64 | 758 | 
| cannam@64 | 759             delete plugin; | 
| cannam@40 | 760         } | 
| cannam@40 | 761     } | 
| cannam@64 | 762 | 
| cannam@240 | 763     if (verbosity == PluginInformation || | 
| cannam@240 | 764         verbosity == PluginInformationDetailed) { | 
| cannam@95 | 765         cout << endl; | 
| cannam@95 | 766     } | 
| cannam@40 | 767 } | 
| cannam@40 | 768 | 
| cannam@40 | 769 void | 
| cannam@99 | 770 printPluginCategoryList() | 
| cannam@16 | 771 { | 
| cannam@99 | 772     PluginLoader *loader = PluginLoader::getInstance(); | 
| cannam@88 | 773 | 
| cannam@99 | 774     vector<PluginLoader::PluginKey> plugins = loader->listPlugins(); | 
| cannam@88 | 775 | 
| cannam@99 | 776     set<string> printedcats; | 
| cannam@99 | 777 | 
| cannam@99 | 778     for (size_t i = 0; i < plugins.size(); ++i) { | 
| cannam@99 | 779 | 
| cannam@99 | 780         PluginLoader::PluginKey key = plugins[i]; | 
| cannam@99 | 781 | 
| cannam@99 | 782         PluginLoader::PluginCategoryHierarchy category = | 
| cannam@99 | 783             loader->getPluginCategory(key); | 
| cannam@99 | 784 | 
| cannam@99 | 785         Plugin *plugin = loader->loadPlugin(key, 48000); | 
| cannam@99 | 786         if (!plugin) continue; | 
| cannam@99 | 787 | 
| cannam@99 | 788         string catstr = ""; | 
| cannam@99 | 789 | 
| cannam@99 | 790         if (category.empty()) catstr = '|'; | 
| cannam@99 | 791         else { | 
| cannam@99 | 792             for (size_t j = 0; j < category.size(); ++j) { | 
| cannam@99 | 793                 catstr += category[j]; | 
| cannam@99 | 794                 catstr += '|'; | 
| cannam@99 | 795                 if (printedcats.find(catstr) == printedcats.end()) { | 
| cannam@99 | 796                     std::cout << catstr << std::endl; | 
| cannam@99 | 797                     printedcats.insert(catstr); | 
| cannam@99 | 798                 } | 
| cannam@99 | 799             } | 
| cannam@16 | 800         } | 
| cannam@88 | 801 | 
| cannam@99 | 802         std::cout << catstr << key << ":::" << plugin->getName() << ":::" << plugin->getMaker() << ":::" << plugin->getDescription() << std::endl; | 
| cannam@16 | 803     } | 
| cannam@16 | 804 } | 
| cannam@16 | 805 |