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 ()
59 ExportElementFactory::TimespanPtr
60 ExportElementFactory::add_timespan ()
62 return TimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
65 ExportElementFactory::ChannelConfigPtr
66 ExportElementFactory::add_channel_config ()
68 return ChannelConfigPtr (new ExportChannelConfiguration (session));
71 ExportElementFactory::FormatPtr
72 ExportElementFactory::add_format ()
74 return FormatPtr (new ExportFormatSpecification (session));
77 ExportElementFactory::FormatPtr
78 ExportElementFactory::add_format (XMLNode const & state)
80 return FormatPtr (new ExportFormatSpecification (session, state));
83 ExportElementFactory::FormatPtr
84 ExportElementFactory::add_format_copy (FormatPtr other)
86 return FormatPtr (new ExportFormatSpecification (*other));
89 ExportElementFactory::FilenamePtr
90 ExportElementFactory::add_filename ()
92 return FilenamePtr (new ExportFilename (session));
95 ExportElementFactory::FilenamePtr
96 ExportElementFactory::add_filename_copy (FilenamePtr other)
98 return FilenamePtr (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 (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename, boost::shared_ptr<AudioGrapher::BroadcastInfo> broadcast_info)
123 FileSpec spec (channel_config, format, filename, broadcast_info);
124 ConfigPair pair (timespan, spec);
125 config_map.insert (pair);
131 ExportHandler::do_export (bool rt)
133 /* Count timespans */
135 export_status->init();
136 std::set<TimespanPtr> timespan_set;
137 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
138 timespan_set.insert (it->first);
140 export_status->total_timespans = timespan_set.size();
149 ExportHandler::start_timespan ()
151 export_status->timespan++;
153 if (config_map.empty()) {
154 // freewheeling has to be stopped from outside the process cycle
155 export_status->running = false;
159 current_timespan = config_map.begin()->first;
161 /* Register file configurations to graph builder */
163 timespan_bounds = config_map.equal_range (current_timespan);
164 graph_builder->reset ();
165 graph_builder->set_current_timespan (current_timespan);
166 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
167 // Filenames can be shared across timespans
168 FileSpec & spec = it->second;
169 spec.filename->set_timespan (it->first);
170 graph_builder->add_config (spec);
176 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
177 process_position = current_timespan->get_start();
178 session.start_audio_export (process_position, realtime);
182 ExportHandler::process (framecnt_t frames)
184 if (!export_status->running) {
186 } else if (normalizing) {
187 return process_normalize ();
189 return process_timespan (frames);
194 ExportHandler::process_timespan (framecnt_t frames)
196 /* update position */
198 framecnt_t frames_to_read = 0;
199 framepos_t const start = current_timespan->get_start();
200 framepos_t const end = current_timespan->get_end();
202 bool const last_cycle = (process_position + frames >= end);
205 frames_to_read = end - process_position;
206 export_status->stop = true;
209 frames_to_read = frames;
212 process_position += frames_to_read;
213 export_status->progress = (float) (process_position - start) / (end - start);
215 /* Do actual processing */
217 return graph_builder->process (frames_to_read, last_cycle);
221 ExportHandler::process_normalize ()
223 if (graph_builder->process_normalize ()) {
231 ExportHandler::finish_timespan ()
233 while (config_map.begin() != timespan_bounds.second) {
234 config_map.erase (config_map.begin());
240 /*** CD Marker sutff ***/
242 struct LocationSortByStart {
243 bool operator() (Location *a, Location *b) {
244 return a->start() < b->start();
249 ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format)
252 string basename = Glib::path_get_basename(filename);
254 size_t ext_pos = basename.rfind('.');
255 if (ext_pos != string::npos) {
256 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
259 void (ExportHandler::*header_func) (CDMarkerStatus &);
260 void (ExportHandler::*track_func) (CDMarkerStatus &);
261 void (ExportHandler::*index_func) (CDMarkerStatus &);
265 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
266 header_func = &ExportHandler::write_toc_header;
267 track_func = &ExportHandler::write_track_info_toc;
268 index_func = &ExportHandler::write_index_info_toc;
271 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
272 header_func = &ExportHandler::write_cue_header;
273 track_func = &ExportHandler::write_track_info_cue;
274 index_func = &ExportHandler::write_index_info_cue;
280 CDMarkerStatus status (filepath, timespan, file_format, filename);
283 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
287 (this->*header_func) (status);
289 /* Get locations and sort */
291 Locations::LocationList const & locations (session.locations()->list());
292 Locations::LocationList::const_iterator i;
293 Locations::LocationList temp;
295 for (i = locations.begin(); i != locations.end(); ++i) {
296 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
302 // TODO One index marker for whole thing
306 LocationSortByStart cmp;
308 Locations::LocationList::const_iterator nexti;
310 /* Start actual marker stuff */
312 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
313 status.track_position = last_start_time - timespan->get_start();
315 for (i = temp.begin(); i != temp.end(); ++i) {
319 if ((*i)->start() < last_end_time) {
320 if ((*i)->is_mark()) {
321 /* Index within track */
323 status.index_position = (*i)->start() - timespan->get_start();
324 (this->*index_func) (status);
330 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
332 status.track_position = last_end_time - timespan->get_start();
333 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
334 status.track_duration = 0;
336 if ((*i)->is_mark()) {
337 // a mark track location needs to look ahead to the next marker's start to determine length
341 if (nexti != temp.end()) {
342 status.track_duration = (*nexti)->start() - last_end_time;
344 last_start_time = (*i)->start();
345 last_end_time = (*nexti)->start();
347 // this was the last marker, use timespan end
348 status.track_duration = timespan->get_end() - last_end_time;
350 last_start_time = (*i)->start();
351 last_end_time = timespan->get_end();
355 status.track_duration = (*i)->end() - last_end_time;
357 last_start_time = (*i)->start();
358 last_end_time = (*i)->end();
361 (this->*track_func) (status);
366 ExportHandler::write_cue_header (CDMarkerStatus & status)
368 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
370 status.out << "REM Cue file generated by Ardour" << endl;
371 status.out << "TITLE \"" << title << "\"" << endl;
373 /* The cue sheet syntax has originally five file types:
374 WAVE : 44.1 kHz, 16 Bit (little endian)
375 AIFF : 44.1 kHz, 16 Bit (big endian)
376 BINARY : 44.1 kHz, 16 Bit (little endian)
377 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
380 We want to use cue sheets not only as CD images but also as general playlyist
381 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
382 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
383 and MOTOROLA we do care, because no header would tell us about a different format.
385 For all other formats we just make up our own file type. MP3 is not supported
389 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
390 if (!status.format->format_name().compare ("WAV")) {
391 status.out << "WAVE";
392 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
393 status.format->sample_format() == ExportFormatBase::SF_16 &&
394 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
395 // Format is RAW 16bit 44.1kHz
396 if (status.format->endianness() == ExportFormatBase::E_Little) {
397 status.out << "BINARY";
399 status.out << "MOTOROLA";
402 // AIFF should return "AIFF"
403 status.out << status.format->format_name();
409 ExportHandler::write_toc_header (CDMarkerStatus & status)
411 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
413 status.out << "CD_DA" << endl;
414 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
415 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
419 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
423 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
424 status.out << buf << endl;
426 status.out << " FLAGS" ;
427 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
428 status.out << " SCMS ";
430 status.out << " DCP ";
433 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
434 status.out << " PRE";
438 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
439 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
442 if (status.marker->name() != "") {
443 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
446 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
447 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
450 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
451 status.out << " SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
454 if (status.track_position != status.track_start_frame) {
455 frames_to_cd_frames_string (buf, status.track_position);
456 status.out << " INDEX 00" << buf << endl;
459 frames_to_cd_frames_string (buf, status.track_start_frame);
460 status.out << " INDEX 01" << buf << endl;
462 status.index_number = 2;
463 status.track_number++;
467 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
471 status.out << endl << "TRACK AUDIO" << endl;
473 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
476 status.out << "COPY" << endl;
478 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
479 status.out << "PRE_EMPHASIS" << endl;
481 status.out << "NO PRE_EMPHASIS" << endl;
484 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
485 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
488 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
489 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
490 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
492 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
493 status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
496 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
497 status.out << " ISRC \"";
498 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
499 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
500 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
501 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
504 status.out << " }" << endl << "}" << endl;
506 frames_to_cd_frames_string (buf, status.track_position);
507 status.out << "FILE \"" << status.filename << "\" " << buf;
509 frames_to_cd_frames_string (buf, status.track_duration);
510 status.out << buf << endl;
512 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
513 status.out << "START" << buf << endl;
517 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
521 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
523 frames_to_cd_frames_string (buf, status.index_position);
524 status.out << buf << endl;
530 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
534 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
535 status.out << "INDEX" << buf << endl;
539 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
541 framecnt_t remainder;
542 framecnt_t fr = session.nominal_frame_rate();
543 int mins, secs, frames;
545 mins = when / (60 * fr);
546 remainder = when - (mins * 60 * fr);
547 secs = remainder / fr;
548 remainder -= secs * fr;
549 frames = remainder / (fr / 75);
550 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
553 } // namespace ARDOUR