reinstate thread buffer debug output
[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 #include <glibmm/convert.h>
25
26 #include "pbd/convert.h"
27
28 #include "ardour/export_graph_builder.h"
29 #include "ardour/export_timespan.h"
30 #include "ardour/export_channel_configuration.h"
31 #include "ardour/export_status.h"
32 #include "ardour/export_format_specification.h"
33 #include "ardour/export_filename.h"
34
35 #include "i18n.h"
36
37 using namespace std;
38 using namespace PBD;
39
40 namespace ARDOUR
41 {
42
43 /*** ExportElementFactory ***/
44
45 ExportElementFactory::ExportElementFactory (Session & session) :
46   session (session)
47 {
48
49 }
50
51 ExportElementFactory::~ExportElementFactory ()
52 {
53
54 }
55
56 ExportTimespanPtr
57 ExportElementFactory::add_timespan ()
58 {
59         return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
60 }
61
62 ExportChannelConfigPtr
63 ExportElementFactory::add_channel_config ()
64 {
65         return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
66 }
67
68 ExportFormatSpecPtr
69 ExportElementFactory::add_format ()
70 {
71         return ExportFormatSpecPtr (new ExportFormatSpecification (session));
72 }
73
74 ExportFormatSpecPtr
75 ExportElementFactory::add_format (XMLNode const & state)
76 {
77         return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
78 }
79
80 ExportFormatSpecPtr
81 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
82 {
83         return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
84 }
85
86 ExportFilenamePtr
87 ExportElementFactory::add_filename ()
88 {
89         return ExportFilenamePtr (new ExportFilename (session));
90 }
91
92 ExportFilenamePtr
93 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
94 {
95         return ExportFilenamePtr (new ExportFilename (*other));
96 }
97
98 /*** ExportHandler ***/
99
100 ExportHandler::ExportHandler (Session & session)
101   : ExportElementFactory (session)
102   , session (session)
103   , graph_builder (new ExportGraphBuilder (session))
104   , export_status (session.get_export_status ())
105   , normalizing (false)
106   , cue_tracknum (0)
107   , cue_indexnum (0)
108 {
109 }
110
111 ExportHandler::~ExportHandler ()
112 {
113         // TODO remove files that were written but not finished
114 }
115
116 /** Add an export to the `to-do' list */
117 bool
118 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
119                                   ExportFormatSpecPtr format, ExportFilenamePtr filename,
120                                   BroadcastInfoPtr broadcast_info)
121 {
122         FileSpec spec (channel_config, format, filename, broadcast_info);
123         config_map.insert (make_pair (timespan, spec));
124
125         return true;
126 }
127
128 void
129 ExportHandler::do_export ()
130 {
131         /* Count timespans */
132
133         export_status->init();
134         std::set<ExportTimespanPtr> timespan_set;
135         for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
136                 bool new_timespan = timespan_set.insert (it->first).second;
137                 if (new_timespan) {
138                         export_status->total_frames += it->first->get_length();
139                 }
140         }
141         export_status->total_timespans = timespan_set.size();
142
143         /* Start export */
144
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         /* finish_timespan pops the config_map entry that has been done, so
160            this is the timespan to do this time
161         */
162         current_timespan = config_map.begin()->first;
163         
164         export_status->total_frames_current_timespan = current_timespan->get_length();
165         export_status->timespan_name = current_timespan->name();
166         export_status->processed_frames_current_timespan = 0;
167
168         /* Register file configurations to graph builder */
169
170         /* Here's the config_map entries that use this timespan */
171         timespan_bounds = config_map.equal_range (current_timespan);
172         graph_builder->reset ();
173         graph_builder->set_current_timespan (current_timespan);
174         for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
175                 // Filenames can be shared across timespans
176                 FileSpec & spec = it->second;
177                 spec.filename->set_timespan (it->first);
178                 graph_builder->add_config (spec);
179         }
180
181         /* start export */
182
183         normalizing = false;
184         session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
185         process_position = current_timespan->get_start();
186         session.start_audio_export (process_position);
187 }
188
189 int
190 ExportHandler::process (framecnt_t frames)
191 {
192         if (!export_status->running) {
193                 return 0;
194         } else if (normalizing) {
195                 return process_normalize ();
196         } else {
197                 return process_timespan (frames);
198         }
199 }
200
201 int
202 ExportHandler::process_timespan (framecnt_t frames)
203 {
204         /* update position */
205
206         framecnt_t frames_to_read = 0;
207         framepos_t const end = current_timespan->get_end();
208
209         bool const last_cycle = (process_position + frames >= end);
210
211         if (last_cycle) {
212                 frames_to_read = end - process_position;
213                 export_status->stop = true;
214         } else {
215                 frames_to_read = frames;
216         }
217
218         process_position += frames_to_read;
219         export_status->processed_frames += frames_to_read;
220         export_status->processed_frames_current_timespan += frames_to_read;
221
222         /* Do actual processing */
223         int ret = graph_builder->process (frames_to_read, last_cycle);
224
225         /* Start normalizing if necessary */
226         if (last_cycle) {
227                 normalizing = graph_builder->will_normalize();
228                 if (normalizing) {
229                         export_status->total_normalize_cycles = graph_builder->get_normalize_cycle_count();
230                         export_status->current_normalize_cycle = 0;
231                 } else {
232                         finish_timespan ();
233                         return 0;
234                 }
235         }
236
237         return ret;
238 }
239
240 int
241 ExportHandler::process_normalize ()
242 {
243         if (graph_builder->process_normalize ()) {
244                 finish_timespan ();
245                 export_status->normalizing = false;
246         } else {
247                 export_status->normalizing = true;
248         }
249
250         export_status->current_normalize_cycle++;
251
252         return 0;
253 }
254
255 void
256 ExportHandler::finish_timespan ()
257 {
258         while (config_map.begin() != timespan_bounds.second) {
259
260                 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
261
262                 if (fmt->with_cue()) {
263                         export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerCUE);
264                 } 
265
266                 if (fmt->with_toc()) {
267                         export_cd_marker_file (current_timespan, fmt, config_map.begin()->second.filename->get_path(fmt), CDMarkerTOC);
268                 }
269
270                 config_map.erase (config_map.begin());
271         }
272
273         start_timespan ();
274 }
275
276 /*** CD Marker sutff ***/
277
278 struct LocationSortByStart {
279     bool operator() (Location *a, Location *b) {
280             return a->start() < b->start();
281     }
282 };
283
284 void
285 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
286                                       std::string filename, CDMarkerFormat format)
287 {
288         string filepath = get_cd_marker_filename(filename, format);
289
290         try {
291                 void (ExportHandler::*header_func) (CDMarkerStatus &);
292                 void (ExportHandler::*track_func) (CDMarkerStatus &);
293                 void (ExportHandler::*index_func) (CDMarkerStatus &);
294
295                 switch (format) {
296                 case CDMarkerTOC:
297                         header_func = &ExportHandler::write_toc_header;
298                         track_func = &ExportHandler::write_track_info_toc;
299                         index_func = &ExportHandler::write_index_info_toc;
300                         break;
301                 case CDMarkerCUE:
302                         header_func = &ExportHandler::write_cue_header;
303                         track_func = &ExportHandler::write_track_info_cue;
304                         index_func = &ExportHandler::write_index_info_cue;
305                         break;
306                 default:
307                         return;
308                 }
309
310                 CDMarkerStatus status (filepath, timespan, file_format, filename);
311
312                 if (!status.out) {
313                         error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
314                         return;
315                 }
316
317                 (this->*header_func) (status);
318
319                 /* Get locations and sort */
320
321                 Locations::LocationList const & locations (session.locations()->list());
322                 Locations::LocationList::const_iterator i;
323                 Locations::LocationList temp;
324
325                 for (i = locations.begin(); i != locations.end(); ++i) {
326                         if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
327                                 temp.push_back (*i);
328                         }
329                 }
330
331                 if (temp.empty()) {
332                         // TODO One index marker for whole thing
333                         return;
334                 }
335
336                 LocationSortByStart cmp;
337                 temp.sort (cmp);
338                 Locations::LocationList::const_iterator nexti;
339
340                 /* Start actual marker stuff */
341
342                 framepos_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
343                 status.track_position = last_start_time - timespan->get_start();
344
345                 for (i = temp.begin(); i != temp.end(); ++i) {
346
347                         status.marker = *i;
348
349                         if ((*i)->start() < last_end_time) {
350                                 if ((*i)->is_mark()) {
351                                         /* Index within track */
352
353                                         status.index_position = (*i)->start() - timespan->get_start();
354                                         (this->*index_func) (status);
355                                 }
356
357                                 continue;
358                         }
359
360                         /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
361
362                         status.track_position = last_end_time - timespan->get_start();
363                         status.track_start_frame = (*i)->start() - timespan->get_start();  // everything before this is the pregap
364                         status.track_duration = 0;
365
366                         if ((*i)->is_mark()) {
367                                 // a mark track location needs to look ahead to the next marker's start to determine length
368                                 nexti = i;
369                                 ++nexti;
370
371                                 if (nexti != temp.end()) {
372                                         status.track_duration = (*nexti)->start() - last_end_time;
373
374                                         last_start_time = (*i)->start();
375                                         last_end_time = (*nexti)->start();
376                                 } else {
377                                         // this was the last marker, use timespan end
378                                         status.track_duration = timespan->get_end() - last_end_time;
379
380                                         last_start_time = (*i)->start();
381                                         last_end_time = timespan->get_end();
382                                 }
383                         } else {
384                                 // range
385                                 status.track_duration = (*i)->end() - last_end_time;
386
387                                 last_start_time = (*i)->start();
388                                 last_end_time = (*i)->end();
389                         }
390
391                         (this->*track_func) (status);
392                 }
393
394         } catch (std::exception& e) {
395                 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
396                 ::unlink (filepath.c_str());
397         } catch (Glib::Exception& e) {
398                 error << string_compose (_("an error occured while writing a TOC/CUE file: %1"), e.what()) << endmsg;
399                 ::unlink (filepath.c_str());
400         }
401 }
402
403 string
404 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
405 {
406         /* do not strip file suffix because there may be more than one format, 
407            and we do not want the CD marker file from one format to overwrite
408            another (e.g. foo.wav.cue > foo.aiff.cue)
409         */
410
411         switch (format) {
412           case CDMarkerTOC:
413                 return filename + ".toc";
414           case CDMarkerCUE:
415                 return filename + ".cue";
416           default:
417                 return filename + ".marker"; // Should not be reached when actually creating a file
418         }
419 }
420
421 void
422 ExportHandler::write_cue_header (CDMarkerStatus & status)
423 {
424         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
425
426         status.out << "REM Cue file generated by " << PROGRAM_NAME << endl;
427         status.out << "TITLE " << cue_escape_cdtext (title) << endl;
428
429         /*  The original cue sheet sepc metions five file types
430                 WAVE, AIFF,
431                 BINARY   = "header-less" audio (44.1 kHz, 16 Bit, little endian),
432                 MOTOROLA = "header-less" audio (44.1 kHz, 16 Bit, big endian),
433                 and MP3
434                 
435                 We try to use these file types whenever appropriate and 
436                 default to our own names otherwise.
437         */
438         status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
439         if (!status.format->format_name().compare ("WAV")  || !status.format->format_name().compare ("BWF")) {
440                 status.out  << "WAVE";
441         } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
442                    status.format->sample_format() == ExportFormatBase::SF_16 &&
443                    status.format->sample_rate() == ExportFormatBase::SR_44_1) {
444                 // Format is RAW 16bit 44.1kHz
445                 if (status.format->endianness() == ExportFormatBase::E_Little) {
446                         status.out << "BINARY";
447                 } else {
448                         status.out << "MOTOROLA";
449                 }
450         } else {
451                 // no special case for AIFF format it's name is already "AIFF"
452                 status.out << status.format->format_name();
453         }
454         status.out << endl;
455 }
456
457 void
458 ExportHandler::write_toc_header (CDMarkerStatus & status)
459 {
460         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
461
462         status.out << "CD_DA" << endl;
463         status.out << "CD_TEXT {" << endl << "  LANGUAGE_MAP {" << endl << "    0 : EN" << endl << "  }" << endl;
464         status.out << "  LANGUAGE 0 {" << endl << "    TITLE " << toc_escape_cdtext (title) << endl ;
465         status.out << "    PERFORMER \"\"" << endl << "  }" << endl << "}" << endl;
466 }
467
468 void
469 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
470 {
471         gchar buf[18];
472
473         snprintf (buf, sizeof(buf), "  TRACK %02d AUDIO", status.track_number);
474         status.out << buf << endl;
475
476         status.out << "    FLAGS" ;
477         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
478                 status.out << " SCMS ";
479         } else {
480                 status.out << " DCP ";
481         }
482
483         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
484                 status.out << " PRE";
485         }
486         status.out << endl;
487
488         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
489                 status.out << "    ISRC " << status.marker->cd_info["isrc"] << endl;
490         }
491
492         if (status.marker->name() != "") {
493                 status.out << "    TITLE " << cue_escape_cdtext (status.marker->name()) << endl;
494         }
495
496         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
497                 status.out <<  "    PERFORMER " << cue_escape_cdtext (status.marker->cd_info["performer"]) << endl;
498         }
499
500         if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
501                 status.out << "    SONGWRITER " << cue_escape_cdtext (status.marker->cd_info["composer"]) << endl;
502         }
503
504         if (status.track_position != status.track_start_frame) {
505                 frames_to_cd_frames_string (buf, status.track_position);
506                 status.out << "    INDEX 00" << buf << endl;
507         }
508
509         frames_to_cd_frames_string (buf, status.track_start_frame);
510         status.out << "    INDEX 01" << buf << endl;
511
512         status.index_number = 2;
513         status.track_number++;
514 }
515
516 void
517 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
518 {
519         gchar buf[18];
520
521         status.out << endl << "TRACK AUDIO" << endl;
522
523         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
524                 status.out << "NO ";
525         }
526         status.out << "COPY" << endl;
527
528         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
529                 status.out << "PRE_EMPHASIS" << endl;
530         } else {
531                 status.out << "NO PRE_EMPHASIS" << endl;
532         }
533
534         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
535                 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
536         }
537
538         status.out << "CD_TEXT {" << endl << "  LANGUAGE 0 {" << endl;
539         status.out << "     TITLE " << toc_escape_cdtext (status.marker->name()) << endl;
540         
541         status.out << "     PERFORMER ";
542         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
543                 status.out << toc_escape_cdtext (status.marker->cd_info["performer"]) << endl;
544         } else {
545                 status.out << "\"\"" << endl;
546         }
547         
548         if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
549                 status.out  << "     SONGWRITER " << toc_escape_cdtext (status.marker->cd_info["composer"]) << endl;
550         }
551
552         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
553                 status.out  << "     ISRC \"";
554                 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
555                 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
556                 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
557                 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
558         }
559
560         status.out << "  }" << endl << "}" << endl;
561
562         frames_to_cd_frames_string (buf, status.track_position);
563         status.out << "FILE " << toc_escape_filename (status.filename) << ' ' << buf;
564
565         frames_to_cd_frames_string (buf, status.track_duration);
566         status.out << buf << endl;
567
568         frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
569         status.out << "START" << buf << endl;
570 }
571
572 void
573 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
574 {
575         gchar buf[18];
576
577         snprintf (buf, sizeof(buf), "    INDEX %02d", cue_indexnum);
578         status.out << buf;
579         frames_to_cd_frames_string (buf, status.index_position);
580         status.out << buf << endl;
581
582         cue_indexnum++;
583 }
584
585 void
586 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
587 {
588         gchar buf[18];
589
590         frames_to_cd_frames_string (buf, status.index_position - status.track_position);
591         status.out << "INDEX" << buf << endl;
592 }
593
594 void
595 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
596 {
597         framecnt_t remainder;
598         framecnt_t fr = session.nominal_frame_rate();
599         int mins, secs, frames;
600
601         mins = when / (60 * fr);
602         remainder = when - (mins * 60 * fr);
603         secs = remainder / fr;
604         remainder -= secs * fr;
605         frames = remainder / (fr / 75);
606         sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
607 }
608
609 std::string
610 ExportHandler::toc_escape_cdtext (const std::string& txt)
611 {
612         Glib::ustring check (txt);
613         std::string out;
614         std::string latin1_txt;
615         char buf[5];
616
617         try {
618                 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
619         } catch (Glib::ConvertError& err) {
620                 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
621         }
622
623         out = '"';
624
625         for (std::string::const_iterator c = latin1_txt.begin(); c != latin1_txt.end(); ++c) {
626
627                 if ((*c) == '"') {
628                         out += "\\\"";
629                 } else if ((*c) == '\\') {
630                         out += "\\134";
631                 } else if (isprint (*c)) {
632                         out += *c;
633                 } else {
634                         snprintf (buf, sizeof (buf), "\\%03o", (int) (unsigned char) *c);
635                         out += buf;
636                 }
637         }
638         
639         out += '"';
640
641         return out;
642 }
643
644 std::string
645 ExportHandler::toc_escape_filename (const std::string& txt)
646 {
647         std::string out;
648
649         out = '"';
650
651         // We iterate byte-wise not character-wise over a UTF-8 string here,
652         // because we only want to translate backslashes and double quotes
653         for (std::string::const_iterator c = txt.begin(); c != txt.end(); ++c) {
654
655                 if (*c == '"') {
656                         out += "\\\"";
657                 } else if (*c == '\\') {
658                         out += "\\134";
659                 } else {
660                         out += *c;
661                 }
662         }
663         
664         out += '"';
665
666         return out;
667 }
668
669 std::string
670 ExportHandler::cue_escape_cdtext (const std::string& txt)
671 {
672         std::string latin1_txt;
673         std::string out;
674         
675         try {
676                 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
677         } catch (Glib::ConvertError& err) {
678                 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
679         }
680         
681         // does not do much mor than UTF-8 to Latin1 translation yet, but
682         // that may have to change if cue parsers in burning programs change 
683         out = '"' + latin1_txt + '"';
684
685         return out;
686 }
687
688 } // namespace ARDOUR