Ardour  9.0-pre0-386-g96ef4d20f2
silence_trimmer.h
Go to the documentation of this file.
1 #ifndef AUDIOGRAPHER_SILENCE_TRIMMER_H
2 #define AUDIOGRAPHER_SILENCE_TRIMMER_H
3 
4 #include <cassert>
5 
9 #include "audiographer/sink.h"
10 #include "audiographer/exception.h"
12 
13 #include <cmath>
14 #include <cstring>
15 
16 namespace AudioGrapher {
17 
18 template<typename T> struct SilenceTester;
19 
20 // this needs to be implemented for every datatype T
21 // currently Ardour always uses Sample aka float
22 template <>
23 struct SilenceTester<float> {
24  public:
25  SilenceTester (const float dB) {
26  threshold = dB > -318.8f ? pow (10.0f, dB * 0.05f) : 0.0f;
27  }
28  bool is_silent (const float d) {
29  return fabsf (d) <= threshold;
30  }
31  private:
32  float threshold;
33 };
34 
35 
37 template<typename T = DefaultSampleType>
38 class /*LIBAUDIOGRAPHER_API*/ SilenceTrimmer
39  : public ListedSource<T>
40  , public Sink<T>
41  , public FlagDebuggable<>
42  , public Throwing<>
43 {
44  public:
45 
47  SilenceTrimmer(samplecnt_t silence_buffer_size_ = 1024, float thresh_dB = -INFINITY)
49  , silence_buffer (0)
50  , tester (thresh_dB)
51  {
52  reset (silence_buffer_size_);
54  }
55 
57  {
58  delete [] silence_buffer;
59  }
60 
66  void reset (samplecnt_t silence_buffer_size_ = 1024)
67  {
68  if (throw_level (ThrowObject) && silence_buffer_size_ == 0) {
69  throw Exception (*this,
70  "Silence trimmer constructor and reset() must be called with a non-zero parameter!");
71  }
72 
73  if (silence_buffer_size != silence_buffer_size_) {
74  silence_buffer_size = silence_buffer_size_;
75  delete [] silence_buffer;
78  }
79 
80  processed_data = false;
81  processing_finished = false;
82  trim_beginning = false;
83  trim_end = false;
84  silence_samples = 0;
86  add_to_beginning = 0;
87  add_to_end = 0;
88  }
89 
94  void add_silence_to_beginning (samplecnt_t samples_per_channel)
95  {
97  throw Exception(*this, "Tried to add silence to beginning after processing started");
98  }
99  add_to_beginning = samples_per_channel;
100  }
101 
106  void add_silence_to_end (samplecnt_t samples_per_channel)
107  {
109  throw Exception(*this, "Tried to add silence to end after processing started");
110  }
111  add_to_end = samples_per_channel;
112  }
113 
118  void set_trim_beginning (bool yn)
119  {
121  throw Exception(*this, "Tried to set beginning trim after processing started");
122  }
123  trim_beginning = yn;
124  }
125 
130  void set_trim_end (bool yn)
131  {
133  throw Exception(*this, "Tried to set end trim after processing started");
134  }
135  trim_end = yn;
136  }
137 
143  void process (ProcessContext<T> const & c)
144  {
145  if (debug_level (DebugVerbose)) {
147  "::process()" << std::endl;
148  }
149 
150  check_flags (*this, c);
151 
153  throw Exception(*this, "process() after reaching end of input");
154  }
155 
156  // delay end of input propagation until output/processing is complete
159 
160  /* TODO this needs a general overhaul.
161  *
162  * - decouple "required silence duration" from buffer-size.
163  * - add hold-times for in/out
164  * - optional high pass filter (for DC offset)
165  * -> allocate a buffer "hold time" worth of samples.
166  * check if all samples in buffer are above/below threshold,
167  *
168  * https://github.com/x42/silan/blob/master/src/main.c#L130
169  * may lend itself for some inspiration.
170  */
171 
172  samplecnt_t output_start_index = 0;
173  samplecnt_t output_sample_count = c.samples();
174 
175  if (!processed_data) {
176  if (trim_beginning) {
177  samplecnt_t first_non_silent_sample_index = 0;
178  if (find_first_non_silent_sample (c, first_non_silent_sample_index)) {
179  // output from start of non-silent data until end of buffer
180  // output_sample_count may also be altered in trim end
181  output_start_index = first_non_silent_sample_index;
182  output_sample_count = c.samples() - first_non_silent_sample_index;
183  processed_data = true;
184  } else {
185  // keep entering this block until non-silence is found to trim
186  processed_data = false;
187  }
188  } else {
189  processed_data = true;
190  }
191 
192  // This block won't be called again so add silence to beginning
194  add_to_beginning *= c.channels ();
196  }
197  }
198 
199  if (processed_data) {
200  if (trim_end) {
201  samplecnt_t first_non_silent_sample_index = 0;
202  if (find_first_non_silent_sample (c, first_non_silent_sample_index)) {
203  // context buffer contains non-silent data, flush any intermediate silence
205 
206  samplecnt_t silent_sample_index = 0;
207  find_last_silent_sample_reverse (c, silent_sample_index);
208 
209  // Count of samples at end of block that are "silent", may be zero.
210  samplecnt_t silent_end_samples = c.samples () - silent_sample_index;
211  samplecnt_t samples_before_silence = c.samples() - silent_end_samples;
212 
213  assert (samples_before_silence + silent_end_samples == c.samples ());
214 
215  // output_start_index may be non-zero if start trim occurred above
216  output_sample_count = samples_before_silence - output_start_index;
217 
218  // keep track of any silent samples not output
219  silence_samples = silent_end_samples;
220 
221  } else {
222  // whole context buffer is silent output nothing
223  silence_samples += c.samples ();
224  output_sample_count = 0;
225  }
226  }
227 
228  // now output data if any
229  ConstProcessContext<T> c_out (c, &c.data()[output_start_index], output_sample_count);
230  ListedSource<T>::output (c_out);
231  }
232 
233  // Finally, if in last process call, add silence to end
235  add_to_end *= c.channels();
237  }
238 
239  if (processing_finished) {
240  // reset flag removed previous to processing above
242 
243  // Finally mark write complete by writing nothing with EndOfInput set
244  // whether or not any data has been written
246  c_out().set_flag (ProcessContext<T>::EndOfInput);
247  ListedSource<T>::output (c_out);
248  }
249 
250  }
251 
252  using Sink<T>::process;
253 
254 private:
255 
257  {
258  for (samplecnt_t i = 0; i < c.samples(); ++i) {
259  if (!tester.is_silent (c.data()[i])) {
260  result_sample = i;
261  // Round down to nearest interleaved "frame" beginning
262  result_sample -= result_sample % c.channels();
263  return true;
264  }
265  }
266  return false;
267  }
268 
278  {
279  samplecnt_t last_sample_index = c.samples() - 1;
280 
281  for (samplecnt_t i = last_sample_index; i >= 0; --i) {
282  if (!tester.is_silent (c.data()[i])) {
283  result_sample = i;
284  // Round down to nearest interleaved "frame" beginning
285  result_sample -= result_sample % c.channels();
286  // Round up to return the "last" silent interleaved sample
287  result_sample += c.channels();
288  return true;
289  }
290  }
291  return false;
292  }
293 
294  void output_silence_samples (ProcessContext<T> const & c, samplecnt_t & total_samples)
295  {
297 
298  while (total_samples > 0) {
299  samplecnt_t samples = std::min (silence_buffer_size, total_samples);
300  if (max_output_frames) {
301  samples = std::min (samples, max_output_frames);
302  }
303  samples -= samples % c.channels();
304 
305  total_samples -= samples;
306  ConstProcessContext<T> c_out (c, silence_buffer, samples);
307  ListedSource<T>::output (c_out);
308  }
309  }
310 
313 
315  bool trim_end;
316 
319 
322 
325 
327 };
328 
329 } // namespace
330 
331 #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:42
@ 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