1 #ifndef AUDIOGRAPHER_SILENCE_TRIMMER_H
2 #define AUDIOGRAPHER_SILENCE_TRIMMER_H
4 #include "audiographer/visibility.h"
5 #include "audiographer/debug_utils.h"
6 #include "audiographer/flag_debuggable.h"
7 #include "audiographer/sink.h"
8 #include "audiographer/exception.h"
9 #include "audiographer/utils/listed_source.h"
13 namespace AudioGrapher {
15 template<typename T> struct SilenceTester;
17 // this needs to be implemented for every datatype T
18 // currently Ardour always uses Sample aka float
20 struct SilenceTester<float> {
22 SilenceTester (const float dB) {
23 threshold = dB > -318.8f ? pow (10.0f, dB * 0.05f) : 0.0f;
25 bool is_silent (const float d) {
26 return fabsf (d) <= threshold;
33 /// Removes and adds silent samples to beginning and/or end of stream
34 template<typename T = DefaultSampleType>
35 class /*LIBAUDIOGRAPHER_API*/ SilenceTrimmer
36 : public ListedSource<T>
38 , public FlagDebuggable<>
43 /// Constructor, \see reset() \n Not RT safe
44 SilenceTrimmer(samplecnt_t silence_buffer_size_ = 1024, float thresh_dB = -INFINITY)
45 : silence_buffer_size (0)
49 reset (silence_buffer_size_);
50 add_supported_flag (ProcessContext<T>::EndOfInput);
55 delete [] silence_buffer;
58 /** Reset state \n Not RT safe
59 * Allocates a buffer the size of \a silence_buffer_size_
60 * This also defines the maximum length of output process context
61 * which can be output during long intermediate silence.
63 void reset (samplecnt_t silence_buffer_size_ = 1024)
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!");
70 if (silence_buffer_size != silence_buffer_size_) {
71 silence_buffer_size = silence_buffer_size_;
72 delete [] silence_buffer;
73 silence_buffer = new T[silence_buffer_size];
74 TypeUtils<T>::zero_fill (silence_buffer, silence_buffer_size);
77 processed_data = false;
78 processing_finished = false;
79 trim_beginning = false;
82 max_output_frames = 0;
87 /** Tells that \a samples_per_channel samples of silence per channel should be added to beginning
88 * Needs to be called before starting processing.
91 void add_silence_to_beginning (samplecnt_t samples_per_channel)
93 if (throw_level (ThrowObject) && processed_data) {
94 throw Exception(*this, "Tried to add silence to beginning after processing started");
96 add_to_beginning = samples_per_channel;
99 /** Tells that \a samples_per_channel samples of silence per channel should be added to end
100 * Needs to be called before end is reached.
103 void add_silence_to_end (samplecnt_t samples_per_channel)
105 if (throw_level (ThrowObject) && processed_data) {
106 throw Exception(*this, "Tried to add silence to end after processing started");
108 add_to_end = samples_per_channel;
111 /** Tells whether ot nor silence should be trimmed from the beginning
112 * Has to be called before starting processing.
115 void set_trim_beginning (bool yn)
117 if (throw_level (ThrowObject) && processed_data) {
118 throw Exception(*this, "Tried to set beginning trim after processing started");
123 /** Tells whether ot nor silence should be trimmed from the end
124 * Has to be called before the is reached.
127 void set_trim_end (bool yn)
129 if (throw_level (ThrowObject) && processed_data) {
130 throw Exception(*this, "Tried to set end trim after processing started");
135 /** Process stream according to current settings.
136 * Note that some calls will not produce any output,
137 * while others may produce many. \see reset()
140 void process (ProcessContext<T> const & c)
142 if (debug_level (DebugVerbose)) {
143 debug_stream () << DebugUtils::demangled_name (*this) <<
144 "::process()" << std::endl;
147 check_flags (*this, c);
149 if (throw_level (ThrowStrict) && processing_finished) {
150 throw Exception(*this, "process() after reaching end of input");
153 // delay end of input propagation until output/processing is complete
154 processing_finished = c.has_flag (ProcessContext<T>::EndOfInput);
155 c.remove_flag (ProcessContext<T>::EndOfInput);
157 /* TODO this needs a general overhaul.
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,
165 * https://github.com/x42/silan/blob/master/src/main.c#L130
166 * may lend itself for some inspiration.
169 samplecnt_t output_start_index = 0;
170 samplecnt_t output_sample_count = c.samples();
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;
182 // keep entering this block until non-silence is found to trim
183 processed_data = false;
186 processed_data = true;
189 // This block won't be called again so add silence to beginning
190 if (processed_data && add_to_beginning) {
191 add_to_beginning *= c.channels ();
192 output_silence_samples (c, add_to_beginning);
196 if (processed_data) {
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
201 output_silence_samples (c, silence_samples);
203 samplecnt_t silent_sample_index = 0;
204 find_last_silent_sample_reverse (c, silent_sample_index);
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;
210 assert (samples_before_silence + silent_end_samples == c.samples ());
212 // output_start_index may be non-zero if start trim occurred above
213 output_sample_count = samples_before_silence - output_start_index;
215 // keep track of any silent samples not output
216 silence_samples = silent_end_samples;
219 // whole context buffer is silent output nothing
220 silence_samples += c.samples ();
221 output_sample_count = 0;
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);
230 // Finally, if in last process call, add silence to end
231 if (processing_finished && processed_data && add_to_end) {
232 add_to_end *= c.channels();
233 output_silence_samples (c, add_to_end);
236 if (processing_finished) {
237 // reset flag removed previous to processing above
238 c.set_flag (ProcessContext<T>::EndOfInput);
240 // Finally mark write complete by writing nothing with EndOfInput set
241 // whether or not any data has been written
242 ConstProcessContext<T> c_out(c, silence_buffer, 0);
243 c_out().set_flag (ProcessContext<T>::EndOfInput);
244 ListedSource<T>::output (c_out);
249 using Sink<T>::process;
253 bool find_first_non_silent_sample (ProcessContext<T> const & c, samplecnt_t & result_sample)
255 for (samplecnt_t i = 0; i < c.samples(); ++i) {
256 if (!tester.is_silent (c.data()[i])) {
258 // Round down to nearest interleaved "frame" beginning
259 result_sample -= result_sample % c.channels();
267 * Reverse find the last silent sample index. If the last sample in the
268 * buffer is non-silent the index will be one past the end of the buffer and
269 * equal to c.samples(). e.g silent_end_samples = c.samples() - result_sample
271 * @return true if result_sample index is valid, false if there were only
272 * silent samples in the context buffer
274 bool find_last_silent_sample_reverse (ProcessContext<T> const & c, samplecnt_t & result_sample)
276 samplecnt_t last_sample_index = c.samples() - 1;
278 for (samplecnt_t i = last_sample_index; i >= 0; --i) {
279 if (!tester.is_silent (c.data()[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();
291 void output_silence_samples (ProcessContext<T> const & c, samplecnt_t & total_samples)
293 assert (!c.has_flag (ProcessContext<T>::EndOfInput));
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);
300 samples -= samples % c.channels();
302 total_samples -= samples;
303 ConstProcessContext<T> c_out (c, silence_buffer, samples);
304 ListedSource<T>::output (c_out);
309 bool processing_finished;
314 samplecnt_t silence_samples;
315 samplecnt_t max_output_frames;
317 samplecnt_t add_to_beginning;
318 samplecnt_t add_to_end;
320 samplecnt_t silence_buffer_size;
323 SilenceTester<T> tester;
328 #endif // AUDIOGRAPHER_SILENCE_TRIMMER_H