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
)
123 FileSpec
spec (channel_config
, format
, filename
);
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 for (ConfigMap::iterator it
= timespan_bounds
.first
; it
!= timespan_bounds
.second
; ++it
) {
166 // Filenames can be shared across timespans
167 FileSpec
& spec
= it
->second
;
168 spec
.filename
->set_timespan (it
->first
);
169 graph_builder
->add_config (spec
);
175 session
.ProcessExport
.connect_same_thread (process_connection
, boost::bind (&ExportHandler::process
, this, _1
));
176 process_position
= current_timespan
->get_start();
177 session
.start_audio_export (process_position
, realtime
);
181 ExportHandler::process (nframes_t frames
)
183 if (!export_status
->running
) {
185 } else if (normalizing
) {
186 return process_normalize ();
188 return process_timespan (frames
);
193 ExportHandler::process_timespan (nframes_t frames
)
195 /* update position */
197 nframes_t frames_to_read
= 0;
198 sframes_t
const start
= current_timespan
->get_start();
199 sframes_t
const end
= current_timespan
->get_end();
201 bool const last_cycle
= (process_position
+ frames
>= end
);
204 frames_to_read
= end
- process_position
;
205 export_status
->stop
= true;
208 frames_to_read
= frames
;
211 process_position
+= frames_to_read
;
212 export_status
->progress
= (float) (process_position
- start
) / (end
- start
);
214 /* Do actual processing */
216 return graph_builder
->process (frames_to_read
, last_cycle
);
220 ExportHandler::process_normalize ()
222 if (graph_builder
->process_normalize ()) {
230 ExportHandler::finish_timespan ()
232 while (config_map
.begin() != timespan_bounds
.second
) {
233 config_map
.erase (config_map
.begin());
239 /*** CD Marker sutff ***/
241 struct LocationSortByStart
{
242 bool operator() (Location
*a
, Location
*b
) {
243 return a
->start() < b
->start();
248 ExportHandler::export_cd_marker_file (TimespanPtr timespan
, FormatPtr file_format
, std::string filename
, CDMarkerFormat format
)
251 string basename
= Glib::path_get_basename(filename
);
253 size_t ext_pos
= basename
.rfind('.');
254 if (ext_pos
!= string::npos
) {
255 basename
= basename
.substr(0, ext_pos
); /* strip file extension, if there is one */
258 void (ExportHandler::*header_func
) (CDMarkerStatus
&);
259 void (ExportHandler::*track_func
) (CDMarkerStatus
&);
260 void (ExportHandler::*index_func
) (CDMarkerStatus
&);
264 filepath
= Glib::build_filename(Glib::path_get_dirname(filename
), basename
+ ".toc");
265 header_func
= &ExportHandler::write_toc_header
;
266 track_func
= &ExportHandler::write_track_info_toc
;
267 index_func
= &ExportHandler::write_index_info_toc
;
270 filepath
= Glib::build_filename(Glib::path_get_dirname(filename
), basename
+ ".cue");
271 header_func
= &ExportHandler::write_cue_header
;
272 track_func
= &ExportHandler::write_track_info_cue
;
273 index_func
= &ExportHandler::write_index_info_cue
;
279 CDMarkerStatus
status (filepath
, timespan
, file_format
, filename
);
282 error
<< string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath
) << endmsg
;
286 (this->*header_func
) (status
);
288 /* Get locations and sort */
290 Locations::LocationList
const & locations (session
.locations()->list());
291 Locations::LocationList::const_iterator i
;
292 Locations::LocationList temp
;
294 for (i
= locations
.begin(); i
!= locations
.end(); ++i
) {
295 if ((*i
)->start() >= timespan
->get_start() && (*i
)->end() <= timespan
->get_end() && (*i
)->is_cd_marker() && !(*i
)->is_session_range()) {
301 // TODO One index marker for whole thing
305 LocationSortByStart cmp
;
307 Locations::LocationList::const_iterator nexti
;
309 /* Start actual marker stuff */
311 sframes_t last_end_time
= timespan
->get_start(), last_start_time
= timespan
->get_start();
312 status
.track_position
= last_start_time
- timespan
->get_start();
314 for (i
= temp
.begin(); i
!= temp
.end(); ++i
) {
318 if ((*i
)->start() < last_end_time
) {
319 if ((*i
)->is_mark()) {
320 /* Index within track */
322 status
.index_position
= (*i
)->start() - timespan
->get_start();
323 (this->*index_func
) (status
);
329 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
331 status
.track_position
= last_end_time
- timespan
->get_start();
332 status
.track_start_frame
= (*i
)->start() - timespan
->get_start(); // everything before this is the pregap
333 status
.track_duration
= 0;
335 if ((*i
)->is_mark()) {
336 // a mark track location needs to look ahead to the next marker's start to determine length
340 if (nexti
!= temp
.end()) {
341 status
.track_duration
= (*nexti
)->start() - last_end_time
;
343 last_start_time
= (*i
)->start();
344 last_end_time
= (*nexti
)->start();
346 // this was the last marker, use timespan end
347 status
.track_duration
= timespan
->get_end() - last_end_time
;
349 last_start_time
= (*i
)->start();
350 last_end_time
= timespan
->get_end();
354 status
.track_duration
= (*i
)->end() - last_end_time
;
356 last_start_time
= (*i
)->start();
357 last_end_time
= (*i
)->end();
360 (this->*track_func
) (status
);
365 ExportHandler::write_cue_header (CDMarkerStatus
& status
)
367 Glib::ustring title
= status
.timespan
->name().compare ("Session") ? status
.timespan
->name() : (Glib::ustring
) session
.name();
369 status
.out
<< "REM Cue file generated by Ardour" << endl
;
370 status
.out
<< "TITLE \"" << title
<< "\"" << endl
;
372 /* The cue sheet syntax has originally five file types:
373 WAVE : 44.1 kHz, 16 Bit (little endian)
374 AIFF : 44.1 kHz, 16 Bit (big endian)
375 BINARY : 44.1 kHz, 16 Bit (little endian)
376 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
379 We want to use cue sheets not only as CD images but also as general playlyist
380 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
381 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
382 and MOTOROLA we do care, because no header would tell us about a different format.
384 For all other formats we just make up our own file type. MP3 is not supported
388 status
.out
<< "FILE \"" << Glib::path_get_basename(status
.filename
) << "\" ";
389 if (!status
.format
->format_name().compare ("WAV")) {
390 status
.out
<< "WAVE";
391 } else if (status
.format
->format_name() == ExportFormatBase::F_RAW
&&
392 status
.format
->sample_format() == ExportFormatBase::SF_16
&&
393 status
.format
->sample_rate() == ExportFormatBase::SR_44_1
) {
394 // Format is RAW 16bit 44.1kHz
395 if (status
.format
->endianness() == ExportFormatBase::E_Little
) {
396 status
.out
<< "BINARY";
398 status
.out
<< "MOTOROLA";
401 // AIFF should return "AIFF"
402 status
.out
<< status
.format
->format_name();
408 ExportHandler::write_toc_header (CDMarkerStatus
& status
)
410 Glib::ustring title
= status
.timespan
->name().compare ("Session") ? status
.timespan
->name() : (Glib::ustring
) session
.name();
412 status
.out
<< "CD_DA" << endl
;
413 status
.out
<< "CD_TEXT {" << endl
<< " LANGUAGE_MAP {" << endl
<< " 0 : EN" << endl
<< " }" << endl
;
414 status
.out
<< " LANGUAGE 0 {" << endl
<< " TITLE \"" << title
<< "\"" << endl
<< " }" << endl
<< "}" << endl
;
418 ExportHandler::write_track_info_cue (CDMarkerStatus
& status
)
422 snprintf (buf
, sizeof(buf
), " TRACK %02d AUDIO", status
.track_number
);
423 status
.out
<< buf
<< endl
;
425 status
.out
<< " FLAGS" ;
426 if (status
.marker
->cd_info
.find("scms") != status
.marker
->cd_info
.end()) {
427 status
.out
<< " SCMS ";
429 status
.out
<< " DCP ";
432 if (status
.marker
->cd_info
.find("preemph") != status
.marker
->cd_info
.end()) {
433 status
.out
<< " PRE";
437 if (status
.marker
->cd_info
.find("isrc") != status
.marker
->cd_info
.end()) {
438 status
.out
<< " ISRC " << status
.marker
->cd_info
["isrc"] << endl
;
441 if (status
.marker
->name() != "") {
442 status
.out
<< " TITLE \"" << status
.marker
->name() << "\"" << endl
;
445 if (status
.marker
->cd_info
.find("performer") != status
.marker
->cd_info
.end()) {
446 status
.out
<< " PERFORMER \"" << status
.marker
->cd_info
["performer"] << "\"" << endl
;
449 if (status
.marker
->cd_info
.find("string_composer") != status
.marker
->cd_info
.end()) {
450 status
.out
<< " SONGWRITER \"" << status
.marker
->cd_info
["string_composer"] << "\"" << endl
;
453 if (status
.track_position
!= status
.track_start_frame
) {
454 frames_to_cd_frames_string (buf
, status
.track_position
);
455 status
.out
<< " INDEX 00" << buf
<< endl
;
458 frames_to_cd_frames_string (buf
, status
.track_start_frame
);
459 status
.out
<< " INDEX 01" << buf
<< endl
;
461 status
.index_number
= 2;
462 status
.track_number
++;
466 ExportHandler::write_track_info_toc (CDMarkerStatus
& status
)
470 status
.out
<< endl
<< "TRACK AUDIO" << endl
;
472 if (status
.marker
->cd_info
.find("scms") != status
.marker
->cd_info
.end()) {
475 status
.out
<< "COPY" << endl
;
477 if (status
.marker
->cd_info
.find("preemph") != status
.marker
->cd_info
.end()) {
478 status
.out
<< "PRE_EMPHASIS" << endl
;
480 status
.out
<< "NO PRE_EMPHASIS" << endl
;
483 if (status
.marker
->cd_info
.find("isrc") != status
.marker
->cd_info
.end()) {
484 status
.out
<< "ISRC \"" << status
.marker
->cd_info
["isrc"] << "\"" << endl
;
487 status
.out
<< "CD_TEXT {" << endl
<< " LANGUAGE 0 {" << endl
<< " TITLE \"" << status
.marker
->name() << "\"" << endl
;
488 if (status
.marker
->cd_info
.find("performer") != status
.marker
->cd_info
.end()) {
489 status
.out
<< " PERFORMER \"" << status
.marker
->cd_info
["performer"] << "\"" << endl
;
491 if (status
.marker
->cd_info
.find("string_composer") != status
.marker
->cd_info
.end()) {
492 status
.out
<< " COMPOSER \"" << status
.marker
->cd_info
["string_composer"] << "\"" << endl
;
495 if (status
.marker
->cd_info
.find("isrc") != status
.marker
->cd_info
.end()) {
496 status
.out
<< " ISRC \"";
497 status
.out
<< status
.marker
->cd_info
["isrc"].substr(0,2) << "-";
498 status
.out
<< status
.marker
->cd_info
["isrc"].substr(2,3) << "-";
499 status
.out
<< status
.marker
->cd_info
["isrc"].substr(5,2) << "-";
500 status
.out
<< status
.marker
->cd_info
["isrc"].substr(7,5) << "\"" << endl
;
503 status
.out
<< " }" << endl
<< "}" << endl
;
505 frames_to_cd_frames_string (buf
, status
.track_position
);
506 status
.out
<< "FILE \"" << status
.filename
<< "\" " << buf
;
508 frames_to_cd_frames_string (buf
, status
.track_duration
);
509 status
.out
<< buf
<< endl
;
511 frames_to_cd_frames_string (buf
, status
.track_start_frame
- status
.track_position
);
512 status
.out
<< "START" << buf
<< endl
;
516 ExportHandler::write_index_info_cue (CDMarkerStatus
& status
)
520 snprintf (buf
, sizeof(buf
), " INDEX %02d", cue_indexnum
);
522 frames_to_cd_frames_string (buf
, status
.index_position
);
523 status
.out
<< buf
<< endl
;
529 ExportHandler::write_index_info_toc (CDMarkerStatus
& status
)
533 frames_to_cd_frames_string (buf
, status
.index_position
- status
.track_position
);
534 status
.out
<< "INDEX" << buf
<< endl
;
538 ExportHandler::frames_to_cd_frames_string (char* buf
, sframes_t when
)
541 nframes_t fr
= session
.nominal_frame_rate();
542 int mins
, secs
, frames
;
544 mins
= when
/ (60 * fr
);
545 remainder
= when
- (mins
* 60 * fr
);
546 secs
= remainder
/ fr
;
547 remainder
-= secs
* fr
;
548 frames
= remainder
/ (fr
/ 75);
549 sprintf (buf
, " %02d:%02d:%02d", mins
, secs
, frames
);
552 } // namespace ARDOUR