second (and hopefully) final part of changes to respond to header format changes...
[ArdourMidi.git] / libs / ardour / export_handler.cc
blob988b9588a8d5f2a50bc893ee90315d157b834ed3
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 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)
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 (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);
127 return true;
130 void
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();
142 /* Start export */
144 realtime = rt;
145 start_timespan ();
148 void
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;
156 return;
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);
172 /* start export */
174 normalizing = false;
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) {
184 return 0;
185 } else if (normalizing) {
186 return process_normalize ();
187 } else {
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);
203 if (last_cycle) {
204 frames_to_read = end - process_position;
205 export_status->stop = true;
206 normalizing = true;
207 } else {
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 ()) {
223 finish_timespan ();
226 return 0;
229 void
230 ExportHandler::finish_timespan ()
232 while (config_map.begin() != timespan_bounds.second) {
233 config_map.erase (config_map.begin());
236 start_timespan ();
239 /*** CD Marker sutff ***/
241 struct LocationSortByStart {
242 bool operator() (Location *a, Location *b) {
243 return a->start() < b->start();
247 void
248 ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format)
250 string filepath;
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 &);
262 switch (format) {
263 case CDMarkerTOC:
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;
268 break;
269 case CDMarkerCUE:
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;
274 break;
275 default:
276 return;
279 CDMarkerStatus status (filepath, timespan, file_format, filename);
281 if (!status.out) {
282 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
283 return;
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()) {
296 temp.push_back (*i);
300 if (temp.empty()) {
301 // TODO One index marker for whole thing
302 return;
305 LocationSortByStart cmp;
306 temp.sort (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) {
316 status.marker = *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);
326 continue;
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
337 nexti = i;
338 ++nexti;
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();
345 } else {
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();
352 } else {
353 // range
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);
364 void
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
385 at the moment.
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";
397 } else {
398 status.out << "MOTOROLA";
400 } else {
401 // AIFF should return "AIFF"
402 status.out << status.format->format_name();
404 status.out << endl;
407 void
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;
417 void
418 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
420 gchar buf[18];
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 ";
428 } else {
429 status.out << " DCP ";
432 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
433 status.out << " PRE";
435 status.out << endl;
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++;
465 void
466 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
468 gchar buf[18];
470 status.out << endl << "TRACK AUDIO" << endl;
472 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
473 status.out << "NO ";
475 status.out << "COPY" << endl;
477 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
478 status.out << "PRE_EMPHASIS" << endl;
479 } else {
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;
515 void
516 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
518 gchar buf[18];
520 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
521 status.out << buf;
522 frames_to_cd_frames_string (buf, status.index_position);
523 status.out << buf << endl;
525 cue_indexnum++;
528 void
529 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
531 gchar buf[18];
533 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
534 status.out << "INDEX" << buf << endl;
537 void
538 ExportHandler::frames_to_cd_frames_string (char* buf, sframes_t when)
540 sframes_t remainder;
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