f19dfe048cec4b7a75b2b7f6871f24e4008a5811
[ardour.git] / libs / ardour / export_handler.cc
1 /*
2     Copyright (C) 2008-2009 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_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"
37
38 #include "i18n.h"
39
40 using namespace std;
41 using namespace PBD;
42
43 namespace ARDOUR
44 {
45
46 /*** ExportElementFactory ***/
47
48 ExportElementFactory::ExportElementFactory (Session & session) :
49   session (session)
50 {
51
52 }
53
54 ExportElementFactory::~ExportElementFactory ()
55 {
56
57 }
58
59 ExportTimespanPtr
60 ExportElementFactory::add_timespan ()
61 {
62         return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
63 }
64
65 ExportChannelConfigPtr
66 ExportElementFactory::add_channel_config ()
67 {
68         return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
69 }
70
71 ExportFormatSpecPtr
72 ExportElementFactory::add_format ()
73 {
74         return ExportFormatSpecPtr (new ExportFormatSpecification (session));
75 }
76
77 ExportFormatSpecPtr
78 ExportElementFactory::add_format (XMLNode const & state)
79 {
80         return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
81 }
82
83 ExportFormatSpecPtr
84 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
85 {
86         return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
87 }
88
89 ExportFilenamePtr
90 ExportElementFactory::add_filename ()
91 {
92         return ExportFilenamePtr (new ExportFilename (session));
93 }
94
95 ExportFilenamePtr
96 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
97 {
98         return ExportFilenamePtr (new ExportFilename (*other));
99 }
100
101 /*** ExportHandler ***/
102
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)
112 {
113 }
114
115 ExportHandler::~ExportHandler ()
116 {
117         // TODO remove files that were written but not finsihed
118 }
119
120 bool
121 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
122                                   ExportFormatSpecPtr format, ExportFilenamePtr filename,
123                                   BroadcastInfoPtr broadcast_info)
124 {
125         FileSpec spec (channel_config, format, filename, broadcast_info);
126         ConfigPair pair (timespan, spec);
127         config_map.insert (pair);
128
129         return true;
130 }
131
132 void
133 ExportHandler::do_export (bool rt)
134 {
135         /* Count timespans */
136
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();
142         }
143         export_status->total_timespans = timespan_set.size();
144
145         /* Start export */
146
147         realtime = rt;
148         start_timespan ();
149 }
150
151 void
152 ExportHandler::start_timespan ()
153 {
154         export_status->timespan++;
155
156         if (config_map.empty()) {
157                 // freewheeling has to be stopped from outside the process cycle
158                 export_status->running = false;
159                 return;
160         }
161
162         current_timespan = config_map.begin()->first;
163
164         /* Register file configurations to graph builder */
165
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);
174         }
175
176         /* start export */
177
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);
182 }
183
184 int
185 ExportHandler::process (framecnt_t frames)
186 {
187         if (!export_status->running) {
188                 return 0;
189         } else if (normalizing) {
190                 return process_normalize ();
191         } else {
192                 return process_timespan (frames);
193         }
194 }
195
196 int
197 ExportHandler::process_timespan (framecnt_t frames)
198 {
199         /* update position */
200
201         framecnt_t frames_to_read = 0;
202         framepos_t const end = current_timespan->get_end();
203
204         bool const last_cycle = (process_position + frames >= end);
205
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;
212         }
213
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;
217
218         /* Do actual processing */
219
220         return graph_builder->process (frames_to_read, last_cycle);
221 }
222
223 int
224 ExportHandler::process_normalize ()
225 {
226         if (graph_builder->process_normalize ()) {
227                 finish_timespan ();
228                 export_status->normalizing = false;
229         } else {
230                 export_status->normalizing = true;
231         }
232
233         return 0;
234 }
235
236 void
237 ExportHandler::finish_timespan ()
238 {
239         while (config_map.begin() != timespan_bounds.second) {
240
241                 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
242
243                 if (fmt->with_cue()) {
244                         export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerCUE);
245                 } 
246
247                 if (fmt->with_toc()) {
248                         export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerTOC);
249                 }
250
251                 config_map.erase (config_map.begin());
252         }
253
254         start_timespan ();
255 }
256
257 /*** CD Marker sutff ***/
258
259 struct LocationSortByStart {
260     bool operator() (Location *a, Location *b) {
261             return a->start() < b->start();
262     }
263 };
264
265 void
266 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
267                                       std::string filename, CDMarkerFormat format)
268 {
269         string filepath;
270
271         /* do not strip file suffix because there may be more than one format, 
272            and we do not want the CD marker file from one format to overwrite
273            another (e.g. foo.wav.cue > foo.aiff.cue)
274         */
275
276         void (ExportHandler::*header_func) (CDMarkerStatus &);
277         void (ExportHandler::*track_func) (CDMarkerStatus &);
278         void (ExportHandler::*index_func) (CDMarkerStatus &);
279
280         switch (format) {
281           case CDMarkerTOC:
282                 filepath = filename;
283                 filepath += ".toc";
284                 header_func = &ExportHandler::write_toc_header;
285                 track_func = &ExportHandler::write_track_info_toc;
286                 index_func = &ExportHandler::write_index_info_toc;
287                 break;
288           case CDMarkerCUE:
289                 filepath = filename;
290                 filepath += ".cue";
291                 header_func = &ExportHandler::write_cue_header;
292                 track_func = &ExportHandler::write_track_info_cue;
293                 index_func = &ExportHandler::write_index_info_cue;
294                 break;
295           default:
296                 return;
297         }
298
299         CDMarkerStatus status (filepath, timespan, file_format, filename);
300
301         if (!status.out) {
302                 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
303                 return;
304         }
305
306         (this->*header_func) (status);
307
308         /* Get locations and sort */
309
310         Locations::LocationList const & locations (session.locations()->list());
311         Locations::LocationList::const_iterator i;
312         Locations::LocationList temp;
313
314         for (i = locations.begin(); i != locations.end(); ++i) {
315                 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
316                         temp.push_back (*i);
317                 }
318         }
319
320         if (temp.empty()) {
321                 // TODO One index marker for whole thing
322                 return;
323         }
324
325         LocationSortByStart cmp;
326         temp.sort (cmp);
327         Locations::LocationList::const_iterator nexti;
328
329         /* Start actual marker stuff */
330
331         framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
332         status.track_position = last_start_time - timespan->get_start();
333
334         for (i = temp.begin(); i != temp.end(); ++i) {
335
336                 status.marker = *i;
337
338                 if ((*i)->start() < last_end_time) {
339                         if ((*i)->is_mark()) {
340                                 /* Index within track */
341
342                                 status.index_position = (*i)->start() - timespan->get_start();
343                                 (this->*index_func) (status);
344                         }
345
346                         continue;
347                 }
348
349                 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
350
351                 status.track_position = last_end_time - timespan->get_start();
352                 status.track_start_frame = (*i)->start() - timespan->get_start();  // everything before this is the pregap
353                 status.track_duration = 0;
354
355                 if ((*i)->is_mark()) {
356                         // a mark track location needs to look ahead to the next marker's start to determine length
357                         nexti = i;
358                         ++nexti;
359
360                         if (nexti != temp.end()) {
361                                 status.track_duration = (*nexti)->start() - last_end_time;
362
363                                 last_start_time = (*i)->start();
364                                 last_end_time = (*nexti)->start();
365                         } else {
366                                 // this was the last marker, use timespan end
367                                 status.track_duration = timespan->get_end() - last_end_time;
368
369                                 last_start_time = (*i)->start();
370                                 last_end_time = timespan->get_end();
371                         }
372                 } else {
373                         // range
374                         status.track_duration = (*i)->end() - last_end_time;
375
376                         last_start_time = (*i)->start();
377                         last_end_time = (*i)->end();
378                 }
379
380                 (this->*track_func) (status);
381         }
382 }
383
384 void
385 ExportHandler::write_cue_header (CDMarkerStatus & status)
386 {
387         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
388
389         status.out << "REM Cue file generated by Ardour" << endl;
390         status.out << "TITLE \"" << title << "\"" << endl;
391
392         /*  The cue sheet syntax has originally five file types:
393                 WAVE     : 44.1 kHz, 16 Bit (little endian)
394                 AIFF     : 44.1 kHz, 16 Bit (big endian)
395                 BINARY   : 44.1 kHz, 16 Bit (little endian)
396                 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
397                 MP3
398
399                 We want to use cue sheets not only as CD images but also as general playlyist
400                 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
401                 soundfile's header shows it anyway.  But for the raw formats, i.e. BINARY
402                 and MOTOROLA we do care, because no header would tell us about a different format.
403
404                 For all other formats we just make up our own file type.  MP3 is not supported
405                 at the moment.
406         */
407
408         status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
409         if (!status.format->format_name().compare ("WAV")) {
410                 status.out  << "WAVE";
411         } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
412                    status.format->sample_format() == ExportFormatBase::SF_16 &&
413                    status.format->sample_rate() == ExportFormatBase::SR_44_1) {
414                 // Format is RAW 16bit 44.1kHz
415                 if (status.format->endianness() == ExportFormatBase::E_Little) {
416                         status.out << "BINARY";
417                 } else {
418                         status.out << "MOTOROLA";
419                 }
420         } else {
421                 // AIFF should return "AIFF"
422                 status.out << status.format->format_name();
423         }
424         status.out << endl;
425 }
426
427 void
428 ExportHandler::write_toc_header (CDMarkerStatus & status)
429 {
430         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
431
432         status.out << "CD_DA" << endl;
433         status.out << "CD_TEXT {" << endl << "  LANGUAGE_MAP {" << endl << "    0 : EN" << endl << "  }" << endl;
434         status.out << "  LANGUAGE 0 {" << endl << "    TITLE \"" << title << "\"" << endl << "  }" << endl << "}" << endl;
435 }
436
437 void
438 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
439 {
440         gchar buf[18];
441
442         snprintf (buf, sizeof(buf), "  TRACK %02d AUDIO", status.track_number);
443         status.out << buf << endl;
444
445         status.out << "    FLAGS" ;
446         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
447                 status.out << " SCMS ";
448         } else {
449                 status.out << " DCP ";
450         }
451
452         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
453                 status.out << " PRE";
454         }
455         status.out << endl;
456
457         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
458                 status.out << "    ISRC " << status.marker->cd_info["isrc"] << endl;
459
460         }
461         if (status.marker->name() != "") {
462                 status.out << "    TITLE \"" << status.marker->name() << "\"" << endl;
463         }
464
465         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
466                 status.out << "    PERFORMER \"" <<  status.marker->cd_info["performer"] << "\"" << endl;
467         }
468
469         if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
470                 status.out << "    SONGWRITER \"" << status.marker->cd_info["composer"]  << "\"" << endl;
471         }
472
473         if (status.track_position != status.track_start_frame) {
474                 frames_to_cd_frames_string (buf, status.track_position);
475                 status.out << "    INDEX 00" << buf << endl;
476         }
477
478         frames_to_cd_frames_string (buf, status.track_start_frame);
479         status.out << "    INDEX 01" << buf << endl;
480
481         status.index_number = 2;
482         status.track_number++;
483 }
484
485 void
486 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
487 {
488         gchar buf[18];
489
490         status.out << endl << "TRACK AUDIO" << endl;
491
492         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
493                 status.out << "NO ";
494         }
495         status.out << "COPY" << endl;
496
497         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
498                 status.out << "PRE_EMPHASIS" << endl;
499         } else {
500                 status.out << "NO PRE_EMPHASIS" << endl;
501         }
502
503         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
504                 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
505         }
506
507         status.out << "CD_TEXT {" << endl << "  LANGUAGE 0 {" << endl << "     TITLE \"" << status.marker->name() << "\"" << endl;
508         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
509                 status.out << "     PERFORMER \"" << status.marker->cd_info["performer"]  << "\"" << endl;
510         }
511         if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
512                 status.out  << "     COMPOSER \"" << status.marker->cd_info["composer"] << "\"" << endl;
513         }
514
515         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
516                 status.out  << "     ISRC \"";
517                 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
518                 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
519                 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
520                 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
521         }
522
523         status.out << "  }" << endl << "}" << endl;
524
525         frames_to_cd_frames_string (buf, status.track_position);
526         status.out << "FILE \"" << status.filename << "\" " << buf;
527
528         frames_to_cd_frames_string (buf, status.track_duration);
529         status.out << buf << endl;
530
531         frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
532         status.out << "START" << buf << endl;
533 }
534
535 void
536 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
537 {
538         gchar buf[18];
539
540         snprintf (buf, sizeof(buf), "    INDEX %02d", cue_indexnum);
541         status.out << buf;
542         frames_to_cd_frames_string (buf, status.index_position);
543         status.out << buf << endl;
544
545         cue_indexnum++;
546 }
547
548 void
549 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
550 {
551         gchar buf[18];
552
553         frames_to_cd_frames_string (buf, status.index_position - status.track_position);
554         status.out << "INDEX" << buf << endl;
555 }
556
557 void
558 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
559 {
560         framecnt_t remainder;
561         framecnt_t fr = session.nominal_frame_rate();
562         int mins, secs, frames;
563
564         mins = when / (60 * fr);
565         remainder = when - (mins * 60 * fr);
566         secs = remainder / fr;
567         remainder -= secs * fr;
568         frames = remainder / (fr / 75);
569         sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
570 }
571
572 } // namespace ARDOUR