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/ardour.h"
30 #include "ardour/configuration.h"
31 #include "ardour/export_graph_builder.h"
32 #include "ardour/export_timespan.h"
33 #include "ardour/export_channel_configuration.h"
34 #include "ardour/export_status.h"
35 #include "ardour/export_format_specification.h"
36 #include "ardour/export_filename.h"
37 #include "ardour/export_failed.h"
47 /*** ExportElementFactory ***/
49 ExportElementFactory::ExportElementFactory (Session & session) :
55 ExportElementFactory::~ExportElementFactory ()
61 ExportElementFactory::add_timespan ()
63 return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
66 ExportChannelConfigPtr
67 ExportElementFactory::add_channel_config ()
69 return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
73 ExportElementFactory::add_format ()
75 return ExportFormatSpecPtr (new ExportFormatSpecification (session));
79 ExportElementFactory::add_format (XMLNode const & state)
81 return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
85 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
87 return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
91 ExportElementFactory::add_filename ()
93 return ExportFilenamePtr (new ExportFilename (session));
97 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
99 return ExportFilenamePtr (new ExportFilename (*other));
102 /*** ExportHandler ***/
104 ExportHandler::ExportHandler (Session & session)
105 : ExportElementFactory (session)
107 , graph_builder (new ExportGraphBuilder (session))
108 , export_status (session.get_export_status ())
110 , normalizing (false)
116 ExportHandler::~ExportHandler ()
118 // TODO remove files that were written but not finsihed
122 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
123 ExportFormatSpecPtr format, ExportFilenamePtr filename,
124 BroadcastInfoPtr broadcast_info)
126 FileSpec spec (channel_config, format, filename, broadcast_info);
127 ConfigPair pair (timespan, spec);
128 config_map.insert (pair);
134 ExportHandler::do_export (bool rt)
136 /* Count timespans */
138 export_status->init();
139 std::set<ExportTimespanPtr> timespan_set;
140 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
141 bool new_timespan = timespan_set.insert (it->first).second;
143 export_status->total_frames += it->first->get_length();
146 export_status->total_timespans = timespan_set.size();
155 ExportHandler::start_timespan ()
157 export_status->timespan++;
159 if (config_map.empty()) {
160 // freewheeling has to be stopped from outside the process cycle
161 export_status->running = false;
165 current_timespan = config_map.begin()->first;
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->progress = (float) export_status->processed_frames /
219 export_status->total_frames;
221 /* Do actual processing */
222 int ret = graph_builder->process (frames_to_read, last_cycle);
224 /* Start normalizing if necessary */
226 normalizing = graph_builder->will_normalize();
228 export_status->total_normalize_cycles = graph_builder->get_normalize_cycle_count();
229 export_status->current_normalize_cycle = 0;
240 ExportHandler::process_normalize ()
242 if (graph_builder->process_normalize ()) {
244 export_status->normalizing = false;
246 export_status->normalizing = true;
249 export_status->progress = (float) export_status->current_normalize_cycle /
250 export_status->total_normalize_cycles;
251 export_status->current_normalize_cycle++;
257 ExportHandler::finish_timespan ()
259 while (config_map.begin() != timespan_bounds.second) {
261 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
263 if (fmt->with_cue()) {
264 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerCUE);
267 if (fmt->with_toc()) {
268 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerTOC);
271 config_map.erase (config_map.begin());
277 /*** CD Marker sutff ***/
279 struct LocationSortByStart {
280 bool operator() (Location *a, Location *b) {
281 return a->start() < b->start();
286 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
287 std::string filename, CDMarkerFormat format)
289 string filepath = get_cd_marker_filename(filename, format);
292 void (ExportHandler::*header_func) (CDMarkerStatus &);
293 void (ExportHandler::*track_func) (CDMarkerStatus &);
294 void (ExportHandler::*index_func) (CDMarkerStatus &);
298 header_func = &ExportHandler::write_toc_header;
299 track_func = &ExportHandler::write_track_info_toc;
300 index_func = &ExportHandler::write_index_info_toc;
303 header_func = &ExportHandler::write_cue_header;
304 track_func = &ExportHandler::write_track_info_cue;
305 index_func = &ExportHandler::write_index_info_cue;
311 CDMarkerStatus status (filepath, timespan, file_format, filename);
314 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
318 (this->*header_func) (status);
320 /* Get locations and sort */
322 Locations::LocationList const & locations (session.locations()->list());
323 Locations::LocationList::const_iterator i;
324 Locations::LocationList temp;
326 for (i = locations.begin(); i != locations.end(); ++i) {
327 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
333 // TODO One index marker for whole thing
337 LocationSortByStart cmp;
339 Locations::LocationList::const_iterator nexti;
341 /* Start actual marker stuff */
343 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
344 status.track_position = last_start_time - timespan->get_start();
346 for (i = temp.begin(); i != temp.end(); ++i) {
350 if ((*i)->start() < last_end_time) {
351 if ((*i)->is_mark()) {
352 /* Index within track */
354 status.index_position = (*i)->start() - timespan->get_start();
355 (this->*index_func) (status);
361 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
363 status.track_position = last_end_time - timespan->get_start();
364 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
365 status.track_duration = 0;
367 if ((*i)->is_mark()) {
368 // a mark track location needs to look ahead to the next marker's start to determine length
372 if (nexti != temp.end()) {
373 status.track_duration = (*nexti)->start() - last_end_time;
375 last_start_time = (*i)->start();
376 last_end_time = (*nexti)->start();
378 // this was the last marker, use timespan end
379 status.track_duration = timespan->get_end() - last_end_time;
381 last_start_time = (*i)->start();
382 last_end_time = timespan->get_end();
386 status.track_duration = (*i)->end() - last_end_time;
388 last_start_time = (*i)->start();
389 last_end_time = (*i)->end();
392 (this->*track_func) (status);
395 } catch (std::exception& e) {
396 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
397 ::unlink (filepath.c_str());
398 } catch (Glib::Exception& e) {
399 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
400 ::unlink (filepath.c_str());
405 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
407 /* do not strip file suffix because there may be more than one format,
408 and we do not want the CD marker file from one format to overwrite
409 another (e.g. foo.wav.cue > foo.aiff.cue)
414 return filename + ".toc";
416 return filename + ".cue";
418 return filename + ".marker"; // Should not be reached when actually creating a file
423 ExportHandler::write_cue_header (CDMarkerStatus & status)
425 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
427 status.out << "REM Cue file generated by Ardour" << endl;
428 status.out << "TITLE \"" << title << "\"" << endl;
430 /* The cue sheet syntax has originally five file types:
431 WAVE : 44.1 kHz, 16 Bit (little endian)
432 AIFF : 44.1 kHz, 16 Bit (big endian)
433 BINARY : 44.1 kHz, 16 Bit (little endian)
434 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
437 We want to use cue sheets not only as CD images but also as general playlyist
438 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
439 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
440 and MOTOROLA we do care, because no header would tell us about a different format.
442 For all other formats we just make up our own file type. MP3 is not supported
446 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
447 if (!status.format->format_name().compare ("WAV")) {
448 status.out << "WAVE";
449 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
450 status.format->sample_format() == ExportFormatBase::SF_16 &&
451 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
452 // Format is RAW 16bit 44.1kHz
453 if (status.format->endianness() == ExportFormatBase::E_Little) {
454 status.out << "BINARY";
456 status.out << "MOTOROLA";
459 // AIFF should return "AIFF"
460 status.out << status.format->format_name();
466 ExportHandler::write_toc_header (CDMarkerStatus & status)
468 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
470 status.out << "CD_DA" << endl;
471 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
472 status.out << " LANGUAGE 0 {" << endl << " TITLE " << cd_marker_file_escape_string (title) << endl ;
473 status.out << " PERFORMER \"\"" << endl << " }" << endl << "}" << endl;
477 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
481 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
482 status.out << buf << endl;
484 status.out << " FLAGS" ;
485 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
486 status.out << " SCMS ";
488 status.out << " DCP ";
491 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
492 status.out << " PRE";
496 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
497 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
501 if (status.marker->name() != "") {
502 status.out << " TITLE \"" << status.marker->name() << '"' << endl;
505 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
506 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << '"' << endl;
508 status.out << " PERFORMER \"\"" << endl;
511 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
512 status.out << " SONGWRITER \"" << status.marker->cd_info["composer"] << '"' << endl;
515 if (status.track_position != status.track_start_frame) {
516 frames_to_cd_frames_string (buf, status.track_position);
517 status.out << " INDEX 00" << buf << endl;
520 frames_to_cd_frames_string (buf, status.track_start_frame);
521 status.out << " INDEX 01" << buf << endl;
523 status.index_number = 2;
524 status.track_number++;
528 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
532 status.out << endl << "TRACK AUDIO" << endl;
534 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
537 status.out << "COPY" << endl;
539 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
540 status.out << "PRE_EMPHASIS" << endl;
542 status.out << "NO PRE_EMPHASIS" << endl;
545 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
546 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
549 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE "
550 << cd_marker_file_escape_string (status.marker->name()) << endl;
552 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
553 status.out << cd_marker_file_escape_string (status.marker->cd_info["performer"]);
555 status.out << " PERFORMER \"\"";
558 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
559 status.out << " COMPOSER " << cd_marker_file_escape_string (status.marker->cd_info["composer"]) << endl;
562 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
563 status.out << " ISRC \"";
564 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
565 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
566 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
567 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
570 status.out << " }" << endl << "}" << endl;
572 frames_to_cd_frames_string (buf, status.track_position);
573 status.out << "FILE " << cd_marker_file_escape_string (status.filename) << ' ' << buf;
575 frames_to_cd_frames_string (buf, status.track_duration);
576 status.out << buf << endl;
578 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
579 status.out << "START" << buf << endl;
583 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
587 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
589 frames_to_cd_frames_string (buf, status.index_position);
590 status.out << buf << endl;
596 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
600 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
601 status.out << "INDEX" << buf << endl;
605 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
607 framecnt_t remainder;
608 framecnt_t fr = session.nominal_frame_rate();
609 int mins, secs, frames;
611 mins = when / (60 * fr);
612 remainder = when - (mins * 60 * fr);
613 secs = remainder / fr;
614 remainder -= secs * fr;
615 frames = remainder / (fr / 75);
616 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
620 ExportHandler::cd_marker_file_escape_string (const std::string& txt)
622 Glib::ustring check (txt);
624 std::string latin1_txt;
628 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
630 throw Glib::ConvertError (Glib::ConvertError::NO_CONVERSION, string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
635 for (std::string::const_iterator c = latin1_txt.begin(); c != latin1_txt.end(); ++c) {
639 } else if ((*c) == '\\') {
641 } else if (isprint (*c)) {
644 snprintf (buf, sizeof (buf), "\\%03o", (int) (unsigned char) *c);
654 } // namespace ARDOUR