| Chris@0 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@0 | 2 | 
| Chris@0 | 3 /* | 
| Chris@0 | 4     Sonic Annotator | 
| Chris@0 | 5     A utility for batch feature extraction from audio files. | 
| Chris@0 | 6     Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. | 
| Chris@0 | 7     Copyright 2007-2008 QMUL. | 
| Chris@0 | 8 | 
| Chris@0 | 9     This program is free software; you can redistribute it and/or | 
| Chris@0 | 10     modify it under the terms of the GNU General Public License as | 
| Chris@0 | 11     published by the Free Software Foundation; either version 2 of the | 
| Chris@0 | 12     License, or (at your option) any later version.  See the file | 
| Chris@0 | 13     COPYING included with this distribution for more information. | 
| Chris@0 | 14 */ | 
| Chris@0 | 15 | 
| Chris@0 | 16 #include "FeatureExtractionManager.h" | 
| Chris@0 | 17 | 
| Chris@0 | 18 #include <vamp-hostsdk/PluginChannelAdapter.h> | 
| Chris@0 | 19 #include <vamp-hostsdk/PluginBufferingAdapter.h> | 
| Chris@0 | 20 #include <vamp-hostsdk/PluginInputDomainAdapter.h> | 
| Chris@0 | 21 #include <vamp-hostsdk/PluginSummarisingAdapter.h> | 
| Chris@8 | 22 #include <vamp-hostsdk/PluginWrapper.h> | 
| Chris@0 | 23 #include <vamp-hostsdk/PluginLoader.h> | 
| Chris@0 | 24 | 
| Chris@21 | 25 #include "base/Exceptions.h" | 
| Chris@21 | 26 | 
| Chris@0 | 27 #include <iostream> | 
| Chris@0 | 28 | 
| Chris@0 | 29 using namespace std; | 
| Chris@0 | 30 | 
| Chris@0 | 31 using Vamp::Plugin; | 
| Chris@0 | 32 using Vamp::PluginBase; | 
| Chris@0 | 33 using Vamp::HostExt::PluginLoader; | 
| Chris@0 | 34 using Vamp::HostExt::PluginChannelAdapter; | 
| Chris@0 | 35 using Vamp::HostExt::PluginBufferingAdapter; | 
| Chris@0 | 36 using Vamp::HostExt::PluginInputDomainAdapter; | 
| Chris@0 | 37 using Vamp::HostExt::PluginSummarisingAdapter; | 
| Chris@8 | 38 using Vamp::HostExt::PluginWrapper; | 
| Chris@0 | 39 | 
| Chris@0 | 40 #include "data/fileio/FileSource.h" | 
| Chris@0 | 41 #include "data/fileio/AudioFileReader.h" | 
| Chris@0 | 42 #include "data/fileio/AudioFileReaderFactory.h" | 
| Chris@0 | 43 #include "data/fileio/PlaylistFileReader.h" | 
| Chris@0 | 44 #include "base/TempDirectory.h" | 
| Chris@0 | 45 #include "base/ProgressPrinter.h" | 
| Chris@0 | 46 #include "transform/TransformFactory.h" | 
| Chris@0 | 47 #include "rdf/RDFTransformFactory.h" | 
| Chris@0 | 48 #include "transform/FeatureWriter.h" | 
| Chris@0 | 49 | 
| Chris@0 | 50 #include <QTextStream> | 
| Chris@0 | 51 #include <QFile> | 
| Chris@0 | 52 #include <QFileInfo> | 
| Chris@0 | 53 | 
| Chris@0 | 54 FeatureExtractionManager::FeatureExtractionManager() : | 
| Chris@0 | 55     m_summariesOnly(false), | 
| Chris@0 | 56     // We can read using an arbitrary fixed block size -- | 
| Chris@0 | 57     // PluginBufferingAdapter handles this for us.  It's likely to be | 
| Chris@0 | 58     // quicker to use larger sizes than smallish ones like 1024 | 
| Chris@0 | 59     m_blockSize(16384), | 
| Chris@0 | 60     m_defaultSampleRate(0), | 
| Chris@0 | 61     m_sampleRate(0), | 
| Chris@45 | 62     m_channels(0) | 
| Chris@0 | 63 { | 
| Chris@0 | 64 } | 
| Chris@0 | 65 | 
| Chris@0 | 66 FeatureExtractionManager::~FeatureExtractionManager() | 
| Chris@0 | 67 { | 
| Chris@0 | 68     for (PluginMap::iterator pi = m_plugins.begin(); | 
| Chris@0 | 69          pi != m_plugins.end(); ++pi) { | 
| Chris@0 | 70         delete pi->first; | 
| Chris@0 | 71     } | 
| Chris@45 | 72     foreach (AudioFileReader *r, m_readyReaders) { | 
| Chris@45 | 73         delete r; | 
| Chris@45 | 74     } | 
| Chris@0 | 75 } | 
| Chris@0 | 76 | 
| Chris@0 | 77 void FeatureExtractionManager::setChannels(int channels) | 
| Chris@0 | 78 { | 
| Chris@0 | 79     m_channels = channels; | 
| Chris@0 | 80 } | 
| Chris@0 | 81 | 
| Chris@0 | 82 void FeatureExtractionManager::setDefaultSampleRate(int sampleRate) | 
| Chris@0 | 83 { | 
| Chris@0 | 84     m_defaultSampleRate = sampleRate; | 
| Chris@0 | 85 } | 
| Chris@0 | 86 | 
| Chris@0 | 87 static PluginSummarisingAdapter::SummaryType | 
| Chris@0 | 88 getSummaryType(string name) | 
| Chris@0 | 89 { | 
| Chris@0 | 90     if (name == "min")      return PluginSummarisingAdapter::Minimum; | 
| Chris@0 | 91     if (name == "max")      return PluginSummarisingAdapter::Maximum; | 
| Chris@0 | 92     if (name == "mean")     return PluginSummarisingAdapter::Mean; | 
| Chris@0 | 93     if (name == "median")   return PluginSummarisingAdapter::Median; | 
| Chris@0 | 94     if (name == "mode")     return PluginSummarisingAdapter::Mode; | 
| Chris@0 | 95     if (name == "sum")      return PluginSummarisingAdapter::Sum; | 
| Chris@0 | 96     if (name == "variance") return PluginSummarisingAdapter::Variance; | 
| Chris@0 | 97     if (name == "sd")       return PluginSummarisingAdapter::StandardDeviation; | 
| Chris@0 | 98     if (name == "count")    return PluginSummarisingAdapter::Count; | 
| Chris@0 | 99     return PluginSummarisingAdapter::UnknownSummaryType; | 
| Chris@0 | 100 } | 
| Chris@0 | 101 | 
| Chris@0 | 102 bool FeatureExtractionManager::setSummaryTypes(const set<string> &names, | 
| Chris@0 | 103                                                bool summariesOnly, | 
| Chris@0 | 104                                                const PluginSummarisingAdapter::SegmentBoundaries &boundaries) | 
| Chris@0 | 105 { | 
| Chris@0 | 106     for (SummaryNameSet::const_iterator i = names.begin(); | 
| Chris@0 | 107          i != names.end(); ++i) { | 
| Chris@0 | 108         if (getSummaryType(*i) == PluginSummarisingAdapter::UnknownSummaryType) { | 
| Chris@0 | 109             cerr << "ERROR: Unknown summary type \"" << *i << "\"" << endl; | 
| Chris@0 | 110             return false; | 
| Chris@0 | 111         } | 
| Chris@0 | 112     } | 
| Chris@0 | 113     m_summaries = names; | 
| Chris@0 | 114     m_summariesOnly = summariesOnly; | 
| Chris@0 | 115     m_boundaries = boundaries; | 
| Chris@0 | 116     return true; | 
| Chris@0 | 117 } | 
| Chris@0 | 118 | 
| Chris@0 | 119 bool FeatureExtractionManager::addFeatureExtractor | 
| Chris@0 | 120 (Transform transform, const vector<FeatureWriter*> &writers) | 
| Chris@0 | 121 { | 
| Chris@0 | 122     //!!! exceptions rather than return values? | 
| Chris@0 | 123 | 
| Chris@0 | 124     if (transform.getSampleRate() == 0) { | 
| Chris@0 | 125         if (m_sampleRate == 0) { | 
| Chris@0 | 126             cerr << "NOTE: Transform does not specify a sample rate, using default rate of " << m_defaultSampleRate << endl; | 
| Chris@0 | 127             transform.setSampleRate(m_defaultSampleRate); | 
| Chris@0 | 128             m_sampleRate = m_defaultSampleRate; | 
| Chris@0 | 129         } else { | 
| Chris@0 | 130             cerr << "NOTE: Transform does not specify a sample rate, using previous transform's rate of " << m_sampleRate << endl; | 
| Chris@0 | 131             transform.setSampleRate(m_sampleRate); | 
| Chris@0 | 132         } | 
| Chris@0 | 133     } | 
| Chris@0 | 134 | 
| Chris@0 | 135     if (m_sampleRate == 0) { | 
| Chris@0 | 136         m_sampleRate = transform.getSampleRate(); | 
| Chris@0 | 137     } | 
| Chris@0 | 138 | 
| Chris@0 | 139     if (transform.getSampleRate() != m_sampleRate) { | 
| Chris@0 | 140         cerr << "WARNING: Transform sample rate " << transform.getSampleRate() << " does not match previously specified transform rate of " << m_sampleRate << " -- only a single rate is supported for each run" << endl; | 
| Chris@0 | 141         cerr << "WARNING: Using previous rate of " << m_sampleRate << " for this transform as well" << endl; | 
| Chris@0 | 142         transform.setSampleRate(m_sampleRate); | 
| Chris@0 | 143     } | 
| Chris@0 | 144 | 
| Chris@0 | 145     Plugin *plugin = 0; | 
| Chris@0 | 146 | 
| Chris@0 | 147     // Remember what the original transform looked like, and index | 
| Chris@0 | 148     // based on this -- because we may be about to fill in the zeros | 
| Chris@0 | 149     // for step and block size, but we want any further copies with | 
| Chris@0 | 150     // the same zeros to match this one | 
| Chris@0 | 151     Transform originalTransform = transform; | 
| Chris@0 | 152 | 
| Chris@0 | 153     if (m_transformPluginMap.find(transform) == m_transformPluginMap.end()) { | 
| Chris@0 | 154 | 
| Chris@0 | 155         // Test whether we already have a transform that is identical | 
| Chris@0 | 156         // to this, except for the output requested and/or the summary | 
| Chris@0 | 157         // type -- if so, they should share plugin instances (a vital | 
| Chris@0 | 158         // optimisation) | 
| Chris@0 | 159 | 
| Chris@0 | 160         for (TransformPluginMap::iterator i = m_transformPluginMap.begin(); | 
| Chris@0 | 161              i != m_transformPluginMap.end(); ++i) { | 
| Chris@0 | 162             Transform test = i->first; | 
| Chris@0 | 163             test.setOutput(transform.getOutput()); | 
| Chris@0 | 164             test.setSummaryType(transform.getSummaryType()); | 
| Chris@0 | 165             if (transform == test) { | 
| Chris@0 | 166                 cerr << "NOTE: Already have transform identical to this one (for \"" | 
| Chris@0 | 167                      << transform.getIdentifier().toStdString() | 
| Chris@0 | 168                      << "\") in every detail except output identifier and/or " | 
| Chris@0 | 169                      << "summary type; sharing its plugin instance" << endl; | 
| Chris@0 | 170                 plugin = i->second; | 
| Chris@0 | 171                 if (transform.getSummaryType() != Transform::NoSummary && | 
| Chris@0 | 172                     !dynamic_cast<PluginSummarisingAdapter *>(plugin)) { | 
| Chris@0 | 173                     plugin = new PluginSummarisingAdapter(plugin); | 
| Chris@0 | 174                     i->second = plugin; | 
| Chris@0 | 175                 } | 
| Chris@0 | 176                 break; | 
| Chris@0 | 177             } | 
| Chris@0 | 178         } | 
| Chris@0 | 179 | 
| Chris@0 | 180         if (!plugin) { | 
| Chris@0 | 181 | 
| Chris@0 | 182             TransformFactory *tf = TransformFactory::getInstance(); | 
| Chris@0 | 183 | 
| Chris@0 | 184             PluginBase *pb = tf->instantiatePluginFor(transform); | 
| Chris@0 | 185             plugin = tf->downcastVampPlugin(pb); | 
| Chris@0 | 186             if (!plugin) { | 
| Chris@0 | 187                 //!!! todo: handle non-Vamp plugins too, or make the main --list | 
| Chris@0 | 188                 // option print out only Vamp transforms | 
| Chris@0 | 189                 cerr << "ERROR: Failed to load plugin for transform \"" | 
| Chris@0 | 190                      << transform.getIdentifier().toStdString() << "\"" << endl; | 
| Chris@0 | 191                 delete pb; | 
| Chris@0 | 192                 return false; | 
| Chris@0 | 193             } | 
| Chris@0 | 194 | 
| Chris@0 | 195             // We will provide the plugin with arbitrary step and | 
| Chris@0 | 196             // block sizes (so that we can use the same read/write | 
| Chris@0 | 197             // block size for all transforms), and to that end we use | 
| Chris@0 | 198             // a PluginBufferingAdapter.  However, we need to know the | 
| Chris@0 | 199             // underlying step size so that we can provide the right | 
| Chris@0 | 200             // context for dense outputs.  (Although, don't forget | 
| Chris@0 | 201             // that the PluginBufferingAdapter rewrites | 
| Chris@0 | 202             // OneSamplePerStep outputs so as to use FixedSampleRate | 
| Chris@0 | 203             // -- so it supplies the sample rate in the output | 
| Chris@0 | 204             // feature.  I'm not sure whether we can easily use that.) | 
| Chris@0 | 205 | 
| Chris@0 | 206             size_t pluginStepSize = plugin->getPreferredStepSize(); | 
| Chris@0 | 207             size_t pluginBlockSize = plugin->getPreferredBlockSize(); | 
| Chris@0 | 208 | 
| Chris@25 | 209             PluginInputDomainAdapter *pida = 0; | 
| Chris@25 | 210 | 
| Chris@0 | 211             // adapt the plugin for buffering, channels, etc. | 
| Chris@0 | 212             if (plugin->getInputDomain() == Plugin::FrequencyDomain) { | 
| Chris@25 | 213                 pida = new PluginInputDomainAdapter(plugin); | 
| Chris@26 | 214                 pida->setProcessTimestampMethod(PluginInputDomainAdapter::ShiftData); | 
| Chris@25 | 215                 plugin = pida; | 
| Chris@0 | 216             } | 
| Chris@0 | 217 | 
| Chris@0 | 218             PluginBufferingAdapter *pba = new PluginBufferingAdapter(plugin); | 
| Chris@0 | 219             plugin = pba; | 
| Chris@0 | 220 | 
| Chris@0 | 221             if (transform.getStepSize() != 0) { | 
| Chris@0 | 222                 pba->setPluginStepSize(transform.getStepSize()); | 
| Chris@0 | 223             } else { | 
| Chris@0 | 224                 transform.setStepSize(pluginStepSize); | 
| Chris@0 | 225             } | 
| Chris@0 | 226 | 
| Chris@0 | 227             if (transform.getBlockSize() != 0) { | 
| Chris@0 | 228                 pba->setPluginBlockSize(transform.getBlockSize()); | 
| Chris@0 | 229             } else { | 
| Chris@0 | 230                 transform.setBlockSize(pluginBlockSize); | 
| Chris@0 | 231             } | 
| Chris@0 | 232 | 
| Chris@0 | 233             plugin = new PluginChannelAdapter(plugin); | 
| Chris@0 | 234 | 
| Chris@0 | 235             if (!m_summaries.empty() || | 
| Chris@0 | 236                 transform.getSummaryType() != Transform::NoSummary) { | 
| Chris@0 | 237                 PluginSummarisingAdapter *adapter = | 
| Chris@0 | 238                     new PluginSummarisingAdapter(plugin); | 
| Chris@0 | 239                 adapter->setSummarySegmentBoundaries(m_boundaries); | 
| Chris@0 | 240                 plugin = adapter; | 
| Chris@0 | 241             } | 
| Chris@0 | 242 | 
| Chris@0 | 243             if (!plugin->initialise(m_channels, m_blockSize, m_blockSize)) { | 
| Chris@0 | 244                 cerr << "ERROR: Plugin initialise (channels = " << m_channels << ", stepSize = " << m_blockSize << ", blockSize = " << m_blockSize << ") failed." << endl; | 
| Chris@0 | 245                 delete plugin; | 
| Chris@0 | 246                 return false; | 
| Chris@0 | 247             } | 
| Chris@0 | 248 | 
| Chris@0 | 249 //            cerr << "Initialised plugin" << endl; | 
| Chris@0 | 250 | 
| Chris@0 | 251             size_t actualStepSize = 0; | 
| Chris@0 | 252             size_t actualBlockSize = 0; | 
| Chris@0 | 253             pba->getActualStepAndBlockSizes(actualStepSize, actualBlockSize); | 
| Chris@0 | 254             transform.setStepSize(actualStepSize); | 
| Chris@0 | 255             transform.setBlockSize(actualBlockSize); | 
| Chris@0 | 256 | 
| Chris@0 | 257             Plugin::OutputList outputs = plugin->getOutputDescriptors(); | 
| Chris@0 | 258             for (int i = 0; i < (int)outputs.size(); ++i) { | 
| Chris@0 | 259 | 
| Chris@0 | 260 //                cerr << "Newly initialised plugin output " << i << " has bin count " << outputs[i].binCount << endl; | 
| Chris@0 | 261 | 
| Chris@0 | 262                 m_pluginOutputs[plugin][outputs[i].identifier] = outputs[i]; | 
| Chris@0 | 263                 m_pluginOutputIndices[outputs[i].identifier] = i; | 
| Chris@0 | 264             } | 
| Chris@0 | 265 | 
| Chris@10 | 266             cerr << "NOTE: Loaded and initialised plugin for transform \"" | 
| Chris@25 | 267                  << transform.getIdentifier().toStdString() | 
| Chris@25 | 268                  << "\" with plugin step size " << actualStepSize | 
| Chris@25 | 269                  << " and block size " << actualBlockSize | 
| Chris@25 | 270                  << " (adapter step and block size " << m_blockSize << ")" | 
| Chris@25 | 271                  << endl; | 
| Chris@25 | 272 | 
| Chris@25 | 273             if (pida) { | 
| Chris@25 | 274                 cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is " | 
| Chris@25 | 275 | 
| Chris@25 | 276                      << pida->getTimestampAdjustment() << endl; | 
| Chris@25 | 277             } | 
| Chris@8 | 278 | 
| Chris@8 | 279         } else { | 
| Chris@8 | 280 | 
| Chris@8 | 281             if (transform.getStepSize() == 0 || transform.getBlockSize() == 0) { | 
| Chris@8 | 282 | 
| Chris@8 | 283                 PluginWrapper *pw = dynamic_cast<PluginWrapper *>(plugin); | 
| Chris@8 | 284                 if (pw) { | 
| Chris@8 | 285                     PluginBufferingAdapter *pba = | 
| Chris@8 | 286                         pw->getWrapper<PluginBufferingAdapter>(); | 
| Chris@8 | 287                     if (pba) { | 
| Chris@8 | 288                         size_t actualStepSize = 0; | 
| Chris@8 | 289                         size_t actualBlockSize = 0; | 
| Chris@8 | 290                         pba->getActualStepAndBlockSizes(actualStepSize, | 
| Chris@8 | 291                                                         actualBlockSize); | 
| Chris@8 | 292                         if (transform.getStepSize() == 0) { | 
| Chris@8 | 293                             transform.setStepSize(actualStepSize); | 
| Chris@8 | 294                         } | 
| Chris@8 | 295                         if (transform.getBlockSize() == 0) { | 
| Chris@8 | 296                             transform.setBlockSize(actualBlockSize); | 
| Chris@8 | 297                         } | 
| Chris@8 | 298                     } | 
| Chris@8 | 299                 } | 
| Chris@8 | 300             } | 
| Chris@0 | 301         } | 
| Chris@0 | 302 | 
| Chris@0 | 303         if (transform.getOutput() == "") { | 
| Chris@0 | 304             transform.setOutput | 
| Chris@0 | 305                 (plugin->getOutputDescriptors()[0].identifier.c_str()); | 
| Chris@0 | 306         } | 
| Chris@0 | 307 | 
| Chris@0 | 308         m_transformPluginMap[transform] = plugin; | 
| Chris@0 | 309 | 
| Chris@0 | 310         if (!(originalTransform == transform)) { | 
| Chris@0 | 311             m_transformPluginMap[originalTransform] = plugin; | 
| Chris@0 | 312         } | 
| Chris@0 | 313 | 
| Chris@0 | 314     } else { | 
| Chris@0 | 315 | 
| Chris@0 | 316         plugin = m_transformPluginMap[transform]; | 
| Chris@0 | 317     } | 
| Chris@0 | 318 | 
| Chris@0 | 319     m_plugins[plugin][transform] = writers; | 
| Chris@0 | 320 | 
| Chris@0 | 321     return true; | 
| Chris@0 | 322 } | 
| Chris@0 | 323 | 
| Chris@0 | 324 bool FeatureExtractionManager::addDefaultFeatureExtractor | 
| Chris@0 | 325 (TransformId transformId, const vector<FeatureWriter*> &writers) | 
| Chris@0 | 326 { | 
| Chris@0 | 327     TransformFactory *tf = TransformFactory::getInstance(); | 
| Chris@0 | 328 | 
| Chris@0 | 329     if (m_sampleRate == 0) { | 
| Chris@0 | 330         if (m_defaultSampleRate == 0) { | 
| Chris@0 | 331             cerr << "ERROR: Default transform requested, but no default sample rate available" << endl; | 
| Chris@0 | 332             return false; | 
| Chris@0 | 333         } else { | 
| Chris@0 | 334             cerr << "NOTE: Using default sample rate of " << m_defaultSampleRate << " for default transform" << endl; | 
| Chris@0 | 335             m_sampleRate = m_defaultSampleRate; | 
| Chris@0 | 336         } | 
| Chris@0 | 337     } | 
| Chris@0 | 338 | 
| Chris@0 | 339     Transform transform = tf->getDefaultTransformFor(transformId, m_sampleRate); | 
| Chris@0 | 340 | 
| Chris@0 | 341     return addFeatureExtractor(transform, writers); | 
| Chris@0 | 342 } | 
| Chris@0 | 343 | 
| Chris@0 | 344 bool FeatureExtractionManager::addFeatureExtractorFromFile | 
| Chris@0 | 345 (QString transformXmlFile, const vector<FeatureWriter*> &writers) | 
| Chris@0 | 346 { | 
| Chris@0 | 347     RDFTransformFactory factory | 
| Chris@0 | 348         (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath()) | 
| Chris@0 | 349          .toString()); | 
| Chris@0 | 350     ProgressPrinter printer("Parsing transforms RDF file"); | 
| Chris@0 | 351     std::vector<Transform> transforms = factory.getTransforms(&printer); | 
| Chris@0 | 352     if (!factory.isOK()) { | 
| Chris@0 | 353         cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl; | 
| Chris@0 | 354         if (factory.isRDF()) { | 
| Chris@0 | 355             return false; // no point trying it as XML | 
| Chris@0 | 356         } | 
| Chris@0 | 357     } | 
| Chris@0 | 358     if (!transforms.empty()) { | 
| Chris@0 | 359         bool success = true; | 
| Chris@0 | 360         for (int i = 0; i < (int)transforms.size(); ++i) { | 
| Chris@0 | 361             if (!addFeatureExtractor(transforms[i], writers)) { | 
| Chris@0 | 362                 success = false; | 
| Chris@0 | 363             } | 
| Chris@0 | 364         } | 
| Chris@0 | 365         return success; | 
| Chris@0 | 366     } | 
| Chris@0 | 367 | 
| Chris@0 | 368     QFile file(transformXmlFile); | 
| Chris@0 | 369     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { | 
| Chris@0 | 370         cerr << "ERROR: Failed to open transform XML file \"" | 
| Chris@0 | 371              << transformXmlFile.toStdString() << "\" for reading" << endl; | 
| Chris@0 | 372         return false; | 
| Chris@0 | 373     } | 
| Chris@0 | 374 | 
| Chris@0 | 375     QTextStream *qts = new QTextStream(&file); | 
| Chris@0 | 376     QString qs = qts->readAll(); | 
| Chris@0 | 377     delete qts; | 
| Chris@0 | 378     file.close(); | 
| Chris@0 | 379 | 
| Chris@0 | 380     Transform transform(qs); | 
| Chris@0 | 381 | 
| Chris@0 | 382     return addFeatureExtractor(transform, writers); | 
| Chris@0 | 383 } | 
| Chris@0 | 384 | 
| Chris@45 | 385 void FeatureExtractionManager::addSource(QString audioSource) | 
| Chris@0 | 386 { | 
| Chris@0 | 387     if (QFileInfo(audioSource).suffix().toLower() == "m3u") { | 
| Chris@45 | 388         ProgressPrinter retrievalProgress("Opening playlist file..."); | 
| Chris@45 | 389         FileSource source(audioSource, &retrievalProgress); | 
| Chris@45 | 390         if (!source.isAvailable()) { | 
| Chris@45 | 391             cerr << "ERROR: File or URL \"" << audioSource.toStdString() | 
| Chris@45 | 392                  << "\" could not be located" << endl; | 
| Chris@45 | 393             throw FileNotFound(audioSource); | 
| Chris@45 | 394         } | 
| Chris@45 | 395         source.waitForData(); | 
| Chris@0 | 396         PlaylistFileReader reader(source); | 
| Chris@0 | 397         if (reader.isOK()) { | 
| Chris@0 | 398             vector<QString> files = reader.load(); | 
| Chris@0 | 399             for (int i = 0; i < (int)files.size(); ++i) { | 
| Chris@45 | 400                 addSource(files[i]); | 
| Chris@0 | 401             } | 
| Chris@0 | 402             return; | 
| Chris@0 | 403         } else { | 
| Chris@0 | 404             cerr << "ERROR: Playlist \"" << audioSource.toStdString() | 
| Chris@0 | 405                  << "\" could not be opened" << endl; | 
| Chris@21 | 406             throw FileNotFound(audioSource); | 
| Chris@0 | 407         } | 
| Chris@0 | 408     } | 
| Chris@0 | 409 | 
| Chris@45 | 410     std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl; | 
| Chris@45 | 411 | 
| Chris@45 | 412     // We don't actually do anything with it here, unless it's the | 
| Chris@45 | 413     // first audio source and we need it to establish default channel | 
| Chris@45 | 414     // count and sample rate | 
| Chris@45 | 415 | 
| Chris@45 | 416     if (m_channels == 0 || m_defaultSampleRate == 0) { | 
| Chris@45 | 417 | 
| Chris@45 | 418         ProgressPrinter retrievalProgress("Determining default rate and channel count from first input file..."); | 
| Chris@45 | 419 | 
| Chris@45 | 420         FileSource source(audioSource, &retrievalProgress); | 
| Chris@45 | 421         if (!source.isAvailable()) { | 
| Chris@45 | 422             cerr << "ERROR: File or URL \"" << audioSource.toStdString() | 
| Chris@45 | 423                  << "\" could not be located" << endl; | 
| Chris@45 | 424             throw FileNotFound(audioSource); | 
| Chris@45 | 425         } | 
| Chris@45 | 426 | 
| Chris@45 | 427         source.waitForData(); | 
| Chris@45 | 428 | 
| Chris@45 | 429         // Open to determine validity, channel count, sample rate only | 
| Chris@45 | 430         // (then close, and open again later with actual desired rate &c) | 
| Chris@45 | 431 | 
| Chris@45 | 432         AudioFileReader *reader = | 
| Chris@45 | 433             AudioFileReaderFactory::createReader(source, 0, &retrievalProgress); | 
| Chris@45 | 434 | 
| Chris@45 | 435         if (!reader) { | 
| Chris@45 | 436             throw FailedToOpenFile(audioSource); | 
| Chris@45 | 437         } | 
| Chris@45 | 438 | 
| Chris@45 | 439         retrievalProgress.done(); | 
| Chris@45 | 440 | 
| Chris@45 | 441         cerr << "File or URL \"" << audioSource.toStdString() << "\" opened successfully" << endl; | 
| Chris@45 | 442 | 
| Chris@45 | 443         if (m_channels == 0) { | 
| Chris@45 | 444             m_channels = reader->getChannelCount(); | 
| Chris@45 | 445             cerr << "Taking default channel count of " | 
| Chris@45 | 446                  << reader->getChannelCount() << " from file" << endl; | 
| Chris@45 | 447         } | 
| Chris@45 | 448 | 
| Chris@45 | 449         if (m_defaultSampleRate == 0) { | 
| Chris@45 | 450             m_defaultSampleRate = reader->getNativeRate(); | 
| Chris@45 | 451             cerr << "Taking default sample rate of " | 
| Chris@45 | 452                  << reader->getNativeRate() << "Hz from file" << endl; | 
| Chris@45 | 453             cerr << "(Note: Default may be overridden by transforms)" << endl; | 
| Chris@45 | 454         } | 
| Chris@45 | 455 | 
| Chris@45 | 456         m_readyReaders[audioSource] = reader; | 
| Chris@45 | 457     } | 
| Chris@45 | 458 } | 
| Chris@45 | 459 | 
| Chris@47 | 460 void FeatureExtractionManager::extractFeatures(QString audioSource, bool force) | 
| Chris@45 | 461 { | 
| Chris@45 | 462     if (m_plugins.empty()) return; | 
| Chris@45 | 463 | 
| Chris@47 | 464     if (QFileInfo(audioSource).suffix().toLower() == "m3u") { | 
| Chris@47 | 465         FileSource source(audioSource); | 
| Chris@47 | 466         PlaylistFileReader reader(source); | 
| Chris@47 | 467         if (reader.isOK()) { | 
| Chris@47 | 468             vector<QString> files = reader.load(); | 
| Chris@47 | 469             for (int i = 0; i < (int)files.size(); ++i) { | 
| Chris@47 | 470                 try { | 
| Chris@47 | 471                     extractFeatures(files[i], force); | 
| Chris@47 | 472                 } catch (const std::exception &e) { | 
| Chris@47 | 473                     if (!force) throw; | 
| Chris@47 | 474                     cerr << "ERROR: Feature extraction failed for playlist entry \"" | 
| Chris@47 | 475                          << files[i].toStdString() | 
| Chris@47 | 476                          << "\": " << e.what() << endl; | 
| Chris@47 | 477                     // print a note only if we have more files to process | 
| Chris@47 | 478                     if (++i != files.size()) { | 
| Chris@47 | 479                         cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl; | 
| Chris@47 | 480                     } | 
| Chris@47 | 481                 } | 
| Chris@47 | 482             } | 
| Chris@47 | 483             return; | 
| Chris@47 | 484         } else { | 
| Chris@47 | 485             cerr << "ERROR: Playlist \"" << audioSource.toStdString() | 
| Chris@47 | 486                  << "\" could not be opened" << endl; | 
| Chris@47 | 487             throw FileNotFound(audioSource); | 
| Chris@47 | 488         } | 
| Chris@47 | 489     } | 
| Chris@47 | 490 | 
| Chris@45 | 491     testOutputFiles(audioSource); | 
| Chris@45 | 492 | 
| Chris@0 | 493     if (m_sampleRate == 0) { | 
| Chris@45 | 494         throw FileOperationFailed | 
| Chris@45 | 495             (audioSource, "internal error: have sources and plugins, but no sample rate"); | 
| Chris@45 | 496     } | 
| Chris@45 | 497     if (m_channels == 0) { | 
| Chris@45 | 498         throw FileOperationFailed | 
| Chris@45 | 499             (audioSource, "internal error: have sources and plugins, but no channel count"); | 
| Chris@0 | 500     } | 
| Chris@0 | 501 | 
| Chris@45 | 502     AudioFileReader *reader = 0; | 
| Chris@45 | 503 | 
| Chris@45 | 504     if (m_readyReaders.contains(audioSource)) { | 
| Chris@45 | 505         reader = m_readyReaders[audioSource]; | 
| Chris@45 | 506         m_readyReaders.remove(audioSource); | 
| Chris@45 | 507         if (reader->getChannelCount() != m_channels || | 
| Chris@45 | 508             reader->getSampleRate() != m_sampleRate) { | 
| Chris@45 | 509             // can't use this; open it again | 
| Chris@45 | 510             delete reader; | 
| Chris@45 | 511             reader = 0; | 
| Chris@45 | 512         } | 
| Chris@45 | 513     } | 
| Chris@45 | 514     if (!reader) { | 
| Chris@45 | 515         ProgressPrinter retrievalProgress("Retrieving audio data..."); | 
| Chris@45 | 516         FileSource source(audioSource, &retrievalProgress); | 
| Chris@45 | 517         source.waitForData(); | 
| Chris@45 | 518         reader = AudioFileReaderFactory::createReader | 
| Chris@45 | 519             (source, m_sampleRate, &retrievalProgress); | 
| Chris@45 | 520         retrievalProgress.done(); | 
| Chris@45 | 521     } | 
| Chris@45 | 522 | 
| Chris@0 | 523     if (!reader) { | 
| Chris@21 | 524         throw FailedToOpenFile(audioSource); | 
| Chris@0 | 525     } | 
| Chris@0 | 526 | 
| Chris@45 | 527     cerr << "Audio file \"" << audioSource.toStdString() << "\": " | 
| Chris@45 | 528          << reader->getChannelCount() << "ch at " | 
| Chris@45 | 529          << reader->getNativeRate() << "Hz" << endl; | 
| Chris@45 | 530     if (reader->getChannelCount() != m_channels || | 
| Chris@45 | 531         reader->getNativeRate() != m_sampleRate) { | 
| Chris@45 | 532         cerr << "NOTE: File will be mixed or resampled for processing: " | 
| Chris@45 | 533              << m_channels << "ch at " | 
| Chris@45 | 534              << m_sampleRate << "Hz" << endl; | 
| Chris@45 | 535     } | 
| Chris@11 | 536 | 
| Chris@0 | 537     // allocate audio buffers | 
| Chris@0 | 538     float **data = new float *[m_channels]; | 
| Chris@0 | 539     for (int c = 0; c < m_channels; ++c) { | 
| Chris@0 | 540         data[c] = new float[m_blockSize]; | 
| Chris@0 | 541     } | 
| Chris@31 | 542 | 
| Chris@31 | 543     struct LifespanMgr { // unintrusive hack introduced to ensure | 
| Chris@31 | 544                          // destruction on exceptions | 
| Chris@31 | 545         AudioFileReader *m_r; | 
| Chris@31 | 546         int m_c; | 
| Chris@31 | 547         float **m_d; | 
| Chris@31 | 548         LifespanMgr(AudioFileReader *r, int c, float **d) : | 
| Chris@31 | 549             m_r(r), m_c(c), m_d(d) { } | 
| Chris@31 | 550         ~LifespanMgr() { destroy(); } | 
| Chris@31 | 551         void destroy() { | 
| Chris@31 | 552             if (!m_r) return; | 
| Chris@31 | 553             delete m_r; | 
| Chris@31 | 554             for (int i = 0; i < m_c; ++i) delete[] m_d[i]; | 
| Chris@31 | 555             delete[] m_d; | 
| Chris@31 | 556             m_r = 0; | 
| Chris@31 | 557         } | 
| Chris@31 | 558     }; | 
| Chris@31 | 559     LifespanMgr lifemgr(reader, m_channels, data); | 
| Chris@0 | 560 | 
| Chris@0 | 561     size_t frameCount = reader->getFrameCount(); | 
| Chris@0 | 562 | 
| Chris@0 | 563 //    cerr << "file has " << frameCount << " frames" << endl; | 
| Chris@0 | 564 | 
| Chris@0 | 565     for (PluginMap::iterator pi = m_plugins.begin(); | 
| Chris@0 | 566          pi != m_plugins.end(); ++pi) { | 
| Chris@0 | 567 | 
| Chris@0 | 568         Plugin *plugin = pi->first; | 
| Chris@0 | 569 | 
| Chris@0 | 570 //        std::cerr << "Calling reset on " << plugin << std::endl; | 
| Chris@0 | 571         plugin->reset(); | 
| Chris@0 | 572 | 
| Chris@0 | 573         for (TransformWriterMap::iterator ti = pi->second.begin(); | 
| Chris@0 | 574              ti != pi->second.end(); ++ti) { | 
| Chris@0 | 575 | 
| Chris@0 | 576             const Transform &transform = ti->first; | 
| Chris@0 | 577 | 
| Chris@0 | 578             //!!! we may want to set the start and duration times for extraction | 
| Chris@0 | 579             // in the transform record (defaults of zero indicate extraction | 
| Chris@0 | 580             // from the whole file) | 
| Chris@0 | 581 //            transform.setStartTime(RealTime::zeroTime); | 
| Chris@0 | 582 //            transform.setDuration | 
| Chris@0 | 583 //                (RealTime::frame2RealTime(reader->getFrameCount(), m_sampleRate)); | 
| Chris@0 | 584 | 
| Chris@0 | 585             string outputId = transform.getOutput().toStdString(); | 
| Chris@0 | 586             if (m_pluginOutputs[plugin].find(outputId) == | 
| Chris@0 | 587                 m_pluginOutputs[plugin].end()) { | 
| Chris@0 | 588                 //!!! throw? | 
| Chris@0 | 589                 cerr << "WARNING: Nonexistent plugin output \"" << outputId << "\" requested for transform \"" | 
| Chris@0 | 590                      << transform.getIdentifier().toStdString() << "\", ignoring this transform" | 
| Chris@0 | 591                      << endl; | 
| Chris@0 | 592 /* | 
| Chris@0 | 593                 cerr << "Known outputs for all plugins are as follows:" << endl; | 
| Chris@0 | 594                 for (PluginOutputMap::const_iterator k = m_pluginOutputs.begin(); | 
| Chris@0 | 595                      k != m_pluginOutputs.end(); ++k) { | 
| Chris@0 | 596                     cerr << "Plugin " << k->first << ": "; | 
| Chris@0 | 597                     if (k->second.empty()) { | 
| Chris@0 | 598                         cerr << "(none)"; | 
| Chris@0 | 599                     } | 
| Chris@0 | 600                     for (OutputMap::const_iterator i = k->second.begin(); | 
| Chris@0 | 601                          i != k->second.end(); ++i) { | 
| Chris@0 | 602                         cerr << "\"" << i->first << "\" "; | 
| Chris@0 | 603                     } | 
| Chris@0 | 604                     cerr << endl; | 
| Chris@0 | 605                 } | 
| Chris@0 | 606 */ | 
| Chris@0 | 607             } | 
| Chris@0 | 608         } | 
| Chris@0 | 609     } | 
| Chris@0 | 610 | 
| Chris@0 | 611     long startFrame = 0; | 
| Chris@0 | 612     long endFrame = frameCount; | 
| Chris@0 | 613 | 
| Chris@0 | 614 /*!!! No -- there is no single transform to pull this stuff from -- | 
| Chris@0 | 615  * the transforms may have various start and end times, need to be far | 
| Chris@0 | 616  * cleverer about this if we're going to support them | 
| Chris@0 | 617 | 
| Chris@0 | 618     RealTime trStartRT = transform.getStartTime(); | 
| Chris@0 | 619     RealTime trDurationRT = transform.getDuration(); | 
| Chris@0 | 620 | 
| Chris@0 | 621     long trStart = RealTime::realTime2Frame(trStartRT, m_sampleRate); | 
| Chris@0 | 622     long trDuration = RealTime::realTime2Frame(trDurationRT, m_sampleRate); | 
| Chris@0 | 623 | 
| Chris@0 | 624     if (trStart == 0 || trStart < startFrame) { | 
| Chris@0 | 625         trStart = startFrame; | 
| Chris@0 | 626     } | 
| Chris@0 | 627 | 
| Chris@0 | 628     if (trDuration == 0) { | 
| Chris@0 | 629         trDuration = endFrame - trStart; | 
| Chris@0 | 630     } | 
| Chris@0 | 631     if (trStart + trDuration > endFrame) { | 
| Chris@0 | 632         trDuration = endFrame - trStart; | 
| Chris@0 | 633     } | 
| Chris@0 | 634 | 
| Chris@0 | 635     startFrame = trStart; | 
| Chris@0 | 636     endFrame = trStart + trDuration; | 
| Chris@0 | 637 */ | 
| Chris@0 | 638 | 
| Chris@0 | 639     for (PluginMap::iterator pi = m_plugins.begin(); | 
| Chris@0 | 640          pi != m_plugins.end(); ++pi) { | 
| Chris@0 | 641 | 
| Chris@0 | 642         for (TransformWriterMap::const_iterator ti = pi->second.begin(); | 
| Chris@0 | 643              ti != pi->second.end(); ++ti) { | 
| Chris@0 | 644 | 
| Chris@0 | 645             const vector<FeatureWriter *> &writers = ti->second; | 
| Chris@0 | 646 | 
| Chris@0 | 647             for (int j = 0; j < (int)writers.size(); ++j) { | 
| Chris@0 | 648                 FeatureWriter::TrackMetadata m; | 
| Chris@0 | 649                 m.title = reader->getTitle(); | 
| Chris@0 | 650                 m.maker = reader->getMaker(); | 
| Chris@19 | 651                 if (m.title != "" && m.maker != "") { | 
| Chris@19 | 652                     writers[j]->setTrackMetadata(audioSource, m); | 
| Chris@19 | 653                 } | 
| Chris@0 | 654             } | 
| Chris@0 | 655         } | 
| Chris@0 | 656     } | 
| Chris@0 | 657 | 
| Chris@0 | 658     ProgressPrinter extractionProgress("Extracting and writing features..."); | 
| Chris@0 | 659     int progress = 0; | 
| Chris@0 | 660 | 
| Chris@0 | 661     for (long i = startFrame; i < endFrame; i += m_blockSize) { | 
| Chris@0 | 662 | 
| Chris@0 | 663         //!!! inefficient, although much of the inefficiency may be | 
| Chris@0 | 664         // susceptible to optimisation | 
| Chris@0 | 665 | 
| Chris@0 | 666         SampleBlock frames; | 
| Chris@0 | 667         reader->getInterleavedFrames(i, m_blockSize, frames); | 
| Chris@0 | 668 | 
| Chris@0 | 669         // We have to do our own channel handling here; we can't just | 
| Chris@0 | 670         // leave it to the plugin adapter because the same plugin | 
| Chris@0 | 671         // adapter may have to serve for input files with various | 
| Chris@0 | 672         // numbers of channels (so the adapter is simply configured | 
| Chris@34 | 673         // with a fixed channel count). | 
| Chris@0 | 674 | 
| Chris@0 | 675         int rc = reader->getChannelCount(); | 
| Chris@0 | 676 | 
| Chris@34 | 677         // m_channels is the number of channels we need for the plugin | 
| Chris@34 | 678 | 
| Chris@34 | 679         int index; | 
| Chris@34 | 680         int fc = (int)frames.size(); | 
| Chris@46 | 681 | 
| Chris@34 | 682         if (m_channels == 1) { // only case in which we can sensibly mix down | 
| Chris@34 | 683             for (int j = 0; j < m_blockSize; ++j) { | 
| Chris@34 | 684                 data[0][j] = 0.f; | 
| Chris@34 | 685             } | 
| Chris@34 | 686             for (int c = 0; c < rc; ++c) { | 
| Chris@34 | 687                 for (int j = 0; j < m_blockSize; ++j) { | 
| Chris@0 | 688                     index = j * rc + c; | 
| Chris@34 | 689                     if (index < fc) data[0][j] += frames[index]; | 
| Chris@0 | 690                 } | 
| Chris@0 | 691             } | 
| Chris@34 | 692             for (int j = 0; j < m_blockSize; ++j) { | 
| Chris@34 | 693                 data[0][j] /= rc; | 
| Chris@34 | 694             } | 
| Chris@34 | 695         } else { | 
| Chris@34 | 696             for (int c = 0; c < m_channels; ++c) { | 
| Chris@34 | 697                 for (int j = 0; j < m_blockSize; ++j) { | 
| Chris@34 | 698                     data[c][j] = 0.f; | 
| Chris@34 | 699                 } | 
| Chris@34 | 700                 if (c < rc) { | 
| Chris@34 | 701                     for (int j = 0; j < m_blockSize; ++j) { | 
| Chris@34 | 702                         index = j * rc + c; | 
| Chris@34 | 703                         if (index < fc) data[c][j] += frames[index]; | 
| Chris@34 | 704                     } | 
| Chris@34 | 705                 } | 
| Chris@34 | 706             } | 
| Chris@34 | 707         } | 
| Chris@0 | 708 | 
| Chris@0 | 709         Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime | 
| Chris@0 | 710             (i, m_sampleRate); | 
| Chris@0 | 711 | 
| Chris@0 | 712         for (PluginMap::iterator pi = m_plugins.begin(); | 
| Chris@0 | 713              pi != m_plugins.end(); ++pi) { | 
| Chris@0 | 714 | 
| Chris@0 | 715             Plugin *plugin = pi->first; | 
| Chris@0 | 716             Plugin::FeatureSet featureSet = plugin->process(data, timestamp); | 
| Chris@0 | 717 | 
| Chris@0 | 718             if (!m_summariesOnly) { | 
| Chris@0 | 719                 writeFeatures(audioSource, plugin, featureSet); | 
| Chris@0 | 720             } | 
| Chris@0 | 721         } | 
| Chris@0 | 722 | 
| Chris@0 | 723         int pp = progress; | 
| Chris@6 | 724         progress = int(((i - startFrame) * 100.0) / (endFrame - startFrame) + 0.1); | 
| Chris@0 | 725         if (progress > pp) extractionProgress.setProgress(progress); | 
| Chris@0 | 726     } | 
| Chris@10 | 727 | 
| Chris@22 | 728 //    std::cerr << "FeatureExtractionManager: deleting audio file reader" << std::endl; | 
| Chris@12 | 729 | 
| Chris@31 | 730     lifemgr.destroy(); // deletes reader, data | 
| Chris@0 | 731 | 
| Chris@0 | 732     for (PluginMap::iterator pi = m_plugins.begin(); | 
| Chris@0 | 733          pi != m_plugins.end(); ++pi) { | 
| Chris@0 | 734 | 
| Chris@0 | 735         Plugin *plugin = pi->first; | 
| Chris@0 | 736         Plugin::FeatureSet featureSet = plugin->getRemainingFeatures(); | 
| Chris@0 | 737 | 
| Chris@0 | 738         if (!m_summariesOnly) { | 
| Chris@0 | 739             writeFeatures(audioSource, plugin, featureSet); | 
| Chris@0 | 740         } | 
| Chris@0 | 741 | 
| Chris@0 | 742         if (!m_summaries.empty()) { | 
| Chris@0 | 743             PluginSummarisingAdapter *adapter = | 
| Chris@0 | 744                 dynamic_cast<PluginSummarisingAdapter *>(plugin); | 
| Chris@0 | 745             if (!adapter) { | 
| Chris@0 | 746                 cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl; | 
| Chris@0 | 747             } else { | 
| Chris@0 | 748                 for (SummaryNameSet::const_iterator sni = m_summaries.begin(); | 
| Chris@0 | 749                      sni != m_summaries.end(); ++sni) { | 
| Chris@0 | 750                     featureSet.clear(); | 
| Chris@0 | 751                     //!!! problem here -- we are requesting summaries | 
| Chris@0 | 752                     //!!! for all outputs, but they in principle have | 
| Chris@0 | 753                     //!!! different averaging requirements depending | 
| Chris@0 | 754                     //!!! on whether their features have duration or | 
| Chris@0 | 755                     //!!! not | 
| Chris@0 | 756                     featureSet = adapter->getSummaryForAllOutputs | 
| Chris@0 | 757                         (getSummaryType(*sni), | 
| Chris@0 | 758                          PluginSummarisingAdapter::ContinuousTimeAverage); | 
| Chris@0 | 759                     writeFeatures(audioSource, plugin, featureSet,//!!! *sni); | 
| Chris@0 | 760                                   Transform::stringToSummaryType(sni->c_str())); | 
| Chris@0 | 761                 } | 
| Chris@0 | 762             } | 
| Chris@0 | 763         } | 
| Chris@0 | 764 | 
| Chris@0 | 765         writeSummaries(audioSource, plugin); | 
| Chris@0 | 766     } | 
| Chris@0 | 767 | 
| Chris@3 | 768     extractionProgress.done(); | 
| Chris@3 | 769 | 
| Chris@0 | 770     finish(); | 
| Chris@0 | 771 | 
| Chris@0 | 772     TempDirectory::getInstance()->cleanup(); | 
| Chris@0 | 773 } | 
| Chris@0 | 774 | 
| Chris@0 | 775 void | 
| Chris@0 | 776 FeatureExtractionManager::writeSummaries(QString audioSource, Plugin *plugin) | 
| Chris@0 | 777 { | 
| Chris@0 | 778     // caller should have ensured plugin is in m_plugins | 
| Chris@0 | 779     PluginMap::iterator pi = m_plugins.find(plugin); | 
| Chris@0 | 780 | 
| Chris@0 | 781     for (TransformWriterMap::const_iterator ti = pi->second.begin(); | 
| Chris@0 | 782          ti != pi->second.end(); ++ti) { | 
| Chris@0 | 783 | 
| Chris@0 | 784         const Transform &transform = ti->first; | 
| Chris@0 | 785         const vector<FeatureWriter *> &writers = ti->second; | 
| Chris@0 | 786 | 
| Chris@0 | 787         Transform::SummaryType summaryType = transform.getSummaryType(); | 
| Chris@0 | 788         PluginSummarisingAdapter::SummaryType pType = | 
| Chris@0 | 789             (PluginSummarisingAdapter::SummaryType)summaryType; | 
| Chris@0 | 790 | 
| Chris@0 | 791         if (transform.getSummaryType() == Transform::NoSummary) { | 
| Chris@0 | 792             continue; | 
| Chris@0 | 793         } | 
| Chris@0 | 794 | 
| Chris@0 | 795         PluginSummarisingAdapter *adapter = | 
| Chris@0 | 796             dynamic_cast<PluginSummarisingAdapter *>(plugin); | 
| Chris@0 | 797         if (!adapter) { | 
| Chris@0 | 798             cerr << "FeatureExtractionManager::writeSummaries: INTERNAL ERROR: Summary requested for transform, but plugin is not a summarising adapter" << endl; | 
| Chris@0 | 799             continue; | 
| Chris@0 | 800         } | 
| Chris@0 | 801 | 
| Chris@0 | 802         Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs | 
| Chris@0 | 803             (pType, PluginSummarisingAdapter::ContinuousTimeAverage); | 
| Chris@0 | 804 | 
| Chris@0 | 805 //        cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl; | 
| Chris@0 | 806 | 
| Chris@0 | 807         writeFeatures(audioSource, plugin, featureSet, summaryType); | 
| Chris@0 | 808     } | 
| Chris@0 | 809 } | 
| Chris@0 | 810 | 
| Chris@0 | 811 void FeatureExtractionManager::writeFeatures(QString audioSource, | 
| Chris@0 | 812                                              Plugin *plugin, | 
| Chris@0 | 813                                              const Plugin::FeatureSet &features, | 
| Chris@0 | 814                                              Transform::SummaryType summaryType) | 
| Chris@0 | 815 { | 
| Chris@0 | 816     // caller should have ensured plugin is in m_plugins | 
| Chris@0 | 817     PluginMap::iterator pi = m_plugins.find(plugin); | 
| Chris@0 | 818 | 
| Chris@0 | 819     for (TransformWriterMap::const_iterator ti = pi->second.begin(); | 
| Chris@0 | 820          ti != pi->second.end(); ++ti) { | 
| Chris@0 | 821 | 
| Chris@0 | 822         const Transform &transform = ti->first; | 
| Chris@0 | 823         const vector<FeatureWriter *> &writers = ti->second; | 
| Chris@0 | 824 | 
| Chris@0 | 825         if (transform.getSummaryType() != Transform::NoSummary && | 
| Chris@0 | 826             m_summaries.empty() && | 
| Chris@0 | 827             summaryType == Transform::NoSummary) { | 
| Chris@0 | 828             continue; | 
| Chris@0 | 829         } | 
| Chris@0 | 830 | 
| Chris@0 | 831         if (transform.getSummaryType() != Transform::NoSummary && | 
| Chris@0 | 832             summaryType != Transform::NoSummary && | 
| Chris@0 | 833             transform.getSummaryType() != summaryType) { | 
| Chris@0 | 834             continue; | 
| Chris@0 | 835         } | 
| Chris@0 | 836 | 
| Chris@0 | 837         string outputId = transform.getOutput().toStdString(); | 
| Chris@0 | 838 | 
| Chris@0 | 839         if (m_pluginOutputs[plugin].find(outputId) == | 
| Chris@0 | 840             m_pluginOutputs[plugin].end()) { | 
| Chris@0 | 841             continue; | 
| Chris@0 | 842         } | 
| Chris@0 | 843 | 
| Chris@0 | 844         const Plugin::OutputDescriptor &desc = | 
| Chris@0 | 845             m_pluginOutputs[plugin][outputId]; | 
| Chris@0 | 846 | 
| Chris@0 | 847         int outputIndex = m_pluginOutputIndices[outputId]; | 
| Chris@0 | 848         Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex); | 
| Chris@0 | 849         if (fsi == features.end()) continue; | 
| Chris@0 | 850 | 
| Chris@0 | 851         for (int j = 0; j < (int)writers.size(); ++j) { | 
| Chris@0 | 852             writers[j]->write | 
| Chris@0 | 853                 (audioSource, transform, desc, fsi->second, | 
| Chris@0 | 854                  Transform::summaryTypeToString(summaryType).toStdString()); | 
| Chris@0 | 855         } | 
| Chris@0 | 856     } | 
| Chris@0 | 857 } | 
| Chris@0 | 858 | 
| Chris@31 | 859 void FeatureExtractionManager::testOutputFiles(QString audioSource) | 
| Chris@31 | 860 { | 
| Chris@31 | 861     for (PluginMap::iterator pi = m_plugins.begin(); | 
| Chris@31 | 862          pi != m_plugins.end(); ++pi) { | 
| Chris@31 | 863 | 
| Chris@31 | 864         for (TransformWriterMap::iterator ti = pi->second.begin(); | 
| Chris@31 | 865              ti != pi->second.end(); ++ti) { | 
| Chris@31 | 866 | 
| Chris@31 | 867             vector<FeatureWriter *> &writers = ti->second; | 
| Chris@31 | 868 | 
| Chris@31 | 869             for (int i = 0; i < (int)writers.size(); ++i) { | 
| Chris@31 | 870                 writers[i]->testOutputFile(audioSource, ti->first.getIdentifier()); | 
| Chris@31 | 871             } | 
| Chris@31 | 872         } | 
| Chris@31 | 873     } | 
| Chris@31 | 874 } | 
| Chris@31 | 875 | 
| Chris@0 | 876 void FeatureExtractionManager::finish() | 
| Chris@0 | 877 { | 
| Chris@0 | 878     for (PluginMap::iterator pi = m_plugins.begin(); | 
| Chris@0 | 879          pi != m_plugins.end(); ++pi) { | 
| Chris@0 | 880 | 
| Chris@0 | 881         for (TransformWriterMap::iterator ti = pi->second.begin(); | 
| Chris@0 | 882              ti != pi->second.end(); ++ti) { | 
| Chris@0 | 883 | 
| Chris@0 | 884             vector<FeatureWriter *> &writers = ti->second; | 
| Chris@0 | 885 | 
| Chris@0 | 886             for (int i = 0; i < (int)writers.size(); ++i) { | 
| Chris@0 | 887                 writers[i]->flush(); | 
| Chris@0 | 888                 writers[i]->finish(); | 
| Chris@0 | 889             } | 
| Chris@0 | 890         } | 
| Chris@0 | 891     } | 
| Chris@0 | 892 } | 
| Chris@0 | 893 | 
| Chris@0 | 894 void FeatureExtractionManager::print(Transform transform) const | 
| Chris@0 | 895 { | 
| Chris@0 | 896     QString qs; | 
| Chris@0 | 897     QTextStream qts(&qs); | 
| Chris@0 | 898     transform.toXml(qts); | 
| Chris@0 | 899     cerr << qs.toStdString() << endl; | 
| Chris@0 | 900 } |