Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: Sonic Annotator Chris@0: A utility for batch feature extraction from audio files. Chris@0: Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. Chris@0: Copyright 2007-2008 QMUL. Chris@0: Chris@0: This program is free software; you can redistribute it and/or Chris@0: modify it under the terms of the GNU General Public License as Chris@0: published by the Free Software Foundation; either version 2 of the Chris@0: License, or (at your option) any later version. See the file Chris@0: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: #include Chris@0: Chris@0: #include Chris@0: Chris@0: #include "AudioDBFeatureWriter.h" Chris@0: Chris@0: using namespace std; Chris@0: using namespace Vamp; Chris@0: Chris@0: string Chris@0: AudioDBFeatureWriter::catalogueIdParam = "catid"; Chris@0: Chris@0: string Chris@0: AudioDBFeatureWriter::baseDirParam = "basedir"; Chris@0: Chris@0: struct AudioDBFeatureWriter::TrackStream Chris@0: { Chris@0: QString trackid; Chris@0: ofstream* ofs; Chris@0: }; Chris@0: Chris@0: AudioDBFeatureWriter::AudioDBFeatureWriter() : Chris@0: catalogueId("catalog"), baseDir("audiodb") Chris@0: { Chris@0: Chris@0: } Chris@0: Chris@0: AudioDBFeatureWriter::~AudioDBFeatureWriter() Chris@0: { Chris@0: // close all open files Chris@0: for (map::iterator iter = dbfiles.begin(); iter != dbfiles.end(); ++iter) Chris@0: { Chris@0: if (iter->second.ofs) { Chris@0: iter->second.ofs->close(); Chris@0: delete iter->second.ofs; Chris@0: } Chris@0: } Chris@0: Chris@0: // TODO: error handling on close Chris@0: } Chris@0: Chris@0: AudioDBFeatureWriter::ParameterList Chris@0: AudioDBFeatureWriter::getSupportedParameters() const Chris@0: { Chris@0: ParameterList pl; Chris@0: Parameter p; Chris@0: Chris@0: p.name = catalogueIdParam; Chris@0: p.description = "Catalogue ID"; Chris@0: p.hasArg = true; Chris@0: pl.push_back(p); Chris@0: Chris@0: p.name = baseDirParam; Chris@0: p.description = "Base output directory path"; Chris@0: p.hasArg = true; Chris@0: pl.push_back(p); Chris@0: Chris@0: return pl; Chris@0: } Chris@0: Chris@0: void Chris@0: AudioDBFeatureWriter::setParameters(map ¶ms) Chris@0: { Chris@0: if (params.find(catalogueIdParam) != params.end()) { Chris@0: setCatalogueId(params[catalogueIdParam]); Chris@0: params.erase(catalogueIdParam); Chris@0: } Chris@0: if (params.find(baseDirParam) != params.end()) { Chris@0: setBaseDirectory(params[baseDirParam]); Chris@0: params.erase(baseDirParam); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: AudioDBFeatureWriter::setCatalogueId(const string &catid) Chris@0: { Chris@0: catalogueId = catid; Chris@0: } Chris@0: Chris@0: void Chris@0: AudioDBFeatureWriter::setBaseDirectory(const string &base) Chris@0: { Chris@0: baseDir = base; Chris@0: } Chris@0: Chris@0: void AudioDBFeatureWriter::write(QString trackid, Chris@0: const Transform &transform, Chris@0: const Vamp::Plugin::OutputDescriptor& output, Chris@0: const Vamp::Plugin::FeatureList& featureList, Chris@0: std::string summaryType) Chris@0: { Chris@0: //!!! use summaryType Chris@0: if (summaryType != "") { Chris@0: //!!! IMPLEMENT Chris@0: cerr << "ERROR: AudioDBFeatureWriter::write: Writing summaries is not yet implemented!" << endl; Chris@0: exit(1); Chris@0: } Chris@0: Chris@0: Chris@0: // binary output for FeatureSet Chris@0: Chris@0: // feature-dimension feature-1 feature-2 ... Chris@0: // timestamp-1 timestamp-2 ... Chris@0: Chris@0: // audioDB has to write each feature to a different file Chris@0: // assume a simple naming convention of Chris@0: // /. Chris@0: // with timestamps in a corresponding /..timestamp file Chris@0: // (start and end times in seconds for each frame -- somewhat optional) Chris@0: Chris@0: // the feature writer holds a map of open file descriptors Chris@0: // the catalog-id is passed in to the feature writer's constructor Chris@0: Chris@0: // NB -- all "floats" in the file should in fact be doubles Chris@0: Chris@0: // TODO: Chris@0: // - write feature end rather than start times, once end time is available in vamp Chris@0: // - write a power file, probably by wrapping plugin in a PluginPowerAdapter :) Chris@0: Chris@0: if (output.binCount == 0) // this kind of feature just outputs timestamps and labels, assume of no interest to audioDB Chris@0: return; Chris@0: Chris@0: for (int i = 0; i < featureList.size(); ++i) Chris@0: { Chris@0: // replace output files if necessary Chris@0: if (replaceDBFile(trackid, output.identifier)) Chris@0: { Chris@0: // write the feature length for the next track feature record Chris@0: // binCount has to be set Chris@0: // - it can be zero, i.e. if the output is really a set of labels + timestamps Chris@0: *dbfiles[output.identifier].ofs /*<< ios::binary*/ << output.binCount; Chris@0: Chris@0: cerr << "writing bin count " << output.binCount << " for " << output.identifier << endl; Chris@0: } Chris@0: Chris@0: if (replaceDBFile(trackid, output.identifier + ".timestamp")) Chris@0: { Chris@0: // write the start time to the timestamp file Chris@0: // as we want it for the first feature in the file Chris@0: *dbfiles[output.identifier + ".timestamp"].ofs << featureList[i].timestamp.toString() << endl; Chris@0: } Chris@0: Chris@0: if (dbfiles[output.identifier].ofs) { Chris@0: for (int j = 0; j < featureList[i].values.size(); ++j) Chris@0: *dbfiles[output.identifier].ofs /*<< ios::binary*/ << featureList[i].values[j]; Chris@0: Chris@0: // write the *end* time of each feature to the timestamp file Chris@0: // NOT IMPLEMENTED YET Chris@0: // *dbfiles[output.identifier + ".timestamp"].ofs << featureList[i].timestamp.toString() << endl; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: bool AudioDBFeatureWriter::openDBFile(QString trackid, const string& identifier) Chris@0: { Chris@0: QString trackBase = QFileInfo(trackid).fileName(); Chris@0: string filepath = baseDir + "/" + catalogueId + "/" Chris@0: + trackBase.toStdString() + "." + identifier; Chris@0: cerr << "AudioDBFeatureWriter::openDBFile: filepath is \"" << filepath << "\"" << endl; Chris@0: ofstream* ofs = new ofstream(filepath.c_str()); Chris@0: if (!*ofs) Chris@0: { Chris@0: cerr << "ERROR AudioDBFeatureWriter::openDBFile(): can't open file " << filepath << endl; Chris@0: return false; Chris@0: } Chris@0: TrackStream ts; Chris@0: ts.trackid = trackid; Chris@0: ts.ofs = ofs; Chris@0: dbfiles[identifier] = ts; Chris@0: return true; Chris@0: } Chris@0: Chris@0: // replace file if no file open for this track, else return false Chris@0: bool AudioDBFeatureWriter::replaceDBFile(QString trackid, Chris@0: const string& identifier) Chris@0: { Chris@0: if (dbfiles.find(identifier) != dbfiles.end() && dbfiles[identifier].trackid == trackid) Chris@0: return false; // have an open file for this track Chris@0: Chris@0: if (dbfiles.find(identifier) != dbfiles.end() && dbfiles[identifier].trackid != trackid) Chris@0: { Chris@0: // close the current file Chris@0: if (dbfiles[identifier].ofs) { Chris@0: dbfiles[identifier].ofs->close(); Chris@0: delete dbfiles[identifier].ofs; Chris@0: dbfiles[identifier].ofs = 0; Chris@0: } Chris@0: } Chris@0: Chris@0: // open a new file Chris@0: if (!openDBFile(trackid, identifier)) { Chris@0: dbfiles[identifier].ofs = 0; Chris@0: return false; //!!! should throw an exception, otherwise we'll try to open the file again and again every time we want to write to it Chris@0: } Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: