2 Copyright (C) 2008-2009 Paul Davis
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.
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.
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.
21 #include "ardour/export_handler.h"
25 #include "pbd/convert.h"
26 #include "pbd/filesystem.h"
28 #include "ardour/ardour.h"
29 #include "ardour/configuration.h"
30 #include "ardour/export_graph_builder.h"
31 #include "ardour/export_timespan.h"
32 #include "ardour/export_channel_configuration.h"
33 #include "ardour/export_status.h"
34 #include "ardour/export_format_specification.h"
35 #include "ardour/export_filename.h"
36 #include "ardour/export_failed.h"
46 /*** ExportElementFactory ***/
48 ExportElementFactory::ExportElementFactory (Session & session) :
54 ExportElementFactory::~ExportElementFactory ()
60 ExportElementFactory::add_timespan ()
62 return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
65 ExportChannelConfigPtr
66 ExportElementFactory::add_channel_config ()
68 return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
72 ExportElementFactory::add_format ()
74 return ExportFormatSpecPtr (new ExportFormatSpecification (session));
78 ExportElementFactory::add_format (XMLNode const & state)
80 return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
84 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
86 return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
90 ExportElementFactory::add_filename ()
92 return ExportFilenamePtr (new ExportFilename (session));
96 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
98 return ExportFilenamePtr (new ExportFilename (*other));
101 /*** ExportHandler ***/
103 ExportHandler::ExportHandler (Session & session)
104 : ExportElementFactory (session)
106 , graph_builder (new ExportGraphBuilder (session))
107 , export_status (session.get_export_status ())
109 , normalizing (false)
115 ExportHandler::~ExportHandler ()
117 // TODO remove files that were written but not finsihed
121 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
122 ExportFormatSpecPtr format, ExportFilenamePtr filename,
123 BroadcastInfoPtr broadcast_info)
125 FileSpec spec (channel_config, format, filename, broadcast_info);
126 ConfigPair pair (timespan, spec);
127 config_map.insert (pair);
133 ExportHandler::do_export (bool rt)
135 /* Count timespans */
137 export_status->init();
138 std::set<ExportTimespanPtr> timespan_set;
139 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
140 timespan_set.insert (it->first);
141 export_status->total_frames += it->first->get_length();
143 export_status->total_timespans = timespan_set.size();
152 ExportHandler::start_timespan ()
154 export_status->timespan++;
156 if (config_map.empty()) {
157 // freewheeling has to be stopped from outside the process cycle
158 export_status->running = false;
162 current_timespan = config_map.begin()->first;
164 /* Register file configurations to graph builder */
166 timespan_bounds = config_map.equal_range (current_timespan);
167 graph_builder->reset ();
168 graph_builder->set_current_timespan (current_timespan);
169 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
170 // Filenames can be shared across timespans
171 FileSpec & spec = it->second;
172 spec.filename->set_timespan (it->first);
173 graph_builder->add_config (spec);
179 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
180 process_position = current_timespan->get_start();
181 session.start_audio_export (process_position, realtime);
185 ExportHandler::process (framecnt_t frames)
187 if (!export_status->running) {
189 } else if (normalizing) {
190 return process_normalize ();
192 return process_timespan (frames);
197 ExportHandler::process_timespan (framecnt_t frames)
199 /* update position */
201 framecnt_t frames_to_read = 0;
202 framepos_t const end = current_timespan->get_end();
204 bool const last_cycle = (process_position + frames >= end);
207 frames_to_read = end - process_position;
208 export_status->stop = true;
211 frames_to_read = frames;
214 process_position += frames_to_read;
215 export_status->processed_frames += frames_to_read;
216 export_status->progress = (float) export_status->processed_frames / export_status->total_frames;
218 /* Do actual processing */
220 return graph_builder->process (frames_to_read, last_cycle);
224 ExportHandler::process_normalize ()
226 if (graph_builder->process_normalize ()) {
228 export_status->normalizing = false;
230 export_status->normalizing = true;
237 ExportHandler::finish_timespan ()
239 while (config_map.begin() != timespan_bounds.second) {
241 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
243 if (fmt->with_cue()) {
244 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerCUE);
247 if (fmt->with_toc()) {
248 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerTOC);
251 config_map.erase (config_map.begin());
257 /*** CD Marker sutff ***/
259 struct LocationSortByStart {
260 bool operator() (Location *a, Location *b) {
261 return a->start() < b->start();
266 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
267 std::string filename, CDMarkerFormat format)
271 /* do not strip file suffix because there may be more than one format,
272 and we do not want the CD marker file from one format to overwrite
273 another (e.g. foo.wav.cue > foo.aiff.cue)
276 void (ExportHandler::*header_func) (CDMarkerStatus &);
277 void (ExportHandler::*track_func) (CDMarkerStatus &);
278 void (ExportHandler::*index_func) (CDMarkerStatus &);
284 header_func = &ExportHandler::write_toc_header;
285 track_func = &ExportHandler::write_track_info_toc;
286 index_func = &ExportHandler::write_index_info_toc;
291 header_func = &ExportHandler::write_cue_header;
292 track_func = &ExportHandler::write_track_info_cue;
293 index_func = &ExportHandler::write_index_info_cue;
299 CDMarkerStatus status (filepath, timespan, file_format, filename);
302 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
306 (this->*header_func) (status);
308 /* Get locations and sort */
310 Locations::LocationList const & locations (session.locations()->list());
311 Locations::LocationList::const_iterator i;
312 Locations::LocationList temp;
314 for (i = locations.begin(); i != locations.end(); ++i) {
315 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
321 // TODO One index marker for whole thing
325 LocationSortByStart cmp;
327 Locations::LocationList::const_iterator nexti;
329 /* Start actual marker stuff */
331 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
332 status.track_position = last_start_time - timespan->get_start();
334 for (i = temp.begin(); i != temp.end(); ++i) {
338 if ((*i)->start() < last_end_time) {
339 if ((*i)->is_mark()) {
340 /* Index within track */
342 status.index_position = (*i)->start() - timespan->get_start();
343 (this->*index_func) (status);
349 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
351 status.track_position = last_end_time - timespan->get_start();
352 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
353 status.track_duration = 0;
355 if ((*i)->is_mark()) {
356 // a mark track location needs to look ahead to the next marker's start to determine length
360 if (nexti != temp.end()) {
361 status.track_duration = (*nexti)->start() - last_end_time;
363 last_start_time = (*i)->start();
364 last_end_time = (*nexti)->start();
366 // this was the last marker, use timespan end
367 status.track_duration = timespan->get_end() - last_end_time;
369 last_start_time = (*i)->start();
370 last_end_time = timespan->get_end();
374 status.track_duration = (*i)->end() - last_end_time;
376 last_start_time = (*i)->start();
377 last_end_time = (*i)->end();
380 (this->*track_func) (status);
385 ExportHandler::write_cue_header (CDMarkerStatus & status)
387 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
389 status.out << "REM Cue file generated by Ardour" << endl;
390 status.out << "TITLE \"" << title << "\"" << endl;
392 /* The cue sheet syntax has originally five file types:
393 WAVE : 44.1 kHz, 16 Bit (little endian)
394 AIFF : 44.1 kHz, 16 Bit (big endian)
395 BINARY : 44.1 kHz, 16 Bit (little endian)
396 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
399 We want to use cue sheets not only as CD images but also as general playlyist
400 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
401 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
402 and MOTOROLA we do care, because no header would tell us about a different format.
404 For all other formats we just make up our own file type. MP3 is not supported
408 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
409 if (!status.format->format_name().compare ("WAV")) {
410 status.out << "WAVE";
411 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
412 status.format->sample_format() == ExportFormatBase::SF_16 &&
413 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
414 // Format is RAW 16bit 44.1kHz
415 if (status.format->endianness() == ExportFormatBase::E_Little) {
416 status.out << "BINARY";
418 status.out << "MOTOROLA";
421 // AIFF should return "AIFF"
422 status.out << status.format->format_name();
428 ExportHandler::write_toc_header (CDMarkerStatus & status)
430 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
432 status.out << "CD_DA" << endl;
433 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
434 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
438 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
442 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
443 status.out << buf << endl;
445 status.out << " FLAGS" ;
446 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
447 status.out << " SCMS ";
449 status.out << " DCP ";
452 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
453 status.out << " PRE";
457 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
458 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
461 if (status.marker->name() != "") {
462 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
465 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
466 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
469 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
470 status.out << " SONGWRITER \"" << status.marker->cd_info["composer"] << "\"" << endl;
473 if (status.track_position != status.track_start_frame) {
474 frames_to_cd_frames_string (buf, status.track_position);
475 status.out << " INDEX 00" << buf << endl;
478 frames_to_cd_frames_string (buf, status.track_start_frame);
479 status.out << " INDEX 01" << buf << endl;
481 status.index_number = 2;
482 status.track_number++;
486 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
490 status.out << endl << "TRACK AUDIO" << endl;
492 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
495 status.out << "COPY" << endl;
497 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
498 status.out << "PRE_EMPHASIS" << endl;
500 status.out << "NO PRE_EMPHASIS" << endl;
503 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
504 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
507 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
508 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
509 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
511 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
512 status.out << " COMPOSER \"" << status.marker->cd_info["composer"] << "\"" << endl;
515 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
516 status.out << " ISRC \"";
517 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
518 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
519 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
520 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
523 status.out << " }" << endl << "}" << endl;
525 frames_to_cd_frames_string (buf, status.track_position);
526 status.out << "FILE \"" << status.filename << "\" " << buf;
528 frames_to_cd_frames_string (buf, status.track_duration);
529 status.out << buf << endl;
531 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
532 status.out << "START" << buf << endl;
536 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
540 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
542 frames_to_cd_frames_string (buf, status.index_position);
543 status.out << buf << endl;
549 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
553 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
554 status.out << "INDEX" << buf << endl;
558 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
560 framecnt_t remainder;
561 framecnt_t fr = session.nominal_frame_rate();
562 int mins, secs, frames;
564 mins = when / (60 * fr);
565 remainder = when - (mins * 60 * fr);
566 secs = remainder / fr;
567 remainder -= secs * fr;
568 frames = remainder / (fr / 75);
569 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
572 } // namespace ARDOUR