1 #ifndef AUDIOGRAPHER_SILENCE_TRIMMER_H
2 #define AUDIOGRAPHER_SILENCE_TRIMMER_H
4 #include "audiographer/debug_utils.h"
5 #include "audiographer/flag_debuggable.h"
6 #include "audiographer/sink.h"
7 #include "audiographer/exception.h"
8 #include "audiographer/utils/listed_source.h"
12 namespace AudioGrapher {
14 /// Removes and adds silent frames to beginning and/or end of stream
15 template<typename T = DefaultSampleType>
17 : public ListedSource<T>
19 , public FlagDebuggable<>
24 /// Constructor, \see reset() \n Not RT safe
25 SilenceTrimmer(framecnt_t silence_buffer_size_ = 1024)
26 : silence_buffer_size (0)
29 reset (silence_buffer_size_);
30 add_supported_flag (ProcessContext<T>::EndOfInput);
35 delete [] silence_buffer;
38 /** Reset state \n Not RT safe
39 * Allocates a buffer the size of \a silence_buffer_size_
40 * This also defines the maximum length of output process context
41 * which can be output during long intermediate silence.
43 void reset (framecnt_t silence_buffer_size_ = 1024)
45 if (throw_level (ThrowObject) && silence_buffer_size_ == 0) {
46 throw Exception (*this,
47 "Silence trimmer constructor and reset() must be called with a non-zero parameter!");
50 if (silence_buffer_size != silence_buffer_size_) {
51 silence_buffer_size = silence_buffer_size_;
52 delete [] silence_buffer;
53 silence_buffer = new T[silence_buffer_size];
54 TypeUtils<T>::zero_fill (silence_buffer, silence_buffer_size);
59 trim_beginning = false;
62 max_output_frames = 0;
67 /** Tells that \a frames_per_channel frames of silence per channel should be added to beginning
68 * Needs to be called before starting processing.
71 void add_silence_to_beginning (framecnt_t frames_per_channel)
73 if (throw_level (ThrowObject) && !in_beginning) {
74 throw Exception(*this, "Tried to add silence to beginning after already outputting data");
76 add_to_beginning = frames_per_channel;
79 /** Tells that \a frames_per_channel frames of silence per channel should be added to end
80 * Needs to be called before end is reached.
83 void add_silence_to_end (framecnt_t frames_per_channel)
85 if (throw_level (ThrowObject) && in_end) {
86 throw Exception(*this, "Tried to add silence to end after already reaching end");
88 add_to_end = frames_per_channel;
91 /** Tells whether ot nor silence should be trimmed from the beginning
92 * Has to be called before starting processing.
95 void set_trim_beginning (bool yn)
97 if (throw_level (ThrowObject) && !in_beginning) {
98 throw Exception(*this, "Tried to set beginning trim after already outputting data");
103 /** Tells whether ot nor silence should be trimmed from the end
104 * Has to be called before the is reached.
107 void set_trim_end (bool yn)
109 if (throw_level (ThrowObject) && in_end) {
110 throw Exception(*this, "Tried to set end trim after already reaching end");
115 /** Process stream according to current settings.
116 * Note that some calls will not produce any output,
117 * while others may produce many. \see reset()
120 void process (ProcessContext<T> const & c)
122 if (debug_level (DebugVerbose)) {
123 debug_stream () << DebugUtils::demangled_name (*this) <<
124 "::process()" << std::endl;
127 check_flags (*this, c);
129 if (throw_level (ThrowStrict) && in_end) {
130 throw Exception(*this, "process() after reacing end of input");
132 in_end = c.has_flag (ProcessContext<T>::EndOfInput);
134 framecnt_t frame_index = 0;
138 bool has_data = true;
140 // only check silence if doing either of these
141 // This will set both has_data and frame_index
142 if (add_to_beginning || trim_beginning) {
143 has_data = find_first_non_zero_sample (c, frame_index);
146 // Added silence if there is silence to add
147 if (add_to_beginning) {
149 if (debug_level (DebugVerbose)) {
150 debug_stream () << DebugUtils::demangled_name (*this) <<
151 " adding to beginning" << std::endl;
154 ConstProcessContext<T> c_copy (c);
155 if (has_data) { // There will be more output, so remove flag
156 c_copy().remove_flag (ProcessContext<T>::EndOfInput);
158 add_to_beginning *= c.channels();
159 output_silence_frames (c_copy, add_to_beginning);
162 // If we are not trimming the beginning, output everything
163 // Then has_data = true and frame_index = 0
164 // Otherwise these reflect the silence state
167 if (debug_level (DebugVerbose)) {
168 debug_stream () << DebugUtils::demangled_name (*this) <<
169 " outputting whole frame to beginning" << std::endl;
172 in_beginning = false;
173 ConstProcessContext<T> c_out (c, &c.data()[frame_index], c.frames() - frame_index);
174 ListedSource<T>::output (c_out);
177 } else if (trim_end) { // Only check zero samples if trimming end
179 if (find_first_non_zero_sample (c, frame_index)) {
181 if (debug_level (DebugVerbose)) {
182 debug_stream () << DebugUtils::demangled_name (*this) <<
183 " flushing intermediate silence and outputting frame" << std::endl;
186 // context contains non-zero data
187 output_silence_frames (c, silence_frames); // flush intermediate silence
188 ListedSource<T>::output (c); // output rest of data
189 } else { // whole context is zero
191 if (debug_level (DebugVerbose)) {
192 debug_stream () << DebugUtils::demangled_name (*this) <<
193 " no, output, adding frames to silence count" << std::endl;
196 silence_frames += c.frames();
199 } else { // no need to do anything special
201 if (debug_level (DebugVerbose)) {
202 debug_stream () << DebugUtils::demangled_name (*this) <<
203 " outputting whole frame in middle" << std::endl;
206 ListedSource<T>::output (c);
209 // Finally, if in end, add silence to end
210 if (in_end && add_to_end) {
212 if (debug_level (DebugVerbose)) {
213 debug_stream () << DebugUtils::demangled_name (*this) <<
214 " adding to end" << std::endl;
217 add_to_end *= c.channels();
218 output_silence_frames (c, add_to_end, true);
222 using Sink<T>::process;
226 bool find_first_non_zero_sample (ProcessContext<T> const & c, framecnt_t & result_frame)
228 for (framecnt_t i = 0; i < c.frames(); ++i) {
229 if (c.data()[i] != static_cast<T>(0.0)) {
231 // Round down to nearest interleaved "frame" beginning
232 result_frame -= result_frame % c.channels();
239 void output_silence_frames (ProcessContext<T> const & c, framecnt_t & total_frames, bool adding_to_end = false)
241 bool end_of_input = c.has_flag (ProcessContext<T>::EndOfInput);
242 c.remove_flag (ProcessContext<T>::EndOfInput);
244 while (total_frames > 0) {
245 framecnt_t frames = std::min (silence_buffer_size, total_frames);
246 if (max_output_frames) {
247 frames = std::min (frames, max_output_frames);
249 frames -= frames % c.channels();
251 total_frames -= frames;
252 ConstProcessContext<T> c_out (c, silence_buffer, frames);
254 // boolean commentation :)
255 bool const no_more_silence_will_be_added = adding_to_end || (add_to_end == 0);
256 bool const is_last_frame_output_in_this_function = (total_frames == 0);
257 if (end_of_input && no_more_silence_will_be_added && is_last_frame_output_in_this_function) {
258 c_out().set_flag (ProcessContext<T>::EndOfInput);
260 ListedSource<T>::output (c_out);
271 framecnt_t silence_frames;
272 framecnt_t max_output_frames;
274 framecnt_t add_to_beginning;
275 framecnt_t add_to_end;
277 framecnt_t silence_buffer_size;
283 #endif // AUDIOGRAPHER_SILENCE_TRIMMER_H