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 /// Removes and adds silent frames to beginning and/or end of stream
16 template<typename T = DefaultSampleType>
17 class LIBAUDIOGRAPHER_API SilenceTrimmer
18 : public ListedSource<T>
20 , public FlagDebuggable<>
25 /// Constructor, \see reset() \n Not RT safe
26 SilenceTrimmer(framecnt_t silence_buffer_size_ = 1024)
27 : silence_buffer_size (0)
30 reset (silence_buffer_size_);
31 add_supported_flag (ProcessContext<T>::EndOfInput);
36 delete [] silence_buffer;
39 /** Reset state \n Not RT safe
40 * Allocates a buffer the size of \a silence_buffer_size_
41 * This also defines the maximum length of output process context
42 * which can be output during long intermediate silence.
44 void reset (framecnt_t silence_buffer_size_ = 1024)
46 if (throw_level (ThrowObject) && silence_buffer_size_ == 0) {
47 throw Exception (*this,
48 "Silence trimmer constructor and reset() must be called with a non-zero parameter!");
51 if (silence_buffer_size != silence_buffer_size_) {
52 silence_buffer_size = silence_buffer_size_;
53 delete [] silence_buffer;
54 silence_buffer = new T[silence_buffer_size];
55 TypeUtils<T>::zero_fill (silence_buffer, silence_buffer_size);
60 trim_beginning = false;
63 max_output_frames = 0;
68 /** Tells that \a frames_per_channel frames of silence per channel should be added to beginning
69 * Needs to be called before starting processing.
72 void add_silence_to_beginning (framecnt_t frames_per_channel)
74 if (throw_level (ThrowObject) && !in_beginning) {
75 throw Exception(*this, "Tried to add silence to beginning after already outputting data");
77 add_to_beginning = frames_per_channel;
80 /** Tells that \a frames_per_channel frames of silence per channel should be added to end
81 * Needs to be called before end is reached.
84 void add_silence_to_end (framecnt_t frames_per_channel)
86 if (throw_level (ThrowObject) && in_end) {
87 throw Exception(*this, "Tried to add silence to end after already reaching end");
89 add_to_end = frames_per_channel;
92 /** Tells whether ot nor silence should be trimmed from the beginning
93 * Has to be called before starting processing.
96 void set_trim_beginning (bool yn)
98 if (throw_level (ThrowObject) && !in_beginning) {
99 throw Exception(*this, "Tried to set beginning trim after already outputting data");
104 /** Tells whether ot nor silence should be trimmed from the end
105 * Has to be called before the is reached.
108 void set_trim_end (bool yn)
110 if (throw_level (ThrowObject) && in_end) {
111 throw Exception(*this, "Tried to set end trim after already reaching end");
116 /** Process stream according to current settings.
117 * Note that some calls will not produce any output,
118 * while others may produce many. \see reset()
121 void process (ProcessContext<T> const & c)
123 if (debug_level (DebugVerbose)) {
124 debug_stream () << DebugUtils::demangled_name (*this) <<
125 "::process()" << std::endl;
128 check_flags (*this, c);
130 if (throw_level (ThrowStrict) && in_end) {
131 throw Exception(*this, "process() after reacing end of input");
133 in_end = c.has_flag (ProcessContext<T>::EndOfInput);
135 framecnt_t frame_index = 0;
139 bool has_data = true;
141 // only check silence if doing either of these
142 // This will set both has_data and frame_index
143 if (add_to_beginning || trim_beginning) {
144 has_data = find_first_non_zero_sample (c, frame_index);
147 // Added silence if there is silence to add
148 if (add_to_beginning) {
150 if (debug_level (DebugVerbose)) {
151 debug_stream () << DebugUtils::demangled_name (*this) <<
152 " adding to beginning" << std::endl;
155 ConstProcessContext<T> c_copy (c);
156 if (has_data) { // There will be more output, so remove flag
157 c_copy().remove_flag (ProcessContext<T>::EndOfInput);
159 add_to_beginning *= c.channels();
160 output_silence_frames (c_copy, add_to_beginning);
163 // If we are not trimming the beginning, output everything
164 // Then has_data = true and frame_index = 0
165 // Otherwise these reflect the silence state
168 if (debug_level (DebugVerbose)) {
169 debug_stream () << DebugUtils::demangled_name (*this) <<
170 " outputting whole frame to beginning" << std::endl;
173 in_beginning = false;
174 ConstProcessContext<T> c_out (c, &c.data()[frame_index], c.frames() - frame_index);
175 ListedSource<T>::output (c_out);
178 } else if (trim_end) { // Only check zero samples if trimming end
180 if (find_first_non_zero_sample (c, frame_index)) {
182 if (debug_level (DebugVerbose)) {
183 debug_stream () << DebugUtils::demangled_name (*this) <<
184 " flushing intermediate silence and outputting frame" << std::endl;
187 // context contains non-zero data
188 output_silence_frames (c, silence_frames); // flush intermediate silence
189 ListedSource<T>::output (c); // output rest of data
190 } else { // whole context is zero
192 if (debug_level (DebugVerbose)) {
193 debug_stream () << DebugUtils::demangled_name (*this) <<
194 " no, output, adding frames to silence count" << std::endl;
197 silence_frames += c.frames();
200 } else { // no need to do anything special
202 if (debug_level (DebugVerbose)) {
203 debug_stream () << DebugUtils::demangled_name (*this) <<
204 " outputting whole frame in middle" << std::endl;
207 ListedSource<T>::output (c);
210 // Finally, if in end, add silence to end
211 if (in_end && add_to_end) {
213 if (debug_level (DebugVerbose)) {
214 debug_stream () << DebugUtils::demangled_name (*this) <<
215 " adding to end" << std::endl;
218 add_to_end *= c.channels();
219 output_silence_frames (c, add_to_end, true);
223 using Sink<T>::process;
227 bool find_first_non_zero_sample (ProcessContext<T> const & c, framecnt_t & result_frame)
229 for (framecnt_t i = 0; i < c.frames(); ++i) {
230 if (c.data()[i] != static_cast<T>(0.0)) {
232 // Round down to nearest interleaved "frame" beginning
233 result_frame -= result_frame % c.channels();
240 void output_silence_frames (ProcessContext<T> const & c, framecnt_t & total_frames, bool adding_to_end = false)
242 bool end_of_input = c.has_flag (ProcessContext<T>::EndOfInput);
243 c.remove_flag (ProcessContext<T>::EndOfInput);
245 while (total_frames > 0) {
246 framecnt_t frames = std::min (silence_buffer_size, total_frames);
247 if (max_output_frames) {
248 frames = std::min (frames, max_output_frames);
250 frames -= frames % c.channels();
252 total_frames -= frames;
253 ConstProcessContext<T> c_out (c, silence_buffer, frames);
255 // boolean commentation :)
256 bool const no_more_silence_will_be_added = adding_to_end || (add_to_end == 0);
257 bool const is_last_frame_output_in_this_function = (total_frames == 0);
258 if (end_of_input && no_more_silence_will_be_added && is_last_frame_output_in_this_function) {
259 c_out().set_flag (ProcessContext<T>::EndOfInput);
261 ListedSource<T>::output (c_out);
264 // Add the flag back if it was removed
265 if (end_of_input) { c.set_flag (ProcessContext<T>::EndOfInput); }
275 framecnt_t silence_frames;
276 framecnt_t max_output_frames;
278 framecnt_t add_to_beginning;
279 framecnt_t add_to_end;
281 framecnt_t silence_buffer_size;
287 #endif // AUDIOGRAPHER_SILENCE_TRIMMER_H