Mercurial > hg > sonic-annotator
comparison runner/FeatureExtractionManager.cpp @ 248:c8e5fcddf8be
Merge
author | Chris Cannam |
---|---|
date | Fri, 18 Mar 2016 15:15:55 +0000 |
parents | 9a10c3ffff47 |
children | 857ce6ecb163 |
comparison
equal
deleted
inserted
replaced
247:5eadb3b687bb | 248:c8e5fcddf8be |
---|---|
321 << "\" with plugin step size " << actualStepSize | 321 << "\" with plugin step size " << actualStepSize |
322 << " and block size " << actualBlockSize | 322 << " and block size " << actualBlockSize |
323 << " (adapter step and block size " << m_blockSize << ")" | 323 << " (adapter step and block size " << m_blockSize << ")" |
324 << endl; | 324 << endl; |
325 | 325 |
326 // cerr << "NOTE: That transform is: " << transform.toXmlString() << endl; | |
327 | |
326 if (pida) { | 328 if (pida) { |
327 cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is " | 329 cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is " |
328 | 330 |
329 << pida->getTimestampAdjustment() << endl; | 331 << pida->getTimestampAdjustment() << endl; |
330 } | 332 } |
380 } | 382 } |
381 } | 383 } |
382 | 384 |
383 m_transformPluginMap[transform] = plugin; | 385 m_transformPluginMap[transform] = plugin; |
384 | 386 |
387 // cerr << "NOTE: Assigned plugin " << plugin << " for transform: " << transform.toXmlString() << endl; | |
388 | |
385 if (!(originalTransform == transform)) { | 389 if (!(originalTransform == transform)) { |
386 m_transformPluginMap[originalTransform] = plugin; | 390 m_transformPluginMap[originalTransform] = plugin; |
391 // cerr << "NOTE: Also assigned plugin " << plugin << " for original transform: " << originalTransform.toXmlString() << endl; | |
387 } | 392 } |
388 | 393 |
389 } else { | 394 } else { |
390 | 395 |
391 plugin = m_transformPluginMap[transform]; | 396 plugin = m_transformPluginMap[transform]; |
425 } | 430 } |
426 return result; | 431 return result; |
427 } | 432 } |
428 | 433 |
429 bool FeatureExtractionManager::addFeatureExtractorFromFile | 434 bool FeatureExtractionManager::addFeatureExtractorFromFile |
430 (QString transformXmlFile, const vector<FeatureWriter*> &writers) | 435 (QString transformFile, const vector<FeatureWriter*> &writers) |
431 { | 436 { |
437 // We support two formats for transform description files, XML (in | |
438 // a format specific to Sonic Annotator) and RDF/Turtle. The RDF | |
439 // format can describe multiple transforms in a single file, the | |
440 // XML only one. | |
441 | |
442 // Possible errors we should report: | |
443 // | |
444 // 1. File does not exist or cannot be opened | |
445 // 2. File is ostensibly XML, but is not parseable | |
446 // 3. File is ostensibly Turtle, but is not parseable | |
447 // 4. File is XML, but contains no valid transform (e.g. is unrelated XML) | |
448 // 5. File is Turtle, but contains no valid transform(s) | |
449 // 6. File is Turtle and contains both valid and invalid transform(s) | |
450 | |
451 { | |
452 // We don't actually need to open this here yet, we just hoist | |
453 // it to the top for error reporting purposes | |
454 QFile file(transformFile); | |
455 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { | |
456 // Error case 1. File does not exist or cannot be opened | |
457 cerr << "ERROR: Failed to open transform file \"" << transformFile | |
458 << "\" for reading" << endl; | |
459 return false; | |
460 } | |
461 } | |
462 | |
432 bool tryRdf = true; | 463 bool tryRdf = true; |
433 | 464 if (transformFile.endsWith(".xml") || transformFile.endsWith(".XML")) { |
434 if (transformXmlFile.endsWith(".xml") || transformXmlFile.endsWith(".XML")) { | |
435 // We don't support RDF-XML (and nor does the underlying | 465 // We don't support RDF-XML (and nor does the underlying |
436 // parser library) so skip the RDF parse if the filename | 466 // parser library) so skip the RDF parse if the filename |
437 // suggests XML, to avoid puking out a load of errors from | 467 // suggests XML, to avoid puking out a load of errors from |
438 // feeding XML to a Turtle parser | 468 // feeding XML to a Turtle parser |
439 tryRdf = false; | 469 tryRdf = false; |
440 } | 470 } |
441 | 471 |
472 bool tryXml = true; | |
473 if (transformFile.endsWith(".ttl") || transformFile.endsWith(".TTL") || | |
474 transformFile.endsWith(".ntriples") || transformFile.endsWith(".NTRIPLES") || | |
475 transformFile.endsWith(".n3") || transformFile.endsWith(".N3")) { | |
476 tryXml = false; | |
477 } | |
478 | |
479 QString rdfError, xmlError; | |
480 | |
442 if (tryRdf) { | 481 if (tryRdf) { |
482 | |
443 RDFTransformFactory factory | 483 RDFTransformFactory factory |
444 (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath()) | 484 (QUrl::fromLocalFile(QFileInfo(transformFile).absoluteFilePath()) |
445 .toString()); | 485 .toString()); |
446 ProgressPrinter printer("Parsing transforms RDF file"); | 486 ProgressPrinter printer("Parsing transforms RDF file"); |
447 std::vector<Transform> transforms = factory.getTransforms(&printer); | 487 std::vector<Transform> transforms = factory.getTransforms(&printer); |
448 if (!factory.isOK()) { | 488 |
449 cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl; | 489 if (factory.isOK()) { |
490 if (transforms.empty()) { | |
491 cerr << "ERROR: Transform file \"" << transformFile | |
492 << "\" is valid RDF but defines no transforms" << endl; | |
493 return false; | |
494 } else { | |
495 bool success = true; | |
496 for (int i = 0; i < (int)transforms.size(); ++i) { | |
497 if (!addFeatureExtractor(transforms[i], writers)) { | |
498 success = false; | |
499 } | |
500 } | |
501 return success; | |
502 } | |
503 } else { // !factory.isOK() | |
450 if (factory.isRDF()) { | 504 if (factory.isRDF()) { |
451 return false; // no point trying it as XML | 505 cerr << "ERROR: Invalid transform RDF file \"" << transformFile |
452 } | 506 << "\": " << factory.getErrorString() << endl; |
453 } | 507 return false; |
454 if (!transforms.empty()) { | 508 } |
455 bool success = true; | 509 |
456 for (int i = 0; i < (int)transforms.size(); ++i) { | 510 // the not-RDF case: fall through without reporting an |
457 if (!addFeatureExtractor(transforms[i], writers)) { | 511 // error, so we try the file as XML, and if that fails, we |
458 success = false; | 512 // print a general unparseable-file error |
459 } | 513 rdfError = factory.getErrorString(); |
460 } | 514 } |
461 return success; | 515 } |
462 } | 516 |
463 } | 517 if (tryXml) { |
464 | 518 |
465 QFile file(transformXmlFile); | 519 QFile file(transformFile); |
466 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { | 520 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
467 cerr << "ERROR: Failed to open transform XML file \"" | 521 cerr << "ERROR: Failed to open transform file \"" |
468 << transformXmlFile.toStdString() << "\" for reading" << endl; | 522 << transformFile.toStdString() << "\" for reading" << endl; |
469 return false; | 523 return false; |
470 } | 524 } |
471 | 525 |
472 QTextStream *qts = new QTextStream(&file); | 526 QTextStream *qts = new QTextStream(&file); |
473 QString qs = qts->readAll(); | 527 QString qs = qts->readAll(); |
474 delete qts; | 528 delete qts; |
475 file.close(); | 529 file.close(); |
476 | 530 |
477 Transform transform(qs); | 531 Transform transform(qs); |
478 | 532 xmlError = transform.getErrorString(); |
479 return addFeatureExtractor(transform, writers); | 533 |
534 if (xmlError == "") { | |
535 | |
536 if (transform.getIdentifier() == "") { | |
537 cerr << "ERROR: Transform file \"" << transformFile | |
538 << "\" is valid XML but defines no transform" << endl; | |
539 return false; | |
540 } | |
541 | |
542 return addFeatureExtractor(transform, writers); | |
543 } | |
544 } | |
545 | |
546 cerr << "ERROR: Transform file \"" << transformFile | |
547 << "\" could not be parsed" << endl; | |
548 if (rdfError != "") { | |
549 cerr << "ERROR: RDF parser reported: " << rdfError << endl; | |
550 } | |
551 if (xmlError != "") { | |
552 cerr << "ERROR: XML parser reported: " << xmlError << endl; | |
553 } | |
554 | |
555 return false; | |
480 } | 556 } |
481 | 557 |
482 void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex) | 558 void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex) |
483 { | 559 { |
484 std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl; | 560 std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl; |
487 // first audio source and we need it to establish default channel | 563 // first audio source and we need it to establish default channel |
488 // count and sample rate | 564 // count and sample rate |
489 | 565 |
490 if (m_channels == 0 || m_defaultSampleRate == 0) { | 566 if (m_channels == 0 || m_defaultSampleRate == 0) { |
491 | 567 |
492 ProgressPrinter retrievalProgress("Determining default rate and channel count from first input file..."); | 568 ProgressPrinter retrievalProgress("Retrieving first input file to determine default rate and channel count..."); |
493 | 569 |
494 FileSource source(audioSource, &retrievalProgress); | 570 FileSource source(audioSource, &retrievalProgress); |
495 if (!source.isAvailable()) { | 571 if (!source.isAvailable()) { |
496 cerr << "ERROR: File or URL \"" << audioSource.toStdString() | 572 cerr << "ERROR: File or URL \"" << audioSource.toStdString() |
497 << "\" could not be located"; | 573 << "\" could not be located"; |
522 | 598 |
523 if (!willMultiplex) { | 599 if (!willMultiplex) { |
524 if (m_channels == 0) { | 600 if (m_channels == 0) { |
525 m_channels = reader->getChannelCount(); | 601 m_channels = reader->getChannelCount(); |
526 cerr << "Taking default channel count of " | 602 cerr << "Taking default channel count of " |
527 << reader->getChannelCount() << " from file" << endl; | 603 << reader->getChannelCount() << " from audio file" << endl; |
528 } | 604 } |
529 } | 605 } |
530 | 606 |
531 if (m_defaultSampleRate == 0) { | 607 if (m_defaultSampleRate == 0) { |
532 m_defaultSampleRate = reader->getNativeRate(); | 608 m_defaultSampleRate = reader->getNativeRate(); |
533 cerr << "Taking default sample rate of " | 609 cerr << "Taking default sample rate of " |
534 << reader->getNativeRate() << "Hz from file" << endl; | 610 << reader->getNativeRate() << "Hz from audio file" << endl; |
535 cerr << "(Note: Default may be overridden by transforms)" << endl; | 611 cerr << "(Note: Default may be overridden by transforms)" << endl; |
536 } | 612 } |
537 | 613 |
538 m_readyReaders[audioSource] = reader; | 614 m_readyReaders[audioSource] = reader; |
539 } | 615 } |
614 retrievalProgress.done(); | 690 retrievalProgress.done(); |
615 } | 691 } |
616 if (!reader) { | 692 if (!reader) { |
617 throw FailedToOpenFile(source); | 693 throw FailedToOpenFile(source); |
618 } | 694 } |
619 return reader; | |
620 } | |
621 | |
622 void | |
623 FeatureExtractionManager::extractFeaturesFor(AudioFileReader *reader, | |
624 QString audioSource) | |
625 { | |
626 // Note: This also deletes reader | |
627 | |
628 cerr << "Audio file \"" << audioSource.toStdString() << "\": " | |
629 << reader->getChannelCount() << "ch at " | |
630 << reader->getNativeRate() << "Hz" << endl; | |
631 if (reader->getChannelCount() != m_channels || | 695 if (reader->getChannelCount() != m_channels || |
632 reader->getNativeRate() != m_sampleRate) { | 696 reader->getNativeRate() != m_sampleRate) { |
633 cerr << "NOTE: File will be mixed or resampled for processing, to: " | 697 cerr << "NOTE: File will be mixed or resampled for processing, to: " |
634 << m_channels << "ch at " | 698 << m_channels << "ch at " |
635 << m_sampleRate << "Hz" << endl; | 699 << m_sampleRate << "Hz" << endl; |
636 } | 700 } |
701 return reader; | |
702 } | |
703 | |
704 void | |
705 FeatureExtractionManager::extractFeaturesFor(AudioFileReader *reader, | |
706 QString audioSource) | |
707 { | |
708 // Note: This also deletes reader | |
709 | |
710 cerr << "Audio file \"" << audioSource.toStdString() << "\": " | |
711 << reader->getChannelCount() << "ch at " | |
712 << reader->getNativeRate() << "Hz" << endl; | |
637 | 713 |
638 // allocate audio buffers | 714 // allocate audio buffers |
639 float **data = new float *[m_channels]; | 715 float **data = new float *[m_channels]; |
640 for (int c = 0; c < m_channels; ++c) { | 716 for (int c = 0; c < m_channels; ++c) { |
641 data[c] = new float[m_blockSize]; | 717 data[c] = new float[m_blockSize]; |
669 | 745 |
670 foreach (Plugin *plugin, m_orderedPlugins) { | 746 foreach (Plugin *plugin, m_orderedPlugins) { |
671 | 747 |
672 PluginMap::iterator pi = m_plugins.find(plugin); | 748 PluginMap::iterator pi = m_plugins.find(plugin); |
673 | 749 |
674 std::cerr << "Calling reset on " << plugin << std::endl; | 750 // std::cerr << "Calling reset on " << plugin << std::endl; |
675 plugin->reset(); | 751 plugin->reset(); |
676 | 752 |
677 for (TransformWriterMap::iterator ti = pi->second.begin(); | 753 for (TransformWriterMap::iterator ti = pi->second.begin(); |
678 ti != pi->second.end(); ++ti) { | 754 ti != pi->second.end(); ++ti) { |
679 | 755 |
850 if (!m_summariesOnly) { | 926 if (!m_summariesOnly) { |
851 writeFeatures(audioSource, plugin, featureSet); | 927 writeFeatures(audioSource, plugin, featureSet); |
852 } | 928 } |
853 | 929 |
854 if (!m_summaries.empty()) { | 930 if (!m_summaries.empty()) { |
931 // Summaries requested on the command line, for all transforms | |
855 PluginSummarisingAdapter *adapter = | 932 PluginSummarisingAdapter *adapter = |
856 dynamic_cast<PluginSummarisingAdapter *>(plugin); | 933 dynamic_cast<PluginSummarisingAdapter *>(plugin); |
857 if (!adapter) { | 934 if (!adapter) { |
858 cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl; | 935 cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl; |
859 } else { | 936 } else { |
866 //!!! on whether their features have duration or | 943 //!!! on whether their features have duration or |
867 //!!! not | 944 //!!! not |
868 featureSet = adapter->getSummaryForAllOutputs | 945 featureSet = adapter->getSummaryForAllOutputs |
869 (getSummaryType(*sni), | 946 (getSummaryType(*sni), |
870 PluginSummarisingAdapter::ContinuousTimeAverage); | 947 PluginSummarisingAdapter::ContinuousTimeAverage); |
871 writeFeatures(audioSource, plugin, featureSet,//!!! *sni); | 948 writeFeatures(audioSource, plugin, featureSet, |
872 Transform::stringToSummaryType(sni->c_str())); | 949 Transform::stringToSummaryType(sni->c_str())); |
873 } | 950 } |
874 } | 951 } |
875 } | 952 } |
876 | 953 |
954 // Summaries specified in transform definitions themselves | |
877 writeSummaries(audioSource, plugin); | 955 writeSummaries(audioSource, plugin); |
878 } | 956 } |
879 | 957 |
880 extractionProgress.done(); | 958 extractionProgress.done(); |
881 | 959 |
893 for (TransformWriterMap::const_iterator ti = pi->second.begin(); | 971 for (TransformWriterMap::const_iterator ti = pi->second.begin(); |
894 ti != pi->second.end(); ++ti) { | 972 ti != pi->second.end(); ++ti) { |
895 | 973 |
896 const Transform &transform = ti->first; | 974 const Transform &transform = ti->first; |
897 | 975 |
976 // cerr << "FeatureExtractionManager::writeSummaries: plugin is " << plugin | |
977 // << ", found transform: " << transform.toXmlString() << endl; | |
978 | |
898 Transform::SummaryType summaryType = transform.getSummaryType(); | 979 Transform::SummaryType summaryType = transform.getSummaryType(); |
899 PluginSummarisingAdapter::SummaryType pType = | 980 PluginSummarisingAdapter::SummaryType pType = |
900 (PluginSummarisingAdapter::SummaryType)summaryType; | 981 (PluginSummarisingAdapter::SummaryType)summaryType; |
901 | 982 |
902 if (transform.getSummaryType() == Transform::NoSummary) { | 983 if (transform.getSummaryType() == Transform::NoSummary) { |
984 // cerr << "(no summary, continuing)" << endl; | |
903 continue; | 985 continue; |
904 } | 986 } |
905 | 987 |
906 PluginSummarisingAdapter *adapter = | 988 PluginSummarisingAdapter *adapter = |
907 dynamic_cast<PluginSummarisingAdapter *>(plugin); | 989 dynamic_cast<PluginSummarisingAdapter *>(plugin); |
911 } | 993 } |
912 | 994 |
913 Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs | 995 Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs |
914 (pType, PluginSummarisingAdapter::ContinuousTimeAverage); | 996 (pType, PluginSummarisingAdapter::ContinuousTimeAverage); |
915 | 997 |
916 // cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl; | 998 // cerr << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl; |
917 | 999 |
918 writeFeatures(audioSource, plugin, featureSet, summaryType); | 1000 writeFeatures(audioSource, plugin, featureSet, summaryType); |
919 } | 1001 } |
920 } | 1002 } |
921 | 1003 |
925 Transform::SummaryType summaryType) | 1007 Transform::SummaryType summaryType) |
926 { | 1008 { |
927 // caller should have ensured plugin is in m_plugins | 1009 // caller should have ensured plugin is in m_plugins |
928 PluginMap::iterator pi = m_plugins.find(plugin); | 1010 PluginMap::iterator pi = m_plugins.find(plugin); |
929 | 1011 |
1012 // Write features from the feature set passed in, according to the | |
1013 // transforms listed for the given plugin with the given summary type | |
1014 | |
930 for (TransformWriterMap::const_iterator ti = pi->second.begin(); | 1015 for (TransformWriterMap::const_iterator ti = pi->second.begin(); |
931 ti != pi->second.end(); ++ti) { | 1016 ti != pi->second.end(); ++ti) { |
932 | 1017 |
933 const Transform &transform = ti->first; | 1018 const Transform &transform = ti->first; |
934 const vector<FeatureWriter *> &writers = ti->second; | 1019 const vector<FeatureWriter *> &writers = ti->second; |
935 | 1020 |
936 if (transform.getSummaryType() != Transform::NoSummary && | 1021 // cerr << "writeFeatures: plugin " << plugin << " has transform: " << transform.toXmlString() << endl; |
937 m_summaries.empty() && | 1022 |
938 summaryType == Transform::NoSummary) { | 1023 if (transform.getSummaryType() == Transform::NoSummary && |
939 continue; | 1024 !m_summaries.empty()) { |
940 } | 1025 // cerr << "transform has no summary, but summaries requested on command line, so going for it anyway" << endl; |
941 | 1026 } else if (transform.getSummaryType() != summaryType) { |
942 if (transform.getSummaryType() != Transform::NoSummary && | 1027 // Either we're not writing a summary and the transform |
943 summaryType != Transform::NoSummary && | 1028 // has one, or we're writing a summary but the transform |
944 transform.getSummaryType() != summaryType) { | 1029 // has none or a different one; either way, skip it |
1030 // cerr << "summary type differs from passed-in one " << summaryType << endl; | |
945 continue; | 1031 continue; |
946 } | 1032 } |
947 | 1033 |
948 string outputId = transform.getOutput().toStdString(); | 1034 string outputId = transform.getOutput().toStdString(); |
949 | 1035 |
957 | 1043 |
958 int outputIndex = m_pluginOutputIndices[outputId]; | 1044 int outputIndex = m_pluginOutputIndices[outputId]; |
959 Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex); | 1045 Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex); |
960 if (fsi == features.end()) continue; | 1046 if (fsi == features.end()) continue; |
961 | 1047 |
1048 // cerr << "this transform has " << writers.size() << " writer(s)" << endl; | |
1049 | |
962 for (int j = 0; j < (int)writers.size(); ++j) { | 1050 for (int j = 0; j < (int)writers.size(); ++j) { |
963 writers[j]->write | 1051 writers[j]->write |
964 (audioSource, transform, desc, fsi->second, | 1052 (audioSource, transform, desc, fsi->second, |
965 Transform::summaryTypeToString(summaryType).toStdString()); | 1053 Transform::summaryTypeToString(summaryType).toStdString()); |
966 } | 1054 } |