comparison src/Silvet.cpp @ 336:d25e4aee73d7 livemode

Add onset+offset output; look up shift counts rather than passing them around
author Chris Cannam
date Fri, 26 Jun 2015 10:23:54 +0100
parents d861f86f2b17
children 3c6f5d2d33e8 705d807ca2ca
comparison
equal deleted inserted replaced
335:d861f86f2b17 336:d25e4aee73d7
265 d.isQuantized = false; 265 d.isQuantized = false;
266 d.sampleType = OutputDescriptor::VariableSampleRate; 266 d.sampleType = OutputDescriptor::VariableSampleRate;
267 d.sampleRate = processingSampleRate / (m_cq ? m_cq->getColumnHop() : 62); 267 d.sampleRate = processingSampleRate / (m_cq ? m_cq->getColumnHop() : 62);
268 d.hasDuration = false; 268 d.hasDuration = false;
269 m_onsetsOutputNo = list.size(); 269 m_onsetsOutputNo = list.size();
270 list.push_back(d);
271
272 d.identifier = "onoffsets";
273 d.name = "Note onsets and offsets";
274 d.description = "Note onsets and offsets as separate events. Each onset event has time, estimated fundamental frequency in Hz, and a synthetic MIDI velocity (1-127) estimated from the strength of the pitch in the mixture. Offsets are represented in the same way but with a velocity of 0.";
275 d.unit = "Hz";
276 d.hasFixedBinCount = true;
277 d.binCount = 2;
278 d.binNames.push_back("Frequency");
279 d.binNames.push_back("Velocity");
280 d.hasKnownExtents = false;
281 d.isQuantized = false;
282 d.sampleType = OutputDescriptor::VariableSampleRate;
283 d.sampleRate = processingSampleRate / (m_cq ? m_cq->getColumnHop() : 62);
284 d.hasDuration = false;
285 m_onOffsetsOutputNo = list.size();
270 list.push_back(d); 286 list.push_back(d);
271 287
272 d.identifier = "timefreq"; 288 d.identifier = "timefreq";
273 d.name = "Time-frequency distribution"; 289 d.name = "Time-frequency distribution";
274 d.description = "Filtered constant-Q time-frequency distribution as used as input to the expectation-maximisation algorithm."; 290 d.description = "Filtered constant-Q time-frequency distribution as used as input to the expectation-maximisation algorithm.";
305 d.hasFixedBinCount = true; 321 d.hasFixedBinCount = true;
306 d.binCount = getPack(0).templateNoteCount; 322 d.binCount = getPack(0).templateNoteCount;
307 d.binNames.clear(); 323 d.binNames.clear();
308 if (m_cq) { 324 if (m_cq) {
309 for (int i = 0; i < getPack(0).templateNoteCount; ++i) { 325 for (int i = 0; i < getPack(0).templateNoteCount; ++i) {
310 d.binNames.push_back(getNoteName(i, 0, 1)); 326 d.binNames.push_back(getNoteName(i, 0));
311 } 327 }
312 } 328 }
313 d.hasKnownExtents = false; 329 d.hasKnownExtents = false;
314 d.isQuantized = false; 330 d.isQuantized = false;
315 d.sampleType = OutputDescriptor::FixedSampleRate; 331 d.sampleType = OutputDescriptor::FixedSampleRate;
379 395
380 return names[pitch]; 396 return names[pitch];
381 } 397 }
382 398
383 std::string 399 std::string
384 Silvet::getNoteName(int note, int shift, int shiftCount) const 400 Silvet::getNoteName(int note, int shift) const
385 { 401 {
386 string n = getChromaName(note % 12); 402 string n = getChromaName(note % 12);
387 403
388 int oct = (note + 9) / 12; 404 int oct = (note + 9) / 12;
389 405
390 char buf[30]; 406 char buf[30];
391 407
392 float pshift = 0.f; 408 float pshift = 0.f;
409 int shiftCount = getShiftCount();
393 if (shiftCount > 1) { 410 if (shiftCount > 1) {
394 // see getNoteFrequency below 411 // see getNoteFrequency below
395 pshift = 412 pshift =
396 float((shiftCount - shift) - int(shiftCount / 2) - 1) / shiftCount; 413 float((shiftCount - shift) - int(shiftCount / 2) - 1) / shiftCount;
397 } 414 }
406 423
407 return buf; 424 return buf;
408 } 425 }
409 426
410 float 427 float
411 Silvet::getNoteFrequency(int note, int shift, int shiftCount) const 428 Silvet::getNoteFrequency(int note, int shift) const
412 { 429 {
413 // Convert shift number to a pitch shift. The given shift number 430 // Convert shift number to a pitch shift. The given shift number
414 // is an offset into the template array, which starts with some 431 // is an offset into the template array, which starts with some
415 // zeros, followed by the template, then some trailing zeros. 432 // zeros, followed by the template, then some trailing zeros.
416 // 433 //
422 // zeros at the start, which is the low-frequency end), for a 439 // zeros at the start, which is the low-frequency end), for a
423 // positive pitch shift; and higher values represent moving it 440 // positive pitch shift; and higher values represent moving it
424 // down in pitch, for a negative pitch shift. 441 // down in pitch, for a negative pitch shift.
425 442
426 float pshift = 0.f; 443 float pshift = 0.f;
444 int shiftCount = getShiftCount();
427 if (shiftCount > 1) { 445 if (shiftCount > 1) {
428 pshift = 446 pshift =
429 float((shiftCount - shift) - int(shiftCount / 2) - 1) / shiftCount; 447 float((shiftCount - shift) - int(shiftCount / 2) - 1) / shiftCount;
430 } 448 }
431 449
601 Silvet::FeatureSet 619 Silvet::FeatureSet
602 Silvet::getRemainingFeatures() 620 Silvet::getRemainingFeatures()
603 { 621 {
604 Grid cqout = m_cq->getRemainingOutput(); 622 Grid cqout = m_cq->getRemainingOutput();
605 FeatureSet fs; 623 FeatureSet fs;
624
606 if (m_columnCount == 0) { 625 if (m_columnCount == 0) {
607 // process() was never called, but we still want these 626 // process() was never called, but we still want these
608 insertTemplateFeatures(fs); 627 insertTemplateFeatures(fs);
609 } else { 628 } else {
629
630 // Complete the transcription
631
610 transcribe(cqout, fs); 632 transcribe(cqout, fs);
611 } 633
634 // And make sure any extant playing notes are finished and returned
635
636 m_pianoRoll.push_back({});
637
638 auto events = noteTrack();
639
640 for (const auto &f : events.notes) {
641 fs[m_notesOutputNo].push_back(f);
642 }
643
644 for (const auto &f : events.onsets) {
645 fs[m_onsetsOutputNo].push_back(f);
646 }
647
648 for (const auto &f : events.onOffsets) {
649 fs[m_onOffsetsOutputNo].push_back(f);
650 }
651 }
652
612 return fs; 653 return fs;
613 } 654 }
614 655
615 void 656 void
616 Silvet::insertTemplateFeatures(FeatureSet &fs) 657 Silvet::insertTemplateFeatures(FeatureSet &fs)
628 .data[i % pack.templateNoteCount]; 669 .data[i % pack.templateNoteCount];
629 fs[m_templateOutputNo].push_back(f); 670 fs[m_templateOutputNo].push_back(f);
630 } 671 }
631 } 672 }
632 673
674 int
675 Silvet::getShiftCount() const
676 {
677 bool wantShifts = (m_mode == HighQualityMode) && m_fineTuning;
678 int shiftCount = 1;
679 if (wantShifts) {
680 const InstrumentPack &pack(getPack(m_instrument));
681 shiftCount = pack.templateMaxShift * 2 + 1;
682 }
683 return shiftCount;
684 }
685
633 void 686 void
634 Silvet::transcribe(const Grid &cqout, Silvet::FeatureSet &fs) 687 Silvet::transcribe(const Grid &cqout, Silvet::FeatureSet &fs)
635 { 688 {
636 Grid filtered = preProcess(cqout); 689 Grid filtered = preProcess(cqout);
637 690
665 fs[m_fcqOutputNo].push_back(f); 718 fs[m_fcqOutputNo].push_back(f);
666 } 719 }
667 720
668 Grid localPitches(width); 721 Grid localPitches(width);
669 722
670 bool wantShifts = (m_mode == HighQualityMode) && m_fineTuning; 723 int shiftCount = getShiftCount();
671 int shiftCount = 1; 724 bool wantShifts = (shiftCount > 1);
672 if (wantShifts) {
673 shiftCount = pack.templateMaxShift * 2 + 1;
674 }
675 725
676 vector<vector<int> > localBestShifts; 726 vector<vector<int> > localBestShifts;
677 if (wantShifts) { 727 if (wantShifts) {
678 localBestShifts = vector<vector<int> >(width); 728 localBestShifts = vector<vector<int> >(width);
679 } 729 }
695 vector<EMFuture> results; 745 vector<EMFuture> results;
696 for (int j = 0; j < emThreadCount && i + j < width; ++j) { 746 for (int j = 0; j < emThreadCount && i + j < width; ++j) {
697 results.push_back 747 results.push_back
698 (async(std::launch::async, 748 (async(std::launch::async,
699 [&](int index) { 749 [&](int index) {
700 return applyEM 750 return applyEM(pack, filtered.at(index));
701 (pack, filtered.at(index), wantShifts);
702 }, i + j)); 751 }, i + j));
703 } 752 }
704 for (int j = 0; j < emThreadCount && i + j < width; ++j) { 753 for (int j = 0; j < emThreadCount && i + j < width; ++j) {
705 auto out = results[j].get(); 754 auto out = results[j].get();
706 localPitches[i+j] = out.first; 755 localPitches[i+j] = out.first;
711 } 760 }
712 #endif 761 #endif
713 762
714 if (emThreadCount == 1) { 763 if (emThreadCount == 1) {
715 for (int i = 0; i < width; ++i) { 764 for (int i = 0; i < width; ++i) {
716 auto out = applyEM(pack, filtered.at(i), wantShifts); 765 auto out = applyEM(pack, filtered.at(i));
717 localPitches[i] = out.first; 766 localPitches[i] = out.first;
718 if (wantShifts) localBestShifts[i] = out.second; 767 if (wantShifts) localBestShifts[i] = out.second;
719 } 768 }
720 } 769 }
721 770
746 } 795 }
747 fs[m_chromaOutputNo].push_back(f); 796 fs[m_chromaOutputNo].push_back(f);
748 797
749 // This pushes the up-to-max-polyphony activation column to 798 // This pushes the up-to-max-polyphony activation column to
750 // m_pianoRoll 799 // m_pianoRoll
751 postProcess(filtered, localBestShifts[i], wantShifts); 800 postProcess(filtered, localBestShifts[i]);
752 801
753 auto events = noteTrack(shiftCount); 802 auto events = noteTrack();
754 803
755 FeatureList noteFeatures = events.first; 804 for (const auto &f : events.notes) {
756 for (FeatureList::const_iterator fi = noteFeatures.begin(); 805 fs[m_notesOutputNo].push_back(f);
757 fi != noteFeatures.end(); ++fi) { 806 }
758 fs[m_notesOutputNo].push_back(*fi); 807
759 } 808 for (const auto &f : events.onsets) {
760 809 fs[m_onsetsOutputNo].push_back(f);
761 FeatureList onsetFeatures = events.second; 810 }
762 for (FeatureList::const_iterator fi = onsetFeatures.begin(); 811
763 fi != onsetFeatures.end(); ++fi) { 812 for (const auto &f : events.onOffsets) {
764 fs[m_onsetsOutputNo].push_back(*fi); 813 fs[m_onOffsetsOutputNo].push_back(f);
765 } 814 }
766 } 815 }
767 } 816 }
768 817
769 pair<vector<double>, vector<int> > 818 pair<vector<double>, vector<int> >
770 Silvet::applyEM(const InstrumentPack &pack, 819 Silvet::applyEM(const InstrumentPack &pack,
771 const vector<double> &column, 820 const vector<double> &column)
772 bool wantShifts)
773 { 821 {
774 double columnThreshold = 1e-5; 822 double columnThreshold = 1e-5;
775 823
776 if (m_mode == LiveMode) { 824 if (m_mode == LiveMode) {
777 columnThreshold /= 15; 825 columnThreshold /= 15;
800 } 848 }
801 849
802 const float *pitchDist = em.getPitchDistribution(); 850 const float *pitchDist = em.getPitchDistribution();
803 const float *const *shiftDist = em.getShifts(); 851 const float *const *shiftDist = em.getShifts();
804 852
805 int shiftCount = 1; 853 int shiftCount = getShiftCount();
806 if (wantShifts) {
807 shiftCount = pack.templateMaxShift * 2 + 1;
808 }
809 854
810 for (int j = 0; j < pack.templateNoteCount; ++j) { 855 for (int j = 0; j < pack.templateNoteCount; ++j) {
811 856
812 pitches[j] = pitchDist[j] * sum; 857 pitches[j] = pitchDist[j] * sum;
813 858
814 int bestShift = 0; 859 int bestShift = 0;
815 float bestShiftValue = 0.0; 860 float bestShiftValue = 0.0;
816 if (wantShifts) { 861 if (shiftCount > 1) {
817 for (int k = 0; k < shiftCount; ++k) { 862 for (int k = 0; k < shiftCount; ++k) {
818 float value = shiftDist[k][j]; 863 float value = shiftDist[k][j];
819 if (k == 0 || value > bestShiftValue) { 864 if (k == 0 || value > bestShiftValue) {
820 bestShiftValue = value; 865 bestShiftValue = value;
821 bestShift = k; 866 bestShift = k;
915 return out; 960 return out;
916 } 961 }
917 962
918 void 963 void
919 Silvet::postProcess(const vector<double> &pitches, 964 Silvet::postProcess(const vector<double> &pitches,
920 const vector<int> &bestShifts, 965 const vector<int> &bestShifts)
921 bool wantShifts)
922 { 966 {
923 const InstrumentPack &pack(getPack(m_instrument)); 967 const InstrumentPack &pack(getPack(m_instrument));
924 968
925 // Threshold for level and reduce number of candidate pitches 969 // Threshold for level and reduce number of candidate pitches
926 970
958 ValueIndexMap::const_iterator si = strengths.end(); 1002 ValueIndexMap::const_iterator si = strengths.end();
959 1003
960 map<int, double> active; 1004 map<int, double> active;
961 map<int, int> activeShifts; 1005 map<int, int> activeShifts;
962 1006
1007 int shiftCount = getShiftCount();
1008
963 while (int(active.size()) < pack.maxPolyphony && si != strengths.begin()) { 1009 while (int(active.size()) < pack.maxPolyphony && si != strengths.begin()) {
964 1010
965 --si; 1011 --si;
966 1012
967 double strength = si->first; 1013 double strength = si->first;
968 int j = si->second; 1014 int j = si->second;
969 1015
970 active[j] = strength; 1016 active[j] = strength;
971 1017
972 if (wantShifts) { 1018 if (shiftCount > 1) {
973 activeShifts[j] = bestShifts[j]; 1019 activeShifts[j] = bestShifts[j];
974 } 1020 }
975 } 1021 }
976 1022
977 m_pianoRoll.push_back(active); 1023 m_pianoRoll.push_back(active);
978 1024
979 if (wantShifts) { 1025 if (shiftCount > 1) {
980 m_pianoRollShifts.push_back(activeShifts); 1026 m_pianoRollShifts.push_back(activeShifts);
981 } 1027 }
982 1028
983 return; 1029 return;
984 } 1030 }
985 1031
986 pair<Vamp::Plugin::FeatureList, Vamp::Plugin::FeatureList> 1032 Silvet::FeatureChunk
987 Silvet::noteTrack(int shiftCount) 1033 Silvet::noteTrack()
988 { 1034 {
989 // Minimum duration pruning, and conversion to notes. We can only 1035 // Minimum duration pruning, and conversion to notes. We can only
990 // report notes that have just ended (i.e. that are absent in the 1036 // report notes that have just ended (i.e. that are absent in the
991 // latest active set but present in the prior set in the piano 1037 // latest active set but present in the prior set in the piano
992 // roll) -- any notes that ended earlier will have been reported 1038 // roll) -- any notes that ended earlier will have been reported
1002 // only keep notes >= 100ms or thereabouts 1048 // only keep notes >= 100ms or thereabouts
1003 double durationThrSec = 0.1; 1049 double durationThrSec = 0.1;
1004 int durationThreshold = floor(durationThrSec / columnDuration); // in cols 1050 int durationThreshold = floor(durationThrSec / columnDuration); // in cols
1005 if (durationThreshold < 1) durationThreshold = 1; 1051 if (durationThreshold < 1) durationThreshold = 1;
1006 1052
1007 FeatureList noteFeatures, onsetFeatures; 1053 FeatureList noteFeatures, onsetFeatures, onOffsetFeatures;
1008 1054
1009 if (width < durationThreshold + 1) { 1055 if (width < durationThreshold + 1) {
1010 return { noteFeatures, onsetFeatures }; 1056 return { noteFeatures, onsetFeatures, onOffsetFeatures };
1011 } 1057 }
1012 1058
1013 for (map<int, double>::const_iterator ni = m_pianoRoll[width-1].begin(); 1059 for (map<int, double>::const_iterator ni = m_pianoRoll[width-1].begin();
1014 ni != m_pianoRoll[width-1].end(); ++ni) { 1060 ni != m_pianoRoll[width-1].end(); ++ni) {
1015 1061
1029 continue; 1075 continue;
1030 } 1076 }
1031 1077
1032 if (duration == durationThreshold) { 1078 if (duration == durationThreshold) {
1033 m_current.insert(note); 1079 m_current.insert(note);
1034 emitOnset(start, note, shiftCount, onsetFeatures); 1080 emitOnset(start, note, onsetFeatures);
1081 emitOnset(start, note, onOffsetFeatures);
1035 } 1082 }
1036 1083
1037 if (active.find(note) == active.end()) { 1084 if (active.find(note) == active.end()) {
1038 // the note was playing but just ended 1085 // the note was playing but just ended
1039 m_current.erase(note); 1086 m_current.erase(note);
1040 emitNote(start, end, note, shiftCount, noteFeatures); 1087 emitNote(start, end, note, noteFeatures);
1088 emitOffset(start, end, note, onOffsetFeatures);
1041 } else { // still playing 1089 } else { // still playing
1042 // repeated note detection: if level is greater than this 1090 // repeated note detection: if level is greater than this
1043 // multiple of its previous value, then we end the note and 1091 // multiple of its previous value, then we end the note and
1044 // restart it with the same pitch 1092 // restart it with the same pitch
1045 double restartFactor = 1.5; 1093 double restartFactor = 1.5;
1046 if (duration >= durationThreshold * 2 && 1094 if (duration >= durationThreshold * 2 &&
1047 (active.find(note)->second > 1095 (active.find(note)->second >
1048 restartFactor * m_pianoRoll[width-1][note])) { 1096 restartFactor * m_pianoRoll[width-1][note])) {
1049 m_current.erase(note); 1097 m_current.erase(note);
1050 emitNote(start, end-1, note, shiftCount, noteFeatures); 1098 emitNote(start, end-1, note, noteFeatures);
1099 emitOffset(start, end-1, note, onOffsetFeatures);
1051 // and remove this so that we start counting the new 1100 // and remove this so that we start counting the new
1052 // note's duration from the current position 1101 // note's duration from the current position
1053 m_pianoRoll[width-1].erase(note); 1102 m_pianoRoll[width-1].erase(note);
1054 } 1103 }
1055 } 1104 }
1056 } 1105 }
1057 1106
1058 // cerr << "returning " << noteFeatures.size() << " complete note(s) " << endl; 1107 // cerr << "returning " << noteFeatures.size() << " complete note(s) " << endl;
1059 1108
1060 return { noteFeatures, onsetFeatures }; 1109 return { noteFeatures, onsetFeatures, onOffsetFeatures };
1061 } 1110 }
1062 1111
1063 void 1112 void
1064 Silvet::emitNote(int start, int end, int note, int shiftCount, 1113 Silvet::emitNote(int start, int end, int note, FeatureList &noteFeatures)
1065 FeatureList &noteFeatures)
1066 { 1114 {
1067 int partStart = start; 1115 int partStart = start;
1068 int partShift = 0; 1116 int partShift = 0;
1069 double partStrength = 0; 1117 double partStrength = 0;
1070 1118
1074 1122
1075 double strength = m_pianoRoll[i][note]; 1123 double strength = m_pianoRoll[i][note];
1076 1124
1077 int shift = 0; 1125 int shift = 0;
1078 1126
1079 if (shiftCount > 1) { 1127 if (getShiftCount() > 1) {
1080 1128
1081 shift = m_pianoRollShifts[i][note]; 1129 shift = m_pianoRollShifts[i][note];
1082 1130
1083 if (i == partStart) { 1131 if (i == partStart) {
1084 partShift = shift; 1132 partShift = shift;
1091 // pitch has changed, emit an intermediate note 1139 // pitch has changed, emit an intermediate note
1092 noteFeatures.push_back(makeNoteFeature(partStart, 1140 noteFeatures.push_back(makeNoteFeature(partStart,
1093 i, 1141 i,
1094 note, 1142 note,
1095 partShift, 1143 partShift,
1096 shiftCount,
1097 partStrength)); 1144 partStrength));
1098 partStart = i; 1145 partStart = i;
1099 partShift = shift; 1146 partShift = shift;
1100 partStrength = 0; 1147 partStrength = 0;
1101 } 1148 }
1109 if (end >= partStart + partThreshold) { 1156 if (end >= partStart + partThreshold) {
1110 noteFeatures.push_back(makeNoteFeature(partStart, 1157 noteFeatures.push_back(makeNoteFeature(partStart,
1111 end, 1158 end,
1112 note, 1159 note,
1113 partShift, 1160 partShift,
1114 shiftCount,
1115 partStrength)); 1161 partStrength));
1116 } 1162 }
1117 } 1163 }
1118 1164
1119 void 1165 void
1120 Silvet::emitOnset(int start, int note, int shiftCount, 1166 Silvet::emitOnset(int start, int note, FeatureList &onOffsetFeatures)
1121 FeatureList &onsetFeatures)
1122 { 1167 {
1123 int len = int(m_pianoRoll.size()); 1168 int len = int(m_pianoRoll.size());
1124 1169
1125 double onsetStrength = 0; 1170 double onsetStrength = 0;
1126 1171
1127 int shift = 0; 1172 int shift = 0;
1128 if (shiftCount > 1) { 1173 if (getShiftCount() > 1) {
1129 shift = m_pianoRollShifts[start][note]; 1174 shift = m_pianoRollShifts[start][note];
1130 } 1175 }
1131 1176
1132 for (int i = start; i < len; ++i) { 1177 for (int i = start; i < len; ++i) {
1133 double strength = m_pianoRoll[i][note]; 1178 double strength = m_pianoRoll[i][note];
1134 if (strength > onsetStrength) { 1179 if (strength > onsetStrength) {
1135 onsetStrength = strength; 1180 onsetStrength = strength;
1136 } 1181 }
1137 } 1182 }
1138 1183
1139 onsetFeatures.push_back(makeOnsetFeature(start, 1184 if (onsetStrength == 0) return;
1140 note, 1185
1141 shift, 1186 onOffsetFeatures.push_back(makeOnsetFeature(start,
1142 shiftCount, 1187 note,
1143 onsetStrength)); 1188 shift,
1189 onsetStrength));
1190 }
1191
1192 void
1193 Silvet::emitOffset(int start, int end, int note, FeatureList &onOffsetFeatures)
1194 {
1195 int shift = 0;
1196 if (getShiftCount() > 1) {
1197 shift = m_pianoRollShifts[start][note];
1198 }
1199
1200 onOffsetFeatures.push_back(makeOffsetFeature(end,
1201 note,
1202 shift));
1144 } 1203 }
1145 1204
1146 RealTime 1205 RealTime
1147 Silvet::getColumnTimestamp(int column) 1206 Silvet::getColumnTimestamp(int column)
1148 { 1207 {
1156 Silvet::Feature 1215 Silvet::Feature
1157 Silvet::makeNoteFeature(int start, 1216 Silvet::makeNoteFeature(int start,
1158 int end, 1217 int end,
1159 int note, 1218 int note,
1160 int shift, 1219 int shift,
1161 int shiftCount,
1162 double strength) 1220 double strength)
1163 { 1221 {
1164 Feature f; 1222 Feature f;
1165 1223
1166 f.hasTimestamp = true; 1224 f.hasTimestamp = true;
1168 1226
1169 f.hasDuration = true; 1227 f.hasDuration = true;
1170 f.duration = getColumnTimestamp(end) - f.timestamp; 1228 f.duration = getColumnTimestamp(end) - f.timestamp;
1171 1229
1172 f.values.clear(); 1230 f.values.clear();
1173 f.values.push_back(getNoteFrequency(note, shift, shiftCount)); 1231 f.values.push_back(getNoteFrequency(note, shift));
1174 f.values.push_back(getVelocityFor(strength, start)); 1232 f.values.push_back(getVelocityFor(strength, start));
1175 1233
1176 f.label = getNoteName(note, shift, shiftCount); 1234 f.label = getNoteName(note, shift);
1177 1235
1178 return f; 1236 return f;
1179 } 1237 }
1180 1238
1181 Silvet::Feature 1239 Silvet::Feature
1182 Silvet::makeOnsetFeature(int start, 1240 Silvet::makeOnsetFeature(int start,
1183 int note, 1241 int note,
1184 int shift, 1242 int shift,
1185 int shiftCount,
1186 double strength) 1243 double strength)
1187 { 1244 {
1188 Feature f; 1245 Feature f;
1189 1246
1190 f.hasTimestamp = true; 1247 f.hasTimestamp = true;
1191 f.timestamp = getColumnTimestamp(start); 1248 f.timestamp = getColumnTimestamp(start);
1192 1249
1193 f.hasDuration = false; 1250 f.hasDuration = false;
1194 1251
1195 f.values.clear(); 1252 f.values.clear();
1196 f.values.push_back(getNoteFrequency(note, shift, shiftCount)); 1253 f.values.push_back(getNoteFrequency(note, shift));
1197 f.values.push_back(getVelocityFor(strength, start)); 1254 f.values.push_back(getVelocityFor(strength, start));
1198 1255
1199 f.label = getNoteName(note, shift, shiftCount); 1256 f.label = getNoteName(note, shift);
1257
1258 return f;
1259 }
1260
1261 Silvet::Feature
1262 Silvet::makeOffsetFeature(int col,
1263 int note,
1264 int shift)
1265 {
1266 Feature f;
1267
1268 f.hasTimestamp = true;
1269 f.timestamp = getColumnTimestamp(col);
1270
1271 f.hasDuration = false;
1272
1273 f.values.clear();
1274 f.values.push_back(getNoteFrequency(note, shift));
1275 f.values.push_back(0); // velocity 0 for offset
1276
1277 f.label = getNoteName(note, shift) + " off";
1200 1278
1201 return f; 1279 return f;
1202 } 1280 }
1203 1281
1204 int 1282 int