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) {
240 config_map.erase (config_map.begin());
246 /*** CD Marker sutff ***/
248 struct LocationSortByStart {
249 bool operator() (Location *a, Location *b) {
250 return a->start() < b->start();
255 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
256 std::string filename, CDMarkerFormat format)
259 string basename = Glib::path_get_basename(filename);
261 size_t ext_pos = basename.rfind('.');
262 if (ext_pos != string::npos) {
263 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
266 void (ExportHandler::*header_func) (CDMarkerStatus &);
267 void (ExportHandler::*track_func) (CDMarkerStatus &);
268 void (ExportHandler::*index_func) (CDMarkerStatus &);
272 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
273 header_func = &ExportHandler::write_toc_header;
274 track_func = &ExportHandler::write_track_info_toc;
275 index_func = &ExportHandler::write_index_info_toc;
278 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
279 header_func = &ExportHandler::write_cue_header;
280 track_func = &ExportHandler::write_track_info_cue;
281 index_func = &ExportHandler::write_index_info_cue;
287 CDMarkerStatus status (filepath, timespan, file_format, filename);
290 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
294 (this->*header_func) (status);
296 /* Get locations and sort */
298 Locations::LocationList const & locations (session.locations()->list());
299 Locations::LocationList::const_iterator i;
300 Locations::LocationList temp;
302 for (i = locations.begin(); i != locations.end(); ++i) {
303 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
309 // TODO One index marker for whole thing
313 LocationSortByStart cmp;
315 Locations::LocationList::const_iterator nexti;
317 /* Start actual marker stuff */
319 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
320 status.track_position = last_start_time - timespan->get_start();
322 for (i = temp.begin(); i != temp.end(); ++i) {
326 if ((*i)->start() < last_end_time) {
327 if ((*i)->is_mark()) {
328 /* Index within track */
330 status.index_position = (*i)->start() - timespan->get_start();
331 (this->*index_func) (status);
337 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
339 status.track_position = last_end_time - timespan->get_start();
340 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
341 status.track_duration = 0;
343 if ((*i)->is_mark()) {
344 // a mark track location needs to look ahead to the next marker's start to determine length
348 if (nexti != temp.end()) {
349 status.track_duration = (*nexti)->start() - last_end_time;
351 last_start_time = (*i)->start();
352 last_end_time = (*nexti)->start();
354 // this was the last marker, use timespan end
355 status.track_duration = timespan->get_end() - last_end_time;
357 last_start_time = (*i)->start();
358 last_end_time = timespan->get_end();
362 status.track_duration = (*i)->end() - last_end_time;
364 last_start_time = (*i)->start();
365 last_end_time = (*i)->end();
368 (this->*track_func) (status);
373 ExportHandler::write_cue_header (CDMarkerStatus & status)
375 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
377 status.out << "REM Cue file generated by Ardour" << endl;
378 status.out << "TITLE \"" << title << "\"" << endl;
380 /* The cue sheet syntax has originally five file types:
381 WAVE : 44.1 kHz, 16 Bit (little endian)
382 AIFF : 44.1 kHz, 16 Bit (big endian)
383 BINARY : 44.1 kHz, 16 Bit (little endian)
384 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
387 We want to use cue sheets not only as CD images but also as general playlyist
388 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
389 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
390 and MOTOROLA we do care, because no header would tell us about a different format.
392 For all other formats we just make up our own file type. MP3 is not supported
396 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
397 if (!status.format->format_name().compare ("WAV")) {
398 status.out << "WAVE";
399 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
400 status.format->sample_format() == ExportFormatBase::SF_16 &&
401 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
402 // Format is RAW 16bit 44.1kHz
403 if (status.format->endianness() == ExportFormatBase::E_Little) {
404 status.out << "BINARY";
406 status.out << "MOTOROLA";
409 // AIFF should return "AIFF"
410 status.out << status.format->format_name();
416 ExportHandler::write_toc_header (CDMarkerStatus & status)
418 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
420 status.out << "CD_DA" << endl;
421 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
422 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
426 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
430 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
431 status.out << buf << endl;
433 status.out << " FLAGS" ;
434 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
435 status.out << " SCMS ";
437 status.out << " DCP ";
440 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
441 status.out << " PRE";
445 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
446 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
449 if (status.marker->name() != "") {
450 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
453 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
454 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
457 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
458 status.out << " SONGWRITER \"" << status.marker->cd_info["composer"] << "\"" << endl;
461 if (status.track_position != status.track_start_frame) {
462 frames_to_cd_frames_string (buf, status.track_position);
463 status.out << " INDEX 00" << buf << endl;
466 frames_to_cd_frames_string (buf, status.track_start_frame);
467 status.out << " INDEX 01" << buf << endl;
469 status.index_number = 2;
470 status.track_number++;
474 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
478 status.out << endl << "TRACK AUDIO" << endl;
480 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
483 status.out << "COPY" << endl;
485 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
486 status.out << "PRE_EMPHASIS" << endl;
488 status.out << "NO PRE_EMPHASIS" << endl;
491 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
492 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
495 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
496 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
497 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
499 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
500 status.out << " COMPOSER \"" << status.marker->cd_info["composer"] << "\"" << endl;
503 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
504 status.out << " ISRC \"";
505 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
506 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
507 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
508 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
511 status.out << " }" << endl << "}" << endl;
513 frames_to_cd_frames_string (buf, status.track_position);
514 status.out << "FILE \"" << status.filename << "\" " << buf;
516 frames_to_cd_frames_string (buf, status.track_duration);
517 status.out << buf << endl;
519 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
520 status.out << "START" << buf << endl;
524 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
528 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
530 frames_to_cd_frames_string (buf, status.index_position);
531 status.out << buf << endl;
537 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
541 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
542 status.out << "INDEX" << buf << endl;
546 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
548 framecnt_t remainder;
549 framecnt_t fr = session.nominal_frame_rate();
550 int mins, secs, frames;
552 mins = when / (60 * fr);
553 remainder = when - (mins * 60 * fr);
554 secs = remainder / fr;
555 remainder -= secs * fr;
556 frames = remainder / (fr / 75);
557 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
560 } // namespace ARDOUR