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