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 ())
107 , normalizing (false)
113 ExportHandler::~ExportHandler ()
115 // TODO remove files that were written but not finsihed
119 ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename)
121 FileSpec spec (channel_config, format, filename);
122 ConfigPair pair (timespan, spec);
123 config_map.insert (pair);
129 ExportHandler::do_export (bool rt)
131 /* Count timespans */
133 export_status->init();
134 std::set<TimespanPtr> timespan_set;
135 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
136 timespan_set.insert (it->first);
138 export_status->total_timespans = timespan_set.size();
147 ExportHandler::start_timespan ()
149 export_status->timespan++;
151 if (config_map.empty()) {
152 // freewheeling has to be stopped from outside the process cycle
153 export_status->running = false;
157 current_timespan = config_map.begin()->first;
159 /* Register file configurations to graph builder */
161 timespan_bounds = config_map.equal_range (current_timespan);
162 graph_builder->reset ();
163 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
164 // Filenames can be shared across timespans
165 FileSpec & spec = it->second;
166 spec.filename->set_timespan (it->first);
167 graph_builder->add_config (spec);
173 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
174 process_position = current_timespan->get_start();
175 session.start_audio_export (process_position, realtime);
179 ExportHandler::process (nframes_t frames)
181 if (!export_status->running) {
183 } else if (normalizing) {
184 return process_normalize ();
186 return process_timespan (frames);
191 ExportHandler::process_timespan (nframes_t frames)
193 /* update position */
195 nframes_t frames_to_read = 0;
196 sframes_t const start = current_timespan->get_start();
197 sframes_t const end = current_timespan->get_end();
199 bool const last_cycle = (process_position + frames >= end);
202 frames_to_read = end - process_position;
203 export_status->stop = true;
206 frames_to_read = frames;
209 process_position += frames_to_read;
210 export_status->progress = (float) (process_position - start) / (end - start);
212 /* Do actual processing */
214 return graph_builder->process (frames_to_read, last_cycle);
218 ExportHandler::process_normalize ()
220 if (graph_builder->process_normalize ()) {
228 ExportHandler::finish_timespan ()
230 while (config_map.begin() != timespan_bounds.second) {
231 config_map.erase (config_map.begin());
237 /*** CD Marker sutff ***/
239 struct LocationSortByStart {
240 bool operator() (Location *a, Location *b) {
241 return a->start() < b->start();
246 ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format)
249 string basename = Glib::path_get_basename(filename);
251 size_t ext_pos = basename.rfind('.');
252 if (ext_pos != string::npos) {
253 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
256 void (ExportHandler::*header_func) (CDMarkerStatus &);
257 void (ExportHandler::*track_func) (CDMarkerStatus &);
258 void (ExportHandler::*index_func) (CDMarkerStatus &);
262 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
263 header_func = &ExportHandler::write_toc_header;
264 track_func = &ExportHandler::write_track_info_toc;
265 index_func = &ExportHandler::write_index_info_toc;
268 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
269 header_func = &ExportHandler::write_cue_header;
270 track_func = &ExportHandler::write_track_info_cue;
271 index_func = &ExportHandler::write_index_info_cue;
277 CDMarkerStatus status (filepath, timespan, file_format, filename);
280 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
284 (this->*header_func) (status);
286 /* Get locations and sort */
288 Locations::LocationList const & locations (session.locations()->list());
289 Locations::LocationList::const_iterator i;
290 Locations::LocationList temp;
292 for (i = locations.begin(); i != locations.end(); ++i) {
293 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_end()) {
299 // TODO One index marker for whole thing
303 LocationSortByStart cmp;
305 Locations::LocationList::const_iterator nexti;
307 /* Start actual marker stuff */
309 sframes_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
310 status.track_position = last_start_time - timespan->get_start();
312 for (i = temp.begin(); i != temp.end(); ++i) {
316 if ((*i)->start() < last_end_time) {
317 if ((*i)->is_mark()) {
318 /* Index within track */
320 status.index_position = (*i)->start() - timespan->get_start();
321 (this->*index_func) (status);
327 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
329 status.track_position = last_end_time - timespan->get_start();
330 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
331 status.track_duration = 0;
333 if ((*i)->is_mark()) {
334 // a mark track location needs to look ahead to the next marker's start to determine length
338 if (nexti != temp.end()) {
339 status.track_duration = (*nexti)->start() - last_end_time;
341 last_start_time = (*i)->start();
342 last_end_time = (*nexti)->start();
344 // this was the last marker, use timespan end
345 status.track_duration = timespan->get_end() - last_end_time;
347 last_start_time = (*i)->start();
348 last_end_time = timespan->get_end();
352 status.track_duration = (*i)->end() - last_end_time;
354 last_start_time = (*i)->start();
355 last_end_time = (*i)->end();
358 (this->*track_func) (status);
363 ExportHandler::write_cue_header (CDMarkerStatus & status)
365 Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
367 status.out << "REM Cue file generated by Ardour" << endl;
368 status.out << "TITLE \"" << title << "\"" << endl;
370 /* The cue sheet syntax has originally five file types:
371 WAVE : 44.1 kHz, 16 Bit (little endian)
372 AIFF : 44.1 kHz, 16 Bit (big endian)
373 BINARY : 44.1 kHz, 16 Bit (little endian)
374 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
377 We want to use cue sheets not only as CD images but also as general playlyist
378 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
379 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
380 and MOTOROLA we do care, because no header would tell us about a different format.
382 For all other formats we just make up our own file type. MP3 is not supported
386 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
387 if (!status.format->format_name().compare ("WAV")) {
388 status.out << "WAVE";
389 } else if (status.format->format_name() == ExportFormatBase::F_RAW &&
390 status.format->sample_format() == ExportFormatBase::SF_16 &&
391 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
392 // Format is RAW 16bit 44.1kHz
393 if (status.format->endianness() == ExportFormatBase::E_Little) {
394 status.out << "BINARY";
396 status.out << "MOTOROLA";
399 // AIFF should return "AIFF"
400 status.out << status.format->format_name();
406 ExportHandler::write_toc_header (CDMarkerStatus & status)
408 Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
410 status.out << "CD_DA" << endl;
411 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
412 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
416 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
420 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
421 status.out << buf << endl;
423 status.out << " FLAGS" ;
424 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
425 status.out << " SCMS ";
427 status.out << " DCP ";
430 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
431 status.out << " PRE";
435 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
436 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
439 if (status.marker->name() != "") {
440 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
443 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
444 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
447 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
448 status.out << " SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
451 if (status.track_position != status.track_start_frame) {
452 frames_to_cd_frames_string (buf, status.track_position);
453 status.out << " INDEX 00" << buf << endl;
456 frames_to_cd_frames_string (buf, status.track_start_frame);
457 status.out << " INDEX 01" << buf << endl;
459 status.index_number = 2;
460 status.track_number++;
464 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
468 status.out << endl << "TRACK AUDIO" << endl;
470 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
473 status.out << "COPY" << endl;
475 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
476 status.out << "PRE_EMPHASIS" << endl;
478 status.out << "NO PRE_EMPHASIS" << endl;
481 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
482 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
485 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
486 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
487 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
489 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
490 status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
493 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
494 status.out << " ISRC \"";
495 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
496 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
497 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
498 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
501 status.out << " }" << endl << "}" << endl;
503 frames_to_cd_frames_string (buf, status.track_position);
504 status.out << "FILE \"" << status.filename << "\" " << buf;
506 frames_to_cd_frames_string (buf, status.track_duration);
507 status.out << buf << endl;
509 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
510 status.out << "START" << buf << endl;
514 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
518 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
520 frames_to_cd_frames_string (buf, status.index_position);
521 status.out << buf << endl;
527 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
531 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
532 status.out << "INDEX" << buf << endl;
536 ExportHandler::frames_to_cd_frames_string (char* buf, sframes_t when)
539 nframes_t fr = session.nominal_frame_rate();
540 int mins, secs, frames;
542 mins = when / (60 * fr);
543 remainder = when - (mins * 60 * fr);
544 secs = remainder / fr;
545 remainder -= secs * fr;
546 frames = remainder / (fr / 75);
547 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
550 } // namespace ARDOUR