1 /*
2  * Copyright (C) 2009-2013 Sakari Bergen <sakari.bergen@beatwaves.net>
3  * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
4  * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2011-2014 David Robillard <d@drobilla.net>
6  * Copyright (C) 2012-2016 Tim Mayberry <mojofunk@gmail.com>
7  * Copyright (C) 2013-2016 John Emmas <john@creativepost.co.uk>
8  * Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 #include <vector>
26 
27 #include <glibmm/miscutils.h>
28 #include <glibmm/timer.h>
29 
30 #include "pbd/uuid.h"
31 #include "pbd/file_utils.h"
32 #include "pbd/cpus.h"
33 
34 #include "audiographer/process_context.h"
35 #include "audiographer/general/chunker.h"
36 #include "audiographer/general/cmdpipe_writer.h"
37 #include "audiographer/general/demo_noise.h"
38 #include "audiographer/general/interleaver.h"
39 #include "audiographer/general/limiter.h"
40 #include "audiographer/general/normalizer.h"
41 #include "audiographer/general/analyser.h"
42 #include "audiographer/general/peak_reader.h"
43 #include "audiographer/general/loudness_reader.h"
44 #include "audiographer/general/sample_format_converter.h"
45 #include "audiographer/general/sr_converter.h"
46 #include "audiographer/general/silence_trimmer.h"
47 #include "audiographer/general/threader.h"
48 #include "audiographer/sndfile/tmp_file.h"
49 #include "audiographer/sndfile/tmp_file_rt.h"
50 #include "audiographer/sndfile/tmp_file_sync.h"
51 #include "audiographer/sndfile/sndfile_writer.h"
52 
53 #include "ardour/audioengine.h"
54 #include "ardour/export_channel_configuration.h"
55 #include "ardour/export_failed.h"
56 #include "ardour/export_filename.h"
57 #include "ardour/export_format_specification.h"
58 #include "ardour/export_graph_builder.h"
59 #include "ardour/export_timespan.h"
60 #include "ardour/filesystem_paths.h"
61 #include "ardour/session_directory.h"
62 #include "ardour/session_metadata.h"
63 #include "ardour/sndfile_helpers.h"
64 #include "ardour/system_exec.h"
65 
66 using namespace AudioGrapher;
67 using std::string;
68 
69 namespace ARDOUR {
70 
ExportGraphBuilder(Session const & session)71 ExportGraphBuilder::ExportGraphBuilder (Session const & session)
72 	: session (session)
73 	, thread_pool (hardware_concurrency())
74 {
75 	process_buffer_samples = session.engine().samples_per_cycle();
76 }
77 
~ExportGraphBuilder()78 ExportGraphBuilder::~ExportGraphBuilder ()
79 {
80 }
81 
82 samplecnt_t
process(samplecnt_t samples,bool last_cycle)83 ExportGraphBuilder::process (samplecnt_t samples, bool last_cycle)
84 {
85 	assert(samples <= process_buffer_samples);
86 
87 	sampleoffset_t off = 0;
88 	for (ChannelMap::iterator it = channels.begin(); it != channels.end(); ++it) {
89 		Sample const * process_buffer = 0;
90 		it->first->read (process_buffer, samples);
91 
92 		if (session.remaining_latency_preroll () >= _master_align + samples) {
93 			/* Skip processing during pre-roll, only read/write export ringbuffers */
94 			return 0;
95 		}
96 
97 		off = 0;
98 		if (session.remaining_latency_preroll () > _master_align) {
99 			off = session.remaining_latency_preroll () - _master_align;
100 			assert (off < samples);
101 		}
102 
103 		ConstProcessContext<Sample> context(&process_buffer[off], samples - off, 1);
104 		if (last_cycle) { context().set_flag (ProcessContext<Sample>::EndOfInput); }
105 		it->second->process (context);
106 	}
107 
108 	return samples - off;
109 }
110 
111 bool
post_process()112 ExportGraphBuilder::post_process ()
113 {
114 	for (std::list<Intermediate *>::iterator it = intermediates.begin(); it != intermediates.end(); /* ++ in loop */) {
115 		if ((*it)->process()) {
116 			it = intermediates.erase (it);
117 		} else {
118 			++it;
119 		}
120 	}
121 
122 	return intermediates.empty();
123 }
124 
125 unsigned
get_postprocessing_cycle_count() const126 ExportGraphBuilder::get_postprocessing_cycle_count() const
127 {
128 	unsigned max = 0;
129 	for (std::list<Intermediate *>::const_iterator it = intermediates.begin(); it != intermediates.end(); ++it) {
130 		max = std::max(max, (*it)->get_postprocessing_cycle_count());
131 	}
132 	return max;
133 }
134 
135 void
reset()136 ExportGraphBuilder::reset ()
137 {
138 	timespan.reset();
139 	channel_configs.clear ();
140 	channels.clear ();
141 	intermediates.clear ();
142 	analysis_map.clear();
143 	_realtime = false;
144 	_master_align = 0;
145 }
146 
147 void
cleanup(bool remove_out_files)148 ExportGraphBuilder::cleanup (bool remove_out_files/*=false*/)
149 {
150 	ChannelConfigList::iterator iter = channel_configs.begin();
151 
152 	while (iter != channel_configs.end() ) {
153 		iter->remove_children(remove_out_files);
154 		iter = channel_configs.erase(iter);
155 	}
156 }
157 
158 void
set_current_timespan(boost::shared_ptr<ExportTimespan> span)159 ExportGraphBuilder::set_current_timespan (boost::shared_ptr<ExportTimespan> span)
160 {
161 	timespan = span;
162 }
163 
164 void
add_config(FileSpec const & config,bool rt)165 ExportGraphBuilder::add_config (FileSpec const & config, bool rt)
166 {
167 	/* calculate common latency, shave off master-bus hardware playback latency (if any) */
168 	_master_align = session.master_out() ? session.master_out()->output()->connected_latency (true) : 0;
169 
170 	ExportChannelConfiguration::ChannelList const & channels = config.channel_config->get_channels();
171 
172 	for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
173 		_master_align = std::min (_master_align, (*it)->common_port_playback_latency ());
174 	}
175 
176 	/* now set-up port-data sniffing and delay-ringbuffers */
177 	for(ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
178 		(*it)->prepare_export (process_buffer_samples, _master_align);
179 	}
180 
181 	_realtime = rt;
182 
183 	/* If the sample rate is "session rate", change it to the real value.
184 	 * However, we need to copy it to not change the config which is saved...
185 	 */
186 	FileSpec new_config (config);
187 	new_config.format.reset(new ExportFormatSpecification(*new_config.format, false));
188 	if(new_config.format->sample_rate() == ExportFormatBase::SR_Session) {
189 		samplecnt_t session_rate = session.nominal_sample_rate();
190 		new_config.format->set_sample_rate(ExportFormatBase::nearest_sample_rate(session_rate));
191 	}
192 
193 	if (!new_config.channel_config->get_split ()) {
194 		add_split_config (new_config);
195 		return;
196 	}
197 
198 	/* Split channel configurations are split into several channel configurations,
199 	 * each corresponding to a file, at this stage
200 	 */
201 	typedef std::list<boost::shared_ptr<ExportChannelConfiguration> > ConfigList;
202 	ConfigList file_configs;
203 	new_config.channel_config->configurations_for_files (file_configs);
204 
205 	unsigned chan = 1;
206 	for (ConfigList::iterator it = file_configs.begin(); it != file_configs.end(); ++it, ++chan) {
207 		FileSpec copy = new_config;
208 		copy.channel_config = *it;
209 
210 		copy.filename.reset (new ExportFilename (*copy.filename));
211 		copy.filename->include_channel = true;
212 		copy.filename->set_channel (chan);
213 
214 		add_split_config (copy);
215 	}
216 }
217 
218 void
get_analysis_results(AnalysisResults & results)219 ExportGraphBuilder::get_analysis_results (AnalysisResults& results) {
220 	for (AnalysisMap::iterator i = analysis_map.begin(); i != analysis_map.end(); ++i) {
221 		ExportAnalysisPtr p = i->second->result ();
222 		if (p) {
223 			results.insert (std::make_pair (i->first, p));
224 		}
225 	}
226 }
227 
228 void
add_split_config(FileSpec const & config)229 ExportGraphBuilder::add_split_config (FileSpec const & config)
230 {
231 	for (ChannelConfigList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) {
232 		if (*it == config) {
233 			it->add_child (config);
234 			return;
235 		}
236 	}
237 
238 	// No duplicate channel config found, create new one
239 	channel_configs.push_back (new ChannelConfig (*this, config, channels));
240 }
241 
242 /* Encoder */
243 
244 template <>
245 boost::shared_ptr<AudioGrapher::Sink<Sample> >
init(FileSpec const & new_config)246 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
247 {
248 	config = new_config;
249 	if (config.format->format_id() == ExportFormatBase::F_FFMPEG) {
250 		init_writer (pipe_writer);
251 		return pipe_writer;
252 	} else {
253 		init_writer (float_writer);
254 		return float_writer;
255 	}
256 }
257 
258 template <>
259 boost::shared_ptr<AudioGrapher::Sink<int> >
init(FileSpec const & new_config)260 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
261 {
262 	config = new_config;
263 	init_writer (int_writer);
264 	return int_writer;
265 }
266 
267 template <>
268 boost::shared_ptr<AudioGrapher::Sink<short> >
init(FileSpec const & new_config)269 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
270 {
271 	config = new_config;
272 	init_writer (short_writer);
273 	return short_writer;
274 }
275 
276 void
add_child(FileSpec const & new_config)277 ExportGraphBuilder::Encoder::add_child (FileSpec const & new_config)
278 {
279 	filenames.push_back (new_config.filename);
280 }
281 
282 void
destroy_writer(bool delete_out_file)283 ExportGraphBuilder::Encoder::destroy_writer (bool delete_out_file)
284 {
285 	if (delete_out_file ) {
286 
287 		if (float_writer) {
288 			float_writer->close ();
289 		}
290 
291 		if (int_writer) {
292 			int_writer->close ();
293 		}
294 
295 		if (short_writer) {
296 			short_writer->close ();
297 		}
298 
299 		if (pipe_writer) {
300 			pipe_writer->close ();
301 		}
302 
303 		if (std::remove(writer_filename.c_str() ) != 0) {
304 			std::cout << "Encoder::destroy_writer () : Error removing file: " << strerror(errno) << std::endl;
305 		}
306 	}
307 
308 	float_writer.reset ();
309 	int_writer.reset ();
310 	short_writer.reset ();
311 	pipe_writer.reset ();
312 }
313 
314 bool
operator ==(FileSpec const & other_config) const315 ExportGraphBuilder::Encoder::operator== (FileSpec const & other_config) const
316 {
317 	return get_real_format (config) == get_real_format (other_config);
318 }
319 
320 int
get_real_format(FileSpec const & config)321 ExportGraphBuilder::Encoder::get_real_format (FileSpec const & config)
322 {
323 	ExportFormatSpecification & format = *config.format;
324 	return format.format_id() | format.sample_format() | format.endianness();
325 }
326 
327 template<typename T>
328 void
init_writer(boost::shared_ptr<AudioGrapher::SndfileWriter<T>> & writer)329 ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::SndfileWriter<T> > & writer)
330 {
331 	unsigned channels = config.channel_config->get_n_chans();
332 	int format = get_real_format (config);
333 	config.filename->set_channel_config(config.channel_config);
334 	writer_filename = config.filename->get_path (config.format);
335 
336 	writer.reset (new AudioGrapher::SndfileWriter<T> (writer_filename, format, channels, config.format->sample_rate(), config.broadcast_info));
337 	writer->FileWritten.connect_same_thread (copy_files_connection, boost::bind (&ExportGraphBuilder::Encoder::copy_files, this, _1));
338 	if (format & ExportFormatBase::SF_Vorbis) {
339 		/* libsndfile uses range 0..1 (worst.. best) for
340 		 * SFC_SET_VBR_ENCODING_QUALITY and maps
341 		 * SFC_SET_COMPRESSION_LEVEL = 1.0 - VBR_ENCODING_QUALITY
342 		 */
343 		double vorbis_quality = config.format->codec_quality () / 100.f;
344 		if (vorbis_quality >= 0 && vorbis_quality <= 1.0) {
345 			writer->command (SFC_SET_VBR_ENCODING_QUALITY, &vorbis_quality, sizeof (double));
346 		}
347 	}
348 }
349 
350 template<typename T>
351 void
init_writer(boost::shared_ptr<AudioGrapher::CmdPipeWriter<T>> & writer)352 ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::CmdPipeWriter<T> > & writer)
353 {
354 	unsigned channels = config.channel_config->get_n_chans();
355 	config.filename->set_channel_config(config.channel_config);
356 	writer_filename = config.filename->get_path (config.format);
357 
358 	std::string ffmpeg_exe;
359 	std::string unused;
360 
361 	if (!ArdourVideoToolPaths::transcoder_exe (ffmpeg_exe, unused)) {
362 		throw ExportFailed ("External encoder (ffmpeg) is not available.");
363 	}
364 
365 	int quality = config.format->codec_quality ();
366 
367 	int a=0;
368 	char **argp = (char**) calloc (100, sizeof(char*));
369 	char tmp[64];
370 	argp[a++] = strdup(ffmpeg_exe.c_str());
371 	argp[a++] = strdup ("-f");
372 	argp[a++] = strdup ("f32le");
373 	argp[a++] = strdup ("-acodec");
374 	argp[a++] = strdup ("pcm_f32le");
375 	argp[a++] = strdup ("-ac");
376 	snprintf (tmp, sizeof(tmp), "%d", channels);
377 	argp[a++] = strdup (tmp);
378 	argp[a++] = strdup ("-ar");
379 	snprintf (tmp, sizeof(tmp), "%d", config.format->sample_rate());
380 	argp[a++] = strdup (tmp);
381 	argp[a++] = strdup ("-i");
382 	argp[a++] = strdup ("pipe:0");
383 
384 	argp[a++] = strdup ("-y");
385 	if (quality <= 0) {
386 		/* variable rate, lower is better */
387 		snprintf (tmp, sizeof(tmp), "%d", -quality);
388 		argp[a++] = strdup ("-q:a"); argp[a++] = strdup (tmp);
389 	} else {
390 		/* fixed bitrate, higher is better */
391 		snprintf (tmp, sizeof(tmp), "%dk", quality); // eg. "192k"
392 		argp[a++] = strdup ("-b:a"); argp[a++] = strdup (tmp);
393 	}
394 
395 	SessionMetadata::MetaDataMap meta;
396 	meta["comment"] = "Created with " PROGRAM_NAME;
397 
398 	if (config.format->tag()) {
399 		ARDOUR::SessionMetadata* session_data = ARDOUR::SessionMetadata::Metadata();
400 		session_data->av_export_tag (meta);
401 	}
402 
403 	for(SessionMetadata::MetaDataMap::const_iterator it = meta.begin(); it != meta.end(); ++it) {
404 		argp[a++] = strdup("-metadata");
405 		argp[a++] = SystemExec::format_key_value_parameter (it->first.c_str(), it->second.c_str());
406 	}
407 
408 	argp[a++] = strdup (writer_filename.c_str());
409 	argp[a] = (char *)0;
410 
411 	/* argp is free()d in ~SystemExec,
412 	 * SystemExec is deleted when writer is destroyed */
413 	ARDOUR::SystemExec* exec = new ARDOUR::SystemExec (ffmpeg_exe, argp);
414 	PBD::info << "Encode command: { " << exec->to_s () << "}" << endmsg;
415 	if (exec->start (SystemExec::MergeWithStdin)) {
416 		throw ExportFailed ("External encoder (ffmpeg) cannot be started.");
417 	}
418 	writer.reset (new AudioGrapher::CmdPipeWriter<T> (exec, writer_filename));
419 	writer->FileWritten.connect_same_thread (copy_files_connection, boost::bind (&ExportGraphBuilder::Encoder::copy_files, this, _1));
420 }
421 
422 void
copy_files(std::string orig_path)423 ExportGraphBuilder::Encoder::copy_files (std::string orig_path)
424 {
425 	while (filenames.size()) {
426 		ExportFilenamePtr & filename = filenames.front();
427 		PBD::copy_file (orig_path, filename->get_path (config.format).c_str());
428 		filenames.pop_front();
429 	}
430 }
431 
432 /* SFC */
433 
SFC(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)434 ExportGraphBuilder::SFC::SFC (ExportGraphBuilder &parent, FileSpec const & new_config, samplecnt_t max_samples)
435 	: data_width(0)
436 {
437 	config = new_config;
438 	data_width = sndfile_data_width (Encoder::get_real_format (config));
439 	unsigned channels = new_config.channel_config->get_n_chans();
440 	_analyse = config.format->analyse();
441 
442 	float ntarget = (config.format->normalize_loudness () || !config.format->normalize()) ? 0.0 : config.format->normalize_dbfs();
443 	normalizer.reset (new AudioGrapher::Normalizer (ntarget, max_samples));
444 	limiter.reset (new AudioGrapher::Limiter (config.format->sample_rate(), channels, max_samples));
445 
446 	normalizer->add_output (limiter);
447 
448 	boost::shared_ptr<AudioGrapher::ListedSource<float> > intermediate = limiter;
449 
450 	if (_analyse) {
451 		samplecnt_t sample_rate = parent.session.nominal_sample_rate();
452 		samplecnt_t sb = config.format->silence_beginning_at (parent.timespan->get_start(), sample_rate);
453 		samplecnt_t se = config.format->silence_end_at (parent.timespan->get_end(), sample_rate);
454 		samplecnt_t duration = parent.timespan->get_length () + sb + se;
455 
456 		max_samples = std::min ((samplecnt_t) 8192 * channels, std::max ((samplecnt_t) 4096 * channels, max_samples));
457 		chunker.reset (new Chunker<Sample> (max_samples));
458 		analyser.reset (new Analyser (config.format->sample_rate(), channels, max_samples,
459 					(samplecnt_t) ceil (duration * config.format->sample_rate () / (double) sample_rate)));
460 
461 		config.filename->set_channel_config (config.channel_config);
462 		parent.add_analyser (config.filename->get_path (config.format), analyser);
463 		limiter->set_result (analyser->result (true));
464 
465 		chunker->add_output (analyser);
466 		intermediate->add_output (chunker);
467 		intermediate = analyser;
468 	}
469 
470 	if (config.format->format_id() == ExportFormatBase::F_None) {
471 		/* do not encode result, stop after chunker/analyzer */
472 		assert (_analyse);
473 		return;
474 	}
475 
476 	if (config.format->demo_noise_duration () > 0 && config.format->demo_noise_interval () > 0) {
477 		samplecnt_t sample_rate = parent.session.nominal_sample_rate();
478 		demo_noise_adder.reset (new DemoNoiseAdder (channels));
479 		demo_noise_adder->init (max_samples,
480 				sample_rate * config.format->demo_noise_interval () / 1000,
481 				sample_rate * config.format->demo_noise_duration () / 1000,
482 				config.format->demo_noise_level ());
483 
484 		intermediate->add_output (demo_noise_adder);
485 		intermediate = demo_noise_adder;
486 	}
487 
488 	if (data_width == 8 || data_width == 16) {
489 		short_converter = ShortConverterPtr (new SampleFormatConverter<short> (channels));
490 		short_converter->init (max_samples, config.format->dither_type(), data_width);
491 		add_child (config);
492 		intermediate->add_output (short_converter);
493 	} else if (data_width == 24 || data_width == 32) {
494 		int_converter = IntConverterPtr (new SampleFormatConverter<int> (channels));
495 		int_converter->init (max_samples, config.format->dither_type(), data_width);
496 		add_child (config);
497 		intermediate->add_output (int_converter);
498 	} else {
499 		int actual_data_width = 8 * sizeof(Sample);
500 		float_converter = FloatConverterPtr (new SampleFormatConverter<Sample> (channels));
501 		float_converter->init (max_samples, config.format->dither_type(), actual_data_width);
502 		add_child (config);
503 		intermediate->add_output (float_converter);
504 	}
505 }
506 
507 void
set_duration(samplecnt_t n_samples)508 ExportGraphBuilder::SFC::set_duration (samplecnt_t n_samples)
509 {
510 	/* update after silence trim */
511 	if (analyser) {
512 		analyser->set_duration (n_samples);
513 	}
514 	if (limiter) {
515 		limiter->set_duration (n_samples);
516 	}
517 }
518 
519 void
set_peak_dbfs(float peak,bool force)520 ExportGraphBuilder::SFC::set_peak_dbfs (float peak, bool force)
521 {
522 	if (!config.format->normalize () && !force) {
523 		return;
524 	}
525 	float gain = normalizer->set_peak (peak);
526 	if (_analyse) {
527 		analyser->set_normalization_gain (gain);
528 	}
529 }
530 
531 void
set_peak_lufs(AudioGrapher::LoudnessReader const & lr)532 ExportGraphBuilder::SFC::set_peak_lufs (AudioGrapher::LoudnessReader const& lr)
533 {
534 	if (!config.format->normalize_loudness ()) {
535 		return;
536 	}
537 	float LUFSi, LUFSs;
538 	if (!config.format->use_tp_limiter ()) {
539 		float peak = lr.calc_peak (config.format->normalize_lufs (), config.format->normalize_dbtp ());
540 		set_peak_dbfs (peak, true);
541 	} else if (lr.get_loudness (&LUFSi, &LUFSs) && (LUFSi > -180 || LUFSs > -180)) {
542 		float lufs = LUFSi > -180 ? LUFSi : LUFSs;
543 		float peak = powf (10.f, .05 * (lufs - config.format->normalize_lufs () - 0.05));
544 		limiter->set_threshold (config.format->normalize_dbtp ());
545 		set_peak_dbfs (peak, true);
546 	}
547 }
548 
549 ExportGraphBuilder::FloatSinkPtr
sink()550 ExportGraphBuilder::SFC::sink ()
551 {
552 	return normalizer;
553 }
554 
555 void
add_child(FileSpec const & new_config)556 ExportGraphBuilder::SFC::add_child (FileSpec const & new_config)
557 {
558 	for (boost::ptr_list<Encoder>::iterator it = children.begin(); it != children.end(); ++it) {
559 		if (*it == new_config) {
560 			it->add_child (new_config);
561 			return;
562 		}
563 	}
564 
565 	children.push_back (new Encoder());
566 	Encoder & encoder = children.back();
567 
568 	if (data_width == 8 || data_width == 16) {
569 		short_converter->add_output (encoder.init<short> (new_config));
570 	} else if (data_width == 24 || data_width == 32) {
571 		int_converter->add_output (encoder.init<int> (new_config));
572 	} else {
573 		float_converter->add_output (encoder.init<Sample> (new_config));
574 	}
575 }
576 
577 void
remove_children(bool remove_out_files)578 ExportGraphBuilder::SFC::remove_children (bool remove_out_files)
579 {
580 	boost::ptr_list<Encoder>::iterator iter = children.begin ();
581 
582 	while (iter != children.end() ) {
583 
584 		if (remove_out_files) {
585 			iter->destroy_writer(remove_out_files);
586 		}
587 		iter = children.erase (iter);
588 	}
589 }
590 
591 bool
operator ==(FileSpec const & other_config) const592 ExportGraphBuilder::SFC::operator== (FileSpec const& other_config) const
593 {
594 	ExportFormatSpecification const& a = *config.format;
595 	ExportFormatSpecification const& b = *other_config.format;
596 
597 	bool id = a.sample_format() == b.sample_format();
598 
599 	if (a.normalize_loudness () == b.normalize_loudness ()) {
600 		id &= a.normalize_lufs () == b.normalize_lufs ();
601 		id &= a.normalize_dbtp () == b.normalize_dbtp ();
602 	} else {
603 		return false;
604 	}
605 	if (a.normalize () == b.normalize ()) {
606 		id &= a.normalize_dbfs () == b.normalize_dbfs ();
607 	} else {
608 		return false;
609 	}
610 
611 	id &= a.demo_noise_duration () == b.demo_noise_duration ();
612 	id &= a.demo_noise_interval () == b.demo_noise_interval ();
613 
614 	return id;
615 }
616 
617 /* Intermediate (Normalizer, TmpFile) */
618 
Intermediate(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)619 ExportGraphBuilder::Intermediate::Intermediate (ExportGraphBuilder & parent, FileSpec const & new_config, samplecnt_t max_samples)
620 	: parent (parent)
621 	, use_loudness (false)
622 	, use_peak (false)
623 {
624 	std::string tmpfile_path = parent.session.session_directory().export_path();
625 	tmpfile_path = Glib::build_filename(tmpfile_path, "XXXXXX");
626 	std::vector<char> tmpfile_path_buf(tmpfile_path.size() + 1);
627 	std::copy(tmpfile_path.begin(), tmpfile_path.end(), tmpfile_path_buf.begin());
628 	tmpfile_path_buf[tmpfile_path.size()] = '\0';
629 
630 	config = new_config;
631 	uint32_t const channels = config.channel_config->get_n_chans();
632 	max_samples_out = 4086 - (4086 % channels); // TODO good chunk size
633 
634 	buffer.reset (new AllocatingProcessContext<Sample> (max_samples_out, channels));
635 
636 	peak_reader.reset (new PeakReader ());
637 	loudness_reader.reset (new LoudnessReader (config.format->sample_rate(), channels, max_samples));
638 	threader.reset (new Threader<Sample> (parent.thread_pool));
639 
640 	int format = ExportFormatBase::F_RAW | ExportFormatBase::SF_Float;
641 
642 	if (parent._realtime) {
643 		tmp_file.reset (new TmpFileRt<float> (&tmpfile_path_buf[0], format, channels, config.format->sample_rate()));
644 	} else {
645 		tmp_file.reset (new TmpFileSync<float> (&tmpfile_path_buf[0], format, channels, config.format->sample_rate()));
646 	}
647 
648 	tmp_file->FileWritten.connect_same_thread (post_processing_connection,
649 	                                           boost::bind (&Intermediate::prepare_post_processing, this));
650 	tmp_file->FileFlushed.connect_same_thread (post_processing_connection,
651 	                                           boost::bind (&Intermediate::start_post_processing, this));
652 
653 	add_child (new_config);
654 
655 	peak_reader->add_output (loudness_reader);
656 	loudness_reader->add_output (tmp_file);
657 }
658 
659 ExportGraphBuilder::FloatSinkPtr
sink()660 ExportGraphBuilder::Intermediate::sink ()
661 {
662 	if (use_peak) {
663 		return peak_reader;
664 	} else if (use_loudness) {
665 		return loudness_reader;
666 	} else {
667 		return tmp_file;
668 	}
669 }
670 
671 void
add_child(FileSpec const & new_config)672 ExportGraphBuilder::Intermediate::add_child (FileSpec const & new_config)
673 {
674 	use_peak     |= new_config.format->normalize ();
675 	use_loudness |= new_config.format->normalize_loudness ();
676 
677 	for (boost::ptr_list<SFC>::iterator it = children.begin(); it != children.end(); ++it) {
678 		if (*it == new_config) {
679 			it->add_child (new_config);
680 			return;
681 		}
682 	}
683 
684 	children.push_back (new SFC (parent, new_config, max_samples_out));
685 	threader->add_output (children.back().sink());
686 }
687 
688 void
remove_children(bool remove_out_files)689 ExportGraphBuilder::Intermediate::remove_children (bool remove_out_files)
690 {
691 	boost::ptr_list<SFC>::iterator iter = children.begin ();
692 
693 	while (iter != children.end() ) {
694 		iter->remove_children (remove_out_files);
695 		iter = children.erase (iter);
696 	}
697 }
698 
699 bool
operator ==(FileSpec const & other_config) const700 ExportGraphBuilder::Intermediate::operator== (FileSpec const & other_config) const
701 {
702 	return true;
703 }
704 
705 unsigned
get_postprocessing_cycle_count() const706 ExportGraphBuilder::Intermediate::get_postprocessing_cycle_count() const
707 {
708 	return static_cast<unsigned>(std::ceil(static_cast<float>(tmp_file->get_samples_written()) /
709 	                                       max_samples_out));
710 }
711 
712 bool
process()713 ExportGraphBuilder::Intermediate::process()
714 {
715 	samplecnt_t samples_read = tmp_file->read (*buffer);
716 	return samples_read != buffer->samples();
717 }
718 
719 void
prepare_post_processing()720 ExportGraphBuilder::Intermediate::prepare_post_processing()
721 {
722 	for (boost::ptr_list<SFC>::iterator i = children.begin(); i != children.end(); ++i) {
723 		if (use_peak) {
724 			(*i).set_peak_dbfs (peak_reader->get_peak());
725 		}
726 		if (use_loudness) {
727 			(*i).set_peak_lufs (*loudness_reader);
728 		}
729 	}
730 
731 	tmp_file->add_output (threader);
732 	parent.intermediates.push_back (this);
733 }
734 
735 void
start_post_processing()736 ExportGraphBuilder::Intermediate::start_post_processing()
737 {
738 	for (boost::ptr_list<SFC>::iterator i = children.begin(); i != children.end(); ++i) {
739 		(*i).set_duration (tmp_file->get_samples_written() / config.channel_config->get_n_chans());
740 	}
741 
742 	tmp_file->seek (0, SEEK_SET);
743 
744 	/* called in disk-thread when exporting in realtime,
745 	 * to enable freewheeling for post-proc.
746 	 *
747 	 * It may also be called to normalize from the
748 	 * freewheeling rt-callback, in which case this
749 	 * will be a no-op.
750 	 *
751 	 * RT Stem export has multiple TmpFileRt threads,
752 	 * prevent concurrent calls to enable freewheel ()
753 	 */
754 	Glib::Threads::Mutex::Lock lm (parent.engine_request_lock);
755 	if (!AudioEngine::instance()->freewheeling ()) {
756 		AudioEngine::instance()->freewheel (true);
757 		while (!AudioEngine::instance()->freewheeling ()) {
758 			Glib::usleep (AudioEngine::instance()->usecs_per_cycle ());
759 		}
760 	}
761 }
762 
763 /* SRC */
764 
SRC(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)765 ExportGraphBuilder::SRC::SRC (ExportGraphBuilder & parent, FileSpec const & new_config, samplecnt_t max_samples)
766 	: parent (parent)
767 {
768 	config = new_config;
769 	converter.reset (new SampleRateConverter (new_config.channel_config->get_n_chans()));
770 	ExportFormatSpecification & format = *new_config.format;
771 	converter->init (parent.session.nominal_sample_rate(), format.sample_rate(), format.src_quality());
772 	max_samples_out = converter->allocate_buffers (max_samples);
773 
774 	add_child (new_config);
775 }
776 
777 ExportGraphBuilder::FloatSinkPtr
sink()778 ExportGraphBuilder::SRC::sink ()
779 {
780 	return converter;
781 }
782 
783 void
add_child(FileSpec const & new_config)784 ExportGraphBuilder::SRC::add_child (FileSpec const & new_config)
785 {
786 	if (new_config.format->normalize() || parent._realtime) {
787 		add_child_to_list (new_config, intermediate_children);
788 	} else {
789 		add_child_to_list (new_config, children);
790 	}
791 }
792 
793 void
remove_children(bool remove_out_files)794 ExportGraphBuilder::SRC::remove_children (bool remove_out_files)
795 {
796 	boost::ptr_list<SFC>::iterator sfc_iter = children.begin();
797 
798 	while (sfc_iter != children.end() ) {
799 		converter->remove_output (sfc_iter->sink() );
800 		sfc_iter->remove_children (remove_out_files);
801 		sfc_iter = children.erase (sfc_iter);
802 	}
803 
804 	boost::ptr_list<Intermediate>::iterator norm_iter = intermediate_children.begin();
805 
806 	while (norm_iter != intermediate_children.end() ) {
807 		converter->remove_output (norm_iter->sink() );
808 		norm_iter->remove_children (remove_out_files);
809 		norm_iter = intermediate_children.erase (norm_iter);
810 	}
811 
812 }
813 
814 template<typename T>
815 void
add_child_to_list(FileSpec const & new_config,boost::ptr_list<T> & list)816 ExportGraphBuilder::SRC::add_child_to_list (FileSpec const & new_config, boost::ptr_list<T> & list)
817 {
818 	for (typename boost::ptr_list<T>::iterator it = list.begin(); it != list.end(); ++it) {
819 		if (*it == new_config) {
820 			it->add_child (new_config);
821 			return;
822 		}
823 	}
824 
825 	list.push_back (new T (parent, new_config, max_samples_out));
826 	converter->add_output (list.back().sink ());
827 }
828 
829 bool
operator ==(FileSpec const & other_config) const830 ExportGraphBuilder::SRC::operator== (FileSpec const & other_config) const
831 {
832 	return config.format->sample_rate() == other_config.format->sample_rate();
833 }
834 
835 /* SilenceHandler */
SilenceHandler(ExportGraphBuilder & parent,FileSpec const & new_config,samplecnt_t max_samples)836 ExportGraphBuilder::SilenceHandler::SilenceHandler (ExportGraphBuilder & parent, FileSpec const & new_config, samplecnt_t max_samples)
837 	: parent (parent)
838 {
839 	config = new_config;
840 	max_samples_in = max_samples;
841 	samplecnt_t sample_rate = parent.session.nominal_sample_rate();
842 
843 	/* work around partsing "-inf" config to "0" -- 7b1f97b
844 	 * silence trim 0dBFS makes no sense, anyway.
845 	 */
846 	float est = Config->get_export_silence_threshold ();
847 	if (est >= 0.f) est = -INFINITY;
848 #ifdef MIXBUS
849 	// Mixbus channelstrip always dithers the signal, cut above dither level
850 	silence_trimmer.reset (new SilenceTrimmer<Sample>(max_samples_in, std::max (-90.f, est)));
851 #else
852 	// TODO silence-threshold should be per export-preset, with Config->get_silence_threshold being the default
853 	silence_trimmer.reset (new SilenceTrimmer<Sample>(max_samples_in, est));
854 #endif
855 	silence_trimmer->set_trim_beginning (config.format->trim_beginning());
856 	silence_trimmer->set_trim_end (config.format->trim_end());
857 
858 	samplecnt_t sb = config.format->silence_beginning_at (parent.timespan->get_start(), sample_rate);
859 	samplecnt_t se = config.format->silence_end_at (parent.timespan->get_end(), sample_rate);
860 
861 	silence_trimmer->add_silence_to_beginning (sb);
862 	silence_trimmer->add_silence_to_end (se);
863 
864 	add_child (new_config);
865 }
866 
867 ExportGraphBuilder::FloatSinkPtr
sink()868 ExportGraphBuilder::SilenceHandler::sink ()
869 {
870 	return silence_trimmer;
871 }
872 
873 void
add_child(FileSpec const & new_config)874 ExportGraphBuilder::SilenceHandler::add_child (FileSpec const & new_config)
875 {
876 	for (boost::ptr_list<SRC>::iterator it = children.begin(); it != children.end(); ++it) {
877 		if (*it == new_config) {
878 			it->add_child (new_config);
879 			return;
880 		}
881 	}
882 
883 	children.push_back (new SRC (parent, new_config, max_samples_in));
884 	silence_trimmer->add_output (children.back().sink());
885 }
886 
887 void
remove_children(bool remove_out_files)888 ExportGraphBuilder::SilenceHandler::remove_children (bool remove_out_files)
889 {
890 	boost::ptr_list<SRC>::iterator iter = children.begin();
891 
892 	while (iter != children.end() ) {
893 		silence_trimmer->remove_output (iter->sink() );
894 		iter->remove_children (remove_out_files);
895 		iter = children.erase (iter);
896 	}
897 }
898 
899 bool
operator ==(FileSpec const & other_config) const900 ExportGraphBuilder::SilenceHandler::operator== (FileSpec const & other_config) const
901 {
902 	ExportFormatSpecification & format = *config.format;
903 	ExportFormatSpecification & other_format = *other_config.format;
904 	return (format.trim_beginning() == other_format.trim_beginning()) &&
905 		(format.trim_end() == other_format.trim_end()) &&
906 		(format.silence_beginning_time() == other_format.silence_beginning_time()) &&
907 		(format.silence_end_time() == other_format.silence_end_time());
908 }
909 
910 /* ChannelConfig */
911 
ChannelConfig(ExportGraphBuilder & parent,FileSpec const & new_config,ChannelMap & channel_map)912 ExportGraphBuilder::ChannelConfig::ChannelConfig (ExportGraphBuilder & parent, FileSpec const & new_config, ChannelMap & channel_map)
913 	: parent (parent)
914 {
915 	typedef ExportChannelConfiguration::ChannelList ChannelList;
916 
917 	config = new_config;
918 
919 	samplecnt_t max_samples = parent.session.engine().samples_per_cycle();
920 	interleaver.reset (new Interleaver<Sample> ());
921 	interleaver->init (new_config.channel_config->get_n_chans(), max_samples);
922 
923 	// Make the chunk size divisible by the channel count
924 	int chan_count = new_config.channel_config->get_n_chans();
925 	max_samples_out = 8192;
926 	if (chan_count > 0) {
927 		max_samples_out -= max_samples_out % chan_count;
928 	}
929 	chunker.reset (new Chunker<Sample> (max_samples_out));
930 	interleaver->add_output(chunker);
931 
932 	ChannelList const & channel_list = config.channel_config->get_channels();
933 	unsigned chan = 0;
934 	for (ChannelList::const_iterator it = channel_list.begin(); it != channel_list.end(); ++it, ++chan) {
935 		ChannelMap::iterator map_it = channel_map.find (*it);
936 		if (map_it == channel_map.end()) {
937 			std::pair<ChannelMap::iterator, bool> result_pair =
938 				channel_map.insert (std::make_pair (*it, IdentityVertexPtr (new IdentityVertex<Sample> ())));
939 			assert (result_pair.second);
940 			map_it = result_pair.first;
941 		}
942 		map_it->second->add_output (interleaver->input (chan));
943 	}
944 
945 	add_child (new_config);
946 }
947 
948 void
add_child(FileSpec const & new_config)949 ExportGraphBuilder::ChannelConfig::add_child (FileSpec const & new_config)
950 {
951 	assert (*this == new_config);
952 
953 	for (boost::ptr_list<SilenceHandler>::iterator it = children.begin(); it != children.end(); ++it) {
954 		if (*it == new_config) {
955 			it->add_child (new_config);
956 			return;
957 		}
958 	}
959 
960 	children.push_back (new SilenceHandler (parent, new_config, max_samples_out));
961 	chunker->add_output (children.back().sink ());
962 }
963 
964 void
remove_children(bool remove_out_files)965 ExportGraphBuilder::ChannelConfig::remove_children (bool remove_out_files)
966 {
967 	boost::ptr_list<SilenceHandler>::iterator iter = children.begin();
968 
969 	while(iter != children.end() ) {
970 
971 		chunker->remove_output (iter->sink ());
972 		iter->remove_children (remove_out_files);
973 		iter = children.erase(iter);
974 	}
975 }
976 
977 bool
operator ==(FileSpec const & other_config) const978 ExportGraphBuilder::ChannelConfig::operator== (FileSpec const & other_config) const
979 {
980 	return config.channel_config == other_config.channel_config;
981 }
982 
983 } // namespace ARDOUR
984