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 timespan_set.insert (it->first);
142 export_status->total_frames += it->first->get_length();
144 export_status->total_timespans = timespan_set.size();
153 ExportHandler::start_timespan ()
155 export_status->timespan++;
157 if (config_map.empty()) {
158 // freewheeling has to be stopped from outside the process cycle
159 export_status->running = false;
163 current_timespan = config_map.begin()->first;
165 /* Register file configurations to graph builder */
167 timespan_bounds = config_map.equal_range (current_timespan);
168 graph_builder->reset ();
169 graph_builder->set_current_timespan (current_timespan);
170 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
171 // Filenames can be shared across timespans
172 FileSpec & spec = it->second;
173 spec.filename->set_timespan (it->first);
174 graph_builder->add_config (spec);
180 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
181 process_position = current_timespan->get_start();
182 session.start_audio_export (process_position, realtime);
186 ExportHandler::process (framecnt_t frames)
188 if (!export_status->running) {
190 } else if (normalizing) {
191 return process_normalize ();
193 return process_timespan (frames);
198 ExportHandler::process_timespan (framecnt_t frames)
200 /* update position */
202 framecnt_t frames_to_read = 0;
203 framepos_t const end = current_timespan->get_end();
205 bool const last_cycle = (process_position + frames >= end);
208 frames_to_read = end - process_position;
209 export_status->stop = true;
212 frames_to_read = frames;
215 process_position += frames_to_read;
216 export_status->processed_frames += frames_to_read;
217 export_status->progress = (float) export_status->processed_frames / export_status->total_frames;
219 /* Do actual processing */
221 return graph_builder->process (frames_to_read, last_cycle);
225 ExportHandler::process_normalize ()
227 if (graph_builder->process_normalize ()) {
229 export_status->normalizing = false;
231 export_status->normalizing = true;
238 ExportHandler::finish_timespan ()
240 while (config_map.begin() != timespan_bounds.second) {
242 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
244 if (fmt->with_cue()) {
245 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerCUE);
248 if (fmt->with_toc()) {
249 export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerTOC);
252 config_map.erase (config_map.begin());
258 /*** CD Marker sutff ***/
260 struct LocationSortByStart {
261 bool operator() (Location *a, Location *b) {
262 return a->start() < b->start();
267 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
268 std::string filename, CDMarkerFormat format)
270 string filepath = get_cd_marker_filename(filename, format);
273 void (ExportHandler::*header_func) (CDMarkerStatus &);
274 void (ExportHandler::*track_func) (CDMarkerStatus &);
275 void (ExportHandler::*index_func) (CDMarkerStatus &);
279 header_func = &ExportHandler::write_toc_header;
280 track_func = &ExportHandler::write_track_info_toc;
281 index_func = &ExportHandler::write_index_info_toc;
284 header_func = &ExportHandler::write_cue_header;
285 track_func = &ExportHandler::write_track_info_cue;
286 index_func = &ExportHandler::write_index_info_cue;
292 CDMarkerStatus status (filepath, timespan, file_format, filename);
295 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
299 (this->*header_func) (status);
301 /* Get locations and sort */
303 Locations::LocationList const & locations (session.locations()->list());
304 Locations::LocationList::const_iterator i;
305 Locations::LocationList temp;
307 for (i = locations.begin(); i != locations.end(); ++i) {
308 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
314 // TODO One index marker for whole thing
318 LocationSortByStart cmp;
320 Locations::LocationList::const_iterator nexti;
322 /* Start actual marker stuff */
324 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
325 status.track_position = last_start_time - timespan->get_start();
327 for (i = temp.begin(); i != temp.end(); ++i) {
331 if ((*i)->start() < last_end_time) {
332 if ((*i)->is_mark()) {
333 /* Index within track */
335 status.index_position = (*i)->start() - timespan->get_start();
336 (this->*index_func) (status);
342 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
344 status.track_position = last_end_time - timespan->get_start();
345 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
346 status.track_duration = 0;
348 if ((*i)->is_mark()) {
349 // a mark track location needs to look ahead to the next marker's start to determine length
353 if (nexti != temp.end()) {
354 status.track_duration = (*nexti)->start() - last_end_time;
356 last_start_time = (*i)->start();
357 last_end_time = (*nexti)->start();
359 // this was the last marker, use timespan end
360 status.track_duration = timespan->get_end() - last_end_time;
362 last_start_time = (*i)->start();
363 last_end_time = timespan->get_end();
367 status.track_duration = (*i)->end() - last_end_time;
369 last_start_time = (*i)->start();
370 last_end_time = (*i)->end();
373 (this->*track_func) (status);
376 } catch (std::exception& e) {
377 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
378 ::unlink (filepath.c_str());
379 } catch (Glib::Exception& e) {
380 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
381 ::unlink (filepath.c_str());
386 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
388 /* do not strip file suffix because there may be more than one format,
389 and we do not want the CD marker file from one format to overwrite
390 another (e.g. foo.wav.cue > foo.aiff.cue)
395 return filename + ".toc";
397 return filename + ".cue";
399 return filename + ".marker"; // Should not be reached when actually creating a file
404 ExportHandler::write_cue_header (CDMarkerStatus & status)
406 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
408 status.out << "REM Cue file generated by Ardour" << endl;
409 status.out << "TITLE \"" << title << "\"" << endl;
411 /* The cue sheet syntax has originally five file types:
412 WAVE : 44.1 kHz, 16 Bit (little endian)
413 AIFF : 44.1 kHz, 16 Bit (big endian)
414 BINARY : 44.1 kHz, 16 Bit (little endian)
415 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
418 We want to use cue sheets not only as CD images but also as general playlyist
419 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
420 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
421 and MOTOROLA we do care, because no header would tell us about a different format.
423 For all other formats we just make up our own file type. MP3 is not supported
427 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
428 if (!status.format->format_name().compare ("WAV")) {
429 status.out << "WAVE";
430 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
431 status.format->sample_format() == ExportFormatBase::SF_16 &&
432 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
433 // Format is RAW 16bit 44.1kHz
434 if (status.format->endianness() == ExportFormatBase::E_Little) {
435 status.out << "BINARY";
437 status.out << "MOTOROLA";
440 // AIFF should return "AIFF"
441 status.out << status.format->format_name();
447 ExportHandler::write_toc_header (CDMarkerStatus & status)
449 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
451 status.out << "CD_DA" << endl;
452 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
453 status.out << " LANGUAGE 0 {" << endl << " TITLE " << cd_marker_file_escape_string (title) << endl ;
454 status.out << " PERFORMER \"\"" << endl << " }" << endl << "}" << endl;
458 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
462 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
463 status.out << buf << endl;
465 status.out << " FLAGS" ;
466 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
467 status.out << " SCMS ";
469 status.out << " DCP ";
472 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
473 status.out << " PRE";
477 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
478 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
481 if (status.marker->name() != "") {
482 status.out << " TITLE " << cd_marker_file_escape_string (status.marker->name()) << endl;
485 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
486 status.out << " PERFORMER " << cd_marker_file_escape_string (status.marker->cd_info["performer"]) << endl;
489 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
490 status.out << " SONGWRITER " << cd_marker_file_escape_string (status.marker->cd_info["composer"]) << endl;
493 if (status.track_position != status.track_start_frame) {
494 frames_to_cd_frames_string (buf, status.track_position);
495 status.out << " INDEX 00" << buf << endl;
498 frames_to_cd_frames_string (buf, status.track_start_frame);
499 status.out << " INDEX 01" << buf << endl;
501 status.index_number = 2;
502 status.track_number++;
506 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
510 status.out << endl << "TRACK AUDIO" << endl;
512 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
515 status.out << "COPY" << endl;
517 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
518 status.out << "PRE_EMPHASIS" << endl;
520 status.out << "NO PRE_EMPHASIS" << endl;
523 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
524 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
527 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE "
528 << cd_marker_file_escape_string (status.marker->name()) << endl;
530 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
531 status.out << " PERFORMER " << cd_marker_file_escape_string (status.marker->cd_info["performer"]);
533 status.out << " PERFORMER \"\"";
536 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
537 status.out << " COMPOSER " << cd_marker_file_escape_string (status.marker->cd_info["composer"]) << endl;
540 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
541 status.out << " ISRC \"";
542 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
543 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
544 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
545 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
548 status.out << " }" << endl << "}" << endl;
550 frames_to_cd_frames_string (buf, status.track_position);
551 status.out << "FILE " << cd_marker_file_escape_string (status.filename) << ' ' << buf;
553 frames_to_cd_frames_string (buf, status.track_duration);
554 status.out << buf << endl;
556 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
557 status.out << "START" << buf << endl;
561 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
565 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
567 frames_to_cd_frames_string (buf, status.index_position);
568 status.out << buf << endl;
574 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
578 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
579 status.out << "INDEX" << buf << endl;
583 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
585 framecnt_t remainder;
586 framecnt_t fr = session.nominal_frame_rate();
587 int mins, secs, frames;
589 mins = when / (60 * fr);
590 remainder = when - (mins * 60 * fr);
591 secs = remainder / fr;
592 remainder -= secs * fr;
593 frames = remainder / (fr / 75);
594 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
598 ExportHandler::cd_marker_file_escape_string (const std::string& txt)
600 Glib::ustring check (txt);
604 if (!check.is_ascii()) {
605 throw Glib::ConvertError (Glib::ConvertError::NO_CONVERSION, string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
610 for (std::string::const_iterator c = txt.begin(); c != txt.end(); ++c) {
614 } else if ((*c) == '\\') {
616 } else if (isprint (*c)) {
619 snprintf (buf, sizeof (buf), "\\%03o", *c);
629 } // namespace ARDOUR