Mercurial > hg > silvet
comparison src/Silvet.cpp @ 55:384338fa460d preshift
Support shifts as an additional dimension (as in the original model). Also return velocity as well.
author | Chris Cannam |
---|---|
date | Tue, 08 Apr 2014 13:30:32 +0100 |
parents | 22553e7b2a63 |
children | 49c0a139f165 |
comparison
equal
deleted
inserted
replaced
54:a54df67e607e | 55:384338fa460d |
---|---|
15 | 15 |
16 #include "Silvet.h" | 16 #include "Silvet.h" |
17 #include "EM.h" | 17 #include "EM.h" |
18 | 18 |
19 #include "maths/MedianFilter.h" | 19 #include "maths/MedianFilter.h" |
20 #include "maths/MathUtilities.h" | |
20 #include "dsp/rateconversion/Resampler.h" | 21 #include "dsp/rateconversion/Resampler.h" |
21 | 22 |
22 #include "constant-q-cpp/cpp-qm-dsp/CQInterpolated.h" | 23 #include "constant-q-cpp/cpp-qm-dsp/CQInterpolated.h" |
23 | 24 |
24 #include <vector> | 25 #include <vector> |
33 | 34 |
34 static int processingSampleRate = 44100; | 35 static int processingSampleRate = 44100; |
35 static int processingBPO = 60; | 36 static int processingBPO = 60; |
36 static int processingHeight = 545; | 37 static int processingHeight = 545; |
37 static int processingNotes = 88; | 38 static int processingNotes = 88; |
38 static int processingShifts = 5; | |
39 static int processingPitches = processingNotes * processingShifts; | |
40 | 39 |
41 Silvet::Silvet(float inputSampleRate) : | 40 Silvet::Silvet(float inputSampleRate) : |
42 Plugin(inputSampleRate), | 41 Plugin(inputSampleRate), |
43 m_resampler(0), | 42 m_resampler(0), |
44 m_cq(0) | 43 m_cq(0) |
233 d.identifier = "pitches"; | 232 d.identifier = "pitches"; |
234 d.name = "Pitch activation"; | 233 d.name = "Pitch activation"; |
235 d.description = "Estimated pitch activation matrix"; | 234 d.description = "Estimated pitch activation matrix"; |
236 d.unit = ""; | 235 d.unit = ""; |
237 d.hasFixedBinCount = true; | 236 d.hasFixedBinCount = true; |
238 d.binCount = processingPitches; | 237 d.binCount = processingNotes; |
239 d.binNames.clear(); | 238 d.binNames.clear(); |
240 for (int i = 0; i < processingPitches; ++i) { | 239 for (int i = 0; i < processingNotes; ++i) { |
241 d.binNames.push_back(noteName(i)); | 240 d.binNames.push_back(noteName(i)); |
242 } | 241 } |
243 d.hasKnownExtents = false; | 242 d.hasKnownExtents = false; |
244 d.isQuantized = false; | 243 d.isQuantized = false; |
245 d.sampleType = OutputDescriptor::FixedSampleRate; | 244 d.sampleType = OutputDescriptor::FixedSampleRate; |
402 em.iterate(filtered[i]); | 401 em.iterate(filtered[i]); |
403 } | 402 } |
404 | 403 |
405 vector<double> pitches = em.getPitchDistribution(); | 404 vector<double> pitches = em.getPitchDistribution(); |
406 | 405 |
407 for (int j = 0; j < processingPitches; ++j) { | 406 for (int j = 0; j < processingNotes; ++j) { |
408 pitches[j] *= sum; | 407 pitches[j] *= sum; |
409 } | 408 } |
410 | 409 |
411 Feature f; | 410 Feature f; |
412 for (int j = 0; j < processingPitches; ++j) { | 411 for (int j = 0; j < processingNotes; ++j) { |
413 f.values.push_back(float(pitches[j])); | 412 f.values.push_back(float(pitches[j])); |
414 } | 413 } |
415 fs[m_pitchOutputNo].push_back(f); | 414 fs[m_pitchOutputNo].push_back(f); |
416 | 415 |
417 FeatureList noteFeatures = postProcess(pitches); | 416 FeatureList noteFeatures = postProcess(pitches); |
502 Silvet::postProcess(const vector<double> &pitches) | 501 Silvet::postProcess(const vector<double> &pitches) |
503 { | 502 { |
504 vector<double> filtered; | 503 vector<double> filtered; |
505 | 504 |
506 for (int j = 0; j < processingNotes; ++j) { | 505 for (int j = 0; j < processingNotes; ++j) { |
507 double noteSum = 0.0; | 506 m_postFilter[j]->push(pitches[j]); |
508 for (int s = 0; s < processingShifts; ++s) { | |
509 double val = pitches[j * processingShifts + s]; | |
510 noteSum += val; | |
511 } | |
512 m_postFilter[j]->push(noteSum); | |
513 filtered.push_back(m_postFilter[j]->get()); | 507 filtered.push_back(m_postFilter[j]->get()); |
514 } | 508 } |
515 | 509 |
516 // Threshold for level and reduce number of candidate pitches | 510 // Threshold for level and reduce number of candidate pitches |
517 | 511 |
523 ValueIndexMap strengths; | 517 ValueIndexMap strengths; |
524 for (int j = 0; j < processingNotes; ++j) { | 518 for (int j = 0; j < processingNotes; ++j) { |
525 strengths.insert(ValueIndexMap::value_type(filtered[j], j)); | 519 strengths.insert(ValueIndexMap::value_type(filtered[j], j)); |
526 } | 520 } |
527 | 521 |
528 set<int> active; | 522 map<int, double> active; |
529 ValueIndexMap::const_iterator si = strengths.end(); | 523 ValueIndexMap::const_iterator si = strengths.end(); |
530 while (int(active.size()) < polyphony) { | 524 while (int(active.size()) < polyphony) { |
531 --si; | 525 --si; |
532 if (si->first < threshold) break; | 526 if (si->first < threshold) break; |
533 cerr << si->second << " : " << si->first << endl; | 527 cerr << si->second << " : " << si->first << endl; |
534 active.insert(si->second); | 528 active[si->second] = si->first; |
535 if (si == strengths.begin()) break; | 529 if (si == strengths.begin()) break; |
536 } | 530 } |
537 | 531 |
538 // Minimum duration pruning, and conversion to notes. We can only | 532 // Minimum duration pruning, and conversion to notes. We can only |
539 // report notes that have just ended (i.e. that are absent in the | 533 // report notes that have just ended (i.e. that are absent in the |
554 } | 548 } |
555 | 549 |
556 // we have 25 columns per second | 550 // we have 25 columns per second |
557 double columnDuration = 1.0 / 25.0; | 551 double columnDuration = 1.0 / 25.0; |
558 | 552 |
559 for (set<int>::const_iterator ni = m_pianoRoll[width-1].begin(); | 553 for (map<int, double>::const_iterator ni = m_pianoRoll[width-1].begin(); |
560 ni != m_pianoRoll[width-1].end(); ++ni) { | 554 ni != m_pianoRoll[width-1].end(); ++ni) { |
561 | 555 |
562 int note = *ni; | 556 int note = ni->first; |
563 | 557 |
564 if (active.find(note) != active.end()) { | 558 if (active.find(note) != active.end()) { |
565 // the note is still playing | 559 // the note is still playing |
566 continue; | 560 continue; |
567 } | 561 } |
568 | 562 |
569 // the note was playing but just ended | 563 // the note was playing but just ended |
570 int end = width; | 564 int end = width; |
571 int start = end-1; | 565 int start = end-1; |
572 | 566 |
567 vector<double> strengths; | |
568 | |
573 while (m_pianoRoll[start].find(note) != m_pianoRoll[start].end()) { | 569 while (m_pianoRoll[start].find(note) != m_pianoRoll[start].end()) { |
570 strengths.push_back(m_pianoRoll[start][note]); | |
574 --start; | 571 --start; |
575 } | 572 } |
576 ++start; | 573 ++start; |
577 | 574 |
578 int duration = width - start; | 575 int duration = width - start; |
579 cerr << "duration " << duration << " for just-ended note " << note << endl; | 576 cerr << "duration " << duration << " for just-ended note " << note << endl; |
580 if (duration < durationThreshold) { | 577 if (duration < durationThreshold) { |
581 // spurious | 578 // spurious |
582 continue; | 579 continue; |
583 } | 580 } |
581 | |
582 double medianStrength = MathUtilities::median | |
583 (strengths.data(), strengths.size()); | |
584 int velocity = medianStrength * 2; | |
585 if (velocity > 127) velocity = 127; | |
584 | 586 |
585 Feature nf; | 587 Feature nf; |
586 nf.hasTimestamp = true; | 588 nf.hasTimestamp = true; |
587 nf.timestamp = RealTime::fromSeconds(columnDuration * start); | 589 nf.timestamp = RealTime::fromSeconds(columnDuration * start); |
588 nf.hasDuration = true; | 590 nf.hasDuration = true; |
589 nf.duration = RealTime::fromSeconds(columnDuration * duration); | 591 nf.duration = RealTime::fromSeconds(columnDuration * duration); |
590 nf.values.push_back(noteFrequency(note)); | 592 nf.values.push_back(noteFrequency(note)); |
591 nf.values.push_back(80.f); //!!! todo: calculate velocity | 593 nf.values.push_back(velocity); |
592 nf.label = noteName(note); | 594 nf.label = noteName(note); |
593 noteFeatures.push_back(nf); | 595 noteFeatures.push_back(nf); |
594 } | 596 } |
595 | 597 |
596 m_pianoRoll.push_back(active); | 598 m_pianoRoll.push_back(active); |