FeatureExtractionManager.cpp

Matthias Mauch, 2011-09-21 12:50 PM

Download (35 KB)

 
1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2

    
3
/*
4
    Sonic Annotator
5
    A utility for batch feature extraction from audio files.
6
    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
7
    Copyright 2007-2008 QMUL.
8

9
    This program is free software; you can redistribute it and/or
10
    modify it under the terms of the GNU General Public License as
11
    published by the Free Software Foundation; either version 2 of the
12
    License, or (at your option) any later version.  See the file
13
    COPYING included with this distribution for more information.
14
*/
15

    
16
#include "FeatureExtractionManager.h"
17

    
18
#include <vamp-hostsdk/PluginChannelAdapter.h>
19
#include <vamp-hostsdk/PluginBufferingAdapter.h>
20
#include <vamp-hostsdk/PluginInputDomainAdapter.h>
21
#include <vamp-hostsdk/PluginSummarisingAdapter.h>
22
#include <vamp-hostsdk/PluginWrapper.h>
23
#include <vamp-hostsdk/PluginLoader.h>
24

    
25
#include "base/Exceptions.h"
26

    
27
#include <algorithm>
28
#include <iostream>
29

    
30
using namespace std;
31

    
32
using Vamp::Plugin;
33
using Vamp::PluginBase;
34
using Vamp::HostExt::PluginLoader;
35
using Vamp::HostExt::PluginChannelAdapter;
36
using Vamp::HostExt::PluginBufferingAdapter;
37
using Vamp::HostExt::PluginInputDomainAdapter;
38
using Vamp::HostExt::PluginSummarisingAdapter;
39
using Vamp::HostExt::PluginWrapper;
40

    
41
#include "data/fileio/FileSource.h"
42
#include "data/fileio/AudioFileReader.h"
43
#include "data/fileio/AudioFileReaderFactory.h"
44
#include "data/fileio/PlaylistFileReader.h"
45
#include "base/TempDirectory.h"
46
#include "base/ProgressPrinter.h"
47
#include "transform/TransformFactory.h"
48
#include "rdf/RDFTransformFactory.h"
49
#include "transform/FeatureWriter.h"
50

    
51
#include <QTextStream>
52
#include <QFile>
53
#include <QFileInfo>
54

    
55
FeatureExtractionManager::FeatureExtractionManager() :
56
    m_summariesOnly(false),
57
    // We can read using an arbitrary fixed block size --
58
    // PluginBufferingAdapter handles this for us.  It's likely to be
59
    // quicker to use larger sizes than smallish ones like 1024
60
    m_blockSize(16384),
61
    m_defaultSampleRate(0),
62
    m_sampleRate(0),
63
    m_channels(0)
64
{
65
}
66

    
67
FeatureExtractionManager::~FeatureExtractionManager()
68
{
69
    for (PluginMap::iterator pi = m_plugins.begin();
70
         pi != m_plugins.end(); ++pi) {
71
        delete pi->first;
72
    }
73
    foreach (AudioFileReader *r, m_readyReaders) {
74
        delete r;
75
    }
76
}
77

    
78
void FeatureExtractionManager::setChannels(int channels)
79
{
80
    m_channels = channels;
81
}
82

    
83
void FeatureExtractionManager::setDefaultSampleRate(int sampleRate)
84
{
85
    m_defaultSampleRate = sampleRate;
86
}
87

    
88
static PluginSummarisingAdapter::SummaryType
89
getSummaryType(string name)
90
{
91
    if (name == "min")      return PluginSummarisingAdapter::Minimum;
92
    if (name == "max")      return PluginSummarisingAdapter::Maximum;
93
    if (name == "mean")     return PluginSummarisingAdapter::Mean;
94
    if (name == "median")   return PluginSummarisingAdapter::Median;
95
    if (name == "mode")     return PluginSummarisingAdapter::Mode;
96
    if (name == "sum")      return PluginSummarisingAdapter::Sum;
97
    if (name == "variance") return PluginSummarisingAdapter::Variance;
98
    if (name == "sd")       return PluginSummarisingAdapter::StandardDeviation;
99
    if (name == "count")    return PluginSummarisingAdapter::Count;
100
    return PluginSummarisingAdapter::UnknownSummaryType;
101
}
102

    
103
bool FeatureExtractionManager::setSummaryTypes(const set<string> &names,
104
                                               bool summariesOnly,
105
                                               const PluginSummarisingAdapter::SegmentBoundaries &boundaries)
106
{
107
    for (SummaryNameSet::const_iterator i = names.begin();
108
         i != names.end(); ++i) {
109
        if (getSummaryType(*i) == PluginSummarisingAdapter::UnknownSummaryType) {
110
            cerr << "ERROR: Unknown summary type \"" << *i << "\"" << endl;
111
            return false;
112
        }
113
    }
114
    m_summaries = names;
115
    m_summariesOnly = summariesOnly;
116
    m_boundaries = boundaries;
117
    return true;
118
}
119

    
120
static PluginInputDomainAdapter::WindowType
121
convertWindowType(WindowType t)
122
{
123
    switch (t) {
124
    case RectangularWindow:
125
        return PluginInputDomainAdapter::RectangularWindow;
126
    case BartlettWindow:
127
        return PluginInputDomainAdapter::BartlettWindow;
128
    case HammingWindow:
129
        return PluginInputDomainAdapter::HammingWindow;
130
    case HanningWindow:
131
        return PluginInputDomainAdapter::HanningWindow;
132
    case BlackmanWindow:
133
        return PluginInputDomainAdapter::BlackmanWindow;
134
    case NuttallWindow:
135
        return PluginInputDomainAdapter::NuttallWindow;
136
    case BlackmanHarrisWindow:
137
        return PluginInputDomainAdapter::BlackmanHarrisWindow;
138
    default:
139
        cerr << "ERROR: Unknown or unsupported window type \"" << t << "\", using Hann (\"" << HanningWindow << "\")" << endl;
140
        return PluginInputDomainAdapter::HanningWindow;
141
    }
142
}
143

    
144
bool FeatureExtractionManager::addFeatureExtractor
145
(Transform transform, const vector<FeatureWriter*> &writers)
146
{
147
    //!!! exceptions rather than return values?
148

    
149
    if (transform.getSampleRate() == 0) {
150
        if (m_sampleRate == 0) {
151
            cerr << "NOTE: Transform does not specify a sample rate, using default rate of " << m_defaultSampleRate << endl;
152
            transform.setSampleRate(m_defaultSampleRate);
153
            m_sampleRate = m_defaultSampleRate;
154
        } else {
155
            cerr << "NOTE: Transform does not specify a sample rate, using previous transform's rate of " << m_sampleRate << endl;
156
            transform.setSampleRate(m_sampleRate);
157
        }
158
    }
159

    
160
    if (m_sampleRate == 0) {
161
        m_sampleRate = transform.getSampleRate();
162
    }
163

    
164
    if (transform.getSampleRate() != m_sampleRate) {
165
        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;
166
        cerr << "WARNING: Using previous rate of " << m_sampleRate << " for this transform as well" << endl;
167
        transform.setSampleRate(m_sampleRate);
168
    }
169

    
170
    Plugin *plugin = 0;
171

    
172
    // Remember what the original transform looked like, and index
173
    // based on this -- because we may be about to fill in the zeros
174
    // for step and block size, but we want any further copies with
175
    // the same zeros to match this one
176
    Transform originalTransform = transform;
177
    
178
    if (m_transformPluginMap.find(transform) == m_transformPluginMap.end()) {
179

    
180
        // Test whether we already have a transform that is identical
181
        // to this, except for the output requested and/or the summary
182
        // type -- if so, they should share plugin instances (a vital
183
        // optimisation)
184

    
185
        for (TransformPluginMap::iterator i = m_transformPluginMap.begin();
186
             i != m_transformPluginMap.end(); ++i) {
187
            Transform test = i->first;
188
            test.setOutput(transform.getOutput());
189
            test.setSummaryType(transform.getSummaryType());
190
            if (transform == test) {
191
                cerr << "NOTE: Already have transform identical to this one (for \""
192
                     << transform.getIdentifier().toStdString()
193
                     << "\") in every detail except output identifier and/or "
194
                     << "summary type; sharing its plugin instance" << endl;
195
                plugin = i->second;
196
                if (transform.getSummaryType() != Transform::NoSummary &&
197
                    !dynamic_cast<PluginSummarisingAdapter *>(plugin)) {
198
                    plugin = new PluginSummarisingAdapter(plugin);
199
                    i->second = plugin;
200
                }
201
                break;
202
            }
203
        }
204

    
205
        if (!plugin) {
206

    
207
            TransformFactory *tf = TransformFactory::getInstance();
208

    
209
            PluginBase *pb = tf->instantiatePluginFor(transform);
210
            plugin = tf->downcastVampPlugin(pb);
211
            if (!plugin) {
212
                //!!! todo: handle non-Vamp plugins too, or make the main --list
213
                // option print out only Vamp transforms
214
                cerr << "ERROR: Failed to load plugin for transform \""
215
                     << transform.getIdentifier().toStdString() << "\"" << endl;
216
                delete pb;
217
                return false;
218
            }
219
            
220
            // We will provide the plugin with arbitrary step and
221
            // block sizes (so that we can use the same read/write
222
            // block size for all transforms), and to that end we use
223
            // a PluginBufferingAdapter.  However, we need to know the
224
            // underlying step size so that we can provide the right
225
            // context for dense outputs.  (Although, don't forget
226
            // that the PluginBufferingAdapter rewrites
227
            // OneSamplePerStep outputs so as to use FixedSampleRate
228
            // -- so it supplies the sample rate in the output
229
            // feature.  I'm not sure whether we can easily use that.)
230

    
231
            size_t pluginStepSize = plugin->getPreferredStepSize();
232
            size_t pluginBlockSize = plugin->getPreferredBlockSize();
233

    
234
            PluginInputDomainAdapter *pida = 0;
235

    
236
            // adapt the plugin for buffering, channels, etc.
237
            if (plugin->getInputDomain() == Plugin::FrequencyDomain) {
238

    
239
                pida = new PluginInputDomainAdapter(plugin);
240
                pida->setProcessTimestampMethod(PluginInputDomainAdapter::ShiftData);
241

    
242
                PluginInputDomainAdapter::WindowType wtype =
243
                    convertWindowType(transform.getWindowType());
244
                pida->setWindowType(wtype);
245
                plugin = pida;
246
            }
247

    
248
            PluginBufferingAdapter *pba = new PluginBufferingAdapter(plugin);
249
            plugin = pba;
250

    
251
            if (transform.getStepSize() != 0) {
252
                pba->setPluginStepSize(transform.getStepSize());
253
            } else {
254
                transform.setStepSize(pluginStepSize);
255
            }
256

    
257
            if (transform.getBlockSize() != 0) {
258
                pba->setPluginBlockSize(transform.getBlockSize());
259
            } else {
260
                transform.setBlockSize(pluginBlockSize);
261
            }
262

    
263
            plugin = new PluginChannelAdapter(plugin);
264

    
265
            if (!m_summaries.empty() ||
266
                transform.getSummaryType() != Transform::NoSummary) {
267
                PluginSummarisingAdapter *adapter =
268
                    new PluginSummarisingAdapter(plugin);
269
                adapter->setSummarySegmentBoundaries(m_boundaries);
270
                plugin = adapter;
271
            }
272

    
273
            if (!plugin->initialise(m_channels, m_blockSize, m_blockSize)) {
274
                cerr << "ERROR: Plugin initialise (channels = " << m_channels << ", stepSize = " << m_blockSize << ", blockSize = " << m_blockSize << ") failed." << endl;    
275
                delete plugin;
276
                return false;
277
            }
278

    
279
//            cerr << "Initialised plugin" << endl;
280

    
281
            size_t actualStepSize = 0;
282
            size_t actualBlockSize = 0;
283
            pba->getActualStepAndBlockSizes(actualStepSize, actualBlockSize);
284
            transform.setStepSize(actualStepSize);
285
            transform.setBlockSize(actualBlockSize);
286

    
287
            Plugin::OutputList outputs = plugin->getOutputDescriptors();
288
            for (int i = 0; i < (int)outputs.size(); ++i) {
289

    
290
//                cerr << "Newly initialised plugin output " << i << " has bin count " << outputs[i].binCount << endl;
291

    
292
                m_pluginOutputs[plugin][outputs[i].identifier] = outputs[i];
293
                m_pluginOutputIndices[outputs[i].identifier] = i;
294
            }
295

    
296
            cerr << "NOTE: Loaded and initialised plugin for transform \""
297
                 << transform.getIdentifier().toStdString()
298
                 << "\" with plugin step size " << actualStepSize
299
                 << " and block size " << actualBlockSize
300
                 << " (adapter step and block size " << m_blockSize << ")"
301
                 << endl;
302

    
303
            if (pida) {
304
                cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is "
305

    
306
                     << pida->getTimestampAdjustment() << endl;
307
            }
308

    
309
        } else {
310

    
311
            if (transform.getStepSize() == 0 || transform.getBlockSize() == 0) {
312

    
313
                PluginWrapper *pw = dynamic_cast<PluginWrapper *>(plugin);
314
                if (pw) {
315
                    PluginBufferingAdapter *pba =
316
                        pw->getWrapper<PluginBufferingAdapter>();
317
                    if (pba) {
318
                        size_t actualStepSize = 0;
319
                        size_t actualBlockSize = 0;
320
                        pba->getActualStepAndBlockSizes(actualStepSize,
321
                                                        actualBlockSize);
322
                        if (transform.getStepSize() == 0) {
323
                            transform.setStepSize(actualStepSize);
324
                        }
325
                        if (transform.getBlockSize() == 0) {
326
                            transform.setBlockSize(actualBlockSize);
327
                        }
328
                    }
329
                }
330
            }
331
        }
332

    
333
        if (transform.getOutput() == "") {
334
            transform.setOutput
335
                (plugin->getOutputDescriptors()[0].identifier.c_str());
336
        }
337

    
338
        m_transformPluginMap[transform] = plugin;
339

    
340
        if (!(originalTransform == transform)) {
341
            m_transformPluginMap[originalTransform] = plugin;
342
        }
343

    
344
    } else {
345
        
346
        plugin = m_transformPluginMap[transform];
347
    }
348

    
349
    m_plugins[plugin][transform] = writers;
350

    
351
    return true;
352
}
353

    
354
bool FeatureExtractionManager::addDefaultFeatureExtractor
355
(TransformId transformId, const vector<FeatureWriter*> &writers)
356
{
357
    TransformFactory *tf = TransformFactory::getInstance();
358

    
359
    if (m_sampleRate == 0) {
360
        if (m_defaultSampleRate == 0) {
361
            cerr << "ERROR: Default transform requested, but no default sample rate available" << endl;
362
            return false;
363
        } else {
364
            cerr << "NOTE: Using default sample rate of " << m_defaultSampleRate << " for default transform" << endl;
365
            m_sampleRate = m_defaultSampleRate;
366
        }
367
    }
368

    
369
    Transform transform = tf->getDefaultTransformFor(transformId, m_sampleRate);
370

    
371
    return addFeatureExtractor(transform, writers);
372
}
373

    
374

    
375
bool FeatureExtractionManager::addFeatureExtractorFromFile
376
(QString transformXmlFile, const vector<FeatureWriter*> &writers)
377
{
378
    RDFTransformFactory factory
379
        (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath())
380
         .toString());
381
    ProgressPrinter printer("Parsing transforms RDF file");
382
    std::vector<Transform> transforms = factory.getTransforms(&printer);
383
    if (!factory.isOK()) {
384
        cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl;
385
        if (factory.isRDF()) {
386
            return false; // no point trying it as XML
387
        }
388
    }
389
    if (!transforms.empty()) {
390
        std::sort(transforms.begin(), transforms.end()); // sort for predictable output - though this may actually not help
391
        bool success = true;
392
        for (int i = 0; i < (int)transforms.size(); ++i) {
393
            if (!addFeatureExtractor(transforms[i], writers)) {
394
                success = false;
395
            }
396
        }
397
        cerr << "\n-------------------\nTransform order:\n";
398
        for (int i = 0; i < (int)transforms.size(); ++i) {
399
            cerr << i << "\t" << transforms[i].getIdentifier() << "\n";
400
        }
401
        cerr << "-------------------\n";
402
        return success;
403
    }
404

    
405
    QFile file(transformXmlFile);
406
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
407
        cerr << "ERROR: Failed to open transform XML file \""
408
             << transformXmlFile.toStdString() << "\" for reading" << endl;
409
        return false;
410
    }
411

    
412
    QTextStream *qts = new QTextStream(&file);
413
    QString qs = qts->readAll();
414
    delete qts;
415
    file.close();
416

    
417
    Transform transform(qs);
418

    
419
    return addFeatureExtractor(transform, writers);
420
}
421

    
422
void FeatureExtractionManager::addSource(QString audioSource)
423
{
424
    if (QFileInfo(audioSource).suffix().toLower() == "m3u") {
425
        ProgressPrinter retrievalProgress("Opening playlist file...");
426
        FileSource source(audioSource, &retrievalProgress);
427
        if (!source.isAvailable()) {
428
            cerr << "ERROR: File or URL \"" << audioSource.toStdString()
429
                 << "\" could not be located" << endl;
430
            throw FileNotFound(audioSource);
431
        }
432
        source.waitForData();
433
        PlaylistFileReader reader(source);
434
        if (reader.isOK()) {
435
            vector<QString> files = reader.load();
436
            for (int i = 0; i < (int)files.size(); ++i) {
437
                addSource(files[i]);
438
            }
439
            return;
440
        } else {
441
            cerr << "ERROR: Playlist \"" << audioSource.toStdString()
442
                 << "\" could not be opened" << endl;
443
            throw FileNotFound(audioSource);
444
        }
445
    }
446

    
447
    std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl;
448

    
449
    // We don't actually do anything with it here, unless it's the
450
    // first audio source and we need it to establish default channel
451
    // count and sample rate
452

    
453
    if (m_channels == 0 || m_defaultSampleRate == 0) {
454

    
455
        ProgressPrinter retrievalProgress("Determining default rate and channel count from first input file...");
456

    
457
        FileSource source(audioSource, &retrievalProgress);
458
        if (!source.isAvailable()) {
459
            cerr << "ERROR: File or URL \"" << audioSource.toStdString()
460
                 << "\" could not be located" << endl;
461
            throw FileNotFound(audioSource);
462
        }
463
    
464
        source.waitForData();
465

    
466
        // Open to determine validity, channel count, sample rate only
467
        // (then close, and open again later with actual desired rate &c)
468

    
469
        AudioFileReader *reader =
470
            AudioFileReaderFactory::createReader(source, 0, &retrievalProgress);
471
    
472
        if (!reader) {
473
            throw FailedToOpenFile(audioSource);
474
        }
475

    
476
        retrievalProgress.done();
477

    
478
        cerr << "File or URL \"" << audioSource.toStdString() << "\" opened successfully" << endl;
479

    
480
        if (m_channels == 0) {
481
            m_channels = reader->getChannelCount();
482
            cerr << "Taking default channel count of "
483
                 << reader->getChannelCount() << " from file" << endl;
484
        }
485

    
486
        if (m_defaultSampleRate == 0) {
487
            m_defaultSampleRate = reader->getNativeRate();
488
            cerr << "Taking default sample rate of "
489
                 << reader->getNativeRate() << "Hz from file" << endl;
490
            cerr << "(Note: Default may be overridden by transforms)" << endl;
491
        }
492

    
493
        m_readyReaders[audioSource] = reader;
494
    }
495
}
496

    
497
void FeatureExtractionManager::extractFeatures(QString audioSource, bool force)
498
{
499
    if (m_plugins.empty()) return;
500

    
501
    if (QFileInfo(audioSource).suffix().toLower() == "m3u") {
502
        FileSource source(audioSource);
503
        PlaylistFileReader reader(source);
504
        if (reader.isOK()) {
505
            vector<QString> files = reader.load();
506
            for (int i = 0; i < (int)files.size(); ++i) {
507
                try {
508
                    extractFeatures(files[i], force);
509
                } catch (const std::exception &e) {
510
                    if (!force) throw;
511
                    cerr << "ERROR: Feature extraction failed for playlist entry \""
512
                         << files[i].toStdString()
513
                         << "\": " << e.what() << endl;
514
                    // print a note only if we have more files to process
515
                    if (++i != files.size()) {
516
                        cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
517
                    }
518
                }
519
            }
520
            return;
521
        } else {
522
            cerr << "ERROR: Playlist \"" << audioSource.toStdString()
523
                 << "\" could not be opened" << endl;
524
            throw FileNotFound(audioSource);
525
        }
526
    }
527

    
528
    testOutputFiles(audioSource);
529

    
530
    if (m_sampleRate == 0) {
531
        throw FileOperationFailed
532
            (audioSource, "internal error: have sources and plugins, but no sample rate");
533
    }
534
    if (m_channels == 0) {
535
        throw FileOperationFailed
536
            (audioSource, "internal error: have sources and plugins, but no channel count");
537
    }
538

    
539
    AudioFileReader *reader = 0;
540

    
541
    if (m_readyReaders.contains(audioSource)) {
542
        reader = m_readyReaders[audioSource];
543
        m_readyReaders.remove(audioSource);
544
        if (reader->getChannelCount() != m_channels ||
545
            reader->getSampleRate() != m_sampleRate) {
546
            // can't use this; open it again
547
            delete reader;
548
            reader = 0;
549
        }
550
    }
551
    if (!reader) {
552
        ProgressPrinter retrievalProgress("Retrieving audio data...");
553
        FileSource source(audioSource, &retrievalProgress);
554
        source.waitForData();
555
        reader = AudioFileReaderFactory::createReader
556
            (source, m_sampleRate, &retrievalProgress);
557
        retrievalProgress.done();
558
    }
559

    
560
    if (!reader) {
561
        throw FailedToOpenFile(audioSource);
562
    }
563

    
564
    cerr << "Audio file \"" << audioSource.toStdString() << "\": "
565
         << reader->getChannelCount() << "ch at " 
566
         << reader->getNativeRate() << "Hz" << endl;
567
    if (reader->getChannelCount() != m_channels ||
568
        reader->getNativeRate() != m_sampleRate) {
569
        cerr << "NOTE: File will be mixed or resampled for processing: "
570
             << m_channels << "ch at " 
571
             << m_sampleRate << "Hz" << endl;
572
    }
573

    
574
    // allocate audio buffers
575
    float **data = new float *[m_channels];
576
    for (int c = 0; c < m_channels; ++c) {
577
        data[c] = new float[m_blockSize];
578
    }
579
    
580
    struct LifespanMgr { // unintrusive hack introduced to ensure
581
                         // destruction on exceptions
582
        AudioFileReader *m_r;
583
        int m_c;
584
        float **m_d;
585
        LifespanMgr(AudioFileReader *r, int c, float **d) :
586
            m_r(r), m_c(c), m_d(d) { }
587
        ~LifespanMgr() { destroy(); }
588
        void destroy() {
589
            if (!m_r) return;
590
            delete m_r;
591
            for (int i = 0; i < m_c; ++i) delete[] m_d[i];
592
            delete[] m_d;
593
            m_r = 0;
594
        }
595
    };
596
    LifespanMgr lifemgr(reader, m_channels, data);
597

    
598
    size_t frameCount = reader->getFrameCount();
599
    
600
//    cerr << "file has " << frameCount << " frames" << endl;
601

    
602
    for (PluginMap::iterator pi = m_plugins.begin();
603
         pi != m_plugins.end(); ++pi) {
604

    
605
        Plugin *plugin = pi->first;
606

    
607
//        std::cerr << "Calling reset on " << plugin << std::endl;
608
        plugin->reset();
609

    
610
        for (TransformWriterMap::iterator ti = pi->second.begin();
611
             ti != pi->second.end(); ++ti) {
612

    
613
            const Transform &transform = ti->first;
614

    
615
            //!!! we may want to set the start and duration times for extraction
616
            // in the transform record (defaults of zero indicate extraction
617
            // from the whole file)
618
//            transform.setStartTime(RealTime::zeroTime);
619
//            transform.setDuration
620
//                (RealTime::frame2RealTime(reader->getFrameCount(), m_sampleRate));
621

    
622
            string outputId = transform.getOutput().toStdString();
623
            if (m_pluginOutputs[plugin].find(outputId) ==
624
                m_pluginOutputs[plugin].end()) {
625
                //!!! throw?
626
                cerr << "WARNING: Nonexistent plugin output \"" << outputId << "\" requested for transform \""
627
                     << transform.getIdentifier().toStdString() << "\", ignoring this transform"
628
                     << endl;
629
/*
630
                cerr << "Known outputs for all plugins are as follows:" << endl;
631
                for (PluginOutputMap::const_iterator k = m_pluginOutputs.begin();
632
                     k != m_pluginOutputs.end(); ++k) {
633
                    cerr << "Plugin " << k->first << ": ";
634
                    if (k->second.empty()) {
635
                        cerr << "(none)";
636
                    }
637
                    for (OutputMap::const_iterator i = k->second.begin();
638
                         i != k->second.end(); ++i) {
639
                        cerr << "\"" << i->first << "\" ";
640
                    }
641
                    cerr << endl;
642
                }
643
*/
644
            }
645
        }
646
    }
647
    
648
    long startFrame = 0;
649
    long endFrame = frameCount;
650

    
651
/*!!! No -- there is no single transform to pull this stuff from --
652
 * the transforms may have various start and end times, need to be far
653
 * cleverer about this if we're going to support them
654

655
    RealTime trStartRT = transform.getStartTime();
656
    RealTime trDurationRT = transform.getDuration();
657

658
    long trStart = RealTime::realTime2Frame(trStartRT, m_sampleRate);
659
    long trDuration = RealTime::realTime2Frame(trDurationRT, m_sampleRate);
660

661
    if (trStart == 0 || trStart < startFrame) {
662
        trStart = startFrame;
663
    }
664

665
    if (trDuration == 0) {
666
        trDuration = endFrame - trStart;
667
    }
668
    if (trStart + trDuration > endFrame) {
669
        trDuration = endFrame - trStart;
670
    }
671

672
    startFrame = trStart;
673
    endFrame = trStart + trDuration;
674
*/
675
    
676
    for (PluginMap::iterator pi = m_plugins.begin();
677
         pi != m_plugins.end(); ++pi) { 
678

    
679
        for (TransformWriterMap::const_iterator ti = pi->second.begin();
680
             ti != pi->second.end(); ++ti) {
681
        
682
            const vector<FeatureWriter *> &writers = ti->second;
683
            
684
            for (int j = 0; j < (int)writers.size(); ++j) {
685
                FeatureWriter::TrackMetadata m;
686
                m.title = reader->getTitle();
687
                m.maker = reader->getMaker();
688
                if (m.title != "" && m.maker != "") {
689
                    writers[j]->setTrackMetadata(audioSource, m);
690
                }
691
            }
692
        }
693
    }
694

    
695
    ProgressPrinter extractionProgress("Extracting and writing features...");
696
    int progress = 0;
697

    
698
    for (long i = startFrame; i < endFrame; i += m_blockSize) {
699
        
700
        //!!! inefficient, although much of the inefficiency may be
701
        // susceptible to optimisation
702
        
703
        SampleBlock frames;
704
        reader->getInterleavedFrames(i, m_blockSize, frames);
705
        
706
        // We have to do our own channel handling here; we can't just
707
        // leave it to the plugin adapter because the same plugin
708
        // adapter may have to serve for input files with various
709
        // numbers of channels (so the adapter is simply configured
710
        // with a fixed channel count).
711

    
712
        int rc = reader->getChannelCount();
713

    
714
        // m_channels is the number of channels we need for the plugin
715

    
716
        int index;
717
        int fc = (int)frames.size();
718

    
719
        if (m_channels == 1) { // only case in which we can sensibly mix down
720
            for (int j = 0; j < m_blockSize; ++j) {
721
                data[0][j] = 0.f;
722
            }
723
            for (int c = 0; c < rc; ++c) {
724
                for (int j = 0; j < m_blockSize; ++j) {
725
                    index = j * rc + c;
726
                    if (index < fc) data[0][j] += frames[index];
727
                }
728
            }
729
            for (int j = 0; j < m_blockSize; ++j) {
730
                data[0][j] /= rc;
731
            }
732
        } else {                
733
            for (int c = 0; c < m_channels; ++c) {
734
                for (int j = 0; j < m_blockSize; ++j) {
735
                    data[c][j] = 0.f;
736
                }
737
                if (c < rc) {
738
                    for (int j = 0; j < m_blockSize; ++j) {
739
                        index = j * rc + c;
740
                        if (index < fc) data[c][j] += frames[index];
741
                    }
742
                }
743
            }
744
        }                
745

    
746
        Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime
747
            (i, m_sampleRate);
748
        
749
        for (PluginMap::iterator pi = m_plugins.begin();
750
             pi != m_plugins.end(); ++pi) {
751

    
752
            Plugin *plugin = pi->first;
753
            Plugin::FeatureSet featureSet = plugin->process(data, timestamp);
754

    
755
            if (!m_summariesOnly) {
756
                writeFeatures(audioSource, plugin, featureSet);
757
            }
758
        }
759

    
760
        int pp = progress;
761
        progress = int(((i - startFrame) * 100.0) / (endFrame - startFrame) + 0.1);
762
        if (progress > pp) extractionProgress.setProgress(progress);
763
    }
764

    
765
//    std::cerr << "FeatureExtractionManager: deleting audio file reader" << std::endl;
766

    
767
    lifemgr.destroy(); // deletes reader, data
768
    
769
    
770
    // Matthias: below (in order to have plugins ordered by outputs) I've done 
771
    // a bit of messing around with the maps, first putting m_plugins pairs into a new
772
    // map in which the keys are the first transform of each plugin, so that they should
773
    // be sorted. then I replaced the loop over m_plugins by a loop over that tempMap...
774
    
775
    map<Transform, pair<Vamp::Plugin *, TransformWriterMap> > tempMap;
776
    
777
    for (PluginMap::iterator pi = m_plugins.begin();
778
         pi != m_plugins.end(); ++pi) {
779
        pair<Vamp::Plugin *, TransformWriterMap> tempTransformWriterPair = (*pi);
780
        Transform tempTransform = (pi->second).begin()->first;
781
        pair<Transform, pair<Vamp::Plugin *, TransformWriterMap> > tempPair (tempTransform,tempTransformWriterPair);
782
        tempMap.insert(tempPair);
783
    }    
784

    
785
    for (map<Transform, pair<Vamp::Plugin *, TransformWriterMap> >::iterator superPi = tempMap.begin();
786
         superPi != tempMap.end(); ++superPi) { 
787
        
788
        pair<Vamp::Plugin *, TransformWriterMap> pi = superPi->second;
789
    // for (PluginMap::iterator pi = m_plugins.begin();
790
    //      pi != m_plugins.end(); ++pi) { 
791
        
792
        Plugin *plugin = pi.first;
793
        Plugin::FeatureSet featureSet = plugin->getRemainingFeatures();
794

    
795
        if (!m_summariesOnly) {
796
            writeFeatures(audioSource, plugin, featureSet);
797
        }
798

    
799
        if (!m_summaries.empty()) {
800
            PluginSummarisingAdapter *adapter =
801
                dynamic_cast<PluginSummarisingAdapter *>(plugin);
802
            if (!adapter) {
803
                cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl;
804
            } else {
805
                for (SummaryNameSet::const_iterator sni = m_summaries.begin();
806
                     sni != m_summaries.end(); ++sni) {
807
                    featureSet.clear();
808
                    //!!! problem here -- we are requesting summaries
809
                    //!!! for all outputs, but they in principle have
810
                    //!!! different averaging requirements depending
811
                    //!!! on whether their features have duration or
812
                    //!!! not
813
                    featureSet = adapter->getSummaryForAllOutputs
814
                        (getSummaryType(*sni),
815
                         PluginSummarisingAdapter::ContinuousTimeAverage);
816
                    writeFeatures(audioSource, plugin, featureSet,//!!! *sni);
817
                                  Transform::stringToSummaryType(sni->c_str()));
818
                }
819
            }
820
        }
821

    
822
        writeSummaries(audioSource, plugin);
823
    }
824

    
825
    extractionProgress.done();
826

    
827
    finish();
828
    
829
    TempDirectory::getInstance()->cleanup();
830
}
831

    
832
void
833
FeatureExtractionManager::writeSummaries(QString audioSource, Plugin *plugin)
834
{
835
    // caller should have ensured plugin is in m_plugins
836
    PluginMap::iterator pi = m_plugins.find(plugin);
837
    
838
    for (TransformWriterMap::const_iterator ti = pi->second.begin();
839
         ti != pi->second.end(); ++ti) {
840
        
841
        const Transform &transform = ti->first;
842
        const vector<FeatureWriter *> &writers = ti->second;
843

    
844
        Transform::SummaryType summaryType = transform.getSummaryType();
845
        PluginSummarisingAdapter::SummaryType pType =
846
            (PluginSummarisingAdapter::SummaryType)summaryType;
847

    
848
        if (transform.getSummaryType() == Transform::NoSummary) {
849
            continue;
850
        }
851

    
852
        PluginSummarisingAdapter *adapter =
853
            dynamic_cast<PluginSummarisingAdapter *>(plugin);
854
        if (!adapter) {
855
            cerr << "FeatureExtractionManager::writeSummaries: INTERNAL ERROR: Summary requested for transform, but plugin is not a summarising adapter" << endl;
856
            continue;
857
        }
858

    
859
        Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs
860
            (pType, PluginSummarisingAdapter::ContinuousTimeAverage);
861

    
862
//        cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
863

    
864
        writeFeatures(audioSource, plugin, featureSet, summaryType);
865
    }
866
}
867

    
868
void FeatureExtractionManager::writeFeatures(QString audioSource,
869
                                             Plugin *plugin,
870
                                             const Plugin::FeatureSet &features,
871
                                             Transform::SummaryType summaryType)
872
{
873
    // caller should have ensured plugin is in m_plugins
874
    PluginMap::iterator pi = m_plugins.find(plugin);
875

    
876
    for (TransformWriterMap::const_iterator ti = pi->second.begin();
877
         ti != pi->second.end(); ++ti) {
878
        
879
        const Transform &transform = ti->first;
880
        const vector<FeatureWriter *> &writers = ti->second;
881
        
882
        if (transform.getSummaryType() != Transform::NoSummary &&
883
            m_summaries.empty() &&
884
            summaryType == Transform::NoSummary) {
885
            continue;
886
        }
887

    
888
        if (transform.getSummaryType() != Transform::NoSummary &&
889
            summaryType != Transform::NoSummary &&
890
            transform.getSummaryType() != summaryType) {
891
            continue;
892
        }
893

    
894
        string outputId = transform.getOutput().toStdString();
895

    
896
        if (m_pluginOutputs[plugin].find(outputId) ==
897
            m_pluginOutputs[plugin].end()) {
898
            continue;
899
        }
900
        
901
        const Plugin::OutputDescriptor &desc =
902
            m_pluginOutputs[plugin][outputId];
903
        
904
        int outputIndex = m_pluginOutputIndices[outputId];
905
        Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex);
906
        if (fsi == features.end()) continue;
907

    
908
        for (int j = 0; j < (int)writers.size(); ++j) {
909
            writers[j]->write
910
                (audioSource, transform, desc, fsi->second,
911
                 Transform::summaryTypeToString(summaryType).toStdString());
912
        }
913
    }
914
}
915

    
916
void FeatureExtractionManager::testOutputFiles(QString audioSource)
917
{
918
    for (PluginMap::iterator pi = m_plugins.begin();
919
         pi != m_plugins.end(); ++pi) {
920

    
921
        for (TransformWriterMap::iterator ti = pi->second.begin();
922
             ti != pi->second.end(); ++ti) {
923
        
924
            vector<FeatureWriter *> &writers = ti->second;
925

    
926
            for (int i = 0; i < (int)writers.size(); ++i) {
927
                writers[i]->testOutputFile(audioSource, ti->first.getIdentifier());
928
            }
929
        }
930
    }
931
}
932

    
933
void FeatureExtractionManager::finish()
934
{
935
    for (PluginMap::iterator pi = m_plugins.begin();
936
         pi != m_plugins.end(); ++pi) {
937

    
938
        for (TransformWriterMap::iterator ti = pi->second.begin();
939
             ti != pi->second.end(); ++ti) {
940
        
941
            vector<FeatureWriter *> &writers = ti->second;
942

    
943
            for (int i = 0; i < (int)writers.size(); ++i) {
944
                writers[i]->flush();
945
                writers[i]->finish();
946
            }
947
        }
948
    }
949
}
950

    
951
void FeatureExtractionManager::print(Transform transform) const
952
{
953
    QString qs;
954
    QTextStream qts(&qs);
955
    transform.toXml(qts);
956
    cerr << qs.toStdString() << endl;
957
}