comparison src/Silvet.cpp @ 319:c37da62ba4e5 livemode

Add onsets output (lower-latency than the notes output)
author Chris Cannam
date Wed, 29 Apr 2015 09:27:38 +0100
parents 92293058368a
children 7f9683c8de69
comparison
equal deleted inserted replaced
318:ba02cdc839db 319:c37da62ba4e5
249 d.isQuantized = false; 249 d.isQuantized = false;
250 d.sampleType = OutputDescriptor::VariableSampleRate; 250 d.sampleType = OutputDescriptor::VariableSampleRate;
251 d.sampleRate = processingSampleRate / (m_cq ? m_cq->getColumnHop() : 62); 251 d.sampleRate = processingSampleRate / (m_cq ? m_cq->getColumnHop() : 62);
252 d.hasDuration = true; 252 d.hasDuration = true;
253 m_notesOutputNo = list.size(); 253 m_notesOutputNo = list.size();
254 list.push_back(d);
255
256 d.identifier = "onsets";
257 d.name = "Note onsets";
258 d.description = "Note onsets, without durations. These can be calculated sooner than complete notes as it isn't necessary to wait for the note to finish. Each 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.";
259 d.unit = "Hz";
260 d.hasFixedBinCount = true;
261 d.binCount = 2;
262 d.binNames.push_back("Frequency");
263 d.binNames.push_back("Velocity");
264 d.hasKnownExtents = false;
265 d.isQuantized = false;
266 d.sampleType = OutputDescriptor::VariableSampleRate;
267 d.sampleRate = processingSampleRate / (m_cq ? m_cq->getColumnHop() : 62);
268 d.hasDuration = false;
269 m_onsetsOutputNo = list.size();
254 list.push_back(d); 270 list.push_back(d);
255 271
256 d.identifier = "timefreq"; 272 d.identifier = "timefreq";
257 d.name = "Time-frequency distribution"; 273 d.name = "Time-frequency distribution";
258 d.description = "Filtered constant-Q time-frequency distribution as used as input to the expectation-maximisation algorithm."; 274 d.description = "Filtered constant-Q time-frequency distribution as used as input to the expectation-maximisation algorithm.";
714 f.values.resize(12); 730 f.values.resize(12);
715 for (int j = 0; j < (int)filtered.size(); ++j) { 731 for (int j = 0; j < (int)filtered.size(); ++j) {
716 f.values[j % 12] += filtered[j] / inputGain; 732 f.values[j % 12] += filtered[j] / inputGain;
717 } 733 }
718 fs[m_chromaOutputNo].push_back(f); 734 fs[m_chromaOutputNo].push_back(f);
719 735
720 FeatureList noteFeatures = noteTrack(shiftCount); 736 auto events = noteTrack(shiftCount);
721 737
738 FeatureList noteFeatures = events.first;
722 for (FeatureList::const_iterator fi = noteFeatures.begin(); 739 for (FeatureList::const_iterator fi = noteFeatures.begin();
723 fi != noteFeatures.end(); ++fi) { 740 fi != noteFeatures.end(); ++fi) {
724 fs[m_notesOutputNo].push_back(*fi); 741 fs[m_notesOutputNo].push_back(*fi);
742 }
743
744 FeatureList onsetFeatures = events.second;
745 for (FeatureList::const_iterator fi = onsetFeatures.begin();
746 fi != onsetFeatures.end(); ++fi) {
747 fs[m_onsetsOutputNo].push_back(*fi);
725 } 748 }
726 } 749 }
727 } 750 }
728 751
729 pair<vector<double>, vector<int> > 752 pair<vector<double>, vector<int> >
942 } 965 }
943 966
944 return filtered; 967 return filtered;
945 } 968 }
946 969
947 Vamp::Plugin::FeatureList 970 pair<Vamp::Plugin::FeatureList, Vamp::Plugin::FeatureList>
948 Silvet::noteTrack(int shiftCount) 971 Silvet::noteTrack(int shiftCount)
949 { 972 {
950 // Minimum duration pruning, and conversion to notes. We can only 973 // Minimum duration pruning, and conversion to notes. We can only
951 // report notes that have just ended (i.e. that are absent in the 974 // report notes that have just ended (i.e. that are absent in the
952 // latest active set but present in the prior set in the piano 975 // latest active set but present in the prior set in the piano
962 985
963 // only keep notes >= 100ms or thereabouts 986 // only keep notes >= 100ms or thereabouts
964 int durationThreshold = floor(0.1 / columnDuration); // columns 987 int durationThreshold = floor(0.1 / columnDuration); // columns
965 if (durationThreshold < 1) durationThreshold = 1; 988 if (durationThreshold < 1) durationThreshold = 1;
966 989
967 FeatureList noteFeatures; 990 FeatureList noteFeatures, onsetFeatures;
968 991
969 if (width < durationThreshold + 1) { 992 if (width < durationThreshold + 1) {
970 return noteFeatures; 993 return { noteFeatures, onsetFeatures };
971 } 994 }
972 995
973 //!!! try: repeated note detection? (look for change in first derivative of the pitch matrix) 996 //!!! try: repeated note detection? (look for change in first derivative of the pitch matrix)
974 997
975 for (map<int, double>::const_iterator ni = m_pianoRoll[width-1].begin(); 998 for (map<int, double>::const_iterator ni = m_pianoRoll[width-1].begin();
976 ni != m_pianoRoll[width-1].end(); ++ni) { 999 ni != m_pianoRoll[width-1].end(); ++ni) {
977 1000
978 int note = ni->first; 1001 int note = ni->first;
979 1002
980 if (active.find(note) != active.end()) {
981 // the note is still playing
982 continue;
983 }
984
985 // the note was playing but just ended
986 int end = width; 1003 int end = width;
987 int start = end-1; 1004 int start = end-1;
988 1005
989 while (m_pianoRoll[start].find(note) != m_pianoRoll[start].end()) { 1006 while (m_pianoRoll[start].find(note) != m_pianoRoll[start].end()) {
990 --start; 1007 --start;
991 } 1008 }
992 ++start; 1009 ++start;
993 1010
994 if ((end - start) < durationThreshold) { 1011 int duration = end - start;
1012
1013 if (duration < durationThreshold) {
995 continue; 1014 continue;
996 } 1015 }
997 1016
998 emitNote(start, end, note, shiftCount, noteFeatures); 1017 if (duration == durationThreshold) {
1018 emitOnset(start, note, shiftCount, onsetFeatures);
1019 }
1020
1021 if (active.find(note) == active.end()) {
1022 // the note was playing but just ended
1023 emitNote(start, end, note, shiftCount, noteFeatures);
1024 }
999 } 1025 }
1000 1026
1001 // cerr << "returning " << noteFeatures.size() << " complete note(s) " << endl; 1027 // cerr << "returning " << noteFeatures.size() << " complete note(s) " << endl;
1002 1028
1003 return noteFeatures; 1029 return { noteFeatures, onsetFeatures };
1004 } 1030 }
1005 1031
1006 void 1032 void
1007 Silvet::emitNote(int start, int end, int note, int shiftCount, 1033 Silvet::emitNote(int start, int end, int note, int shiftCount,
1008 FeatureList &noteFeatures) 1034 FeatureList &noteFeatures)
1063 shiftCount, 1089 shiftCount,
1064 partVelocity)); 1090 partVelocity));
1065 } 1091 }
1066 } 1092 }
1067 1093
1094 void
1095 Silvet::emitOnset(int start, int note, int shiftCount,
1096 FeatureList &onsetFeatures)
1097 {
1098 int len = int(m_pianoRoll.size());
1099 int velocity = 0;
1100
1101 int shift = 0;
1102 if (shiftCount > 1) {
1103 shift = m_pianoRollShifts[start][note];
1104 }
1105
1106 for (int i = start; i < len; ++i) {
1107
1108 double strength = m_pianoRoll[i][note];
1109
1110 int v;
1111 if (m_mode == LiveMode) {
1112 v = round(strength * 20);
1113 } else {
1114 v = round(strength * 2);
1115 }
1116 if (v > velocity) {
1117 velocity = v;
1118 }
1119 }
1120
1121 onsetFeatures.push_back(makeOnsetFeature(start,
1122 note,
1123 shift,
1124 shiftCount,
1125 velocity));
1126 }
1127
1068 RealTime 1128 RealTime
1069 Silvet::getColumnTimestamp(int column) 1129 Silvet::getColumnTimestamp(int column)
1070 { 1130 {
1071 double columnDuration = 1.0 / m_colsPerSec; 1131 double columnDuration = 1.0 / m_colsPerSec;
1072 int postFilterLatency = int(m_postFilter[0]->getSize() / 2); 1132 int postFilterLatency = int(m_postFilter[0]->getSize() / 2);
1106 f.label = noteName(note, shift, shiftCount); 1166 f.label = noteName(note, shift, shiftCount);
1107 1167
1108 return f; 1168 return f;
1109 } 1169 }
1110 1170
1171 Silvet::Feature
1172 Silvet::makeOnsetFeature(int start,
1173 int note,
1174 int shift,
1175 int shiftCount,
1176 int velocity)
1177 {
1178 Feature f;
1179
1180 f.hasTimestamp = true;
1181 f.timestamp = getColumnTimestamp(start);
1182
1183 f.hasDuration = false;
1184
1185 f.values.clear();
1186
1187 f.values.push_back
1188 (noteFrequency(note, shift, shiftCount));
1189
1190 float inputGain = getInputGainAt(f.timestamp);
1191 velocity = round(velocity / inputGain);
1192 if (velocity > 127) velocity = 127;
1193 if (velocity < 1) velocity = 1;
1194 f.values.push_back(velocity);
1195
1196 f.label = noteName(note, shift, shiftCount);
1197
1198 return f;
1199 }
1200
1111 float 1201 float
1112 Silvet::getInputGainAt(RealTime t) 1202 Silvet::getInputGainAt(RealTime t)
1113 { 1203 {
1114 map<RealTime, float>::const_iterator i = m_inputGains.lower_bound(t); 1204 map<RealTime, float>::const_iterator i = m_inputGains.lower_bound(t);
1115 1205