annotate runner/MIDIFeatureWriter.cpp @ 399:a3912193ce69 tip

Default branch is now named default on git as well as hg, in case we ever want to switch to mirroring in the other direction
author Chris Cannam
date Thu, 27 Aug 2020 15:57:37 +0100
parents e39307a8d22d
children
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@331 79 sv_samplerate_t 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@331 107 RealTime timestamp(feature.timestamp);
Chris@331 108 sv_frame_t frame = RealTime::realTime2Frame(timestamp, sampleRate);
Chris@141 109
Chris@331 110 sv_frame_t duration = 1;
Chris@141 111 if (feature.hasDuration) {
Chris@331 112 RealTime rduration(feature.duration);
Chris@331 113 duration = RealTime::realTime2Frame(rduration, sampleRate);
Chris@141 114 }
Chris@196 115
Chris@196 116 #ifdef DEBUG_MIDI_FEATURE_WRITER
Chris@324 117 SVCERR << "feature timestamp = " << feature.timestamp << ", sampleRate = " << sampleRate << ", frame = " << frame << endl;
Chris@324 118 SVCERR << "feature duration = " << feature.duration << ", sampleRate = " << sampleRate << ", duration = " << duration << endl;
Chris@196 119 #endif
Chris@141 120
Chris@141 121 int pitch = 60;
Chris@141 122 if (feature.values.size() > 0) {
Chris@141 123 float pval = feature.values[0];
Chris@141 124 if (freq) {
Chris@141 125 pitch = Pitch::getPitchForFrequency(pval);
Chris@141 126 } else {
Chris@141 127 pitch = int(pval + 0.5);
Chris@141 128 }
Chris@141 129 }
Chris@141 130
Chris@141 131 int velocity = 100;
Chris@141 132 if (feature.values.size() > 1) {
Chris@141 133 float vval = feature.values[1];
Chris@141 134 if (vval < 128) {
Chris@141 135 velocity = int(vval + 0.5);
Chris@141 136 }
Chris@141 137 }
Chris@141 138
Chris@141 139 NoteData note(frame, duration, pitch, velocity);
Chris@141 140
Chris@175 141 note.channel = m_channels[transform];
Chris@141 142
Chris@141 143 notes.push_back(note);
Chris@141 144 }
Chris@140 145
Chris@140 146 m_notes[filename] = notes;
Chris@137 147 }
Chris@137 148
Chris@137 149 void
Chris@137 150 MIDIFeatureWriter::finish()
Chris@137 151 {
Chris@137 152 for (NoteMap::const_iterator i = m_notes.begin(); i != m_notes.end(); ++i) {
Chris@137 153
Chris@137 154 QString filename = i->first;
Chris@137 155 NoteList notes = i->second;
Chris@331 156 sv_samplerate_t rate = m_rates[filename];
Chris@137 157
Chris@137 158 TrivialNoteExportable exportable(notes);
Chris@137 159
Chris@137 160 {
Chris@137 161 MIDIFileWriter writer(filename, &exportable, rate);
Chris@137 162 if (!writer.isOK()) {
Chris@324 163 SVCERR << "ERROR: Failed to create MIDI writer: "
Chris@324 164 << writer.getError() << endl;
Chris@137 165 throw FileOperationFailed(filename, "create MIDI writer");
Chris@137 166 }
Chris@147 167
Chris@137 168 writer.write();
Chris@147 169
Chris@147 170 if (!writer.isOK()) {
Chris@324 171 SVCERR << "ERROR: Failed to write to MIDI file: "
Chris@324 172 << writer.getError() << endl;
Chris@147 173 throw FileOperationFailed(filename, "MIDI write");
Chris@147 174 }
Chris@137 175 }
Chris@137 176 }
Chris@145 177
Chris@152 178 m_notes.clear();
Chris@152 179
Chris@145 180 FileFeatureWriter::finish();
Chris@137 181 }
Chris@137 182