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