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"
44 /*** ExportElementFactory ***/
46 ExportElementFactory::ExportElementFactory (Session & session) :
52 ExportElementFactory::~ExportElementFactory ()
57 ExportElementFactory::TimespanPtr
58 ExportElementFactory::add_timespan ()
60 return TimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
63 ExportElementFactory::ChannelConfigPtr
64 ExportElementFactory::add_channel_config ()
66 return ChannelConfigPtr (new ExportChannelConfiguration (session));
69 ExportElementFactory::FormatPtr
70 ExportElementFactory::add_format ()
72 return FormatPtr (new ExportFormatSpecification (session));
75 ExportElementFactory::FormatPtr
76 ExportElementFactory::add_format (XMLNode const & state)
78 return FormatPtr (new ExportFormatSpecification (session, state));
81 ExportElementFactory::FormatPtr
82 ExportElementFactory::add_format_copy (FormatPtr other)
84 return FormatPtr (new ExportFormatSpecification (*other));
87 ExportElementFactory::FilenamePtr
88 ExportElementFactory::add_filename ()
90 return FilenamePtr (new ExportFilename (session));
93 ExportElementFactory::FilenamePtr
94 ExportElementFactory::add_filename_copy (FilenamePtr other)
96 return FilenamePtr (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 ())
111 ExportHandler::~ExportHandler ()
113 // TODO remove files that were written but not finsihed
117 ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename)
119 FileSpec spec (channel_config, format, filename);
120 ConfigPair pair (timespan, spec);
121 config_map.insert (pair);
127 ExportHandler::do_export (bool rt)
129 /* Count timespans */
131 export_status->init();
132 std::set<TimespanPtr> timespan_set;
133 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
134 timespan_set.insert (it->first);
136 export_status->total_timespans = timespan_set.size();
145 ExportHandler::start_timespan ()
147 export_status->timespan++;
149 if (config_map.empty()) {
150 // freewheeling has to be stopped from outside the process cycle
151 export_status->running = false;
155 current_timespan = config_map.begin()->first;
157 /* Register file configurations to graph builder */
159 timespan_bounds = config_map.equal_range (current_timespan);
160 graph_builder->reset ();
161 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
162 // Filenames can be shared across timespans
163 FileSpec & spec = it->second;
164 spec.filename->set_timespan (it->first);
165 graph_builder->add_config (spec);
171 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
172 process_position = current_timespan->get_start();
173 session.start_audio_export (process_position, realtime);
177 ExportHandler::process (nframes_t frames)
179 if (!export_status->running) {
181 } else if (normalizing) {
182 return process_normalize ();
184 return process_timespan (frames);
189 ExportHandler::process_timespan (nframes_t frames)
191 /* update position */
193 nframes_t frames_to_read = 0;
194 sframes_t const start = current_timespan->get_start();
195 sframes_t const end = current_timespan->get_end();
197 bool const last_cycle = (process_position + frames >= end);
200 frames_to_read = end - process_position;
201 export_status->stop = true;
204 frames_to_read = frames;
207 process_position += frames_to_read;
208 export_status->progress = (float) (process_position - start) / (end - start);
210 /* Do actual processing */
212 return graph_builder->process (frames_to_read, last_cycle);
216 ExportHandler::process_normalize ()
218 if (graph_builder->process_normalize ()) {
226 ExportHandler::finish_timespan ()
228 while (config_map.begin() != timespan_bounds.second) {
229 config_map.erase (config_map.begin());
235 /*** CD Marker sutff ***/
237 struct LocationSortByStart {
238 bool operator() (Location *a, Location *b) {
239 return a->start() < b->start();
244 ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format)
247 string basename = Glib::path_get_basename(filename);
249 size_t ext_pos = basename.rfind('.');
250 if (ext_pos != string::npos) {
251 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
254 void (ExportHandler::*header_func) (CDMarkerStatus &);
255 void (ExportHandler::*track_func) (CDMarkerStatus &);
256 void (ExportHandler::*index_func) (CDMarkerStatus &);
260 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
261 header_func = &ExportHandler::write_toc_header;
262 track_func = &ExportHandler::write_track_info_toc;
263 index_func = &ExportHandler::write_index_info_toc;
266 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
267 header_func = &ExportHandler::write_cue_header;
268 track_func = &ExportHandler::write_track_info_cue;
269 index_func = &ExportHandler::write_index_info_cue;
275 CDMarkerStatus status (filepath, timespan, file_format, filename);
278 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
282 (this->*header_func) (status);
284 /* Get locations and sort */
286 Locations::LocationList const & locations (session.locations()->list());
287 Locations::LocationList::const_iterator i;
288 Locations::LocationList temp;
290 for (i = locations.begin(); i != locations.end(); ++i) {
291 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_end()) {
297 // TODO One index marker for whole thing
301 LocationSortByStart cmp;
303 Locations::LocationList::const_iterator nexti;
305 /* Start actual marker stuff */
307 sframes_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
308 status.track_position = last_start_time - timespan->get_start();
310 for (i = temp.begin(); i != temp.end(); ++i) {
314 if ((*i)->start() < last_end_time) {
315 if ((*i)->is_mark()) {
316 /* Index within track */
318 status.index_position = (*i)->start() - timespan->get_start();
319 (this->*index_func) (status);
325 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
327 status.track_position = last_end_time - timespan->get_start();
328 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
329 status.track_duration = 0;
331 if ((*i)->is_mark()) {
332 // a mark track location needs to look ahead to the next marker's start to determine length
336 if (nexti != temp.end()) {
337 status.track_duration = (*nexti)->start() - last_end_time;
339 last_start_time = (*i)->start();
340 last_end_time = (*nexti)->start();
342 // this was the last marker, use timespan end
343 status.track_duration = timespan->get_end() - last_end_time;
345 last_start_time = (*i)->start();
346 last_end_time = timespan->get_end();
350 status.track_duration = (*i)->end() - last_end_time;
352 last_start_time = (*i)->start();
353 last_end_time = (*i)->end();
356 (this->*track_func) (status);
361 ExportHandler::write_cue_header (CDMarkerStatus & status)
363 Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
365 status.out << "REM Cue file generated by Ardour" << endl;
366 status.out << "TITLE \"" << title << "\"" << endl;
368 /* The cue sheet syntax has originally five file types:
369 WAVE : 44.1 kHz, 16 Bit (little endian)
370 AIFF : 44.1 kHz, 16 Bit (big endian)
371 BINARY : 44.1 kHz, 16 Bit (little endian)
372 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
375 We want to use cue sheets not only as CD images but also as general playlyist
376 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
377 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
378 and MOTOROLA we do care, because no header would tell us about a different format.
380 For all other formats we just make up our own file type. MP3 is not supported
384 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
385 if (!status.format->format_name().compare ("WAV")) {
386 status.out << "WAVE";
387 } else if (status.format->format_name() == ExportFormatBase::F_RAW &&
388 status.format->sample_format() == ExportFormatBase::SF_16 &&
389 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
390 // Format is RAW 16bit 44.1kHz
391 if (status.format->endianness() == ExportFormatBase::E_Little) {
392 status.out << "BINARY";
394 status.out << "MOTOROLA";
397 // AIFF should return "AIFF"
398 status.out << status.format->format_name();
404 ExportHandler::write_toc_header (CDMarkerStatus & status)
406 Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
408 status.out << "CD_DA" << endl;
409 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
410 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
414 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
418 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
419 status.out << buf << endl;
421 status.out << " FLAGS" ;
422 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
423 status.out << " SCMS ";
425 status.out << " DCP ";
428 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
429 status.out << " PRE";
433 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
434 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
437 if (status.marker->name() != "") {
438 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
441 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
442 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
445 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
446 status.out << " SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
449 if (status.track_position != status.track_start_frame) {
450 frames_to_cd_frames_string (buf, status.track_position);
451 status.out << " INDEX 00" << buf << endl;
454 frames_to_cd_frames_string (buf, status.track_start_frame);
455 status.out << " INDEX 01" << buf << endl;
457 status.index_number = 2;
458 status.track_number++;
462 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
466 status.out << endl << "TRACK AUDIO" << endl;
468 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
471 status.out << "COPY" << endl;
473 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
474 status.out << "PRE_EMPHASIS" << endl;
476 status.out << "NO PRE_EMPHASIS" << endl;
479 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
480 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
483 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
484 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
485 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
487 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
488 status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
491 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
492 status.out << " ISRC \"";
493 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
494 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
495 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
496 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
499 status.out << " }" << endl << "}" << endl;
501 frames_to_cd_frames_string (buf, status.track_position);
502 status.out << "FILE \"" << status.filename << "\" " << buf;
504 frames_to_cd_frames_string (buf, status.track_duration);
505 status.out << buf << endl;
507 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
508 status.out << "START" << buf << endl;
512 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
516 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
518 frames_to_cd_frames_string (buf, status.index_position);
519 status.out << buf << endl;
525 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
529 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
530 status.out << "INDEX" << buf << endl;
534 ExportHandler::frames_to_cd_frames_string (char* buf, sframes_t when)
537 nframes_t fr = session.nominal_frame_rate();
538 int mins, secs, frames;
540 mins = when / (60 * fr);
541 remainder = when - (mins * 60 * fr);
542 secs = remainder / fr;
543 remainder -= secs * fr;
544 frames = remainder / (fr / 75);
545 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
548 } // namespace ARDOUR