annotate runner/MIDIFeatureWriter.cpp @ 325:d5caf5e91a86 default-writer-writes-to-files

If the default writer were to be able to write to files (but this is backward-incompatible so probably unwise)
author Chris Cannam
date Fri, 18 May 2018 12:36:48 +0100
parents ef03350baec7
children e39307a8d22d
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@324 23 #include "base/Debug.h"
Chris@137 24 #include "data/fileio/MIDIFileWriter.h"
Chris@137 25
Chris@196 26 //#define DEBUG_MIDI_FEATURE_WRITER 1
Chris@196 27
Chris@137 28 MIDIFeatureWriter::MIDIFeatureWriter() :
Chris@137 29 FileFeatureWriter(SupportOneFilePerTrackTransform |
Chris@137 30 SupportOneFilePerTrack |
Chris@137 31 SupportOneFileTotal,
Chris@137 32 "mid")
Chris@137 33 {
Chris@137 34 }
Chris@137 35
Chris@137 36 MIDIFeatureWriter::~MIDIFeatureWriter()
Chris@137 37 {
Chris@137 38 }
Chris@137 39
Chris@144 40 string
Chris@144 41 MIDIFeatureWriter::getDescription() const
Chris@144 42 {
Chris@197 43 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 44 }
Chris@144 45
Chris@137 46 MIDIFeatureWriter::ParameterList
Chris@137 47 MIDIFeatureWriter::getSupportedParameters() const
Chris@137 48 {
Chris@137 49 ParameterList pl = FileFeatureWriter::getSupportedParameters();
Chris@137 50 return pl;
Chris@137 51 }
Chris@137 52
Chris@137 53 void
Chris@137 54 MIDIFeatureWriter::setParameters(map<string, string> &params)
Chris@137 55 {
Chris@137 56 FileFeatureWriter::setParameters(params);
Chris@137 57 }
Chris@137 58
Chris@137 59 void
Chris@137 60 MIDIFeatureWriter::setTrackMetadata(QString, TrackMetadata)
Chris@137 61 {
Chris@324 62 SVCERR << "MIDIFeatureWriter::setTrackMetadata: not supported (yet?)" << endl;
Chris@137 63 }
Chris@137 64
Chris@137 65 void
Chris@137 66 MIDIFeatureWriter::write(QString trackId,
Chris@137 67 const Transform &transform,
Chris@137 68 const Plugin::OutputDescriptor& output,
Chris@137 69 const Plugin::FeatureList& features,
Chris@141 70 std::string /* summaryType */)
Chris@137 71 {
Chris@140 72 QString transformId = transform.getIdentifier();
Chris@140 73
Chris@140 74 QString filename = getOutputFilename(trackId, transformId);
Chris@137 75 if (filename == "") {
Chris@140 76 throw FailedToOpenOutputStream(trackId, transformId);
Chris@137 77 }
Chris@137 78
Chris@141 79 int sampleRate = transform.getSampleRate();
Chris@141 80
Chris@140 81 if (m_rates.find(filename) == m_rates.end()) {
Chris@140 82 m_rates[filename] = sampleRate;
Chris@140 83 }
Chris@140 84
Chris@175 85 if (m_fileTransforms[filename].find(transform) ==
Chris@140 86 m_fileTransforms[filename].end()) {
Chris@140 87
Chris@140 88 // This transform is new to the file, give it a channel number
Chris@140 89
Chris@140 90 int channel = m_nextChannels[filename];
Chris@140 91 m_nextChannels[filename] = channel + 1;
Chris@140 92
Chris@175 93 m_fileTransforms[filename].insert(transform);
Chris@175 94 m_channels[transform] = channel;
Chris@140 95 }
Chris@140 96
Chris@140 97 NoteList notes = m_notes[filename];
Chris@140 98
Chris@141 99 bool freq = (output.unit == "Hz" ||
Chris@141 100 output.unit == "hz" ||
Chris@141 101 output.unit == "HZ");
Chris@141 102
Chris@141 103 for (int i = 0; i < (int)features.size(); ++i) {
Chris@141 104
Chris@141 105 const Plugin::Feature &feature(features[i]);
Chris@141 106
Chris@141 107 Vamp::RealTime timestamp = feature.timestamp;
Chris@141 108 int frame = Vamp::RealTime::realTime2Frame(timestamp, sampleRate);
Chris@141 109
Chris@141 110 int duration = 1;
Chris@141 111 if (feature.hasDuration) {
Chris@141 112 duration = Vamp::RealTime::realTime2Frame(feature.duration, sampleRate);
Chris@141 113 }
Chris@196 114
Chris@196 115 #ifdef DEBUG_MIDI_FEATURE_WRITER
Chris@324 116 SVCERR << "feature timestamp = " << feature.timestamp << ", sampleRate = " << sampleRate << ", frame = " << frame << endl;
Chris@324 117 SVCERR << "feature duration = " << feature.duration << ", sampleRate = " << sampleRate << ", duration = " << duration << endl;
Chris@196 118 #endif
Chris@141 119
Chris@141 120 int pitch = 60;
Chris@141 121 if (feature.values.size() > 0) {
Chris@141 122 float pval = feature.values[0];
Chris@141 123 if (freq) {
Chris@141 124 pitch = Pitch::getPitchForFrequency(pval);
Chris@141 125 } else {
Chris@141 126 pitch = int(pval + 0.5);
Chris@141 127 }
Chris@141 128 }
Chris@141 129
Chris@141 130 int velocity = 100;
Chris@141 131 if (feature.values.size() > 1) {
Chris@141 132 float vval = feature.values[1];
Chris@141 133 if (vval < 128) {
Chris@141 134 velocity = int(vval + 0.5);
Chris@141 135 }
Chris@141 136 }
Chris@141 137
Chris@141 138 NoteData note(frame, duration, pitch, velocity);
Chris@141 139
Chris@175 140 note.channel = m_channels[transform];
Chris@141 141
Chris@141 142 notes.push_back(note);
Chris@141 143 }
Chris@140 144
Chris@140 145 m_notes[filename] = notes;
Chris@137 146 }
Chris@137 147
Chris@137 148 void
Chris@137 149 MIDIFeatureWriter::finish()
Chris@137 150 {
Chris@137 151 for (NoteMap::const_iterator i = m_notes.begin(); i != m_notes.end(); ++i) {
Chris@137 152
Chris@137 153 QString filename = i->first;
Chris@137 154 NoteList notes = i->second;
Chris@137 155 float rate = m_rates[filename];
Chris@137 156
Chris@137 157 TrivialNoteExportable exportable(notes);
Chris@137 158
Chris@137 159 {
Chris@137 160 MIDIFileWriter writer(filename, &exportable, rate);
Chris@137 161 if (!writer.isOK()) {
Chris@324 162 SVCERR << "ERROR: Failed to create MIDI writer: "
Chris@324 163 << writer.getError() << endl;
Chris@137 164 throw FileOperationFailed(filename, "create MIDI writer");
Chris@137 165 }
Chris@147 166
Chris@137 167 writer.write();
Chris@147 168
Chris@147 169 if (!writer.isOK()) {
Chris@324 170 SVCERR << "ERROR: Failed to write to MIDI file: "
Chris@324 171 << writer.getError() << endl;
Chris@147 172 throw FileOperationFailed(filename, "MIDI write");
Chris@147 173 }
Chris@137 174 }
Chris@137 175 }
Chris@145 176
Chris@152 177 m_notes.clear();
Chris@152 178
Chris@145 179 FileFeatureWriter::finish();
Chris@137 180 }
Chris@137 181