Mercurial > hg > silvet
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 ¬eFeatures) | 1034 FeatureList ¬eFeatures) |
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 |