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