Merge branch 'cairocanvas'
[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/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"
10
11 #include <cstring>
12
13 namespace AudioGrapher {
14
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>
19   , public Sink<T>
20   , public FlagDebuggable<>
21   , public Throwing<>
22 {
23   public:
24
25         /// Constructor, \see reset() \n Not RT safe
26         SilenceTrimmer(framecnt_t silence_buffer_size_ = 1024)
27           : silence_buffer_size (0)
28           , silence_buffer (0)
29         {
30                 reset (silence_buffer_size_);
31                 add_supported_flag (ProcessContext<T>::EndOfInput);
32         }
33
34         ~SilenceTrimmer()
35         {
36                 delete [] silence_buffer;
37         }
38
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.
43           */
44         void reset (framecnt_t silence_buffer_size_ = 1024)
45         {
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!");
49                 }
50                 
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);
56                 }
57                 
58                 in_beginning = true;
59                 in_end = false;
60                 trim_beginning = false;
61                 trim_end = false;
62                 silence_frames = 0;
63                 max_output_frames = 0;
64                 add_to_beginning = 0;
65                 add_to_end = 0;
66         }
67         
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.
70           * \n RT safe
71           */
72         void add_silence_to_beginning (framecnt_t frames_per_channel)
73         {
74                 if (throw_level (ThrowObject) && !in_beginning) {
75                         throw Exception(*this, "Tried to add silence to beginning after already outputting data");
76                 }
77                 add_to_beginning = frames_per_channel;
78         }
79         
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.
82           * \n RT safe
83           */
84         void add_silence_to_end (framecnt_t frames_per_channel)
85         {
86                 if (throw_level (ThrowObject) && in_end) {
87                         throw Exception(*this, "Tried to add silence to end after already reaching end");
88                 }
89                 add_to_end = frames_per_channel;
90         }
91         
92         /** Tells whether ot nor silence should be trimmed from the beginning
93           * Has to be called before starting processing.
94           * \n RT safe
95           */
96         void set_trim_beginning (bool yn)
97         {
98                 if (throw_level (ThrowObject) && !in_beginning) {
99                         throw Exception(*this, "Tried to set beginning trim after already outputting data");
100                 }
101                 trim_beginning = yn;
102         }
103         
104         /** Tells whether ot nor silence should be trimmed from the end
105           * Has to be called before the is reached.
106           * \n RT safe
107           */
108         void set_trim_end (bool yn)
109         {
110                 if (throw_level (ThrowObject) && in_end) {
111                         throw Exception(*this, "Tried to set end trim after already reaching end");
112                 }
113                 trim_end = yn;
114         }
115
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()
119           * \n RT safe
120           */
121         void process (ProcessContext<T> const & c)
122         {
123                 if (debug_level (DebugVerbose)) {
124                         debug_stream () << DebugUtils::demangled_name (*this) <<
125                                 "::process()" << std::endl;
126                 }
127                 
128                 check_flags (*this, c);
129                 
130                 if (throw_level (ThrowStrict) && in_end) {
131                         throw Exception(*this, "process() after reacing end of input");
132                 }
133                 in_end = c.has_flag (ProcessContext<T>::EndOfInput);
134
135                 // If adding to end, delay end of input propagation
136                 if (add_to_end) { c.remove_flag(ProcessContext<T>::EndOfInput); }
137                 
138                 framecnt_t frame_index = 0;
139                 
140                 if (in_beginning) {
141                         
142                         bool has_data = true;
143                         
144                         // only check silence if doing either of these
145                         // This will set both has_data and frame_index
146                         if (add_to_beginning || trim_beginning) {
147                                 has_data = find_first_non_zero_sample (c, frame_index);
148                         }
149                         
150                         // Added silence if there is silence to add
151                         if (add_to_beginning) {
152                                 
153                                 if (debug_level (DebugVerbose)) {
154                                         debug_stream () << DebugUtils::demangled_name (*this) <<
155                                                 " adding to beginning" << std::endl;
156                                 }
157                                 
158                                 ConstProcessContext<T> c_copy (c);
159                                 if (has_data) { // There will be more output, so remove flag
160                                         c_copy().remove_flag (ProcessContext<T>::EndOfInput);
161                                 }
162                                 add_to_beginning *= c.channels();
163                                 output_silence_frames (c_copy, add_to_beginning);
164                         }
165                         
166                         // If we are not trimming the beginning, output everything
167                         // Then has_data = true and frame_index = 0
168                         // Otherwise these reflect the silence state
169                         if (has_data) {
170                                 
171                                 if (debug_level (DebugVerbose)) {
172                                         debug_stream () << DebugUtils::demangled_name (*this) <<
173                                                 " outputting whole frame to beginning" << std::endl;
174                                 }
175                                 
176                                 in_beginning = false;
177                                 ConstProcessContext<T> c_out (c, &c.data()[frame_index], c.frames() - frame_index);
178                                 ListedSource<T>::output (c_out);
179                         }
180                         
181                 } else if (trim_end) { // Only check zero samples if trimming end
182                         
183                         if (find_first_non_zero_sample (c, frame_index)) {
184                                 
185                                 if (debug_level (DebugVerbose)) {
186                                         debug_stream () << DebugUtils::demangled_name (*this) <<
187                                                 " flushing intermediate silence and outputting frame" << std::endl;
188                                 }
189                                 
190                                 // context contains non-zero data
191                                 output_silence_frames (c, silence_frames); // flush intermediate silence
192                                 ListedSource<T>::output (c); // output rest of data
193                         } else { // whole context is zero
194                                 
195                                 if (debug_level (DebugVerbose)) {
196                                         debug_stream () << DebugUtils::demangled_name (*this) <<
197                                                 " no, output, adding frames to silence count" << std::endl;
198                                 }
199                                 
200                                 silence_frames += c.frames();
201                         }
202                         
203                 } else { // no need to do anything special
204                         
205                         if (debug_level (DebugVerbose)) {
206                                 debug_stream () << DebugUtils::demangled_name (*this) <<
207                                         " outputting whole frame in middle" << std::endl;
208                         }
209                         
210                         ListedSource<T>::output (c);
211                 }
212                 
213                 // Finally, if in end, add silence to end
214                 if (in_end && add_to_end) {
215                         c.set_flag (ProcessContext<T>::EndOfInput);
216
217                         if (debug_level (DebugVerbose)) {
218                                 debug_stream () << DebugUtils::demangled_name (*this) <<
219                                         " adding to end" << std::endl;
220                         }
221                         
222                         add_to_end *= c.channels();
223                         output_silence_frames (c, add_to_end, true);
224                 }
225         }
226
227         using Sink<T>::process;
228
229   private:
230
231         bool find_first_non_zero_sample (ProcessContext<T> const & c, framecnt_t & result_frame)
232         {
233                 for (framecnt_t i = 0; i < c.frames(); ++i) {
234                         if (c.data()[i] != static_cast<T>(0.0)) {
235                                 result_frame = i;
236                                 // Round down to nearest interleaved "frame" beginning
237                                 result_frame -= result_frame % c.channels();
238                                 return true;
239                         }
240                 }
241                 return false;
242         }
243         
244         void output_silence_frames (ProcessContext<T> const & c, framecnt_t & total_frames, bool adding_to_end = false)
245         {
246                 bool end_of_input = c.has_flag (ProcessContext<T>::EndOfInput);
247                 c.remove_flag (ProcessContext<T>::EndOfInput);
248                 
249                 while (total_frames > 0) {
250                         framecnt_t frames = std::min (silence_buffer_size, total_frames);
251                         if (max_output_frames) {
252                                 frames = std::min (frames, max_output_frames);
253                         }
254                         frames -= frames % c.channels();
255                         
256                         total_frames -= frames;
257                         ConstProcessContext<T> c_out (c, silence_buffer, frames);
258                         
259                         // boolean commentation :)
260                         bool const no_more_silence_will_be_added = adding_to_end || (add_to_end == 0);
261                         bool const is_last_frame_output_in_this_function = (total_frames == 0);
262                         if (end_of_input && no_more_silence_will_be_added && is_last_frame_output_in_this_function) {
263                                 c_out().set_flag (ProcessContext<T>::EndOfInput);
264                         }
265                         ListedSource<T>::output (c_out);
266                 }
267
268                 // Add the flag back if it was removed
269                 if (end_of_input) { c.set_flag (ProcessContext<T>::EndOfInput); }
270         }
271
272
273         bool       in_beginning;
274         bool       in_end;
275         
276         bool       trim_beginning;
277         bool       trim_end;
278         
279         framecnt_t silence_frames;
280         framecnt_t max_output_frames;
281         
282         framecnt_t add_to_beginning;
283         framecnt_t add_to_end;
284         
285         framecnt_t silence_buffer_size;
286         T *        silence_buffer;
287 };
288
289 } // namespace
290
291 #endif // AUDIOGRAPHER_SILENCE_TRIMMER_H