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);
142 export_status->total_timespans = timespan_set.size();
151 ExportHandler::start_timespan ()
153 export_status->timespan++;
155 if (config_map.empty()) {
156 // freewheeling has to be stopped from outside the process cycle
157 export_status->running = false;
161 current_timespan = config_map.begin()->first;
163 /* Register file configurations to graph builder */
165 timespan_bounds = config_map.equal_range (current_timespan);
166 graph_builder->reset ();
167 graph_builder->set_current_timespan (current_timespan);
168 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
169 // Filenames can be shared across timespans
170 FileSpec & spec = it->second;
171 spec.filename->set_timespan (it->first);
172 graph_builder->add_config (spec);
178 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
179 process_position = current_timespan->get_start();
180 session.start_audio_export (process_position, realtime);
184 ExportHandler::process (framecnt_t frames)
186 if (!export_status->running) {
188 } else if (normalizing) {
189 return process_normalize ();
191 return process_timespan (frames);
196 ExportHandler::process_timespan (framecnt_t frames)
198 /* update position */
200 framecnt_t frames_to_read = 0;
201 framepos_t const start = current_timespan->get_start();
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->progress = (float) (process_position - start) / (end - start);
217 /* Do actual processing */
219 return graph_builder->process (frames_to_read, last_cycle);
223 ExportHandler::process_normalize ()
225 if (graph_builder->process_normalize ()) {
233 ExportHandler::finish_timespan ()
235 while (config_map.begin() != timespan_bounds.second) {
236 config_map.erase (config_map.begin());
242 /*** CD Marker sutff ***/
244 struct LocationSortByStart {
245 bool operator() (Location *a, Location *b) {
246 return a->start() < b->start();
251 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
252 std::string filename, CDMarkerFormat format)
255 string basename = Glib::path_get_basename(filename);
257 size_t ext_pos = basename.rfind('.');
258 if (ext_pos != string::npos) {
259 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
262 void (ExportHandler::*header_func) (CDMarkerStatus &);
263 void (ExportHandler::*track_func) (CDMarkerStatus &);
264 void (ExportHandler::*index_func) (CDMarkerStatus &);
268 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
269 header_func = &ExportHandler::write_toc_header;
270 track_func = &ExportHandler::write_track_info_toc;
271 index_func = &ExportHandler::write_index_info_toc;
274 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
275 header_func = &ExportHandler::write_cue_header;
276 track_func = &ExportHandler::write_track_info_cue;
277 index_func = &ExportHandler::write_index_info_cue;
283 CDMarkerStatus status (filepath, timespan, file_format, filename);
286 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
290 (this->*header_func) (status);
292 /* Get locations and sort */
294 Locations::LocationList const & locations (session.locations()->list());
295 Locations::LocationList::const_iterator i;
296 Locations::LocationList temp;
298 for (i = locations.begin(); i != locations.end(); ++i) {
299 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
305 // TODO One index marker for whole thing
309 LocationSortByStart cmp;
311 Locations::LocationList::const_iterator nexti;
313 /* Start actual marker stuff */
315 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
316 status.track_position = last_start_time - timespan->get_start();
318 for (i = temp.begin(); i != temp.end(); ++i) {
322 if ((*i)->start() < last_end_time) {
323 if ((*i)->is_mark()) {
324 /* Index within track */
326 status.index_position = (*i)->start() - timespan->get_start();
327 (this->*index_func) (status);
333 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
335 status.track_position = last_end_time - timespan->get_start();
336 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
337 status.track_duration = 0;
339 if ((*i)->is_mark()) {
340 // a mark track location needs to look ahead to the next marker's start to determine length
344 if (nexti != temp.end()) {
345 status.track_duration = (*nexti)->start() - last_end_time;
347 last_start_time = (*i)->start();
348 last_end_time = (*nexti)->start();
350 // this was the last marker, use timespan end
351 status.track_duration = timespan->get_end() - last_end_time;
353 last_start_time = (*i)->start();
354 last_end_time = timespan->get_end();
358 status.track_duration = (*i)->end() - last_end_time;
360 last_start_time = (*i)->start();
361 last_end_time = (*i)->end();
364 (this->*track_func) (status);
369 ExportHandler::write_cue_header (CDMarkerStatus & status)
371 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
373 status.out << "REM Cue file generated by Ardour" << endl;
374 status.out << "TITLE \"" << title << "\"" << endl;
376 /* The cue sheet syntax has originally five file types:
377 WAVE : 44.1 kHz, 16 Bit (little endian)
378 AIFF : 44.1 kHz, 16 Bit (big endian)
379 BINARY : 44.1 kHz, 16 Bit (little endian)
380 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
383 We want to use cue sheets not only as CD images but also as general playlyist
384 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
385 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
386 and MOTOROLA we do care, because no header would tell us about a different format.
388 For all other formats we just make up our own file type. MP3 is not supported
392 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
393 if (!status.format->format_name().compare ("WAV")) {
394 status.out << "WAVE";
395 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
396 status.format->sample_format() == ExportFormatBase::SF_16 &&
397 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
398 // Format is RAW 16bit 44.1kHz
399 if (status.format->endianness() == ExportFormatBase::E_Little) {
400 status.out << "BINARY";
402 status.out << "MOTOROLA";
405 // AIFF should return "AIFF"
406 status.out << status.format->format_name();
412 ExportHandler::write_toc_header (CDMarkerStatus & status)
414 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
416 status.out << "CD_DA" << endl;
417 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
418 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
422 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
426 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
427 status.out << buf << endl;
429 status.out << " FLAGS" ;
430 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
431 status.out << " SCMS ";
433 status.out << " DCP ";
436 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
437 status.out << " PRE";
441 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
442 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
445 if (status.marker->name() != "") {
446 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
449 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
450 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
453 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
454 status.out << " SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
457 if (status.track_position != status.track_start_frame) {
458 frames_to_cd_frames_string (buf, status.track_position);
459 status.out << " INDEX 00" << buf << endl;
462 frames_to_cd_frames_string (buf, status.track_start_frame);
463 status.out << " INDEX 01" << buf << endl;
465 status.index_number = 2;
466 status.track_number++;
470 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
474 status.out << endl << "TRACK AUDIO" << endl;
476 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
479 status.out << "COPY" << endl;
481 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
482 status.out << "PRE_EMPHASIS" << endl;
484 status.out << "NO PRE_EMPHASIS" << endl;
487 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
488 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
491 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
492 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
493 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
495 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
496 status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
499 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
500 status.out << " ISRC \"";
501 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
502 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
503 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
504 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
507 status.out << " }" << endl << "}" << endl;
509 frames_to_cd_frames_string (buf, status.track_position);
510 status.out << "FILE \"" << status.filename << "\" " << buf;
512 frames_to_cd_frames_string (buf, status.track_duration);
513 status.out << buf << endl;
515 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
516 status.out << "START" << buf << endl;
520 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
524 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
526 frames_to_cd_frames_string (buf, status.index_position);
527 status.out << buf << endl;
533 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
537 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
538 status.out << "INDEX" << buf << endl;
542 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
544 framecnt_t remainder;
545 framecnt_t fr = session.nominal_frame_rate();
546 int mins, secs, frames;
548 mins = when / (60 * fr);
549 remainder = when - (mins * 60 * fr);
550 secs = remainder / fr;
551 remainder -= secs * fr;
552 frames = remainder / (fr / 75);
553 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
556 } // namespace ARDOUR