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