use a note tracker to resolve notes cut off during render by the end of the region
[ardour.git] / libs / ardour / transient_detector.cc
1 /*
2  * Copyright (C) 2008-2011 David Robillard <d@drobilla.net>
3  * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
5  * Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <cmath>
23
24 #include "ardour/readable.h"
25 #include "ardour/transient_detector.h"
26
27 #include "pbd/i18n.h"
28
29 using namespace Vamp;
30 using namespace ARDOUR;
31 using namespace std;
32
33 /* need a static initializer function for this */
34
35 string TransientDetector::_op_id = X_("qm-onset");
36
37 TransientDetector::TransientDetector (float sr)
38         : AudioAnalyser (sr, X_("libardourvampplugins:qm-onsetdetector"))
39 {
40         threshold = 0.00;
41 }
42
43 TransientDetector::~TransientDetector()
44 {
45 }
46
47 string
48 TransientDetector::operational_identifier()
49 {
50         return _op_id;
51 }
52
53 int
54 TransientDetector::run (const std::string& path, Readable* src, uint32_t channel, AnalysisFeatureList& results)
55 {
56         current_results = &results;
57         int ret = analyse (path, src, channel);
58
59         current_results = 0;
60
61         return ret;
62 }
63
64 int
65 TransientDetector::use_features (Plugin::FeatureSet& features, ostream* out)
66 {
67         const Plugin::FeatureList& fl (features[0]);
68
69         for (Plugin::FeatureList::const_iterator f = fl.begin(); f != fl.end(); ++f) {
70
71                 if (f->hasTimestamp) {
72
73                         if (out) {
74                                 (*out) << (*f).timestamp.toString() << endl;
75                         }
76
77                         current_results->push_back (RealTime::realTime2Frame (f->timestamp, (samplecnt_t) floor(sample_rate)));
78                 }
79         }
80
81         return 0;
82 }
83
84 void
85 TransientDetector::set_threshold (float val)
86 {
87         threshold = val;
88 }
89
90 void
91 TransientDetector::set_sensitivity (uint32_t mode, float val)
92 {
93         if (plugin) {
94                 // see libs/vamp-plugins/OnsetDetect.cpp
95                 //plugin->selectProgram ("General purpose"); // dftype = 3, sensitivity = 50, whiten = 0 (default)
96                 //plugin->selectProgram ("Percussive onsets"); // dftype = 4, sensitivity = 40, whiten = 0
97                 plugin->setParameter ("dftype", mode);
98                 plugin->setParameter ("sensitivity", std::min (100.f, std::max (0.f, val)));
99                 plugin->setParameter ("whiten", 0);
100         }
101 }
102
103 void
104 TransientDetector::cleanup_transients (AnalysisFeatureList& t, float sr, float gap_msecs)
105 {
106         if (t.empty()) {
107                 return;
108         }
109
110         t.sort ();
111
112         /* remove duplicates or other things that are too close */
113
114         AnalysisFeatureList::iterator i = t.begin();
115         AnalysisFeatureList::iterator f, b;
116         const samplecnt_t gap_samples = (samplecnt_t) floor (gap_msecs * (sr / 1000.0));
117
118         while (i != t.end()) {
119
120                 // move front iterator to just past i, and back iterator the same place
121
122                 f = i;
123                 ++f;
124                 b = f;
125
126                 // move f until we find a new value that is far enough away
127
128                 while ((f != t.end()) && gap_samples > 0 && (((*f) - (*i)) < gap_samples)) {
129                         ++f;
130                 }
131
132                 i = f;
133
134                 // if f moved forward from b, we had duplicates/too-close points: get rid of them
135
136                 if (b != f) {
137                         t.erase (b, f);
138                 }
139         }
140 }
141
142 void
143 TransientDetector::update_positions (Readable* src, uint32_t channel, AnalysisFeatureList& positions)
144 {
145         int const buff_size = 1024;
146         int const step_size = 64;
147
148         Sample* data = new Sample[buff_size];
149
150         AnalysisFeatureList::iterator i = positions.begin();
151
152         while (i != positions.end()) {
153
154                 /* read from source */
155                 samplecnt_t const to_read = buff_size;
156
157                 if (src->read (data, (*i) - buff_size, to_read, channel) != to_read) {
158                         break;
159                 }
160
161                 // Simple heuristic for locating approx correct cut position.
162
163                 for (int j = 0; j < (buff_size - step_size); ) {
164
165                         Sample const s = abs (data[j]);
166                         Sample const s2 = abs (data[j + step_size]);
167
168                         if ((s2 - s) > threshold) {
169                                 //cerr << "Thresh exceeded. Moving pos from: " << (*i) << " to: " << (*i) - buff_size + (j + 16) << endl;
170                                 (*i) = (*i) - buff_size + (j + 24);
171                                 break;
172                         }
173
174                         j += step_size;
175                 }
176
177                 ++i;
178         }
179
180         delete [] data;
181 }