a78c983f0f03096616a1ae8ab957b13e99bddf32
[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                 (
486                  (!config.format->normalize_loudness () && config.format->normalize_dbfs() == other_config.format->normalize_dbfs())
487                  ||
488                  (config.format->normalize_loudness () /* lufs/dbtp is a result option, not an instantaion option */)
489                 );
490 }
491
492 unsigned
493 ExportGraphBuilder::Normalizer::get_normalize_cycle_count() const
494 {
495         return static_cast<unsigned>(std::ceil(static_cast<float>(tmp_file->get_frames_written()) /
496                                                max_frames_out));
497 }
498
499 bool
500 ExportGraphBuilder::Normalizer::process()
501 {
502         framecnt_t frames_read = tmp_file->read (*buffer);
503         return frames_read != buffer->frames();
504 }
505
506 void
507 ExportGraphBuilder::Normalizer::start_post_processing()
508 {
509         float gain;
510         if (use_loudness) {
511                 gain = normalizer->set_peak (loudness_reader->get_peak (config.format->normalize_lufs (), config.format->normalize_dbtp ()));
512         } else {
513                 gain = normalizer->set_peak (peak_reader->get_peak());
514         }
515         for (boost::ptr_list<SFC>::iterator i = children.begin(); i != children.end(); ++i) {
516                 (*i).set_peak (gain);
517         }
518         tmp_file->seek (0, SEEK_SET);
519         tmp_file->add_output (normalizer);
520         parent.normalizers.push_back (this);
521 }
522
523 /* SRC */
524
525 ExportGraphBuilder::SRC::SRC (ExportGraphBuilder & parent, FileSpec const & new_config, framecnt_t max_frames)
526         : parent (parent)
527 {
528         config = new_config;
529         converter.reset (new SampleRateConverter (new_config.channel_config->get_n_chans()));
530         ExportFormatSpecification & format = *new_config.format;
531         converter->init (parent.session.nominal_frame_rate(), format.sample_rate(), format.src_quality());
532         max_frames_out = converter->allocate_buffers (max_frames);
533
534         add_child (new_config);
535 }
536
537 ExportGraphBuilder::FloatSinkPtr
538 ExportGraphBuilder::SRC::sink ()
539 {
540         return converter;
541 }
542
543 void
544 ExportGraphBuilder::SRC::add_child (FileSpec const & new_config)
545 {
546         if (new_config.format->normalize()) {
547                 add_child_to_list (new_config, normalized_children);
548         } else {
549                 add_child_to_list (new_config, children);
550         }
551 }
552
553 void
554 ExportGraphBuilder::SRC::remove_children (bool remove_out_files)
555 {
556         boost::ptr_list<SFC>::iterator sfc_iter = children.begin();
557
558         while (sfc_iter != children.end() ) {
559                 converter->remove_output (sfc_iter->sink() );
560                 sfc_iter->remove_children (remove_out_files);
561                 sfc_iter = children.erase (sfc_iter);
562         }
563
564         boost::ptr_list<Normalizer>::iterator norm_iter = normalized_children.begin();
565
566         while (norm_iter != normalized_children.end() ) {
567                 converter->remove_output (norm_iter->sink() );
568                 norm_iter->remove_children (remove_out_files);
569                 norm_iter = normalized_children.erase (norm_iter);
570         }
571
572 }
573
574 template<typename T>
575 void
576 ExportGraphBuilder::SRC::add_child_to_list (FileSpec const & new_config, boost::ptr_list<T> & list)
577 {
578         for (typename boost::ptr_list<T>::iterator it = list.begin(); it != list.end(); ++it) {
579                 if (*it == new_config) {
580                         it->add_child (new_config);
581                         return;
582                 }
583         }
584
585         list.push_back (new T (parent, new_config, max_frames_out));
586         converter->add_output (list.back().sink ());
587 }
588
589 bool
590 ExportGraphBuilder::SRC::operator== (FileSpec const & other_config) const
591 {
592         return config.format->sample_rate() == other_config.format->sample_rate();
593 }
594
595 /* SilenceHandler */
596 ExportGraphBuilder::SilenceHandler::SilenceHandler (ExportGraphBuilder & parent, FileSpec const & new_config, framecnt_t max_frames)
597         : parent (parent)
598 {
599         config = new_config;
600         max_frames_in = max_frames;
601         framecnt_t sample_rate = parent.session.nominal_frame_rate();
602
603 #ifdef MIXBUS
604         silence_trimmer.reset (new SilenceTrimmer<Sample>(max_frames_in, -90));
605 #else
606         // TODO silence-threshold should be per export-preset, with Config->get_silence_threshold being the default
607         silence_trimmer.reset (new SilenceTrimmer<Sample>(max_frames_in, Config->get_export_silence_threshold ()));
608 #endif
609         silence_trimmer->set_trim_beginning (config.format->trim_beginning());
610         silence_trimmer->set_trim_end (config.format->trim_end());
611
612         framecnt_t sb = config.format->silence_beginning_at (parent.timespan->get_start(), sample_rate);
613         framecnt_t se = config.format->silence_end_at (parent.timespan->get_end(), sample_rate);
614
615         silence_trimmer->add_silence_to_beginning (sb);
616         silence_trimmer->add_silence_to_end (se);
617
618         add_child (new_config);
619 }
620
621 ExportGraphBuilder::FloatSinkPtr
622 ExportGraphBuilder::SilenceHandler::sink ()
623 {
624         return silence_trimmer;
625 }
626
627 void
628 ExportGraphBuilder::SilenceHandler::add_child (FileSpec const & new_config)
629 {
630         for (boost::ptr_list<SRC>::iterator it = children.begin(); it != children.end(); ++it) {
631                 if (*it == new_config) {
632                         it->add_child (new_config);
633                         return;
634                 }
635         }
636
637         children.push_back (new SRC (parent, new_config, max_frames_in));
638         silence_trimmer->add_output (children.back().sink());
639 }
640
641 void
642 ExportGraphBuilder::SilenceHandler::remove_children (bool remove_out_files)
643 {
644         boost::ptr_list<SRC>::iterator iter = children.begin();
645
646         while (iter != children.end() ) {
647                 silence_trimmer->remove_output (iter->sink() );
648                 iter->remove_children (remove_out_files);
649                 iter = children.erase (iter);
650         }
651 }
652
653 bool
654 ExportGraphBuilder::SilenceHandler::operator== (FileSpec const & other_config) const
655 {
656         ExportFormatSpecification & format = *config.format;
657         ExportFormatSpecification & other_format = *other_config.format;
658         return (format.trim_beginning() == other_format.trim_beginning()) &&
659                 (format.trim_end() == other_format.trim_end()) &&
660                 (format.silence_beginning_time() == other_format.silence_beginning_time()) &&
661                 (format.silence_end_time() == other_format.silence_end_time());
662 }
663
664 /* ChannelConfig */
665
666 ExportGraphBuilder::ChannelConfig::ChannelConfig (ExportGraphBuilder & parent, FileSpec const & new_config, ChannelMap & channel_map)
667         : parent (parent)
668 {
669         typedef ExportChannelConfiguration::ChannelList ChannelList;
670
671         config = new_config;
672
673         framecnt_t max_frames = parent.session.engine().samples_per_cycle();
674         interleaver.reset (new Interleaver<Sample> ());
675         interleaver->init (new_config.channel_config->get_n_chans(), max_frames);
676
677         // Make the chunk size divisible by the channel count
678         int chan_count = new_config.channel_config->get_n_chans();
679         max_frames_out = 8192;
680         if (chan_count > 0) {
681                 max_frames_out -= max_frames_out % chan_count;
682         }
683         chunker.reset (new Chunker<Sample> (max_frames_out));
684         interleaver->add_output(chunker);
685
686         ChannelList const & channel_list = config.channel_config->get_channels();
687         unsigned chan = 0;
688         for (ChannelList::const_iterator it = channel_list.begin(); it != channel_list.end(); ++it, ++chan) {
689                 ChannelMap::iterator map_it = channel_map.find (*it);
690                 if (map_it == channel_map.end()) {
691                         std::pair<ChannelMap::iterator, bool> result_pair =
692                                 channel_map.insert (std::make_pair (*it, IdentityVertexPtr (new IdentityVertex<Sample> ())));
693                         assert (result_pair.second);
694                         map_it = result_pair.first;
695                 }
696                 map_it->second->add_output (interleaver->input (chan));
697         }
698
699         add_child (new_config);
700 }
701
702 void
703 ExportGraphBuilder::ChannelConfig::add_child (FileSpec const & new_config)
704 {
705         assert (*this == new_config);
706
707         for (boost::ptr_list<SilenceHandler>::iterator it = children.begin(); it != children.end(); ++it) {
708                 if (*it == new_config) {
709                         it->add_child (new_config);
710                         return;
711                 }
712         }
713
714         children.push_back (new SilenceHandler (parent, new_config, max_frames_out));
715         chunker->add_output (children.back().sink ());
716 }
717
718 void
719 ExportGraphBuilder::ChannelConfig::remove_children (bool remove_out_files)
720 {
721         boost::ptr_list<SilenceHandler>::iterator iter = children.begin();
722
723         while(iter != children.end() ) {
724
725                 chunker->remove_output (iter->sink ());
726                 iter->remove_children (remove_out_files);
727                 iter = children.erase(iter);
728         }
729 }
730
731 bool
732 ExportGraphBuilder::ChannelConfig::operator== (FileSpec const & other_config) const
733 {
734         return config.channel_config == other_config.channel_config;
735 }
736
737 } // namespace ARDOUR