annotate runner/MIDIFeatureWriter.cpp @ 302:1e6cee321cc0

Added tag sonic-annotator-1.5 for changeset 929e37acf0d9
author Chris Cannam
date Thu, 25 May 2017 15:01:41 +0100
parents 3b7ec45abd1c
children ef03350baec7
rev   line source
Chris@137 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@137 2
Chris@137 3 /*
Chris@137 4 Sonic Annotator
Chris@137 5 A utility for batch feature extraction from audio files.
Chris@137 6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
Chris@137 7 Copyright 2007-2014 QMUL.
Chris@137 8
Chris@137 9 This program is free software; you can redistribute it and/or
Chris@137 10 modify it under the terms of the GNU General Public License as
Chris@137 11 published by the Free Software Foundation; either version 2 of the
Chris@137 12 License, or (at your option) any later version. See the file
Chris@137 13 COPYING included with this distribution for more information.
Chris@137 14 */
Chris@137 15
Chris@137 16 #include "MIDIFeatureWriter.h"
Chris@137 17
Chris@137 18 using namespace std;
Chris@137 19 using Vamp::Plugin;
Chris@137 20 using Vamp::PluginBase;
Chris@137 21
Chris@137 22 #include "base/Exceptions.h"
Chris@137 23 #include "data/fileio/MIDIFileWriter.h"
Chris@137 24
Chris@196 25 //#define DEBUG_MIDI_FEATURE_WRITER 1
Chris@196 26
Chris@137 27 MIDIFeatureWriter::MIDIFeatureWriter() :
Chris@137 28 FileFeatureWriter(SupportOneFilePerTrackTransform |
Chris@137 29 SupportOneFilePerTrack |
Chris@137 30 SupportOneFileTotal,
Chris@137 31 "mid")
Chris@137 32 {
Chris@137 33 }
Chris@137 34
Chris@137 35 MIDIFeatureWriter::~MIDIFeatureWriter()
Chris@137 36 {
Chris@137 37 }
Chris@137 38
Chris@144 39 string
Chris@144 40 MIDIFeatureWriter::getDescription() const
Chris@144 41 {
Chris@197 42 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 43 }
Chris@144 44
Chris@137 45 MIDIFeatureWriter::ParameterList
Chris@137 46 MIDIFeatureWriter::getSupportedParameters() const
Chris@137 47 {
Chris@137 48 ParameterList pl = FileFeatureWriter::getSupportedParameters();
Chris@137 49 return pl;
Chris@137 50 }
Chris@137 51
Chris@137 52 void
Chris@137 53 MIDIFeatureWriter::setParameters(map<string, string> &params)
Chris@137 54 {
Chris@137 55 FileFeatureWriter::setParameters(params);
Chris@137 56 }
Chris@137 57
Chris@137 58 void
Chris@137 59 MIDIFeatureWriter::setTrackMetadata(QString, TrackMetadata)
Chris@137 60 {
Chris@137 61 cerr << "MIDIFeatureWriter::setTrackMetadata: not supported (yet?)" << endl;
Chris@137 62 }
Chris@137 63
Chris@137 64 void
Chris@137 65 MIDIFeatureWriter::write(QString trackId,
Chris@137 66 const Transform &transform,
Chris@137 67 const Plugin::OutputDescriptor& output,
Chris@137 68 const Plugin::FeatureList& features,
Chris@141 69 std::string /* summaryType */)
Chris@137 70 {
Chris@140 71 QString transformId = transform.getIdentifier();
Chris@140 72
Chris@140 73 QString filename = getOutputFilename(trackId, transformId);
Chris@137 74 if (filename == "") {
Chris@140 75 throw FailedToOpenOutputStream(trackId, transformId);
Chris@137 76 }
Chris@137 77
Chris@141 78 int sampleRate = transform.getSampleRate();
Chris@141 79
Chris@140 80 if (m_rates.find(filename) == m_rates.end()) {
Chris@140 81 m_rates[filename] = sampleRate;
Chris@140 82 }
Chris@140 83
Chris@175 84 if (m_fileTransforms[filename].find(transform) ==
Chris@140 85 m_fileTransforms[filename].end()) {
Chris@140 86
Chris@140 87 // This transform is new to the file, give it a channel number
Chris@140 88
Chris@140 89 int channel = m_nextChannels[filename];
Chris@140 90 m_nextChannels[filename] = channel + 1;
Chris@140 91
Chris@175 92 m_fileTransforms[filename].insert(transform);
Chris@175 93 m_channels[transform] = channel;
Chris@140 94 }
Chris@140 95
Chris@140 96 NoteList notes = m_notes[filename];
Chris@140 97
Chris@141 98 bool freq = (output.unit == "Hz" ||
Chris@141 99 output.unit == "hz" ||
Chris@141 100 output.unit == "HZ");
Chris@141 101
Chris@141 102 for (int i = 0; i < (int)features.size(); ++i) {
Chris@141 103
Chris@141 104 const Plugin::Feature &feature(features[i]);
Chris@141 105
Chris@141 106 Vamp::RealTime timestamp = feature.timestamp;
Chris@141 107 int frame = Vamp::RealTime::realTime2Frame(timestamp, sampleRate);
Chris@141 108
Chris@141 109 int duration = 1;
Chris@141 110 if (feature.hasDuration) {
Chris@141 111 duration = Vamp::RealTime::realTime2Frame(feature.duration, sampleRate);
Chris@141 112 }
Chris@196 113
Chris@196 114 #ifdef DEBUG_MIDI_FEATURE_WRITER
Chris@196 115 cerr << "feature timestamp = " << feature.timestamp << ", sampleRate = " << sampleRate << ", frame = " << frame << endl;
Chris@196 116 cerr << "feature duration = " << feature.duration << ", sampleRate = " << sampleRate << ", duration = " << duration << endl;
Chris@196 117 #endif
Chris@141 118
Chris@141 119 int pitch = 60;
Chris@141 120 if (feature.values.size() > 0) {
Chris@141 121 float pval = feature.values[0];
Chris@141 122 if (freq) {
Chris@141 123 pitch = Pitch::getPitchForFrequency(pval);
Chris@141 124 } else {
Chris@141 125 pitch = int(pval + 0.5);
Chris@141 126 }
Chris@141 127 }
Chris@141 128
Chris@141 129 int velocity = 100;
Chris@141 130 if (feature.values.size() > 1) {
Chris@141 131 float vval = feature.values[1];
Chris@141 132 if (vval < 128) {
Chris@141 133 velocity = int(vval + 0.5);
Chris@141 134 }
Chris@141 135 }
Chris@141 136
Chris@141 137 NoteData note(frame, duration, pitch, velocity);
Chris@141 138
Chris@175 139 note.channel = m_channels[transform];
Chris@141 140
Chris@141 141 notes.push_back(note);
Chris@141 142 }
Chris@140 143
Chris@140 144 m_notes[filename] = notes;
Chris@137 145 }
Chris@137 146
Chris@137 147 void
Chris@137 148 MIDIFeatureWriter::finish()
Chris@137 149 {
Chris@137 150 for (NoteMap::const_iterator i = m_notes.begin(); i != m_notes.end(); ++i) {
Chris@137 151
Chris@137 152 QString filename = i->first;
Chris@137 153 NoteList notes = i->second;
Chris@137 154 float rate = m_rates[filename];
Chris@137 155
Chris@137 156 TrivialNoteExportable exportable(notes);
Chris@137 157
Chris@137 158 {
Chris@137 159 MIDIFileWriter writer(filename, &exportable, rate);
Chris@137 160 if (!writer.isOK()) {
Chris@137 161 cerr << "ERROR: Failed to create MIDI writer: "
Chris@137 162 << writer.getError() << endl;
Chris@137 163 throw FileOperationFailed(filename, "create MIDI writer");
Chris@137 164 }
Chris@147 165
Chris@137 166 writer.write();
Chris@147 167
Chris@147 168 if (!writer.isOK()) {
Chris@147 169 cerr << "ERROR: Failed to write to MIDI file: "
Chris@147 170 << writer.getError() << endl;
Chris@147 171 throw FileOperationFailed(filename, "MIDI write");
Chris@147 172 }
Chris@137 173 }
Chris@137 174 }
Chris@145 175
Chris@152 176 m_notes.clear();
Chris@152 177
Chris@145 178 FileFeatureWriter::finish();
Chris@137 179 }
Chris@137 180