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"
24 #include <glibmm/convert.h>
26 #include "pbd/convert.h"
27 #include "pbd/filesystem.h"
29 #include "ardour/export_graph_builder.h"
30 #include "ardour/export_timespan.h"
31 #include "ardour/export_channel_configuration.h"
32 #include "ardour/export_status.h"
33 #include "ardour/export_format_specification.h"
34 #include "ardour/export_filename.h"
44 /*** ExportElementFactory ***/
46 ExportElementFactory::ExportElementFactory (Session & session) :
52 ExportElementFactory::~ExportElementFactory ()
58 ExportElementFactory::add_timespan ()
60 return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
63 ExportChannelConfigPtr
64 ExportElementFactory::add_channel_config ()
66 return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
70 ExportElementFactory::add_format ()
72 return ExportFormatSpecPtr (new ExportFormatSpecification (session));
76 ExportElementFactory::add_format (XMLNode const & state)
78 return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
82 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
84 return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
88 ExportElementFactory::add_filename ()
90 return ExportFilenamePtr (new ExportFilename (session));
94 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
96 return ExportFilenamePtr (new ExportFilename (*other));
99 /*** ExportHandler ***/
101 ExportHandler::ExportHandler (Session & session)
102 : ExportElementFactory (session)
104 , graph_builder (new ExportGraphBuilder (session))
105 , export_status (session.get_export_status ())
107 , normalizing (false)
113 ExportHandler::~ExportHandler ()
115 // TODO remove files that were written but not finsihed
119 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
120 ExportFormatSpecPtr format, ExportFilenamePtr filename,
121 BroadcastInfoPtr broadcast_info)
123 FileSpec spec (channel_config, format, filename, broadcast_info);
124 ConfigPair pair (timespan, spec);
125 config_map.insert (pair);
131 ExportHandler::do_export (bool rt)
133 /* Count timespans */
135 export_status->init();
136 std::set<ExportTimespanPtr> timespan_set;
137 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
138 bool new_timespan = timespan_set.insert (it->first).second;
140 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;
163 export_status->total_frames_current_timespan = current_timespan->get_length();
164 export_status->timespan_name = current_timespan->name();
165 export_status->processed_frames_current_timespan = 0;
167 /* Register file configurations to graph builder */
169 timespan_bounds = config_map.equal_range (current_timespan);
170 graph_builder->reset ();
171 graph_builder->set_current_timespan (current_timespan);
172 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
173 // Filenames can be shared across timespans
174 FileSpec & spec = it->second;
175 spec.filename->set_timespan (it->first);
176 graph_builder->add_config (spec);
182 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
183 process_position = current_timespan->get_start();
184 session.start_audio_export (process_position, realtime);
188 ExportHandler::process (framecnt_t frames)
190 if (!export_status->running) {
192 } else if (normalizing) {
193 return process_normalize ();
195 return process_timespan (frames);
200 ExportHandler::process_timespan (framecnt_t frames)
202 /* update position */
204 framecnt_t frames_to_read = 0;
205 framepos_t const end = current_timespan->get_end();
207 bool const last_cycle = (process_position + frames >= end);
210 frames_to_read = end - process_position;
211 export_status->stop = true;
213 frames_to_read = frames;
216 process_position += frames_to_read;
217 export_status->processed_frames += frames_to_read;
218 export_status->processed_frames_current_timespan += frames_to_read;
220 /* Do actual processing */
221 int ret = graph_builder->process (frames_to_read, last_cycle);
223 /* Start normalizing if necessary */
225 normalizing = graph_builder->will_normalize();
227 export_status->total_normalize_cycles = graph_builder->get_normalize_cycle_count();
228 export_status->current_normalize_cycle = 0;
239 ExportHandler::process_normalize ()
241 if (graph_builder->process_normalize ()) {
243 export_status->normalizing = false;
245 export_status->normalizing = true;
248 export_status->current_normalize_cycle++;
254 ExportHandler::finish_timespan ()
256 while (config_map.begin() != timespan_bounds.second) {
258 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
260 if (fmt->with_cue()) {
261 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerCUE);
264 if (fmt->with_toc()) {
265 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerTOC);
268 config_map.erase (config_map.begin());
274 /*** CD Marker sutff ***/
276 struct LocationSortByStart {
277 bool operator() (Location *a, Location *b) {
278 return a->start() < b->start();
283 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
284 std::string filename, CDMarkerFormat format)
286 string filepath = get_cd_marker_filename(filename, format);
289 void (ExportHandler::*header_func) (CDMarkerStatus &);
290 void (ExportHandler::*track_func) (CDMarkerStatus &);
291 void (ExportHandler::*index_func) (CDMarkerStatus &);
295 header_func = &ExportHandler::write_toc_header;
296 track_func = &ExportHandler::write_track_info_toc;
297 index_func = &ExportHandler::write_index_info_toc;
300 header_func = &ExportHandler::write_cue_header;
301 track_func = &ExportHandler::write_track_info_cue;
302 index_func = &ExportHandler::write_index_info_cue;
308 CDMarkerStatus status (filepath, timespan, file_format, filename);
311 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
315 (this->*header_func) (status);
317 /* Get locations and sort */
319 Locations::LocationList const & locations (session.locations()->list());
320 Locations::LocationList::const_iterator i;
321 Locations::LocationList temp;
323 for (i = locations.begin(); i != locations.end(); ++i) {
324 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
330 // TODO One index marker for whole thing
334 LocationSortByStart cmp;
336 Locations::LocationList::const_iterator nexti;
338 /* Start actual marker stuff */
340 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
341 status.track_position = last_start_time - timespan->get_start();
343 for (i = temp.begin(); i != temp.end(); ++i) {
347 if ((*i)->start() < last_end_time) {
348 if ((*i)->is_mark()) {
349 /* Index within track */
351 status.index_position = (*i)->start() - timespan->get_start();
352 (this->*index_func) (status);
358 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
360 status.track_position = last_end_time - timespan->get_start();
361 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
362 status.track_duration = 0;
364 if ((*i)->is_mark()) {
365 // a mark track location needs to look ahead to the next marker's start to determine length
369 if (nexti != temp.end()) {
370 status.track_duration = (*nexti)->start() - last_end_time;
372 last_start_time = (*i)->start();
373 last_end_time = (*nexti)->start();
375 // this was the last marker, use timespan end
376 status.track_duration = timespan->get_end() - last_end_time;
378 last_start_time = (*i)->start();
379 last_end_time = timespan->get_end();
383 status.track_duration = (*i)->end() - last_end_time;
385 last_start_time = (*i)->start();
386 last_end_time = (*i)->end();
389 (this->*track_func) (status);
392 } catch (std::exception& e) {
393 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
394 ::unlink (filepath.c_str());
395 } catch (Glib::Exception& e) {
396 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
397 ::unlink (filepath.c_str());
402 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
404 /* do not strip file suffix because there may be more than one format,
405 and we do not want the CD marker file from one format to overwrite
406 another (e.g. foo.wav.cue > foo.aiff.cue)
411 return filename + ".toc";
413 return filename + ".cue";
415 return filename + ".marker"; // Should not be reached when actually creating a file
420 ExportHandler::write_cue_header (CDMarkerStatus & status)
422 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
424 status.out << "REM Cue file generated by " << PROGRAM_NAME << endl;
425 status.out << "TITLE " << cue_escape_cdtext (title) << endl;
427 /* The original cue sheet sepc metions five file types
429 BINARY = "header-less" audio (44.1 kHz, 16 Bit, little endian),
430 MOTOROLA = "header-less" audio (44.1 kHz, 16 Bit, big endian),
433 We try to use these file types whenever appropriate and
434 default to our own names otherwise.
436 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
437 if (!status.format->format_name().compare ("WAV") || !status.format->format_name().compare ("BWF")) {
438 status.out << "WAVE";
439 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
440 status.format->sample_format() == ExportFormatBase::SF_16 &&
441 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
442 // Format is RAW 16bit 44.1kHz
443 if (status.format->endianness() == ExportFormatBase::E_Little) {
444 status.out << "BINARY";
446 status.out << "MOTOROLA";
449 // no special case for AIFF format it's name is already "AIFF"
450 status.out << status.format->format_name();
456 ExportHandler::write_toc_header (CDMarkerStatus & status)
458 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
460 status.out << "CD_DA" << endl;
461 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
462 status.out << " LANGUAGE 0 {" << endl << " TITLE " << toc_escape_cdtext (title) << endl ;
463 status.out << " PERFORMER \"\"" << endl << " }" << endl << "}" << endl;
467 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
471 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
472 status.out << buf << endl;
474 status.out << " FLAGS" ;
475 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
476 status.out << " SCMS ";
478 status.out << " DCP ";
481 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
482 status.out << " PRE";
486 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
487 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
490 if (status.marker->name() != "") {
491 status.out << " TITLE " << cue_escape_cdtext (status.marker->name()) << endl;
494 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
495 status.out << " PERFORMER " << cue_escape_cdtext (status.marker->cd_info["performer"]) << endl;
498 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
499 status.out << " SONGWRITER " << cue_escape_cdtext (status.marker->cd_info["composer"]) << endl;
502 if (status.track_position != status.track_start_frame) {
503 frames_to_cd_frames_string (buf, status.track_position);
504 status.out << " INDEX 00" << buf << endl;
507 frames_to_cd_frames_string (buf, status.track_start_frame);
508 status.out << " INDEX 01" << buf << endl;
510 status.index_number = 2;
511 status.track_number++;
515 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
519 status.out << endl << "TRACK AUDIO" << endl;
521 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
524 status.out << "COPY" << endl;
526 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
527 status.out << "PRE_EMPHASIS" << endl;
529 status.out << "NO PRE_EMPHASIS" << endl;
532 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
533 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
536 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl;
537 status.out << " TITLE " << toc_escape_cdtext (status.marker->name()) << endl;
539 status.out << " PERFORMER ";
540 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
541 status.out << toc_escape_cdtext (status.marker->cd_info["performer"]) << endl;
543 status.out << "\"\"" << endl;
546 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
547 status.out << " SONGWRITER " << toc_escape_cdtext (status.marker->cd_info["composer"]) << endl;
550 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
551 status.out << " ISRC \"";
552 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
553 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
554 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
555 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
558 status.out << " }" << endl << "}" << endl;
560 frames_to_cd_frames_string (buf, status.track_position);
561 status.out << "FILE " << toc_escape_filename (status.filename) << ' ' << buf;
563 frames_to_cd_frames_string (buf, status.track_duration);
564 status.out << buf << endl;
566 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
567 status.out << "START" << buf << endl;
571 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
575 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
577 frames_to_cd_frames_string (buf, status.index_position);
578 status.out << buf << endl;
584 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
588 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
589 status.out << "INDEX" << buf << endl;
593 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
595 framecnt_t remainder;
596 framecnt_t fr = session.nominal_frame_rate();
597 int mins, secs, frames;
599 mins = when / (60 * fr);
600 remainder = when - (mins * 60 * fr);
601 secs = remainder / fr;
602 remainder -= secs * fr;
603 frames = remainder / (fr / 75);
604 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
608 ExportHandler::toc_escape_cdtext (const std::string& txt)
610 Glib::ustring check (txt);
612 std::string latin1_txt;
616 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
617 } catch (Glib::ConvertError& err) {
618 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
623 for (std::string::const_iterator c = latin1_txt.begin(); c != latin1_txt.end(); ++c) {
627 } else if ((*c) == '\\') {
629 } else if (isprint (*c)) {
632 snprintf (buf, sizeof (buf), "\\%03o", (int) (unsigned char) *c);
643 ExportHandler::toc_escape_filename (const std::string& txt)
649 // We iterate byte-wise not character-wise over a UTF-8 string here,
650 // because we only want to translate backslashes and double quotes
651 for (std::string::const_iterator c = txt.begin(); c != txt.end(); ++c) {
655 } else if (*c == '\\') {
668 ExportHandler::cue_escape_cdtext (const std::string& txt)
670 std::string latin1_txt;
674 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
675 } catch (Glib::ConvertError& err) {
676 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
679 // does not do much mor than UTF-8 to Latin1 translation yet, but
680 // that may have to change if cue parsers in burning programs change
681 out = '"' + latin1_txt + '"';
686 } // namespace ARDOUR