annotate src/Silvet.cpp @ 135:8db5e4ab56ce

Ground-truth data in CSV and lab format, converted from the MIDI using Sonic Visualiser and then to lab using the script here
author Chris Cannam
date Thu, 08 May 2014 12:59:09 +0100
parents 230920148ee5
children 450f9987f041
rev   line source
Chris@31 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@31 2
Chris@31 3 /*
Chris@31 4 Silvet
Chris@31 5
Chris@31 6 A Vamp plugin for note transcription.
Chris@31 7 Centre for Digital Music, Queen Mary University of London.
Chris@31 8
Chris@31 9 This program is free software; you can redistribute it and/or
Chris@31 10 modify it under the terms of the GNU General Public License as
Chris@31 11 published by the Free Software Foundation; either version 2 of the
Chris@31 12 License, or (at your option) any later version. See the file
Chris@31 13 COPYING included with this distribution for more information.
Chris@31 14 */
Chris@31 15
Chris@31 16 #include "Silvet.h"
Chris@34 17 #include "EM.h"
Chris@31 18
Chris@32 19 #include "maths/MedianFilter.h"
Chris@55 20 #include "maths/MathUtilities.h"
Chris@31 21 #include "dsp/rateconversion/Resampler.h"
Chris@31 22
Chris@32 23 #include "constant-q-cpp/cpp-qm-dsp/CQInterpolated.h"
Chris@31 24
Chris@31 25 #include <vector>
Chris@31 26
Chris@32 27 #include <cstdio>
Chris@32 28
Chris@31 29 using std::vector;
Chris@48 30 using std::cout;
Chris@31 31 using std::cerr;
Chris@31 32 using std::endl;
Chris@40 33 using Vamp::RealTime;
Chris@31 34
Chris@31 35 static int processingSampleRate = 44100;
Chris@31 36 static int processingBPO = 60;
Chris@32 37 static int processingHeight = 545;
Chris@38 38 static int processingNotes = 88;
Chris@31 39
Chris@31 40 Silvet::Silvet(float inputSampleRate) :
Chris@31 41 Plugin(inputSampleRate),
Chris@31 42 m_resampler(0),
Chris@110 43 m_cq(0),
Chris@113 44 m_hqMode(true)
Chris@31 45 {
Chris@31 46 }
Chris@31 47
Chris@31 48 Silvet::~Silvet()
Chris@31 49 {
Chris@31 50 delete m_resampler;
Chris@31 51 delete m_cq;
Chris@41 52 for (int i = 0; i < (int)m_postFilter.size(); ++i) {
Chris@41 53 delete m_postFilter[i];
Chris@41 54 }
Chris@31 55 }
Chris@31 56
Chris@31 57 string
Chris@31 58 Silvet::getIdentifier() const
Chris@31 59 {
Chris@31 60 return "silvet";
Chris@31 61 }
Chris@31 62
Chris@31 63 string
Chris@31 64 Silvet::getName() const
Chris@31 65 {
Chris@31 66 return "Silvet Note Transcription";
Chris@31 67 }
Chris@31 68
Chris@31 69 string
Chris@31 70 Silvet::getDescription() const
Chris@31 71 {
Chris@31 72 // Return something helpful here!
Chris@31 73 return "";
Chris@31 74 }
Chris@31 75
Chris@31 76 string
Chris@31 77 Silvet::getMaker() const
Chris@31 78 {
Chris@31 79 // Your name here
Chris@31 80 return "";
Chris@31 81 }
Chris@31 82
Chris@31 83 int
Chris@31 84 Silvet::getPluginVersion() const
Chris@31 85 {
Chris@31 86 return 1;
Chris@31 87 }
Chris@31 88
Chris@31 89 string
Chris@31 90 Silvet::getCopyright() const
Chris@31 91 {
Chris@31 92 // This function is not ideally named. It does not necessarily
Chris@31 93 // need to say who made the plugin -- getMaker does that -- but it
Chris@31 94 // should indicate the terms under which it is distributed. For
Chris@31 95 // example, "Copyright (year). All Rights Reserved", or "GPL"
Chris@31 96 return "";
Chris@31 97 }
Chris@31 98
Chris@31 99 Silvet::InputDomain
Chris@31 100 Silvet::getInputDomain() const
Chris@31 101 {
Chris@31 102 return TimeDomain;
Chris@31 103 }
Chris@31 104
Chris@31 105 size_t
Chris@31 106 Silvet::getPreferredBlockSize() const
Chris@31 107 {
Chris@31 108 return 0;
Chris@31 109 }
Chris@31 110
Chris@31 111 size_t
Chris@31 112 Silvet::getPreferredStepSize() const
Chris@31 113 {
Chris@31 114 return 0;
Chris@31 115 }
Chris@31 116
Chris@31 117 size_t
Chris@31 118 Silvet::getMinChannelCount() const
Chris@31 119 {
Chris@31 120 return 1;
Chris@31 121 }
Chris@31 122
Chris@31 123 size_t
Chris@31 124 Silvet::getMaxChannelCount() const
Chris@31 125 {
Chris@31 126 return 1;
Chris@31 127 }
Chris@31 128
Chris@31 129 Silvet::ParameterList
Chris@31 130 Silvet::getParameterDescriptors() const
Chris@31 131 {
Chris@31 132 ParameterList list;
Chris@110 133
Chris@110 134 ParameterDescriptor desc;
Chris@110 135 desc.identifier = "mode";
Chris@110 136 desc.name = "Processing mode";
Chris@110 137 desc.unit = "";
Chris@110 138 desc.description = "Determines the tradeoff of processing speed against transcription quality";
Chris@110 139 desc.minValue = 0;
Chris@110 140 desc.maxValue = 1;
Chris@113 141 desc.defaultValue = 1;
Chris@110 142 desc.isQuantized = true;
Chris@110 143 desc.quantizeStep = 1;
Chris@110 144 desc.valueNames.push_back("Draft (faster)");
Chris@110 145 desc.valueNames.push_back("Intensive (higher quality)");
Chris@110 146 list.push_back(desc);
Chris@110 147
Chris@31 148 return list;
Chris@31 149 }
Chris@31 150
Chris@31 151 float
Chris@31 152 Silvet::getParameter(string identifier) const
Chris@31 153 {
Chris@110 154 if (identifier == "mode") {
Chris@110 155 return m_hqMode ? 1.f : 0.f;
Chris@110 156 }
Chris@31 157 return 0;
Chris@31 158 }
Chris@31 159
Chris@31 160 void
Chris@31 161 Silvet::setParameter(string identifier, float value)
Chris@31 162 {
Chris@110 163 if (identifier == "mode") {
Chris@110 164 m_hqMode = (value > 0.5);
Chris@110 165 }
Chris@31 166 }
Chris@31 167
Chris@31 168 Silvet::ProgramList
Chris@31 169 Silvet::getPrograms() const
Chris@31 170 {
Chris@31 171 ProgramList list;
Chris@31 172 return list;
Chris@31 173 }
Chris@31 174
Chris@31 175 string
Chris@31 176 Silvet::getCurrentProgram() const
Chris@31 177 {
Chris@31 178 return "";
Chris@31 179 }
Chris@31 180
Chris@31 181 void
Chris@31 182 Silvet::selectProgram(string name)
Chris@31 183 {
Chris@31 184 }
Chris@31 185
Chris@31 186 Silvet::OutputList
Chris@31 187 Silvet::getOutputDescriptors() const
Chris@31 188 {
Chris@31 189 OutputList list;
Chris@31 190
Chris@31 191 OutputDescriptor d;
Chris@51 192 d.identifier = "notes";
Chris@51 193 d.name = "Note transcription";
Chris@51 194 d.description = "Overall note transcription across all instruments";
Chris@41 195 d.unit = "Hz";
Chris@31 196 d.hasFixedBinCount = true;
Chris@31 197 d.binCount = 2;
Chris@41 198 d.binNames.push_back("Frequency");
Chris@31 199 d.binNames.push_back("Velocity");
Chris@31 200 d.hasKnownExtents = false;
Chris@31 201 d.isQuantized = false;
Chris@31 202 d.sampleType = OutputDescriptor::VariableSampleRate;
Chris@51 203 d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 62);
Chris@31 204 d.hasDuration = true;
Chris@32 205 m_notesOutputNo = list.size();
Chris@32 206 list.push_back(d);
Chris@32 207
Chris@51 208 d.identifier = "cq";
Chris@51 209 d.name = "Raw constant-Q";
Chris@51 210 d.description = "Unfiltered constant-Q time-frequency distribution";
Chris@51 211 d.unit = "";
Chris@51 212 d.hasFixedBinCount = true;
Chris@51 213 d.binCount = processingHeight + 55;
Chris@51 214 d.binNames.clear();
Chris@51 215 if (m_cq) {
Chris@51 216 char name[20];
Chris@51 217 for (int i = 0; i < processingHeight + 55; ++i) {
Chris@51 218 float freq = m_cq->getBinFrequency(i);
Chris@51 219 sprintf(name, "%.1f Hz", freq);
Chris@51 220 d.binNames.push_back(name);
Chris@51 221 }
Chris@51 222 }
Chris@51 223 d.hasKnownExtents = false;
Chris@51 224 d.isQuantized = false;
Chris@51 225 d.sampleType = OutputDescriptor::FixedSampleRate;
Chris@51 226 d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 62);
Chris@51 227 d.hasDuration = false;
Chris@51 228 m_cqOutputNo = list.size();
Chris@51 229 list.push_back(d);
Chris@51 230
Chris@32 231 d.identifier = "inputgrid";
Chris@51 232 d.name = "Filtered constant-Q";
Chris@51 233 d.description = "Filtered constant-Q time-frequency distribution used as input to the PLCA step";
Chris@32 234 d.unit = "";
Chris@32 235 d.hasFixedBinCount = true;
Chris@32 236 d.binCount = processingHeight;
Chris@32 237 d.binNames.clear();
Chris@32 238 if (m_cq) {
Chris@32 239 char name[20];
Chris@32 240 for (int i = 0; i < processingHeight; ++i) {
Chris@32 241 float freq = m_cq->getBinFrequency(i + 55);
Chris@32 242 sprintf(name, "%.1f Hz", freq);
Chris@32 243 d.binNames.push_back(name);
Chris@32 244 }
Chris@32 245 }
Chris@32 246 d.hasKnownExtents = false;
Chris@32 247 d.isQuantized = false;
Chris@32 248 d.sampleType = OutputDescriptor::FixedSampleRate;
Chris@32 249 d.sampleRate = 25;
Chris@32 250 d.hasDuration = false;
Chris@51 251 m_fcqOutputNo = list.size();
Chris@31 252 list.push_back(d);
Chris@31 253
Chris@51 254 d.identifier = "pitches";
Chris@51 255 d.name = "Pitch activation";
Chris@51 256 d.description = "Estimated pitch activation matrix";
Chris@38 257 d.unit = "";
Chris@38 258 d.hasFixedBinCount = true;
Chris@55 259 d.binCount = processingNotes;
Chris@38 260 d.binNames.clear();
Chris@55 261 for (int i = 0; i < processingNotes; ++i) {
Chris@38 262 d.binNames.push_back(noteName(i));
Chris@38 263 }
Chris@38 264 d.hasKnownExtents = false;
Chris@38 265 d.isQuantized = false;
Chris@38 266 d.sampleType = OutputDescriptor::FixedSampleRate;
Chris@38 267 d.sampleRate = 25;
Chris@38 268 d.hasDuration = false;
Chris@38 269 m_pitchOutputNo = list.size();
Chris@38 270 list.push_back(d);
Chris@38 271
Chris@31 272 return list;
Chris@31 273 }
Chris@31 274
Chris@38 275 std::string
Chris@38 276 Silvet::noteName(int i) const
Chris@38 277 {
Chris@38 278 static const char *names[] = {
Chris@38 279 "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"
Chris@38 280 };
Chris@38 281
Chris@38 282 const char *n = names[i % 12];
Chris@38 283
Chris@38 284 int oct = (i + 9) / 12;
Chris@38 285
Chris@38 286 char buf[20];
Chris@38 287 sprintf(buf, "%s%d", n, oct);
Chris@38 288
Chris@38 289 return buf;
Chris@38 290 }
Chris@38 291
Chris@41 292 float
Chris@41 293 Silvet::noteFrequency(int note) const
Chris@41 294 {
Chris@41 295 return float(27.5 * pow(2.0, note / 12.0));
Chris@41 296 }
Chris@41 297
Chris@31 298 bool
Chris@31 299 Silvet::initialise(size_t channels, size_t stepSize, size_t blockSize)
Chris@31 300 {
Chris@31 301 if (channels < getMinChannelCount() ||
Chris@31 302 channels > getMaxChannelCount()) return false;
Chris@31 303
Chris@31 304 if (stepSize != blockSize) {
Chris@31 305 cerr << "Silvet::initialise: Step size must be the same as block size ("
Chris@31 306 << stepSize << " != " << blockSize << ")" << endl;
Chris@31 307 return false;
Chris@31 308 }
Chris@31 309
Chris@31 310 m_blockSize = blockSize;
Chris@31 311
Chris@31 312 reset();
Chris@31 313
Chris@31 314 return true;
Chris@31 315 }
Chris@31 316
Chris@31 317 void
Chris@31 318 Silvet::reset()
Chris@31 319 {
Chris@31 320 delete m_resampler;
Chris@31 321 delete m_cq;
Chris@31 322
Chris@31 323 if (m_inputSampleRate != processingSampleRate) {
Chris@31 324 m_resampler = new Resampler(m_inputSampleRate, processingSampleRate);
Chris@31 325 } else {
Chris@31 326 m_resampler = 0;
Chris@31 327 }
Chris@31 328
Chris@32 329 m_cq = new CQInterpolated
Chris@32 330 (processingSampleRate, 27.5, processingSampleRate / 3, processingBPO,
Chris@32 331 CQInterpolated::Linear);
Chris@31 332
Chris@41 333 for (int i = 0; i < (int)m_postFilter.size(); ++i) {
Chris@41 334 delete m_postFilter[i];
Chris@41 335 }
Chris@41 336 m_postFilter.clear();
Chris@41 337 for (int i = 0; i < processingNotes; ++i) {
Chris@41 338 m_postFilter.push_back(new MedianFilter<double>(3));
Chris@41 339 }
Chris@41 340 m_pianoRoll.clear();
Chris@32 341 m_columnCount = 0;
Chris@32 342 m_reducedColumnCount = 0;
Chris@40 343 m_startTime = RealTime::zeroTime;
Chris@31 344 }
Chris@31 345
Chris@31 346 Silvet::FeatureSet
Chris@31 347 Silvet::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
Chris@31 348 {
Chris@40 349 if (m_columnCount == 0) {
Chris@40 350 m_startTime = timestamp;
Chris@40 351 }
Chris@40 352
Chris@31 353 vector<double> data;
Chris@40 354 for (int i = 0; i < m_blockSize; ++i) {
Chris@40 355 data.push_back(inputBuffers[0][i]);
Chris@40 356 }
Chris@31 357
Chris@31 358 if (m_resampler) {
Chris@31 359 data = m_resampler->process(data.data(), data.size());
Chris@31 360 }
Chris@31 361
Chris@32 362 Grid cqout = m_cq->process(data);
Chris@51 363 FeatureSet fs = transcribe(cqout);
Chris@51 364
Chris@51 365 for (int i = 0; i < (int)cqout.size(); ++i) {
Chris@51 366 Feature f;
Chris@51 367 for (int j = 0; j < (int)cqout[i].size(); ++j) {
Chris@51 368 f.values.push_back(float(cqout[i][j]));
Chris@51 369 }
Chris@51 370 fs[m_cqOutputNo].push_back(f);
Chris@51 371 }
Chris@51 372
Chris@51 373 return fs;
Chris@34 374 }
Chris@34 375
Chris@34 376 Silvet::FeatureSet
Chris@34 377 Silvet::getRemainingFeatures()
Chris@34 378 {
Chris@34 379 Grid cqout = m_cq->getRemainingBlocks();
Chris@51 380 FeatureSet fs = transcribe(cqout);
Chris@51 381
Chris@51 382 for (int i = 0; i < (int)cqout.size(); ++i) {
Chris@51 383 Feature f;
Chris@51 384 for (int j = 0; j < (int)cqout[i].size(); ++j) {
Chris@51 385 f.values.push_back(float(cqout[i][j]));
Chris@51 386 }
Chris@51 387 fs[m_cqOutputNo].push_back(f);
Chris@51 388 }
Chris@51 389
Chris@51 390 return fs;
Chris@34 391 }
Chris@34 392
Chris@34 393 Silvet::FeatureSet
Chris@34 394 Silvet::transcribe(const Grid &cqout)
Chris@34 395 {
Chris@32 396 Grid filtered = preProcess(cqout);
Chris@31 397
Chris@32 398 FeatureSet fs;
Chris@32 399
Chris@104 400 if (filtered.empty()) return fs;
Chris@104 401
Chris@32 402 for (int i = 0; i < (int)filtered.size(); ++i) {
Chris@32 403 Feature f;
Chris@32 404 for (int j = 0; j < processingHeight; ++j) {
Chris@32 405 f.values.push_back(float(filtered[i][j]));
Chris@32 406 }
Chris@51 407 fs[m_fcqOutputNo].push_back(f);
Chris@32 408 }
Chris@32 409
Chris@34 410 int width = filtered.size();
Chris@34 411
Chris@34 412 int iterations = 12;
Chris@34 413
Chris@123 414 Grid pitchMatrix(width, vector<double>(processingNotes));
Chris@37 415
Chris@123 416 #pragma omp parallel for
Chris@123 417 for (int i = 0; i < width; ++i) {
Chris@104 418
Chris@123 419 double sum = 0.0;
Chris@123 420 for (int j = 0; j < processingHeight; ++j) {
Chris@123 421 sum += filtered.at(i).at(j);
Chris@37 422 }
Chris@37 423
Chris@123 424 if (sum < 1e-5) continue;
Chris@37 425
Chris@123 426 EM em(m_hqMode);
Chris@104 427
Chris@123 428 for (int j = 0; j < iterations; ++j) {
Chris@123 429 em.iterate(filtered.at(i).data());
Chris@34 430 }
Chris@104 431
Chris@123 432 const double *pitches = em.getPitchDistribution();
Chris@123 433
Chris@123 434 for (int j = 0; j < processingNotes; ++j) {
Chris@123 435 pitchMatrix[i][j] = pitches[j] * sum;
Chris@123 436 }
Chris@123 437 }
Chris@37 438
Chris@123 439 for (int i = 0; i < width; ++i) {
Chris@123 440
Chris@123 441 Feature f;
Chris@123 442 for (int j = 0; j < processingNotes; ++j) {
Chris@123 443 f.values.push_back(float(pitchMatrix[i][j]));
Chris@123 444 }
Chris@123 445 fs[m_pitchOutputNo].push_back(f);
Chris@41 446
Chris@123 447 FeatureList noteFeatures = postProcess(pitchMatrix[i]);
Chris@38 448
Chris@123 449 for (FeatureList::const_iterator fi = noteFeatures.begin();
Chris@123 450 fi != noteFeatures.end(); ++fi) {
Chris@123 451 fs[m_notesOutputNo].push_back(*fi);
Chris@40 452 }
Chris@34 453 }
Chris@34 454
Chris@32 455 return fs;
Chris@31 456 }
Chris@31 457
Chris@32 458 Silvet::Grid
Chris@32 459 Silvet::preProcess(const Grid &in)
Chris@32 460 {
Chris@32 461 int width = in.size();
Chris@32 462
Chris@32 463 // reduce to 100 columns per second, or one column every 441 samples
Chris@32 464
Chris@32 465 int spacing = processingSampleRate / 100;
Chris@32 466
Chris@32 467 Grid out;
Chris@32 468
Chris@58 469 // We count the CQ latency in terms of processing hops, but
Chris@58 470 // actually it probably isn't an exact number of hops so this
Chris@58 471 // isn't quite accurate. But the small constant offset is
Chris@58 472 // practically irrelevant compared to the jitter from the 40ms
Chris@58 473 // frame size we reduce to in a moment
Chris@33 474 int latentColumns = m_cq->getLatency() / m_cq->getColumnHop();
Chris@33 475
Chris@32 476 for (int i = 0; i < width; ++i) {
Chris@32 477
Chris@33 478 if (m_columnCount < latentColumns) {
Chris@33 479 ++m_columnCount;
Chris@33 480 continue;
Chris@33 481 }
Chris@33 482
Chris@32 483 int prevSampleNo = (m_columnCount - 1) * m_cq->getColumnHop();
Chris@32 484 int sampleNo = m_columnCount * m_cq->getColumnHop();
Chris@32 485
Chris@32 486 bool select = (sampleNo / spacing != prevSampleNo / spacing);
Chris@32 487
Chris@32 488 if (select) {
Chris@32 489 vector<double> inCol = in[i];
Chris@32 490 vector<double> outCol(processingHeight);
Chris@32 491
Chris@32 492 // we reverse the column as we go (the CQ output is
Chris@32 493 // "upside-down", with high frequencies at the start of
Chris@32 494 // each column, and we want it the other way around) and
Chris@32 495 // then ignore the first 55 (lowest-frequency) bins,
Chris@32 496 // giving us 545 bins instead of 600
Chris@32 497
Chris@32 498 for (int j = 0; j < processingHeight; ++j) {
Chris@46 499 int ix = inCol.size() - j - 55;
Chris@46 500 outCol[j] = inCol[ix];
Chris@46 501 }
Chris@32 502
Chris@46 503 vector<double> noiseLevel1 =
Chris@46 504 MedianFilter<double>::filter(40, outCol);
Chris@46 505 for (int j = 0; j < processingHeight; ++j) {
Chris@46 506 noiseLevel1[j] = std::min(outCol[j], noiseLevel1[j]);
Chris@46 507 }
Chris@32 508
Chris@46 509 vector<double> noiseLevel2 =
Chris@46 510 MedianFilter<double>::filter(40, noiseLevel1);
Chris@46 511 for (int j = 0; j < processingHeight; ++j) {
Chris@46 512 outCol[j] = std::max(outCol[j] - noiseLevel2[j], 0.0);
Chris@32 513 }
Chris@32 514
Chris@32 515 // then we only use every fourth filtered column, for 25
Chris@32 516 // columns per second in the eventual grid
Chris@32 517
Chris@32 518 if (m_reducedColumnCount % 4 == 0) {
Chris@32 519 out.push_back(outCol);
Chris@32 520 }
Chris@32 521
Chris@32 522 ++m_reducedColumnCount;
Chris@32 523 }
Chris@32 524
Chris@32 525 ++m_columnCount;
Chris@32 526 }
Chris@32 527
Chris@32 528 return out;
Chris@32 529 }
Chris@32 530
Chris@41 531 Vamp::Plugin::FeatureList
Chris@41 532 Silvet::postProcess(const vector<double> &pitches)
Chris@41 533 {
Chris@41 534 vector<double> filtered;
Chris@41 535
Chris@41 536 for (int j = 0; j < processingNotes; ++j) {
Chris@55 537 m_postFilter[j]->push(pitches[j]);
Chris@41 538 filtered.push_back(m_postFilter[j]->get());
Chris@41 539 }
Chris@41 540
Chris@69 541 int postFilterLatency = int(m_postFilter[0]->getSize() / 2);
Chris@69 542
Chris@41 543 // Threshold for level and reduce number of candidate pitches
Chris@41 544
Chris@41 545 int polyphony = 5;
Chris@41 546 double threshold = 4.8;
Chris@41 547
Chris@41 548 typedef std::multimap<double, int> ValueIndexMap;
Chris@41 549
Chris@41 550 ValueIndexMap strengths;
Chris@41 551 for (int j = 0; j < processingNotes; ++j) {
Chris@41 552 strengths.insert(ValueIndexMap::value_type(filtered[j], j));
Chris@41 553 }
Chris@41 554
Chris@55 555 map<int, double> active;
Chris@41 556 ValueIndexMap::const_iterator si = strengths.end();
Chris@45 557 while (int(active.size()) < polyphony) {
Chris@41 558 --si;
Chris@41 559 if (si->first < threshold) break;
Chris@41 560 cerr << si->second << " : " << si->first << endl;
Chris@55 561 active[si->second] = si->first;
Chris@45 562 if (si == strengths.begin()) break;
Chris@41 563 }
Chris@41 564
Chris@41 565 // Minimum duration pruning, and conversion to notes. We can only
Chris@41 566 // report notes that have just ended (i.e. that are absent in the
Chris@41 567 // latest active set but present in the last set in the piano
Chris@41 568 // roll) -- any notes that ended earlier will have been reported
Chris@41 569 // already, and if they haven't ended, we don't know their
Chris@41 570 // duration.
Chris@41 571
Chris@41 572 int width = m_pianoRoll.size();
Chris@41 573
Chris@41 574 int durationThreshold = 2; // columns
Chris@41 575
Chris@41 576 FeatureList noteFeatures;
Chris@41 577
Chris@41 578 if (width < durationThreshold + 1) {
Chris@41 579 m_pianoRoll.push_back(active);
Chris@41 580 return noteFeatures;
Chris@41 581 }
Chris@41 582
Chris@41 583 // we have 25 columns per second
Chris@41 584 double columnDuration = 1.0 / 25.0;
Chris@41 585
Chris@55 586 for (map<int, double>::const_iterator ni = m_pianoRoll[width-1].begin();
Chris@41 587 ni != m_pianoRoll[width-1].end(); ++ni) {
Chris@41 588
Chris@55 589 int note = ni->first;
Chris@41 590
Chris@41 591 if (active.find(note) != active.end()) {
Chris@41 592 // the note is still playing
Chris@41 593 continue;
Chris@41 594 }
Chris@41 595
Chris@41 596 // the note was playing but just ended
Chris@41 597 int end = width;
Chris@41 598 int start = end-1;
Chris@41 599
Chris@57 600 double maxStrength = 0.0;
Chris@55 601
Chris@41 602 while (m_pianoRoll[start].find(note) != m_pianoRoll[start].end()) {
Chris@57 603 double strength = m_pianoRoll[start][note];
Chris@57 604 if (strength > maxStrength) {
Chris@57 605 maxStrength = strength;
Chris@57 606 }
Chris@41 607 --start;
Chris@41 608 }
Chris@41 609 ++start;
Chris@41 610
Chris@41 611 int duration = width - start;
Chris@62 612 // cerr << "duration " << duration << " for just-ended note " << note << endl;
Chris@41 613 if (duration < durationThreshold) {
Chris@41 614 // spurious
Chris@41 615 continue;
Chris@41 616 }
Chris@41 617
Chris@57 618 int velocity = maxStrength * 2;
Chris@55 619 if (velocity > 127) velocity = 127;
Chris@55 620
Chris@62 621 cerr << "Found a genuine note, starting at " << columnDuration * start << " with duration " << columnDuration * duration << endl;
Chris@62 622
Chris@41 623 Feature nf;
Chris@41 624 nf.hasTimestamp = true;
Chris@69 625 nf.timestamp = RealTime::fromSeconds
Chris@69 626 (columnDuration * (start - postFilterLatency));
Chris@41 627 nf.hasDuration = true;
Chris@69 628 nf.duration = RealTime::fromSeconds
Chris@69 629 (columnDuration * duration);
Chris@41 630 nf.values.push_back(noteFrequency(note));
Chris@55 631 nf.values.push_back(velocity);
Chris@41 632 nf.label = noteName(note);
Chris@41 633 noteFeatures.push_back(nf);
Chris@41 634 }
Chris@41 635
Chris@41 636 m_pianoRoll.push_back(active);
Chris@41 637
Chris@62 638 // cerr << "returning " << noteFeatures.size() << " complete note(s) " << endl;
Chris@41 639
Chris@41 640 return noteFeatures;
Chris@41 641 }
Chris@41 642