Chris@137: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@137: Chris@137: /* Chris@137: Sonic Annotator Chris@137: A utility for batch feature extraction from audio files. Chris@137: Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. Chris@137: Copyright 2007-2014 QMUL. Chris@137: Chris@137: This program is free software; you can redistribute it and/or Chris@137: modify it under the terms of the GNU General Public License as Chris@137: published by the Free Software Foundation; either version 2 of the Chris@137: License, or (at your option) any later version. See the file Chris@137: COPYING included with this distribution for more information. Chris@137: */ Chris@137: Chris@137: #include "MIDIFeatureWriter.h" Chris@137: Chris@137: using namespace std; Chris@137: using Vamp::Plugin; Chris@137: using Vamp::PluginBase; Chris@137: Chris@137: #include "base/Exceptions.h" Chris@324: #include "base/Debug.h" Chris@137: #include "data/fileio/MIDIFileWriter.h" Chris@137: Chris@196: //#define DEBUG_MIDI_FEATURE_WRITER 1 Chris@196: Chris@137: MIDIFeatureWriter::MIDIFeatureWriter() : Chris@137: FileFeatureWriter(SupportOneFilePerTrackTransform | Chris@137: SupportOneFilePerTrack | Chris@137: SupportOneFileTotal, Chris@137: "mid") Chris@137: { Chris@137: } Chris@137: Chris@137: MIDIFeatureWriter::~MIDIFeatureWriter() Chris@137: { Chris@137: } Chris@137: Chris@144: string Chris@144: MIDIFeatureWriter::getDescription() const Chris@144: { Chris@197: return "Write features to MIDI files. All features are written as MIDI notes. If a feature has at least one value, its first value will be used as the note pitch, the second value (if present) for velocity. If a feature has units of Hz, then its pitch will be converted from frequency to an integer value in MIDI range; otherwise it will simply be rounded to the nearest integer and written directly. (Be aware that MIDI cannot represent overlapping notes of equal pitch, so features with durations may be misrepresented if they do not have distinct enough values.) Multiple (up to 16) transforms can be written to a single MIDI file, where they will be given separate MIDI channel numbers."; Chris@144: } Chris@144: Chris@137: MIDIFeatureWriter::ParameterList Chris@137: MIDIFeatureWriter::getSupportedParameters() const Chris@137: { Chris@137: ParameterList pl = FileFeatureWriter::getSupportedParameters(); Chris@137: return pl; Chris@137: } Chris@137: Chris@137: void Chris@137: MIDIFeatureWriter::setParameters(map ¶ms) Chris@137: { Chris@137: FileFeatureWriter::setParameters(params); Chris@137: } Chris@137: Chris@137: void Chris@137: MIDIFeatureWriter::setTrackMetadata(QString, TrackMetadata) Chris@137: { Chris@324: SVCERR << "MIDIFeatureWriter::setTrackMetadata: not supported (yet?)" << endl; Chris@137: } Chris@137: Chris@137: void Chris@137: MIDIFeatureWriter::write(QString trackId, Chris@137: const Transform &transform, Chris@137: const Plugin::OutputDescriptor& output, Chris@137: const Plugin::FeatureList& features, Chris@141: std::string /* summaryType */) Chris@137: { Chris@140: QString transformId = transform.getIdentifier(); Chris@140: Chris@140: QString filename = getOutputFilename(trackId, transformId); Chris@137: if (filename == "") { Chris@140: throw FailedToOpenOutputStream(trackId, transformId); Chris@137: } Chris@137: Chris@331: sv_samplerate_t sampleRate = transform.getSampleRate(); Chris@141: Chris@140: if (m_rates.find(filename) == m_rates.end()) { Chris@140: m_rates[filename] = sampleRate; Chris@140: } Chris@140: Chris@175: if (m_fileTransforms[filename].find(transform) == Chris@140: m_fileTransforms[filename].end()) { Chris@140: Chris@140: // This transform is new to the file, give it a channel number Chris@140: Chris@140: int channel = m_nextChannels[filename]; Chris@140: m_nextChannels[filename] = channel + 1; Chris@140: Chris@175: m_fileTransforms[filename].insert(transform); Chris@175: m_channels[transform] = channel; Chris@140: } Chris@140: Chris@140: NoteList notes = m_notes[filename]; Chris@140: Chris@141: bool freq = (output.unit == "Hz" || Chris@141: output.unit == "hz" || Chris@141: output.unit == "HZ"); Chris@141: Chris@141: for (int i = 0; i < (int)features.size(); ++i) { Chris@141: Chris@141: const Plugin::Feature &feature(features[i]); Chris@141: Chris@331: RealTime timestamp(feature.timestamp); Chris@331: sv_frame_t frame = RealTime::realTime2Frame(timestamp, sampleRate); Chris@141: Chris@331: sv_frame_t duration = 1; Chris@141: if (feature.hasDuration) { Chris@331: RealTime rduration(feature.duration); Chris@331: duration = RealTime::realTime2Frame(rduration, sampleRate); Chris@141: } Chris@196: Chris@196: #ifdef DEBUG_MIDI_FEATURE_WRITER Chris@324: SVCERR << "feature timestamp = " << feature.timestamp << ", sampleRate = " << sampleRate << ", frame = " << frame << endl; Chris@324: SVCERR << "feature duration = " << feature.duration << ", sampleRate = " << sampleRate << ", duration = " << duration << endl; Chris@196: #endif Chris@141: Chris@141: int pitch = 60; Chris@141: if (feature.values.size() > 0) { Chris@141: float pval = feature.values[0]; Chris@141: if (freq) { Chris@141: pitch = Pitch::getPitchForFrequency(pval); Chris@141: } else { Chris@141: pitch = int(pval + 0.5); Chris@141: } Chris@141: } Chris@141: Chris@141: int velocity = 100; Chris@141: if (feature.values.size() > 1) { Chris@141: float vval = feature.values[1]; Chris@141: if (vval < 128) { Chris@141: velocity = int(vval + 0.5); Chris@141: } Chris@141: } Chris@141: Chris@141: NoteData note(frame, duration, pitch, velocity); Chris@141: Chris@175: note.channel = m_channels[transform]; Chris@141: Chris@141: notes.push_back(note); Chris@141: } Chris@140: Chris@140: m_notes[filename] = notes; Chris@137: } Chris@137: Chris@137: void Chris@137: MIDIFeatureWriter::finish() Chris@137: { Chris@137: for (NoteMap::const_iterator i = m_notes.begin(); i != m_notes.end(); ++i) { Chris@137: Chris@137: QString filename = i->first; Chris@137: NoteList notes = i->second; Chris@331: sv_samplerate_t rate = m_rates[filename]; Chris@137: Chris@137: TrivialNoteExportable exportable(notes); Chris@137: Chris@137: { Chris@137: MIDIFileWriter writer(filename, &exportable, rate); Chris@137: if (!writer.isOK()) { Chris@324: SVCERR << "ERROR: Failed to create MIDI writer: " Chris@324: << writer.getError() << endl; Chris@137: throw FileOperationFailed(filename, "create MIDI writer"); Chris@137: } Chris@147: Chris@137: writer.write(); Chris@147: Chris@147: if (!writer.isOK()) { Chris@324: SVCERR << "ERROR: Failed to write to MIDI file: " Chris@324: << writer.getError() << endl; Chris@147: throw FileOperationFailed(filename, "MIDI write"); Chris@147: } Chris@137: } Chris@137: } Chris@145: Chris@152: m_notes.clear(); Chris@152: Chris@145: FileFeatureWriter::finish(); Chris@137: } Chris@137: