attempt to be compliant with gcc 4.6 (assuming that its more compliant with standards...
[ardour.git] / libs / audiographer / audiographer / general / silence_trimmer.h
1 #ifndef AUDIOGRAPHER_SILENCE_TRIMMER_H
2 #define AUDIOGRAPHER_SILENCE_TRIMMER_H
3
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"
9
10 #include <cstring>
11
12 namespace AudioGrapher {
13
14 /// Removes and adds silent frames to beginning and/or end of stream
15 template<typename T = DefaultSampleType>
16 class SilenceTrimmer
17   : public ListedSource<T>
18   , public Sink<T>
19   , public FlagDebuggable<>
20   , public Throwing<>
21 {
22   public:
23
24         /// Constructor, \see reset() \n Not RT safe
25         SilenceTrimmer(framecnt_t silence_buffer_size_ = 1024)
26           : silence_buffer_size (0)
27           , silence_buffer (0)
28         {
29                 reset (silence_buffer_size_);
30                 add_supported_flag (ProcessContext<T>::EndOfInput);
31         }
32
33         ~SilenceTrimmer()
34         {
35                 delete [] silence_buffer;
36         }
37
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.
42           */
43         void reset (framecnt_t silence_buffer_size_ = 1024)
44         {
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!");
48                 }
49                 
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);
55                 }
56                 
57                 in_beginning = true;
58                 in_end = false;
59                 trim_beginning = false;
60                 trim_end = false;
61                 silence_frames = 0;
62                 max_output_frames = 0;
63                 add_to_beginning = 0;
64                 add_to_end = 0;
65         }
66         
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.
69           * \n RT safe
70           */
71         void add_silence_to_beginning (framecnt_t frames_per_channel)
72         {
73                 if (throw_level (ThrowObject) && !in_beginning) {
74                         throw Exception(*this, "Tried to add silence to beginning after already outputting data");
75                 }
76                 add_to_beginning = frames_per_channel;
77         }
78         
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.
81           * \n RT safe
82           */
83         void add_silence_to_end (framecnt_t frames_per_channel)
84         {
85                 if (throw_level (ThrowObject) && in_end) {
86                         throw Exception(*this, "Tried to add silence to end after already reaching end");
87                 }
88                 add_to_end = frames_per_channel;
89         }
90         
91         /** Tells whether ot nor silence should be trimmed from the beginning
92           * Has to be called before starting processing.
93           * \n RT safe
94           */
95         void set_trim_beginning (bool yn)
96         {
97                 if (throw_level (ThrowObject) && !in_beginning) {
98                         throw Exception(*this, "Tried to set beginning trim after already outputting data");
99                 }
100                 trim_beginning = yn;
101         }
102         
103         /** Tells whether ot nor silence should be trimmed from the end
104           * Has to be called before the is reached.
105           * \n RT safe
106           */
107         void set_trim_end (bool yn)
108         {
109                 if (throw_level (ThrowObject) && in_end) {
110                         throw Exception(*this, "Tried to set end trim after already reaching end");
111                 }
112                 trim_end = yn;
113         }
114
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()
118           * \n RT safe
119           */
120         void process (ProcessContext<T> const & c)
121         {
122                 if (debug_level (DebugVerbose)) {
123                         debug_stream () << DebugUtils::demangled_name (*this) <<
124                                 "::process()" << std::endl;
125                 }
126                 
127                 check_flags (*this, c);
128                 
129                 if (throw_level (ThrowStrict) && in_end) {
130                         throw Exception(*this, "process() after reacing end of input");
131                 }
132                 in_end = c.has_flag (ProcessContext<T>::EndOfInput);
133                 
134                 framecnt_t frame_index = 0;
135                 
136                 if (in_beginning) {
137                         
138                         bool has_data = true;
139                         
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);
144                         }
145                         
146                         // Added silence if there is silence to add
147                         if (add_to_beginning) {
148                                 
149                                 if (debug_level (DebugVerbose)) {
150                                         debug_stream () << DebugUtils::demangled_name (*this) <<
151                                                 " adding to beginning" << std::endl;
152                                 }
153                                 
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);
157                                 }
158                                 add_to_beginning *= c.channels();
159                                 output_silence_frames (c_copy, add_to_beginning);
160                         }
161                         
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
165                         if (has_data) {
166                                 
167                                 if (debug_level (DebugVerbose)) {
168                                         debug_stream () << DebugUtils::demangled_name (*this) <<
169                                                 " outputting whole frame to beginning" << std::endl;
170                                 }
171                                 
172                                 in_beginning = false;
173                                 ConstProcessContext<T> c_out (c, &c.data()[frame_index], c.frames() - frame_index);
174                                 ListedSource<T>::output (c_out);
175                         }
176                         
177                 } else if (trim_end) { // Only check zero samples if trimming end
178                         
179                         if (find_first_non_zero_sample (c, frame_index)) {
180                                 
181                                 if (debug_level (DebugVerbose)) {
182                                         debug_stream () << DebugUtils::demangled_name (*this) <<
183                                                 " flushing intermediate silence and outputting frame" << std::endl;
184                                 }
185                                 
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
190                                 
191                                 if (debug_level (DebugVerbose)) {
192                                         debug_stream () << DebugUtils::demangled_name (*this) <<
193                                                 " no, output, adding frames to silence count" << std::endl;
194                                 }
195                                 
196                                 silence_frames += c.frames();
197                         }
198                         
199                 } else { // no need to do anything special
200                         
201                         if (debug_level (DebugVerbose)) {
202                                 debug_stream () << DebugUtils::demangled_name (*this) <<
203                                         " outputting whole frame in middle" << std::endl;
204                         }
205                         
206                         ListedSource<T>::output (c);
207                 }
208                 
209                 // Finally, if in end, add silence to end
210                 if (in_end && add_to_end) {
211                         
212                         if (debug_level (DebugVerbose)) {
213                                 debug_stream () << DebugUtils::demangled_name (*this) <<
214                                         " adding to end" << std::endl;
215                         }
216                         
217                         add_to_end *= c.channels();
218                         output_silence_frames (c, add_to_end, true);
219                 }
220         }
221
222         using Sink<T>::process;
223
224   private:
225
226         bool find_first_non_zero_sample (ProcessContext<T> const & c, framecnt_t & result_frame)
227         {
228                 for (framecnt_t i = 0; i < c.frames(); ++i) {
229                         if (c.data()[i] != static_cast<T>(0.0)) {
230                                 result_frame = i;
231                                 // Round down to nearest interleaved "frame" beginning
232                                 result_frame -= result_frame % c.channels();
233                                 return true;
234                         }
235                 }
236                 return false;
237         }
238         
239         void output_silence_frames (ProcessContext<T> const & c, framecnt_t & total_frames, bool adding_to_end = false)
240         {
241                 bool end_of_input = c.has_flag (ProcessContext<T>::EndOfInput);
242                 c.remove_flag (ProcessContext<T>::EndOfInput);
243                 
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);
248                         }
249                         frames -= frames % c.channels();
250                         
251                         total_frames -= frames;
252                         ConstProcessContext<T> c_out (c, silence_buffer, frames);
253                         
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);
259                         }
260                         ListedSource<T>::output (c_out);
261                 }
262         }
263
264
265         bool       in_beginning;
266         bool       in_end;
267         
268         bool       trim_beginning;
269         bool       trim_end;
270         
271         framecnt_t silence_frames;
272         framecnt_t max_output_frames;
273         
274         framecnt_t add_to_beginning;
275         framecnt_t add_to_end;
276         
277         framecnt_t silence_buffer_size;
278         T *        silence_buffer;
279 };
280
281 } // namespace
282
283 #endif // AUDIOGRAPHER_SILENCE_TRIMMER_H