fix bypassing plugins with sidechain i/o
[ardour.git] / libs / ardour / export_graph_builder.cc
1 /*
2   Copyright (C) 2008-2012 Paul Davis
3   Author: Sakari Bergen
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include "ardour/export_graph_builder.h"
22
23 #include <vector>
24
25 #include <glibmm/miscutils.h>
26
27 #include "audiographer/process_context.h"
28 #include "audiographer/general/chunker.h"
29 #include "audiographer/general/interleaver.h"
30 #include "audiographer/general/normalizer.h"
31 #include "audiographer/general/analyser.h"
32 #include "audiographer/general/peak_reader.h"
33 #include "audiographer/general/loudness_reader.h"
34 #include "audiographer/general/sample_format_converter.h"
35 #include "audiographer/general/sr_converter.h"
36 #include "audiographer/general/silence_trimmer.h"
37 #include "audiographer/general/threader.h"
38 #include "audiographer/sndfile/tmp_file.h"
39 #include "audiographer/sndfile/sndfile_writer.h"
40
41 #include "ardour/audioengine.h"
42 #include "ardour/export_channel_configuration.h"
43 #include "ardour/export_filename.h"
44 #include "ardour/export_format_specification.h"
45 #include "ardour/export_timespan.h"
46 #include "ardour/session_directory.h"
47 #include "ardour/sndfile_helpers.h"
48
49 #include "pbd/file_utils.h"
50 #include "pbd/cpus.h"
51
52 using namespace AudioGrapher;
53 using std::string;
54
55 namespace ARDOUR {
56
57 ExportGraphBuilder::ExportGraphBuilder (Session const & session)
58         : session (session)
59         , thread_pool (hardware_concurrency())
60 {
61         process_buffer_frames = session.engine().samples_per_cycle();
62 }
63
64 ExportGraphBuilder::~ExportGraphBuilder ()
65 {
66 }
67
68 int
69 ExportGraphBuilder::process (framecnt_t frames, bool last_cycle)
70 {
71         assert(frames <= process_buffer_frames);
72
73         for (ChannelMap::iterator it = channels.begin(); it != channels.end(); ++it) {
74                 Sample const * process_buffer = 0;
75                 it->first->read (process_buffer, frames);
76                 ConstProcessContext<Sample> context(process_buffer, frames, 1);
77                 if (last_cycle) { context().set_flag (ProcessContext<Sample>::EndOfInput); }
78                 it->second->process (context);
79         }
80
81         return 0;
82 }
83
84 bool
85 ExportGraphBuilder::process_normalize ()
86 {
87         for (std::list<Normalizer *>::iterator it = normalizers.begin(); it != normalizers.end(); /* ++ in loop */) {
88                 if ((*it)->process()) {
89                         it = normalizers.erase (it);
90                 } else {
91                         ++it;
92                 }
93         }
94
95         return normalizers.empty();
96 }
97
98 unsigned
99 ExportGraphBuilder::get_normalize_cycle_count() const
100 {
101         unsigned max = 0;
102         for (std::list<Normalizer *>::const_iterator it = normalizers.begin(); it != normalizers.end(); ++it) {
103                 max = std::max(max, (*it)->get_normalize_cycle_count());
104         }
105         return max;
106 }
107
108 void
109 ExportGraphBuilder::reset ()
110 {
111         timespan.reset();
112         channel_configs.clear ();
113         channels.clear ();
114         normalizers.clear ();
115         analysis_map.clear();
116 }
117
118 void
119 ExportGraphBuilder::cleanup (bool remove_out_files/*=false*/)
120 {
121         ChannelConfigList::iterator iter = channel_configs.begin();
122
123         while (iter != channel_configs.end() ) {
124                 iter->remove_children(remove_out_files);
125                 iter = channel_configs.erase(iter);
126         }
127 }
128
129 void
130 ExportGraphBuilder::set_current_timespan (boost::shared_ptr<ExportTimespan> span)
131 {
132         timespan = span;
133 }
134
135 void
136 ExportGraphBuilder::add_config (FileSpec const & config)
137 {
138         ExportChannelConfiguration::ChannelList const & channels =
139                 config.channel_config->get_channels();
140         for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin();
141             it != channels.end(); ++it) {
142                 (*it)->set_max_buffer_size(process_buffer_frames);
143         }
144
145         // If the sample rate is "session rate", change it to the real value.
146         // However, we need to copy it to not change the config which is saved...
147         FileSpec new_config (config);
148         new_config.format.reset(new ExportFormatSpecification(*new_config.format, false));
149         if(new_config.format->sample_rate() == ExportFormatBase::SR_Session) {
150                 framecnt_t session_rate = session.nominal_frame_rate();
151                 new_config.format->set_sample_rate(ExportFormatBase::nearest_sample_rate(session_rate));
152         }
153
154
155         if (!new_config.channel_config->get_split ()) {
156                 add_split_config (new_config);
157                 return;
158         }
159
160         // Split channel configurations are split into several channel configurations,
161         // each corresponding to a file, at this stage
162         typedef std::list<boost::shared_ptr<ExportChannelConfiguration> > ConfigList;
163         ConfigList file_configs;
164         new_config.channel_config->configurations_for_files (file_configs);
165
166         unsigned chan = 1;
167         for (ConfigList::iterator it = file_configs.begin(); it != file_configs.end(); ++it, ++chan) {
168                 FileSpec copy = new_config;
169                 copy.channel_config = *it;
170
171                 copy.filename.reset (new ExportFilename (*copy.filename));
172                 copy.filename->include_channel = true;
173                 copy.filename->set_channel (chan);
174
175                 add_split_config (copy);
176         }
177 }
178
179 void
180 ExportGraphBuilder::get_analysis_results (AnalysisResults& results) {
181         for (AnalysisMap::iterator i = analysis_map.begin(); i != analysis_map.end(); ++i) {
182                 ExportAnalysisPtr p = i->second->result ();
183                 if (p) {
184                         results.insert (std::make_pair (i->first, p));
185                 }
186         }
187 }
188
189 void
190 ExportGraphBuilder::add_split_config (FileSpec const & config)
191 {
192         for (ChannelConfigList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) {
193                 if (*it == config) {
194                         it->add_child (config);
195                         return;
196                 }
197         }
198
199         // No duplicate channel config found, create new one
200         channel_configs.push_back (new ChannelConfig (*this, config, channels));
201 }
202
203 /* Encoder */
204
205 template <>
206 boost::shared_ptr<AudioGrapher::Sink<Sample> >
207 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
208 {
209         config = new_config;
210         init_writer (float_writer);
211         return float_writer;
212 }
213
214 template <>
215 boost::shared_ptr<AudioGrapher::Sink<int> >
216 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
217 {
218         config = new_config;
219         init_writer (int_writer);
220         return int_writer;
221 }
222
223 template <>
224 boost::shared_ptr<AudioGrapher::Sink<short> >
225 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
226 {
227         config = new_config;
228         init_writer (short_writer);
229         return short_writer;
230 }
231
232 void
233 ExportGraphBuilder::Encoder::add_child (FileSpec const & new_config)
234 {
235         filenames.push_back (new_config.filename);
236 }
237
238 void
239 ExportGraphBuilder::Encoder::destroy_writer (bool delete_out_file)
240 {
241         if (delete_out_file ) {
242
243                 if (float_writer) {
244                         float_writer->close ();
245                 }
246
247                 if (int_writer) {
248                         int_writer->close ();
249                 }
250
251                 if (short_writer) {
252                         short_writer->close ();
253                 }
254
255                 if (std::remove(writer_filename.c_str() ) != 0) {
256                         std::cout << "Encoder::destroy_writer () : Error removing file: " << strerror(errno) << std::endl;
257                 }
258         }
259
260         float_writer.reset ();
261         int_writer.reset ();
262         short_writer.reset ();
263 }
264
265 bool
266 ExportGraphBuilder::Encoder::operator== (FileSpec const & other_config) const
267 {
268         return get_real_format (config) == get_real_format (other_config);
269 }
270
271 int
272 ExportGraphBuilder::Encoder::get_real_format (FileSpec const & config)
273 {
274         ExportFormatSpecification & format = *config.format;
275         return format.format_id() | format.sample_format() | format.endianness();
276 }
277
278 template<typename T>
279 void
280 ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::SndfileWriter<T> > & writer)
281 {
282         unsigned channels = config.channel_config->get_n_chans();
283         int format = get_real_format (config);
284         config.filename->set_channel_config(config.channel_config);
285         writer_filename = config.filename->get_path (config.format);
286
287         writer.reset (new AudioGrapher::SndfileWriter<T> (writer_filename, format, channels, config.format->sample_rate(), config.broadcast_info));
288         writer->FileWritten.connect_same_thread (copy_files_connection, boost::bind (&ExportGraphBuilder::Encoder::copy_files, this, _1));
289 }
290
291 void
292 ExportGraphBuilder::Encoder::copy_files (std::string orig_path)
293 {
294         while (filenames.size()) {
295                 ExportFilenamePtr & filename = filenames.front();
296                 PBD::copy_file (orig_path, filename->get_path (config.format).c_str());
297                 filenames.pop_front();
298         }
299 }
300
301 /* SFC */
302
303 ExportGraphBuilder::SFC::SFC (ExportGraphBuilder &parent, FileSpec const & new_config, framecnt_t max_frames)
304         : data_width(0)
305 {
306         config = new_config;
307         data_width = sndfile_data_width (Encoder::get_real_format (config));
308         unsigned channels = new_config.channel_config->get_n_chans();
309         _analyse = config.format->analyse();
310         if (_analyse) {
311                 framecnt_t sample_rate = parent.session.nominal_frame_rate();
312                 framecnt_t sb = config.format->silence_beginning_at (parent.timespan->get_start(), sample_rate);
313                 framecnt_t se = config.format->silence_end_at (parent.timespan->get_end(), sample_rate);
314                 framecnt_t duration = parent.timespan->get_length () + sb + se;
315                 max_frames = min ((framecnt_t) 8192 * channels, max ((framecnt_t) 4096 * channels, max_frames));
316                 chunker.reset (new Chunker<Sample> (max_frames));
317                 analyser.reset (new Analyser (config.format->sample_rate(), channels, max_frames,
318                                         (framecnt_t) ceil (duration * config.format->sample_rate () / (double) sample_rate)));
319                 chunker->add_output (analyser);
320                 parent.add_analyser (config.filename->get_path (config.format), analyser);
321         }
322
323         if (data_width == 8 || data_width == 16) {
324                 short_converter = ShortConverterPtr (new SampleFormatConverter<short> (channels));
325                 short_converter->init (max_frames, config.format->dither_type(), data_width);
326                 add_child (config);
327                 if (_analyse) { analyser->add_output (short_converter); }
328
329         } else if (data_width == 24 || data_width == 32) {
330                 int_converter = IntConverterPtr (new SampleFormatConverter<int> (channels));
331                 int_converter->init (max_frames, config.format->dither_type(), data_width);
332                 add_child (config);
333                 if (_analyse) { analyser->add_output (int_converter); }
334         } else {
335                 int actual_data_width = 8 * sizeof(Sample);
336                 float_converter = FloatConverterPtr (new SampleFormatConverter<Sample> (channels));
337                 float_converter->init (max_frames, config.format->dither_type(), actual_data_width);
338                 add_child (config);
339                 if (_analyse) { analyser->add_output (float_converter); }
340         }
341 }
342
343 void
344 ExportGraphBuilder::SFC::set_peak (float gain)
345 {
346         if (_analyse) {
347                 analyser->set_normalization_gain (gain);
348         }
349 }
350
351 ExportGraphBuilder::FloatSinkPtr
352 ExportGraphBuilder::SFC::sink ()
353 {
354         if (_analyse) {
355                 return chunker;
356         } else if (data_width == 8 || data_width == 16) {
357                 return short_converter;
358         } else if (data_width == 24 || data_width == 32) {
359                 return int_converter;
360         } else {
361                 return float_converter;
362         }
363 }
364
365 void
366 ExportGraphBuilder::SFC::add_child (FileSpec const & new_config)
367 {
368         for (boost::ptr_list<Encoder>::iterator it = children.begin(); it != children.end(); ++it) {
369                 if (*it == new_config) {
370                         it->add_child (new_config);
371                         return;
372                 }
373         }
374
375         children.push_back (new Encoder());
376         Encoder & encoder = children.back();
377
378         if (data_width == 8 || data_width == 16) {
379                 short_converter->add_output (encoder.init<short> (new_config));
380         } else if (data_width == 24 || data_width == 32) {
381                 int_converter->add_output (encoder.init<int> (new_config));
382         } else {
383                 float_converter->add_output (encoder.init<Sample> (new_config));
384         }
385 }
386
387 void
388 ExportGraphBuilder::SFC::remove_children (bool remove_out_files)
389 {
390         boost::ptr_list<Encoder>::iterator iter = children.begin ();
391
392         while (iter != children.end() ) {
393
394                 if (remove_out_files) {
395                         iter->destroy_writer(remove_out_files);
396                 }
397                 iter = children.erase (iter);
398         }
399 }
400
401 bool
402 ExportGraphBuilder::SFC::operator== (FileSpec const & other_config) const
403 {
404         return config.format->sample_format() == other_config.format->sample_format();
405 }
406
407 /* Normalizer */
408
409 ExportGraphBuilder::Normalizer::Normalizer (ExportGraphBuilder & parent, FileSpec const & new_config, framecnt_t max_frames)
410         : parent (parent)
411         , use_loudness (false)
412 {
413         std::string tmpfile_path = parent.session.session_directory().export_path();
414         tmpfile_path = Glib::build_filename(tmpfile_path, "XXXXXX");
415         std::vector<char> tmpfile_path_buf(tmpfile_path.size() + 1);
416         std::copy(tmpfile_path.begin(), tmpfile_path.end(), tmpfile_path_buf.begin());
417         tmpfile_path_buf[tmpfile_path.size()] = '\0';
418
419         config = new_config;
420         uint32_t const channels = config.channel_config->get_n_chans();
421         max_frames_out = 4086 - (4086 % channels); // TODO good chunk size
422         use_loudness = config.format->normalize_loudness ();
423
424         buffer.reset (new AllocatingProcessContext<Sample> (max_frames_out, channels));
425         peak_reader.reset (new PeakReader ());
426         loudness_reader.reset (new LoudnessReader (config.format->sample_rate(), channels, max_frames));
427         normalizer.reset (new AudioGrapher::Normalizer (use_loudness ? 0.0 : config.format->normalize_dbfs()));
428         threader.reset (new Threader<Sample> (parent.thread_pool));
429
430         normalizer->alloc_buffer (max_frames_out);
431         normalizer->add_output (threader);
432
433         int format = ExportFormatBase::F_RAW | ExportFormatBase::SF_Float;
434         tmp_file.reset (new TmpFile<float> (&tmpfile_path_buf[0], format, channels, config.format->sample_rate()));
435         tmp_file->FileWritten.connect_same_thread (post_processing_connection,
436                                                    boost::bind (&Normalizer::start_post_processing, this));
437
438         add_child (new_config);
439
440         if (use_loudness) {
441                 loudness_reader->add_output (tmp_file);
442         } else {
443                 peak_reader->add_output (tmp_file);
444         }
445 }
446
447 ExportGraphBuilder::FloatSinkPtr
448 ExportGraphBuilder::Normalizer::sink ()
449 {
450         if (use_loudness) {
451                 return loudness_reader;
452         }
453         return peak_reader;
454 }
455
456 void
457 ExportGraphBuilder::Normalizer::add_child (FileSpec const & new_config)
458 {
459         for (boost::ptr_list<SFC>::iterator it = children.begin(); it != children.end(); ++it) {
460                 if (*it == new_config) {
461                         it->add_child (new_config);
462                         return;
463                 }
464         }
465
466         children.push_back (new SFC (parent, new_config, max_frames_out));
467         threader->add_output (children.back().sink());
468 }
469
470 void
471 ExportGraphBuilder::Normalizer::remove_children (bool remove_out_files)
472 {
473         boost::ptr_list<SFC>::iterator iter = children.begin ();
474
475         while (iter != children.end() ) {
476                 iter->remove_children (remove_out_files);
477                 iter = children.erase (iter);
478         }
479 }
480
481 bool
482 ExportGraphBuilder::Normalizer::operator== (FileSpec const & other_config) const
483 {
484         return config.format->normalize() == other_config.format->normalize() &&
485                 config.format->normalize_loudness () == other_config.format->normalize_loudness() &&
486                 (
487                  (!config.format->normalize_loudness () && config.format->normalize_dbfs() == other_config.format->normalize_dbfs())
488                  ||
489                  // FIXME: allow simultaneous export of two formats with different loundness normalization settings
490                  (config.format->normalize_loudness () /* lufs/dbtp is a result option, not an instantaion option */)
491                 );
492 }
493
494 unsigned
495 ExportGraphBuilder::Normalizer::get_normalize_cycle_count() const
496 {
497         return static_cast<unsigned>(std::ceil(static_cast<float>(tmp_file->get_frames_written()) /
498                                                max_frames_out));
499 }
500
501 bool
502 ExportGraphBuilder::Normalizer::process()
503 {
504         framecnt_t frames_read = tmp_file->read (*buffer);
505         return frames_read != buffer->frames();
506 }
507
508 void
509 ExportGraphBuilder::Normalizer::start_post_processing()
510 {
511         float gain;
512         if (use_loudness) {
513                 gain = normalizer->set_peak (loudness_reader->get_peak (config.format->normalize_lufs (), config.format->normalize_dbtp ()));
514         } else {
515                 gain = normalizer->set_peak (peak_reader->get_peak());
516         }
517         for (boost::ptr_list<SFC>::iterator i = children.begin(); i != children.end(); ++i) {
518                 (*i).set_peak (gain);
519         }
520         tmp_file->seek (0, SEEK_SET);
521         tmp_file->add_output (normalizer);
522         parent.normalizers.push_back (this);
523 }
524
525 /* SRC */
526
527 ExportGraphBuilder::SRC::SRC (ExportGraphBuilder & parent, FileSpec const & new_config, framecnt_t max_frames)
528         : parent (parent)
529 {
530         config = new_config;
531         converter.reset (new SampleRateConverter (new_config.channel_config->get_n_chans()));
532         ExportFormatSpecification & format = *new_config.format;
533         converter->init (parent.session.nominal_frame_rate(), format.sample_rate(), format.src_quality());
534         max_frames_out = converter->allocate_buffers (max_frames);
535
536         add_child (new_config);
537 }
538
539 ExportGraphBuilder::FloatSinkPtr
540 ExportGraphBuilder::SRC::sink ()
541 {
542         return converter;
543 }
544
545 void
546 ExportGraphBuilder::SRC::add_child (FileSpec const & new_config)
547 {
548         if (new_config.format->normalize()) {
549                 add_child_to_list (new_config, normalized_children);
550         } else {
551                 add_child_to_list (new_config, children);
552         }
553 }
554
555 void
556 ExportGraphBuilder::SRC::remove_children (bool remove_out_files)
557 {
558         boost::ptr_list<SFC>::iterator sfc_iter = children.begin();
559
560         while (sfc_iter != children.end() ) {
561                 converter->remove_output (sfc_iter->sink() );
562                 sfc_iter->remove_children (remove_out_files);
563                 sfc_iter = children.erase (sfc_iter);
564         }
565
566         boost::ptr_list<Normalizer>::iterator norm_iter = normalized_children.begin();
567
568         while (norm_iter != normalized_children.end() ) {
569                 converter->remove_output (norm_iter->sink() );
570                 norm_iter->remove_children (remove_out_files);
571                 norm_iter = normalized_children.erase (norm_iter);
572         }
573
574 }
575
576 template<typename T>
577 void
578 ExportGraphBuilder::SRC::add_child_to_list (FileSpec const & new_config, boost::ptr_list<T> & list)
579 {
580         for (typename boost::ptr_list<T>::iterator it = list.begin(); it != list.end(); ++it) {
581                 if (*it == new_config) {
582                         it->add_child (new_config);
583                         return;
584                 }
585         }
586
587         list.push_back (new T (parent, new_config, max_frames_out));
588         converter->add_output (list.back().sink ());
589 }
590
591 bool
592 ExportGraphBuilder::SRC::operator== (FileSpec const & other_config) const
593 {
594         return config.format->sample_rate() == other_config.format->sample_rate();
595 }
596
597 /* SilenceHandler */
598 ExportGraphBuilder::SilenceHandler::SilenceHandler (ExportGraphBuilder & parent, FileSpec const & new_config, framecnt_t max_frames)
599         : parent (parent)
600 {
601         config = new_config;
602         max_frames_in = max_frames;
603         framecnt_t sample_rate = parent.session.nominal_frame_rate();
604
605 #ifdef MIXBUS
606         silence_trimmer.reset (new SilenceTrimmer<Sample>(max_frames_in, -90));
607 #else
608         // TODO silence-threshold should be per export-preset, with Config->get_silence_threshold being the default
609         silence_trimmer.reset (new SilenceTrimmer<Sample>(max_frames_in, Config->get_export_silence_threshold ()));
610 #endif
611         silence_trimmer->set_trim_beginning (config.format->trim_beginning());
612         silence_trimmer->set_trim_end (config.format->trim_end());
613
614         framecnt_t sb = config.format->silence_beginning_at (parent.timespan->get_start(), sample_rate);
615         framecnt_t se = config.format->silence_end_at (parent.timespan->get_end(), sample_rate);
616
617         silence_trimmer->add_silence_to_beginning (sb);
618         silence_trimmer->add_silence_to_end (se);
619
620         add_child (new_config);
621 }
622
623 ExportGraphBuilder::FloatSinkPtr
624 ExportGraphBuilder::SilenceHandler::sink ()
625 {
626         return silence_trimmer;
627 }
628
629 void
630 ExportGraphBuilder::SilenceHandler::add_child (FileSpec const & new_config)
631 {
632         for (boost::ptr_list<SRC>::iterator it = children.begin(); it != children.end(); ++it) {
633                 if (*it == new_config) {
634                         it->add_child (new_config);
635                         return;
636                 }
637         }
638
639         children.push_back (new SRC (parent, new_config, max_frames_in));
640         silence_trimmer->add_output (children.back().sink());
641 }
642
643 void
644 ExportGraphBuilder::SilenceHandler::remove_children (bool remove_out_files)
645 {
646         boost::ptr_list<SRC>::iterator iter = children.begin();
647
648         while (iter != children.end() ) {
649                 silence_trimmer->remove_output (iter->sink() );
650                 iter->remove_children (remove_out_files);
651                 iter = children.erase (iter);
652         }
653 }
654
655 bool
656 ExportGraphBuilder::SilenceHandler::operator== (FileSpec const & other_config) const
657 {
658         ExportFormatSpecification & format = *config.format;
659         ExportFormatSpecification & other_format = *other_config.format;
660         return (format.trim_beginning() == other_format.trim_beginning()) &&
661                 (format.trim_end() == other_format.trim_end()) &&
662                 (format.silence_beginning_time() == other_format.silence_beginning_time()) &&
663                 (format.silence_end_time() == other_format.silence_end_time());
664 }
665
666 /* ChannelConfig */
667
668 ExportGraphBuilder::ChannelConfig::ChannelConfig (ExportGraphBuilder & parent, FileSpec const & new_config, ChannelMap & channel_map)
669         : parent (parent)
670 {
671         typedef ExportChannelConfiguration::ChannelList ChannelList;
672
673         config = new_config;
674
675         framecnt_t max_frames = parent.session.engine().samples_per_cycle();
676         interleaver.reset (new Interleaver<Sample> ());
677         interleaver->init (new_config.channel_config->get_n_chans(), max_frames);
678
679         // Make the chunk size divisible by the channel count
680         int chan_count = new_config.channel_config->get_n_chans();
681         max_frames_out = 8192;
682         if (chan_count > 0) {
683                 max_frames_out -= max_frames_out % chan_count;
684         }
685         chunker.reset (new Chunker<Sample> (max_frames_out));
686         interleaver->add_output(chunker);
687
688         ChannelList const & channel_list = config.channel_config->get_channels();
689         unsigned chan = 0;
690         for (ChannelList::const_iterator it = channel_list.begin(); it != channel_list.end(); ++it, ++chan) {
691                 ChannelMap::iterator map_it = channel_map.find (*it);
692                 if (map_it == channel_map.end()) {
693                         std::pair<ChannelMap::iterator, bool> result_pair =
694                                 channel_map.insert (std::make_pair (*it, IdentityVertexPtr (new IdentityVertex<Sample> ())));
695                         assert (result_pair.second);
696                         map_it = result_pair.first;
697                 }
698                 map_it->second->add_output (interleaver->input (chan));
699         }
700
701         add_child (new_config);
702 }
703
704 void
705 ExportGraphBuilder::ChannelConfig::add_child (FileSpec const & new_config)
706 {
707         assert (*this == new_config);
708
709         for (boost::ptr_list<SilenceHandler>::iterator it = children.begin(); it != children.end(); ++it) {
710                 if (*it == new_config) {
711                         it->add_child (new_config);
712                         return;
713                 }
714         }
715
716         children.push_back (new SilenceHandler (parent, new_config, max_frames_out));
717         chunker->add_output (children.back().sink ());
718 }
719
720 void
721 ExportGraphBuilder::ChannelConfig::remove_children (bool remove_out_files)
722 {
723         boost::ptr_list<SilenceHandler>::iterator iter = children.begin();
724
725         while(iter != children.end() ) {
726
727                 chunker->remove_output (iter->sink ());
728                 iter->remove_children (remove_out_files);
729                 iter = children.erase(iter);
730         }
731 }
732
733 bool
734 ExportGraphBuilder::ChannelConfig::operator== (FileSpec const & other_config) const
735 {
736         return config.channel_config == other_config.channel_config;
737 }
738
739 } // namespace ARDOUR