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)
269 string filepath = get_cd_marker_filename(filename, format);
271 void (ExportHandler::*header_func) (CDMarkerStatus &);
272 void (ExportHandler::*track_func) (CDMarkerStatus &);
273 void (ExportHandler::*index_func) (CDMarkerStatus &);
277 header_func = &ExportHandler::write_toc_header;
278 track_func = &ExportHandler::write_track_info_toc;
279 index_func = &ExportHandler::write_index_info_toc;
282 header_func = &ExportHandler::write_cue_header;
283 track_func = &ExportHandler::write_track_info_cue;
284 index_func = &ExportHandler::write_index_info_cue;
290 CDMarkerStatus status (filepath, timespan, file_format, filename);
293 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
297 (this->*header_func) (status);
299 /* Get locations and sort */
301 Locations::LocationList const & locations (session.locations()->list());
302 Locations::LocationList::const_iterator i;
303 Locations::LocationList temp;
305 for (i = locations.begin(); i != locations.end(); ++i) {
306 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
312 // TODO One index marker for whole thing
316 LocationSortByStart cmp;
318 Locations::LocationList::const_iterator nexti;
320 /* Start actual marker stuff */
322 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
323 status.track_position = last_start_time - timespan->get_start();
325 for (i = temp.begin(); i != temp.end(); ++i) {
329 if ((*i)->start() < last_end_time) {
330 if ((*i)->is_mark()) {
331 /* Index within track */
333 status.index_position = (*i)->start() - timespan->get_start();
334 (this->*index_func) (status);
340 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
342 status.track_position = last_end_time - timespan->get_start();
343 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
344 status.track_duration = 0;
346 if ((*i)->is_mark()) {
347 // a mark track location needs to look ahead to the next marker's start to determine length
351 if (nexti != temp.end()) {
352 status.track_duration = (*nexti)->start() - last_end_time;
354 last_start_time = (*i)->start();
355 last_end_time = (*nexti)->start();
357 // this was the last marker, use timespan end
358 status.track_duration = timespan->get_end() - last_end_time;
360 last_start_time = (*i)->start();
361 last_end_time = timespan->get_end();
365 status.track_duration = (*i)->end() - last_end_time;
367 last_start_time = (*i)->start();
368 last_end_time = (*i)->end();
371 (this->*track_func) (status);
376 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
378 /* do not strip file suffix because there may be more than one format,
379 and we do not want the CD marker file from one format to overwrite
380 another (e.g. foo.wav.cue > foo.aiff.cue)
385 return filename + ".toc";
387 return filename + ".cue";
389 return filename + ".marker"; // Should not be reached when actually creating a file
394 ExportHandler::write_cue_header (CDMarkerStatus & status)
396 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
398 status.out << "REM Cue file generated by Ardour" << endl;
399 status.out << "TITLE \"" << title << "\"" << endl;
401 /* The cue sheet syntax has originally five file types:
402 WAVE : 44.1 kHz, 16 Bit (little endian)
403 AIFF : 44.1 kHz, 16 Bit (big endian)
404 BINARY : 44.1 kHz, 16 Bit (little endian)
405 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
408 We want to use cue sheets not only as CD images but also as general playlyist
409 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
410 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
411 and MOTOROLA we do care, because no header would tell us about a different format.
413 For all other formats we just make up our own file type. MP3 is not supported
417 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
418 if (!status.format->format_name().compare ("WAV")) {
419 status.out << "WAVE";
420 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
421 status.format->sample_format() == ExportFormatBase::SF_16 &&
422 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
423 // Format is RAW 16bit 44.1kHz
424 if (status.format->endianness() == ExportFormatBase::E_Little) {
425 status.out << "BINARY";
427 status.out << "MOTOROLA";
430 // AIFF should return "AIFF"
431 status.out << status.format->format_name();
437 ExportHandler::write_toc_header (CDMarkerStatus & status)
439 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
441 status.out << "CD_DA" << endl;
442 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
443 status.out << " LANGUAGE 0 {" << endl << " TITLE " << toc_escape_string (title) << endl ;
444 status.out << " PERFORMER \"\"" << endl << " }" << endl << "}" << endl;
448 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
452 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
453 status.out << buf << endl;
455 status.out << " FLAGS" ;
456 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
457 status.out << " SCMS ";
459 status.out << " DCP ";
462 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
463 status.out << " PRE";
467 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
468 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
471 if (status.marker->name() != "") {
472 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
475 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
476 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
479 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
480 status.out << " SONGWRITER \"" << status.marker->cd_info["composer"] << "\"" << endl;
483 if (status.track_position != status.track_start_frame) {
484 frames_to_cd_frames_string (buf, status.track_position);
485 status.out << " INDEX 00" << buf << endl;
488 frames_to_cd_frames_string (buf, status.track_start_frame);
489 status.out << " INDEX 01" << buf << endl;
491 status.index_number = 2;
492 status.track_number++;
496 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
500 status.out << endl << "TRACK AUDIO" << endl;
502 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
505 status.out << "COPY" << endl;
507 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
508 status.out << "PRE_EMPHASIS" << endl;
510 status.out << "NO PRE_EMPHASIS" << endl;
513 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
514 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
517 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE "
518 << toc_escape_string (status.marker->name()) << endl;
520 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
521 status.out << " PERFORMER " << toc_escape_string (status.marker->cd_info["performer"]);
523 status.out << " PERFORMER \"\"";
526 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
527 status.out << " COMPOSER " << toc_escape_string (status.marker->cd_info["composer"]) << endl;
530 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
531 status.out << " ISRC \"";
532 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
533 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
534 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
535 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
538 status.out << " }" << endl << "}" << endl;
540 frames_to_cd_frames_string (buf, status.track_position);
541 status.out << "FILE " << toc_escape_string (status.filename) << ' ' << buf;
543 frames_to_cd_frames_string (buf, status.track_duration);
544 status.out << buf << endl;
546 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
547 status.out << "START" << buf << endl;
551 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
555 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
557 frames_to_cd_frames_string (buf, status.index_position);
558 status.out << buf << endl;
564 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
568 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
569 status.out << "INDEX" << buf << endl;
573 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
575 framecnt_t remainder;
576 framecnt_t fr = session.nominal_frame_rate();
577 int mins, secs, frames;
579 mins = when / (60 * fr);
580 remainder = when - (mins * 60 * fr);
581 secs = remainder / fr;
582 remainder -= secs * fr;
583 frames = remainder / (fr / 75);
584 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
588 ExportHandler::toc_escape_string (const std::string& txt)
590 Glib::ustring utxt (txt);
596 for (Glib::ustring::iterator c = utxt.begin(); c != utxt.end(); ++c) {
600 } else if (g_unichar_isprint (*c)) {
603 /* this isn't really correct */
604 snprintf (buf, sizeof (buf), "\\%03o", *c);
611 return std::string (out);
614 } // namespace ARDOUR