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