annotate src/Silvet.cpp @ 167:416b555df3b2 finetune

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