Move panner bypass state up to the PannerShell so that it is preserved even when...
[ardour2.git] / libs / ardour / export_handler.cc
blobf34e8c326b498f5144316e10b47c59a3ef3ab9f0
1 /*
2 Copyright (C) 2008-2009 Paul Davis
3 Author: Sakari Bergen
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"
23 #include <glibmm.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"
38 #include "i18n.h"
40 using namespace std;
41 using namespace PBD;
43 namespace ARDOUR
46 /*** ExportElementFactory ***/
48 ExportElementFactory::ExportElementFactory (Session & session) :
49 session (session)
54 ExportElementFactory::~ExportElementFactory ()
59 ExportTimespanPtr
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));
71 ExportFormatSpecPtr
72 ExportElementFactory::add_format ()
74 return ExportFormatSpecPtr (new ExportFormatSpecification (session));
77 ExportFormatSpecPtr
78 ExportElementFactory::add_format (XMLNode const & state)
80 return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
83 ExportFormatSpecPtr
84 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
86 return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
89 ExportFilenamePtr
90 ExportElementFactory::add_filename ()
92 return ExportFilenamePtr (new ExportFilename (session));
95 ExportFilenamePtr
96 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
98 return ExportFilenamePtr (new ExportFilename (*other));
101 /*** ExportHandler ***/
103 ExportHandler::ExportHandler (Session & session)
104 : ExportElementFactory (session)
105 , session (session)
106 , graph_builder (new ExportGraphBuilder (session))
107 , export_status (session.get_export_status ())
108 , realtime (false)
109 , normalizing (false)
110 , cue_tracknum (0)
111 , cue_indexnum (0)
115 ExportHandler::~ExportHandler ()
117 // TODO remove files that were written but not finsihed
120 bool
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);
129 return true;
132 void
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);
141 export_status->total_frames += it->first->get_length();
143 export_status->total_timespans = timespan_set.size();
145 /* Start export */
147 realtime = rt;
148 start_timespan ();
151 void
152 ExportHandler::start_timespan ()
154 export_status->timespan++;
156 if (config_map.empty()) {
157 // freewheeling has to be stopped from outside the process cycle
158 export_status->running = false;
159 return;
162 current_timespan = config_map.begin()->first;
164 /* Register file configurations to graph builder */
166 timespan_bounds = config_map.equal_range (current_timespan);
167 graph_builder->reset ();
168 graph_builder->set_current_timespan (current_timespan);
169 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
170 // Filenames can be shared across timespans
171 FileSpec & spec = it->second;
172 spec.filename->set_timespan (it->first);
173 graph_builder->add_config (spec);
176 /* start export */
178 normalizing = false;
179 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
180 process_position = current_timespan->get_start();
181 session.start_audio_export (process_position, realtime);
185 ExportHandler::process (framecnt_t frames)
187 if (!export_status->running) {
188 return 0;
189 } else if (normalizing) {
190 return process_normalize ();
191 } else {
192 return process_timespan (frames);
197 ExportHandler::process_timespan (framecnt_t frames)
199 /* update position */
201 framecnt_t frames_to_read = 0;
202 framepos_t const end = current_timespan->get_end();
204 bool const last_cycle = (process_position + frames >= end);
206 if (last_cycle) {
207 frames_to_read = end - process_position;
208 export_status->stop = true;
209 normalizing = true;
210 } else {
211 frames_to_read = frames;
214 process_position += frames_to_read;
215 export_status->processed_frames += frames_to_read;
216 export_status->progress = (float) export_status->processed_frames / export_status->total_frames;
218 /* Do actual processing */
220 return graph_builder->process (frames_to_read, last_cycle);
224 ExportHandler::process_normalize ()
226 if (graph_builder->process_normalize ()) {
227 finish_timespan ();
228 export_status->normalizing = false;
229 } else {
230 export_status->normalizing = true;
233 return 0;
236 void
237 ExportHandler::finish_timespan ()
239 while (config_map.begin() != timespan_bounds.second) {
240 config_map.erase (config_map.begin());
243 start_timespan ();
246 /*** CD Marker sutff ***/
248 struct LocationSortByStart {
249 bool operator() (Location *a, Location *b) {
250 return a->start() < b->start();
254 void
255 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
256 std::string filename, CDMarkerFormat format)
258 string filepath;
259 string basename = Glib::path_get_basename(filename);
261 size_t ext_pos = basename.rfind('.');
262 if (ext_pos != string::npos) {
263 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
266 void (ExportHandler::*header_func) (CDMarkerStatus &);
267 void (ExportHandler::*track_func) (CDMarkerStatus &);
268 void (ExportHandler::*index_func) (CDMarkerStatus &);
270 switch (format) {
271 case CDMarkerTOC:
272 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
273 header_func = &ExportHandler::write_toc_header;
274 track_func = &ExportHandler::write_track_info_toc;
275 index_func = &ExportHandler::write_index_info_toc;
276 break;
277 case CDMarkerCUE:
278 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
279 header_func = &ExportHandler::write_cue_header;
280 track_func = &ExportHandler::write_track_info_cue;
281 index_func = &ExportHandler::write_index_info_cue;
282 break;
283 default:
284 return;
287 CDMarkerStatus status (filepath, timespan, file_format, filename);
289 if (!status.out) {
290 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
291 return;
294 (this->*header_func) (status);
296 /* Get locations and sort */
298 Locations::LocationList const & locations (session.locations()->list());
299 Locations::LocationList::const_iterator i;
300 Locations::LocationList temp;
302 for (i = locations.begin(); i != locations.end(); ++i) {
303 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
304 temp.push_back (*i);
308 if (temp.empty()) {
309 // TODO One index marker for whole thing
310 return;
313 LocationSortByStart cmp;
314 temp.sort (cmp);
315 Locations::LocationList::const_iterator nexti;
317 /* Start actual marker stuff */
319 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
320 status.track_position = last_start_time - timespan->get_start();
322 for (i = temp.begin(); i != temp.end(); ++i) {
324 status.marker = *i;
326 if ((*i)->start() < last_end_time) {
327 if ((*i)->is_mark()) {
328 /* Index within track */
330 status.index_position = (*i)->start() - timespan->get_start();
331 (this->*index_func) (status);
334 continue;
337 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
339 status.track_position = last_end_time - timespan->get_start();
340 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
341 status.track_duration = 0;
343 if ((*i)->is_mark()) {
344 // a mark track location needs to look ahead to the next marker's start to determine length
345 nexti = i;
346 ++nexti;
348 if (nexti != temp.end()) {
349 status.track_duration = (*nexti)->start() - last_end_time;
351 last_start_time = (*i)->start();
352 last_end_time = (*nexti)->start();
353 } else {
354 // this was the last marker, use timespan end
355 status.track_duration = timespan->get_end() - last_end_time;
357 last_start_time = (*i)->start();
358 last_end_time = timespan->get_end();
360 } else {
361 // range
362 status.track_duration = (*i)->end() - last_end_time;
364 last_start_time = (*i)->start();
365 last_end_time = (*i)->end();
368 (this->*track_func) (status);
372 void
373 ExportHandler::write_cue_header (CDMarkerStatus & status)
375 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
377 status.out << "REM Cue file generated by Ardour" << endl;
378 status.out << "TITLE \"" << title << "\"" << endl;
380 /* The cue sheet syntax has originally five file types:
381 WAVE : 44.1 kHz, 16 Bit (little endian)
382 AIFF : 44.1 kHz, 16 Bit (big endian)
383 BINARY : 44.1 kHz, 16 Bit (little endian)
384 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
387 We want to use cue sheets not only as CD images but also as general playlyist
388 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
389 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
390 and MOTOROLA we do care, because no header would tell us about a different format.
392 For all other formats we just make up our own file type. MP3 is not supported
393 at the moment.
396 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
397 if (!status.format->format_name().compare ("WAV")) {
398 status.out << "WAVE";
399 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
400 status.format->sample_format() == ExportFormatBase::SF_16 &&
401 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
402 // Format is RAW 16bit 44.1kHz
403 if (status.format->endianness() == ExportFormatBase::E_Little) {
404 status.out << "BINARY";
405 } else {
406 status.out << "MOTOROLA";
408 } else {
409 // AIFF should return "AIFF"
410 status.out << status.format->format_name();
412 status.out << endl;
415 void
416 ExportHandler::write_toc_header (CDMarkerStatus & status)
418 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
420 status.out << "CD_DA" << endl;
421 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
422 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
425 void
426 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
428 gchar buf[18];
430 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
431 status.out << buf << endl;
433 status.out << " FLAGS" ;
434 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
435 status.out << " SCMS ";
436 } else {
437 status.out << " DCP ";
440 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
441 status.out << " PRE";
443 status.out << endl;
445 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
446 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
449 if (status.marker->name() != "") {
450 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
453 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
454 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
457 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
458 status.out << " SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
461 if (status.track_position != status.track_start_frame) {
462 frames_to_cd_frames_string (buf, status.track_position);
463 status.out << " INDEX 00" << buf << endl;
466 frames_to_cd_frames_string (buf, status.track_start_frame);
467 status.out << " INDEX 01" << buf << endl;
469 status.index_number = 2;
470 status.track_number++;
473 void
474 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
476 gchar buf[18];
478 status.out << endl << "TRACK AUDIO" << endl;
480 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
481 status.out << "NO ";
483 status.out << "COPY" << endl;
485 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
486 status.out << "PRE_EMPHASIS" << endl;
487 } else {
488 status.out << "NO PRE_EMPHASIS" << endl;
491 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
492 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
495 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
496 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
497 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
499 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
500 status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
503 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
504 status.out << " ISRC \"";
505 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
506 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
507 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
508 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
511 status.out << " }" << endl << "}" << endl;
513 frames_to_cd_frames_string (buf, status.track_position);
514 status.out << "FILE \"" << status.filename << "\" " << buf;
516 frames_to_cd_frames_string (buf, status.track_duration);
517 status.out << buf << endl;
519 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
520 status.out << "START" << buf << endl;
523 void
524 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
526 gchar buf[18];
528 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
529 status.out << buf;
530 frames_to_cd_frames_string (buf, status.index_position);
531 status.out << buf << endl;
533 cue_indexnum++;
536 void
537 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
539 gchar buf[18];
541 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
542 status.out << "INDEX" << buf << endl;
545 void
546 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
548 framecnt_t remainder;
549 framecnt_t fr = session.nominal_frame_rate();
550 int mins, secs, frames;
552 mins = when / (60 * fr);
553 remainder = when - (mins * 60 * fr);
554 secs = remainder / fr;
555 remainder -= secs * fr;
556 frames = remainder / (fr / 75);
557 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
560 } // namespace ARDOUR