Skeleton tests for BBT_Time.
[ardour.git] / libs / ardour / export_handler.cc
1 /*
2     Copyright (C) 2008 Paul Davis
3     Author: Sakari Bergen
4
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.
9
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.
14
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.
18
19 */
20
21 #include "ardour/export_handler.h"
22
23 #include <glibmm.h>
24
25 #include "pbd/convert.h"
26 #include "pbd/filesystem.h"
27
28 #include "ardour/ardour.h"
29 #include "ardour/configuration.h"
30 #include "ardour/export_timespan.h"
31 #include "ardour/export_channel_configuration.h"
32 #include "ardour/export_status.h"
33 #include "ardour/export_format_specification.h"
34 #include "ardour/export_filename.h"
35 #include "ardour/export_processor.h"
36 #include "ardour/export_failed.h"
37
38 using namespace std;
39 using namespace PBD;
40
41 namespace ARDOUR
42 {
43
44 /*** ExportElementFactory ***/
45
46 ExportElementFactory::ExportElementFactory (Session & session) :
47   session (session)
48 {
49
50 }
51
52 ExportElementFactory::~ExportElementFactory ()
53 {
54
55 }
56
57 ExportElementFactory::TimespanPtr
58 ExportElementFactory::add_timespan ()
59 {
60         return TimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
61 }
62
63 ExportElementFactory::ChannelConfigPtr
64 ExportElementFactory::add_channel_config ()
65 {
66         return ChannelConfigPtr (new ExportChannelConfiguration (session));
67 }
68
69 ExportElementFactory::FormatPtr
70 ExportElementFactory::add_format ()
71 {
72         return FormatPtr (new ExportFormatSpecification (session));
73 }
74
75 ExportElementFactory::FormatPtr
76 ExportElementFactory::add_format (XMLNode const & state)
77 {
78         return FormatPtr (new ExportFormatSpecification (session, state));
79 }
80
81 ExportElementFactory::FormatPtr
82 ExportElementFactory::add_format_copy (FormatPtr other)
83 {
84         return FormatPtr (new ExportFormatSpecification (*other));
85 }
86
87 ExportElementFactory::FilenamePtr
88 ExportElementFactory::add_filename ()
89 {
90         return FilenamePtr (new ExportFilename (session));
91 }
92
93 ExportElementFactory::FilenamePtr
94 ExportElementFactory::add_filename_copy (FilenamePtr other)
95 {
96         return FilenamePtr (new ExportFilename (*other));
97 }
98
99 /*** ExportHandler ***/
100
101 ExportHandler::ExportHandler (Session & session) :
102   ExportElementFactory (session),
103   session (session),
104   export_status (session.get_export_status ()),
105   realtime (false)
106 {
107         processor.reset (new ExportProcessor (session));
108
109         files_written_connection = ExportProcessor::WritingFile.connect (sigc::mem_fun (files_written, &std::list<Glib::ustring>::push_back));
110 }
111
112 ExportHandler::~ExportHandler ()
113 {
114         if (export_status->aborted()) {
115                 for (std::list<Glib::ustring>::iterator it = files_written.begin(); it != files_written.end(); ++it) {
116                         sys::remove (sys::path (*it));
117                 }
118         }
119
120         channel_config_connection.disconnect();
121         files_written_connection.disconnect();
122 }
123
124 bool
125 ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename)
126 {
127         FileSpec spec (channel_config, format, filename);
128         ConfigPair pair (timespan, spec);
129         config_map.insert (pair);
130
131         return true;
132 }
133
134 /// Starts exporting the registered configurations
135 /** The following happens, when do_export is called:
136  * 1. Session is prepared in do_export
137  * 2. start_timespan is called, which then registers all necessary channel configs to a timespan
138  * 3. The timespan reads each unique channel into a tempfile and calls Session::stop_export when the end is reached
139  * 4. stop_export emits ExportReadFinished after stopping the transport, this ends up calling finish_timespan
140  * 5. finish_timespan registers all the relevant formats and filenames to relevant channel configurations
141  * 6. finish_timespan does a manual call to timespan_thread_finished, which gets the next channel configuration
142  *    for the current timespan, calling write_files for it
143  * 7. write_files writes the actual export files, composing them from the individual channels from tempfiles and
144  *    emits FilesWritten when it is done, which ends up calling timespan_thread_finished
145  * 8. Once all channel configs are written, a new timespan is started by calling start_timespan
146  * 9. When all timespans are written the session is taken out of export.
147  */
148
149 void
150 ExportHandler::do_export (bool rt)
151 {
152         /* Count timespans */
153
154         export_status->init();
155         std::set<TimespanPtr> timespan_set;
156         for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
157                 timespan_set.insert (it->first);
158         }
159         export_status->total_timespans = timespan_set.size();
160
161         /* Start export */
162
163         realtime = rt;
164
165         session.ExportReadFinished.connect (sigc::mem_fun (*this, &ExportHandler::finish_timespan));
166         start_timespan ();
167 }
168
169 struct LocationSortByStart {
170     bool operator() (Location *a, Location *b) {
171             return a->start() < b->start();
172     }
173 };
174
175 void
176 ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format)
177 {
178         string filepath;
179         string basename = Glib::path_get_basename(filename);
180
181         size_t ext_pos = basename.rfind('.');
182         if (ext_pos != string::npos) {
183                 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
184         }
185
186         void (ExportHandler::*header_func) (CDMarkerStatus &);
187         void (ExportHandler::*track_func) (CDMarkerStatus &);
188         void (ExportHandler::*index_func) (CDMarkerStatus &);
189
190         switch (format) {
191           case CDMarkerTOC:
192                 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
193                 header_func = &ExportHandler::write_toc_header;
194                 track_func = &ExportHandler::write_track_info_toc;
195                 index_func = &ExportHandler::write_index_info_toc;
196                 break;
197           case CDMarkerCUE:
198                 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
199                 header_func = &ExportHandler::write_cue_header;
200                 track_func = &ExportHandler::write_track_info_cue;
201                 index_func = &ExportHandler::write_index_info_cue;
202                 break;
203           default:
204                 return;
205         }
206
207         CDMarkerStatus status (filepath, timespan, file_format, filename);
208
209         if (!status.out) {
210                 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
211                 return;
212         }
213
214         (this->*header_func) (status);
215
216         /* Get locations and sort */
217
218         Locations::LocationList const & locations (session.locations()->list());
219         Locations::LocationList::const_iterator i;
220         Locations::LocationList temp;
221
222         for (i = locations.begin(); i != locations.end(); ++i) {
223                 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_end()) {
224                         temp.push_back (*i);
225                 }
226         }
227
228         if (temp.empty()) {
229                 // TODO One index marker for whole thing
230                 return;
231         }
232
233         LocationSortByStart cmp;
234         temp.sort (cmp);
235         Locations::LocationList::const_iterator nexti;
236
237         /* Start actual marker stuff */
238
239         nframes_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
240         status.track_position = last_start_time - timespan->get_start();
241
242         for (i = temp.begin(); i != temp.end(); ++i) {
243
244                 status.marker = *i;
245
246                 if ((*i)->start() < last_end_time) {
247                         if ((*i)->is_mark()) {
248                                 /* Index within track */
249
250                                 status.index_position = (*i)->start() - timespan->get_start();
251                                 (this->*index_func) (status);
252                         }
253
254                         continue;
255                 }
256
257                 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
258
259                 status.track_position = last_end_time - timespan->get_start();
260                 status.track_start_frame = (*i)->start() - timespan->get_start();  // everything before this is the pregap
261                 status.track_duration = 0;
262
263                 if ((*i)->is_mark()) {
264                         // a mark track location needs to look ahead to the next marker's start to determine length
265                         nexti = i;
266                         ++nexti;
267
268                         if (nexti != temp.end()) {
269                                 status.track_duration = (*nexti)->start() - last_end_time;
270
271                                 last_start_time = (*i)->start();
272                                 last_end_time = (*nexti)->start();
273                         } else {
274                                 // this was the last marker, use timespan end
275                                 status.track_duration = timespan->get_end() - last_end_time;
276
277                                 last_start_time = (*i)->start();
278                                 last_end_time = timespan->get_end();
279                         }
280                 } else {
281                         // range
282                         status.track_duration = (*i)->end() - last_end_time;
283
284                         last_start_time = (*i)->start();
285                         last_end_time = (*i)->end();
286                 }
287
288                 (this->*track_func) (status);
289         }
290 }
291
292 void
293 ExportHandler::write_cue_header (CDMarkerStatus & status)
294 {
295         Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
296
297         status.out << "REM Cue file generated by Ardour" << endl;
298         status.out << "TITLE \"" << title << "\"" << endl;
299
300         /*  The cue sheet syntax has originally five file types:
301                 WAVE     : 44.1 kHz, 16 Bit (little endian)
302                 AIFF     : 44.1 kHz, 16 Bit (big endian)
303                 BINARY   : 44.1 kHz, 16 Bit (little endian)
304                 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
305                 MP3
306
307                 We want to use cue sheets not only as CD images but also as general playlyist
308                 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
309                 soundfile's header shows it anyway.  But for the raw formats, i.e. BINARY
310                 and MOTOROLA we do care, because no header would tell us about a different format.
311
312                 For all other formats we just make up our own file type.  MP3 is not supported
313                 at the moment.
314         */
315
316         status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
317         if (!status.format->format_name().compare ("WAV")) {
318                 status.out  << "WAVE";
319         } else if (status.format->format_name() == ExportFormatBase::F_RAW &&
320                    status.format->sample_format() == ExportFormatBase::SF_16 &&
321                    status.format->sample_rate() == ExportFormatBase::SR_44_1) {
322                 // Format is RAW 16bit 44.1kHz
323                 if (status.format->endianness() == ExportFormatBase::E_Little) {
324                         status.out << "BINARY";
325                 } else {
326                         status.out << "MOTOROLA";
327                 }
328         } else {
329                 // AIFF should return "AIFF"
330                 status.out << status.format->format_name();
331         }
332         status.out << endl;
333 }
334
335 void
336 ExportHandler::write_toc_header (CDMarkerStatus & status)
337 {
338         Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
339
340         status.out << "CD_DA" << endl;
341         status.out << "CD_TEXT {" << endl << "  LANGUAGE_MAP {" << endl << "    0 : EN" << endl << "  }" << endl;
342         status.out << "  LANGUAGE 0 {" << endl << "    TITLE \"" << title << "\"" << endl << "  }" << endl << "}" << endl;
343 }
344
345 void
346 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
347 {
348         gchar buf[18];
349
350         snprintf (buf, sizeof(buf), "  TRACK %02d AUDIO", status.track_number);
351         status.out << buf << endl;
352
353         status.out << "    FLAGS" ;
354         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
355                 status.out << " SCMS ";
356         } else {
357                 status.out << " DCP ";
358         }
359
360         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
361                 status.out << " PRE";
362         }
363         status.out << endl;
364
365         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
366                 status.out << "    ISRC " << status.marker->cd_info["isrc"] << endl;
367
368         }
369         if (status.marker->name() != "") {
370                 status.out << "    TITLE \"" << status.marker->name() << "\"" << endl;
371         }
372
373         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
374                 status.out << "    PERFORMER \"" <<  status.marker->cd_info["performer"] << "\"" << endl;
375         }
376
377         if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
378                 status.out << "    SONGWRITER \"" << status.marker->cd_info["string_composer"]  << "\"" << endl;
379         }
380
381         if (status.track_position != status.track_start_frame) {
382                 frames_to_cd_frames_string (buf, status.track_position);
383                 status.out << "    INDEX 00" << buf << endl;
384         }
385
386         frames_to_cd_frames_string (buf, status.track_start_frame);
387         status.out << "    INDEX 01" << buf << endl;
388
389         status.index_number = 2;
390         status.track_number++;
391 }
392
393 void
394 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
395 {
396         gchar buf[18];
397
398         status.out << endl << "TRACK AUDIO" << endl;
399
400         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
401                 status.out << "NO ";
402         }
403         status.out << "COPY" << endl;
404
405         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
406                 status.out << "PRE_EMPHASIS" << endl;
407         } else {
408                 status.out << "NO PRE_EMPHASIS" << endl;
409         }
410
411         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
412                 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
413         }
414
415         status.out << "CD_TEXT {" << endl << "  LANGUAGE 0 {" << endl << "     TITLE \"" << status.marker->name() << "\"" << endl;
416         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
417                 status.out << "     PERFORMER \"" << status.marker->cd_info["performer"]  << "\"" << endl;
418         }
419         if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
420                 status.out  << "     COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
421         }
422
423         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
424                 status.out  << "     ISRC \"";
425                 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
426                 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
427                 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
428                 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
429         }
430
431         status.out << "  }" << endl << "}" << endl;
432
433         frames_to_cd_frames_string (buf, status.track_position);
434         status.out << "FILE \"" << status.filename << "\" " << buf;
435
436         frames_to_cd_frames_string (buf, status.track_duration);
437         status.out << buf << endl;
438
439         frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
440         status.out << "START" << buf << endl;
441 }
442
443 void
444 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
445 {
446         gchar buf[18];
447
448         snprintf (buf, sizeof(buf), "    INDEX %02d", cue_indexnum);
449         status.out << buf;
450         frames_to_cd_frames_string (buf, status.index_position);
451         status.out << buf << endl;
452
453         cue_indexnum++;
454 }
455
456 void
457 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
458 {
459         gchar buf[18];
460
461         frames_to_cd_frames_string (buf, status.index_position - status.track_position);
462         status.out << "INDEX" << buf << endl;
463 }
464
465 void
466 ExportHandler::frames_to_cd_frames_string (char* buf, nframes_t when)
467 {
468         nframes_t remainder;
469         nframes_t fr = session.nominal_frame_rate();
470         int mins, secs, frames;
471
472         mins = when / (60 * fr);
473         remainder = when - (mins * 60 * fr);
474         secs = remainder / fr;
475         remainder -= secs * fr;
476         frames = remainder / (fr / 75);
477         sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
478 }
479
480 void
481 ExportHandler::start_timespan ()
482 {
483         export_status->timespan++;
484
485         if (config_map.empty()) {
486                 export_status->finish ();
487                 return;
488         }
489
490         current_timespan = config_map.begin()->first;
491
492         /* Register channel configs with timespan */
493
494         timespan_bounds = config_map.equal_range (current_timespan);
495
496         for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
497                 it->second.channel_config->register_with_timespan (current_timespan);
498         }
499
500         /* connect stuff and start export */
501
502         current_timespan->process_connection = session.ProcessExport.connect (sigc::mem_fun (*current_timespan, &ExportTimespan::process));
503         session.start_audio_export (current_timespan->get_start(), realtime);
504 }
505
506 void
507 ExportHandler::finish_timespan ()
508 {
509         current_timespan->process_connection.disconnect ();
510
511         /* Register formats and filenames to relevant channel configs */
512
513         export_status->total_formats = 0;
514         export_status->format = 0;
515
516         for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
517
518                 export_status->total_formats++;
519
520                 /* Setup filename */
521
522                 it->second.filename->set_timespan (current_timespan);
523                 it->second.filename->set_channel_config (it->second.channel_config);
524
525                 /* Do actual registration */
526
527                 ChannelConfigPtr chan_config = it->second.channel_config;
528                 chan_config->register_file_config (it->second.format, it->second.filename);
529         }
530
531         /* Start writing files by doing a manual call to timespan_thread_finished */
532
533         current_map_it = timespan_bounds.first;
534         timespan_thread_finished ();
535 }
536
537 void
538 ExportHandler::timespan_thread_finished ()
539 {
540         channel_config_connection.disconnect();
541
542         if (current_map_it != timespan_bounds.second) {
543
544                 /* Get next configuration as long as no new export process is started */
545
546                 ChannelConfigPtr cc = current_map_it->second.channel_config;
547                 while (!cc->write_files(processor)) {
548
549                         ++current_map_it;
550
551                         if (current_map_it == timespan_bounds.second) {
552
553                                 /* reached end of bounds, this call will end up in the else block below */
554
555                                 timespan_thread_finished ();
556                                 return;
557                         }
558
559                         cc = current_map_it->second.channel_config;
560                 }
561
562                 channel_config_connection = cc->FilesWritten.connect (sigc::mem_fun (*this, &ExportHandler::timespan_thread_finished));
563                 ++current_map_it;
564
565         } else { /* All files are written from current timespan, reset timespan and start new */
566
567                 /* Unregister configs and remove configs with this timespan */
568
569                 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second;) {
570                         it->second.channel_config->unregister_all ();
571
572                         ConfigMap::iterator to_erase = it;
573                         ++it;
574                         config_map.erase (to_erase);
575                 }
576
577                 /* Start new timespan */
578
579                 start_timespan ();
580
581         }
582 }
583
584 } // namespace ARDOUR