9279c73266bc1aeaee2c28bdc7613788ce9d2576
[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         }
142         export_status->total_timespans = timespan_set.size();
143
144         /* Start export */
145
146         realtime = rt;
147         start_timespan ();
148 }
149
150 void
151 ExportHandler::start_timespan ()
152 {
153         export_status->timespan++;
154
155         if (config_map.empty()) {
156                 // freewheeling has to be stopped from outside the process cycle
157                 export_status->running = false;
158                 return;
159         }
160
161         current_timespan = config_map.begin()->first;
162
163         /* Register file configurations to graph builder */
164
165         timespan_bounds = config_map.equal_range (current_timespan);
166         graph_builder->reset ();
167         graph_builder->set_current_timespan (current_timespan);
168         for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
169                 // Filenames can be shared across timespans
170                 FileSpec & spec = it->second;
171                 spec.filename->set_timespan (it->first);
172                 graph_builder->add_config (spec);
173         }
174
175         /* start export */
176
177         normalizing = false;
178         session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
179         process_position = current_timespan->get_start();
180         session.start_audio_export (process_position, realtime);
181 }
182
183 int
184 ExportHandler::process (framecnt_t frames)
185 {
186         if (!export_status->running) {
187                 return 0;
188         } else if (normalizing) {
189                 return process_normalize ();
190         } else {
191                 return process_timespan (frames);
192         }
193 }
194
195 int
196 ExportHandler::process_timespan (framecnt_t frames)
197 {
198         /* update position */
199
200         framecnt_t frames_to_read = 0;
201         framepos_t const start = current_timespan->get_start();
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->progress = (float) (process_position - start) / (end - start);
216
217         /* Do actual processing */
218
219         return graph_builder->process (frames_to_read, last_cycle);
220 }
221
222 int
223 ExportHandler::process_normalize ()
224 {
225         if (graph_builder->process_normalize ()) {
226                 finish_timespan ();
227         }
228
229         return 0;
230 }
231
232 void
233 ExportHandler::finish_timespan ()
234 {
235         while (config_map.begin() != timespan_bounds.second) {
236                 config_map.erase (config_map.begin());
237         }
238
239         start_timespan ();
240 }
241
242 /*** CD Marker sutff ***/
243
244 struct LocationSortByStart {
245     bool operator() (Location *a, Location *b) {
246             return a->start() < b->start();
247     }
248 };
249
250 void
251 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
252                                       std::string filename, CDMarkerFormat format)
253 {
254         string filepath;
255         string basename = Glib::path_get_basename(filename);
256
257         size_t ext_pos = basename.rfind('.');
258         if (ext_pos != string::npos) {
259                 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
260         }
261
262         void (ExportHandler::*header_func) (CDMarkerStatus &);
263         void (ExportHandler::*track_func) (CDMarkerStatus &);
264         void (ExportHandler::*index_func) (CDMarkerStatus &);
265
266         switch (format) {
267           case CDMarkerTOC:
268                 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
269                 header_func = &ExportHandler::write_toc_header;
270                 track_func = &ExportHandler::write_track_info_toc;
271                 index_func = &ExportHandler::write_index_info_toc;
272                 break;
273           case CDMarkerCUE:
274                 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
275                 header_func = &ExportHandler::write_cue_header;
276                 track_func = &ExportHandler::write_track_info_cue;
277                 index_func = &ExportHandler::write_index_info_cue;
278                 break;
279           default:
280                 return;
281         }
282
283         CDMarkerStatus status (filepath, timespan, file_format, filename);
284
285         if (!status.out) {
286                 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
287                 return;
288         }
289
290         (this->*header_func) (status);
291
292         /* Get locations and sort */
293
294         Locations::LocationList const & locations (session.locations()->list());
295         Locations::LocationList::const_iterator i;
296         Locations::LocationList temp;
297
298         for (i = locations.begin(); i != locations.end(); ++i) {
299                 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
300                         temp.push_back (*i);
301                 }
302         }
303
304         if (temp.empty()) {
305                 // TODO One index marker for whole thing
306                 return;
307         }
308
309         LocationSortByStart cmp;
310         temp.sort (cmp);
311         Locations::LocationList::const_iterator nexti;
312
313         /* Start actual marker stuff */
314
315         framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
316         status.track_position = last_start_time - timespan->get_start();
317
318         for (i = temp.begin(); i != temp.end(); ++i) {
319
320                 status.marker = *i;
321
322                 if ((*i)->start() < last_end_time) {
323                         if ((*i)->is_mark()) {
324                                 /* Index within track */
325
326                                 status.index_position = (*i)->start() - timespan->get_start();
327                                 (this->*index_func) (status);
328                         }
329
330                         continue;
331                 }
332
333                 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
334
335                 status.track_position = last_end_time - timespan->get_start();
336                 status.track_start_frame = (*i)->start() - timespan->get_start();  // everything before this is the pregap
337                 status.track_duration = 0;
338
339                 if ((*i)->is_mark()) {
340                         // a mark track location needs to look ahead to the next marker's start to determine length
341                         nexti = i;
342                         ++nexti;
343
344                         if (nexti != temp.end()) {
345                                 status.track_duration = (*nexti)->start() - last_end_time;
346
347                                 last_start_time = (*i)->start();
348                                 last_end_time = (*nexti)->start();
349                         } else {
350                                 // this was the last marker, use timespan end
351                                 status.track_duration = timespan->get_end() - last_end_time;
352
353                                 last_start_time = (*i)->start();
354                                 last_end_time = timespan->get_end();
355                         }
356                 } else {
357                         // range
358                         status.track_duration = (*i)->end() - last_end_time;
359
360                         last_start_time = (*i)->start();
361                         last_end_time = (*i)->end();
362                 }
363
364                 (this->*track_func) (status);
365         }
366 }
367
368 void
369 ExportHandler::write_cue_header (CDMarkerStatus & status)
370 {
371         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
372
373         status.out << "REM Cue file generated by Ardour" << endl;
374         status.out << "TITLE \"" << title << "\"" << endl;
375
376         /*  The cue sheet syntax has originally five file types:
377                 WAVE     : 44.1 kHz, 16 Bit (little endian)
378                 AIFF     : 44.1 kHz, 16 Bit (big endian)
379                 BINARY   : 44.1 kHz, 16 Bit (little endian)
380                 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
381                 MP3
382
383                 We want to use cue sheets not only as CD images but also as general playlyist
384                 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
385                 soundfile's header shows it anyway.  But for the raw formats, i.e. BINARY
386                 and MOTOROLA we do care, because no header would tell us about a different format.
387
388                 For all other formats we just make up our own file type.  MP3 is not supported
389                 at the moment.
390         */
391
392         status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
393         if (!status.format->format_name().compare ("WAV")) {
394                 status.out  << "WAVE";
395         } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
396                    status.format->sample_format() == ExportFormatBase::SF_16 &&
397                    status.format->sample_rate() == ExportFormatBase::SR_44_1) {
398                 // Format is RAW 16bit 44.1kHz
399                 if (status.format->endianness() == ExportFormatBase::E_Little) {
400                         status.out << "BINARY";
401                 } else {
402                         status.out << "MOTOROLA";
403                 }
404         } else {
405                 // AIFF should return "AIFF"
406                 status.out << status.format->format_name();
407         }
408         status.out << endl;
409 }
410
411 void
412 ExportHandler::write_toc_header (CDMarkerStatus & status)
413 {
414         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
415
416         status.out << "CD_DA" << endl;
417         status.out << "CD_TEXT {" << endl << "  LANGUAGE_MAP {" << endl << "    0 : EN" << endl << "  }" << endl;
418         status.out << "  LANGUAGE 0 {" << endl << "    TITLE \"" << title << "\"" << endl << "  }" << endl << "}" << endl;
419 }
420
421 void
422 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
423 {
424         gchar buf[18];
425
426         snprintf (buf, sizeof(buf), "  TRACK %02d AUDIO", status.track_number);
427         status.out << buf << endl;
428
429         status.out << "    FLAGS" ;
430         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
431                 status.out << " SCMS ";
432         } else {
433                 status.out << " DCP ";
434         }
435
436         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
437                 status.out << " PRE";
438         }
439         status.out << endl;
440
441         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
442                 status.out << "    ISRC " << status.marker->cd_info["isrc"] << endl;
443
444         }
445         if (status.marker->name() != "") {
446                 status.out << "    TITLE \"" << status.marker->name() << "\"" << endl;
447         }
448
449         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
450                 status.out << "    PERFORMER \"" <<  status.marker->cd_info["performer"] << "\"" << endl;
451         }
452
453         if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
454                 status.out << "    SONGWRITER \"" << status.marker->cd_info["string_composer"]  << "\"" << endl;
455         }
456
457         if (status.track_position != status.track_start_frame) {
458                 frames_to_cd_frames_string (buf, status.track_position);
459                 status.out << "    INDEX 00" << buf << endl;
460         }
461
462         frames_to_cd_frames_string (buf, status.track_start_frame);
463         status.out << "    INDEX 01" << buf << endl;
464
465         status.index_number = 2;
466         status.track_number++;
467 }
468
469 void
470 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
471 {
472         gchar buf[18];
473
474         status.out << endl << "TRACK AUDIO" << endl;
475
476         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
477                 status.out << "NO ";
478         }
479         status.out << "COPY" << endl;
480
481         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
482                 status.out << "PRE_EMPHASIS" << endl;
483         } else {
484                 status.out << "NO PRE_EMPHASIS" << endl;
485         }
486
487         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
488                 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
489         }
490
491         status.out << "CD_TEXT {" << endl << "  LANGUAGE 0 {" << endl << "     TITLE \"" << status.marker->name() << "\"" << endl;
492         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
493                 status.out << "     PERFORMER \"" << status.marker->cd_info["performer"]  << "\"" << endl;
494         }
495         if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
496                 status.out  << "     COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
497         }
498
499         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
500                 status.out  << "     ISRC \"";
501                 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
502                 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
503                 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
504                 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
505         }
506
507         status.out << "  }" << endl << "}" << endl;
508
509         frames_to_cd_frames_string (buf, status.track_position);
510         status.out << "FILE \"" << status.filename << "\" " << buf;
511
512         frames_to_cd_frames_string (buf, status.track_duration);
513         status.out << buf << endl;
514
515         frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
516         status.out << "START" << buf << endl;
517 }
518
519 void
520 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
521 {
522         gchar buf[18];
523
524         snprintf (buf, sizeof(buf), "    INDEX %02d", cue_indexnum);
525         status.out << buf;
526         frames_to_cd_frames_string (buf, status.index_position);
527         status.out << buf << endl;
528
529         cue_indexnum++;
530 }
531
532 void
533 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
534 {
535         gchar buf[18];
536
537         frames_to_cd_frames_string (buf, status.index_position - status.track_position);
538         status.out << "INDEX" << buf << endl;
539 }
540
541 void
542 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
543 {
544         framecnt_t remainder;
545         framecnt_t fr = session.nominal_frame_rate();
546         int mins, secs, frames;
547
548         mins = when / (60 * fr);
549         remainder = when - (mins * 60 * fr);
550         secs = remainder / fr;
551         remainder -= secs * fr;
552         frames = remainder / (fr / 75);
553         sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
554 }
555
556 } // namespace ARDOUR