Ardour  8.12
silence_trimmer.h
Go to the documentation of this file.
1 #ifndef AUDIOGRAPHER_SILENCE_TRIMMER_H
2 #define AUDIOGRAPHER_SILENCE_TRIMMER_H
3 
7 #include "audiographer/sink.h"
10 
11 #include <cstring>
12 
13 namespace AudioGrapher {
14 
15 template<typename T> struct SilenceTester;
16 
17 // this needs to be implemented for every datatype T
18 // currently Ardour always uses Sample aka float
19 template <>
20 struct SilenceTester<float> {
21  public:
22  SilenceTester (const float dB) {
23  threshold = dB > -318.8f ? pow (10.0f, dB * 0.05f) : 0.0f;
24  }
25  bool is_silent (const float d) {
26  return fabsf (d) <= threshold;
27  }
28  private:
29  float threshold;
30 };
31 
32 
34 template<typename T = DefaultSampleType>
35 class /*LIBAUDIOGRAPHER_API*/ SilenceTrimmer
36  : public ListedSource<T>
37  , public Sink<T>
38  , public FlagDebuggable<>
39  , public Throwing<>
40 {
41  public:
42 
44  SilenceTrimmer(samplecnt_t silence_buffer_size_ = 1024, float thresh_dB = -INFINITY)
46  , silence_buffer (0)
47  , tester (thresh_dB)
48  {
49  reset (silence_buffer_size_);
51  }
52 
54  {
55  delete [] silence_buffer;
56  }
57 
63  void reset (samplecnt_t silence_buffer_size_ = 1024)
64  {
65  if (throw_level (ThrowObject) && silence_buffer_size_ == 0) {
66  throw Exception (*this,
67  "Silence trimmer constructor and reset() must be called with a non-zero parameter!");
68  }
69 
70  if (silence_buffer_size != silence_buffer_size_) {
71  silence_buffer_size = silence_buffer_size_;
72  delete [] silence_buffer;
75  }
76 
77  processed_data = false;
78  processing_finished = false;
79  trim_beginning = false;
80  trim_end = false;
81  silence_samples = 0;
83  add_to_beginning = 0;
84  add_to_end = 0;
85  }
86 
91  void add_silence_to_beginning (samplecnt_t samples_per_channel)
92  {
94  throw Exception(*this, "Tried to add silence to beginning after processing started");
95  }
96  add_to_beginning = samples_per_channel;
97  }
98 
103  void add_silence_to_end (samplecnt_t samples_per_channel)
104  {
106  throw Exception(*this, "Tried to add silence to end after processing started");
107  }
108  add_to_end = samples_per_channel;
109  }
110 
115  void set_trim_beginning (bool yn)
116  {
118  throw Exception(*this, "Tried to set beginning trim after processing started");
119  }
120  trim_beginning = yn;
121  }
122 
127  void set_trim_end (bool yn)
128  {
130  throw Exception(*this, "Tried to set end trim after processing started");
131  }
132  trim_end = yn;
133  }
134 
140  void process (ProcessContext<T> const & c)
141  {
142  if (debug_level (DebugVerbose)) {
144  "::process()" << std::endl;
145  }
146 
147  check_flags (*this, c);
148 
150  throw Exception(*this, "process() after reaching end of input");
151  }
152 
153  // delay end of input propagation until output/processing is complete
156 
157  /* TODO this needs a general overhaul.
158  *
159  * - decouple "required silence duration" from buffer-size.
160  * - add hold-times for in/out
161  * - optional high pass filter (for DC offset)
162  * -> allocate a buffer "hold time" worth of samples.
163  * check if all samples in buffer are above/below threshold,
164  *
165  * https://github.com/x42/silan/blob/master/src/main.c#L130
166  * may lend itself for some inspiration.
167  */
168 
169  samplecnt_t output_start_index = 0;
170  samplecnt_t output_sample_count = c.samples();
171 
172  if (!processed_data) {
173  if (trim_beginning) {
174  samplecnt_t first_non_silent_sample_index = 0;
175  if (find_first_non_silent_sample (c, first_non_silent_sample_index)) {
176  // output from start of non-silent data until end of buffer
177  // output_sample_count may also be altered in trim end
178  output_start_index = first_non_silent_sample_index;
179  output_sample_count = c.samples() - first_non_silent_sample_index;
180  processed_data = true;
181  } else {
182  // keep entering this block until non-silence is found to trim
183  processed_data = false;
184  }
185  } else {
186  processed_data = true;
187  }
188 
189  // This block won't be called again so add silence to beginning
191  add_to_beginning *= c.channels ();
193  }
194  }
195 
196  if (processed_data) {
197  if (trim_end) {
198  samplecnt_t first_non_silent_sample_index = 0;
199  if (find_first_non_silent_sample (c, first_non_silent_sample_index)) {
200  // context buffer contains non-silent data, flush any intermediate silence
202 
203  samplecnt_t silent_sample_index = 0;
204  find_last_silent_sample_reverse (c, silent_sample_index);
205 
206  // Count of samples at end of block that are "silent", may be zero.
207  samplecnt_t silent_end_samples = c.samples () - silent_sample_index;
208  samplecnt_t samples_before_silence = c.samples() - silent_end_samples;
209 
210  assert (samples_before_silence + silent_end_samples == c.samples ());
211 
212  // output_start_index may be non-zero if start trim occurred above
213  output_sample_count = samples_before_silence - output_start_index;
214 
215  // keep track of any silent samples not output
216  silence_samples = silent_end_samples;
217 
218  } else {
219  // whole context buffer is silent output nothing
220  silence_samples += c.samples ();
221  output_sample_count = 0;
222  }
223  }
224 
225  // now output data if any
226  ConstProcessContext<T> c_out (c, &c.data()[output_start_index], output_sample_count);
227  ListedSource<T>::output (c_out);
228  }
229 
230  // Finally, if in last process call, add silence to end
232  add_to_end *= c.channels();
234  }
235 
236  if (processing_finished) {
237  // reset flag removed previous to processing above
239 
240  // Finally mark write complete by writing nothing with EndOfInput set
241  // whether or not any data has been written
243  c_out().set_flag (ProcessContext<T>::EndOfInput);
244  ListedSource<T>::output (c_out);
245  }
246 
247  }
248 
249  using Sink<T>::process;
250 
251 private:
252 
254  {
255  for (samplecnt_t i = 0; i < c.samples(); ++i) {
256  if (!tester.is_silent (c.data()[i])) {
257  result_sample = i;
258  // Round down to nearest interleaved "frame" beginning
259  result_sample -= result_sample % c.channels();
260  return true;
261  }
262  }
263  return false;
264  }
265 
275  {
276  samplecnt_t last_sample_index = c.samples() - 1;
277 
278  for (samplecnt_t i = last_sample_index; i >= 0; --i) {
279  if (!tester.is_silent (c.data()[i])) {
280  result_sample = i;
281  // Round down to nearest interleaved "frame" beginning
282  result_sample -= result_sample % c.channels();
283  // Round up to return the "last" silent interleaved sample
284  result_sample += c.channels();
285  return true;
286  }
287  }
288  return false;
289  }
290 
291  void output_silence_samples (ProcessContext<T> const & c, samplecnt_t & total_samples)
292  {
294 
295  while (total_samples > 0) {
296  samplecnt_t samples = std::min (silence_buffer_size, total_samples);
297  if (max_output_frames) {
298  samples = std::min (samples, max_output_frames);
299  }
300  samples -= samples % c.channels();
301 
302  total_samples -= samples;
303  ConstProcessContext<T> c_out (c, silence_buffer, samples);
304  ListedSource<T>::output (c_out);
305  }
306  }
307 
310 
312  bool trim_end;
313 
316 
319 
322 
324 };
325 
326 } // namespace
327 
328 #endif // AUDIOGRAPHER_SILENCE_TRIMMER_H
A wrapper for a const ProcesContext which can be created from const data.
bool debug_level(DebugLevel level)
Definition: debuggable.h:47
A debugging class for nodes that support a certain set of flags.
void check_flags(SelfType &self, ProcessContext< ContextType > context)
Prints debug output if context contains flags that are not supported by this class.
void add_supported_flag(Flag flag)
Adds a flag to the set of flags supported.
An generic Source that uses a std::list for managing outputs.
Definition: listed_source.h:17
void output(ProcessContext< T > const &c)
Helper for derived classes.
Definition: listed_source.h:28
ChannelCount const & channels() const
bool has_flag(Flag flag) const
T const * data() const
data points to the array of data to process
samplecnt_t const & samples() const
samples tells how many samples the array pointed by data contains
void set_flag(Flag flag) const
void remove_flag(Flag flag) const
Removes and adds silent samples to beginning and/or end of stream.
void add_silence_to_end(samplecnt_t samples_per_channel)
void add_silence_to_beginning(samplecnt_t samples_per_channel)
bool find_last_silent_sample_reverse(ProcessContext< T > const &c, samplecnt_t &result_sample)
void reset(samplecnt_t silence_buffer_size_=1024)
void output_silence_samples(ProcessContext< T > const &c, samplecnt_t &total_samples)
bool find_first_non_silent_sample(ProcessContext< T > const &c, samplecnt_t &result_sample)
SilenceTrimmer(samplecnt_t silence_buffer_size_=1024, float thresh_dB=-INFINITY)
Constructor,.
void process(ProcessContext< T > const &c)
bool throw_level(ThrowLevel level)
Definition: throwing.h:47
static void zero_fill(T *buffer, samplecnt_t samples)
Definition: type_utils.h:45
@ ThrowObject
Object level stuff, ctors, initalizers etc.
Definition: throwing.h:22
@ ThrowStrict
Stricter checks than ThrowProcess, less than ThrowSample.
Definition: throwing.h:24
@ DebugVerbose
Lots of output, not on sample level.
Definition: debuggable.h:22
static std::string demangled_name(T const &obj)
Returns the demangled name of the object passed as the parameter.
Definition: debug_utils.h:24