fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / session_export.cc
index 87bf5a5b034b6c1e049faaee7cf75d777caac1a1..463f50441121fc1e5d090c9b6ac6092f69341cde 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 1999-2002 Paul Davis 
+    Copyright (C) 1999-2008 Paul Davis
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 
 */
 
-/* see gdither.cc for why we have to do this */
 
-#define        _ISOC9X_SOURCE  1
-#define _ISOC99_SOURCE 1
-#include <cmath>
-#undef  _ISOC99_SOURCE
-#undef  _ISOC9X_SOURCE
-#undef  __USE_SVID 
-#define __USE_SVID 1
-#include <cstdlib>
-#undef  __USE_SVID
+#include "pbd/error.h"
+#include <glibmm/threads.h>
 
-#include <unistd.h>
-#include <inttypes.h>
-#include <float.h>
+#include <midi++/mmc.h>
 
-#include <sigc++/bind.h>
+#include "ardour/audioengine.h"
+#include "ardour/butler.h"
+#include "ardour/export_handler.h"
+#include "ardour/export_status.h"
+#include "ardour/process_thread.h"
+#include "ardour/session.h"
+#include "ardour/track.h"
 
-#include <pbd/error.h>
-#include <glibmm/thread.h>
-
-#include <ardour/gdither.h>
-#include <ardour/timestamps.h>
-#include <ardour/ardour.h>
-#include <ardour/session.h>
-#include <ardour/export.h>
-#include <ardour/sndfile_helpers.h>
-#include <ardour/port.h>
-#include <ardour/audioengine.h>
-#include <ardour/audio_diskstream.h>
-#include <ardour/panner.h>
-
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
-static int
-convert_spec_to_info (AudioExportSpecification& spec, SF_INFO& sfinfo)
+boost::shared_ptr<ExportHandler>
+Session::get_export_handler ()
 {
-       if (spec.path.length() == 0) {
-               error << _("Export: no output file specified") << endmsg;
-               return -1;
+       if (!export_handler) {
+               export_handler.reset (new ExportHandler (*this));
        }
 
-       /* XXX add checks that the directory path exists, and also 
-          check if we are overwriting an existing file...
-       */
-
-       sfinfo.format = spec.format;
-       sfinfo.samplerate = spec.sample_rate;
-       sfinfo.frames = spec.end_frame - spec.start_frame + 1;
-       sfinfo.channels = min (spec.channels, 2U);
-
-       return 0;
-}
-
-AudioExportSpecification::AudioExportSpecification ()
-{
-       init ();
-}
-
-AudioExportSpecification::~AudioExportSpecification ()
-{
-       clear ();
+       return export_handler;
 }
 
-void
-AudioExportSpecification::init ()
+boost::shared_ptr<ExportStatus>
+Session::get_export_status ()
 {
-       src_state = 0;
-       pos = 0;
-       total_frames = 0;
-       out = 0;
-       channels = 0;
-       running = false;
-       stop = false;
-       progress = 0.0;
-       status = 0;
-       dither = 0;
-       start_frame = 0;
-       end_frame = 0;
-       dataF = 0;
-       dataF2 = 0;
-       leftoverF = 0;
-       max_leftover_frames = 0;
-       leftover_frames = 0;
-       output_data = 0;
-       out_samples_max = 0;
-       data_width = 0;
-       do_freewheel = false;
-}
-
-void
-AudioExportSpecification::clear ()
-{
-       if (out) {
-               sf_close (out);
-               out = 0;
-       }
-
-       if (src_state) {
-               src_delete (src_state);
-               src_state = 0;
-       }
-
-       if (dither) {
-               gdither_free (dither);
-               dither = 0;
+       if (!export_status) {
+               export_status.reset (new ExportStatus ());
        }
 
-       if (output_data) {
-               free (output_data);
-               output_data = 0;
-       }
-       if (dataF) {
-               delete [] dataF;
-               dataF = 0;
-       }
-       if (dataF2) {
-               delete [] dataF2;
-               dataF2 = 0;
-       }
-       if (leftoverF) {
-               delete [] leftoverF;
-               leftoverF = 0;
-       }
-
-       freewheel_connection.disconnect ();
-
-       init ();
+       return export_status;
 }
 
+
 int
-AudioExportSpecification::prepare (nframes_t blocksize, nframes_t frate)
+Session::pre_export ()
 {
-       char errbuf[256];
-       GDitherSize dither_size;
+       get_export_status (); // Init export_status
 
-       frame_rate = frate;
-
-       if (channels == 0) {
-               error << _("illegal frame range in export specification") << endmsg;
-               return -1;
-       }
+       /* take everyone out of awrite to avoid disasters */
 
-       if (start_frame >= end_frame) {
-               error << _("illegal frame range in export specification") << endmsg;
-               return -1;
-       }
+       {
+               boost::shared_ptr<RouteList> r = routes.reader ();
 
-       if ((data_width = sndfile_data_width(format)) == 0) {
-               error << _("Bad data width size.  Report me!") << endmsg;
-               return -1;
+               for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+                       (*i)->protect_automation ();
+               }
        }
 
-       switch (data_width) {
-       case 8:
-               dither_size = GDither8bit;
-               break;
-
-       case 16:
-               dither_size = GDither16bit;
-               break;
-
-       case 24:
-               dither_size = GDither32bit;
-               break;
-
-       default:
-               dither_size = GDitherFloat;
-               break;
-       }
+       /* prepare transport */
 
-       if (convert_spec_to_info (*this, sfinfo)) {
-               return -1;
-       }
+       realtime_stop (true, true);
 
-       /* XXX make sure we have enough disk space for the output */
-       
-       if ((out = sf_open (path.c_str(), SFM_WRITE, &sfinfo)) == 0) {
-               sf_error_str (0, errbuf, sizeof (errbuf) - 1);
-               error << string_compose(_("Export: cannot open output file \"%1\" (%2)"), path, errbuf) << endmsg;
-               return -1;
+       if (get_record_enabled()) {
+               disable_record (false);
        }
 
-       dataF = new float[blocksize * channels];
+       unset_play_loop ();
 
-       if (sample_rate != frame_rate) {
-               int err;
+       /* no slaving */
 
-               if ((src_state = src_new (src_quality, channels, &err)) == 0) {
-                       error << string_compose (_("cannot initialize sample rate conversion: %1"), src_strerror (err)) << endmsg;
-                       return -1;
-               }
-               
-               src_data.src_ratio = sample_rate / (double) frame_rate;
-               out_samples_max = (nframes_t) ceil (blocksize * src_data.src_ratio * channels);
-               dataF2 = new float[out_samples_max];
+       post_export_sync = config.get_external_sync ();
+       post_export_position = _transport_frame;
 
-               max_leftover_frames = 4 * blocksize;
-               leftoverF = new float[max_leftover_frames * channels];
-               leftover_frames = 0;
+       config.set_external_sync (false);
 
-       } else {
-               out_samples_max = blocksize * channels;
-       }
+       _exporting = true;
+       export_status->set_running (true);
+       export_status->Finished.connect_same_thread (*this, boost::bind (&Session::finalize_audio_export, this));
 
-       dither = gdither_new (dither_type, channels, dither_size, data_width);
+       /* disable MMC output early */
 
-       /* allocate buffers where dithering and output will occur */
+       _pre_export_mmc_enabled = _mmc->send_enabled ();
+       _mmc->enable_send (false);
 
-       switch (data_width) {
-       case 8:
-               sample_bytes = 1;
-               break;
+       return 0;
+}
 
-       case 16:
-               sample_bytes = 2;
-               break;
+/** Called for each range that is being exported */
+int
+Session::start_audio_export (framepos_t position, bool realtime)
+{
+       if (!_exporting) {
+               pre_export ();
+       }
 
-       case 24:
-       case 32:
-               sample_bytes = 4;
-               break;
+       _realtime_export = realtime;
 
-       default:
-               sample_bytes = 0; // float format
-               break;
+       if (realtime) {
+               _export_preroll = nominal_frame_rate ();
+       } else {
+               _export_preroll = Config->get_export_preroll() * nominal_frame_rate ();
        }
 
-       if (sample_bytes) {
-               output_data = (void*) malloc (sample_bytes * out_samples_max);
+       if (_export_preroll == 0) {
+               // must be > 0 so that transport is started in sync.
+               _export_preroll = 1;
        }
 
-       return 0;
-}
+       /* "worst_track_latency" is the correct value for stem-exports
+        * see to Route::add_export_point(),
+        *
+        * for master-bus export, we'd need to add the master's latency.
+        * or actually longest-total-session-latency.
+        *
+        * We can't use worst_playback_latency because that includes
+        * includes external latencies and would overcompensate.
+        */
+       _export_latency = worst_track_latency ();
+
+       /* We're about to call Track::seek, so the butler must have finished everything
+          up otherwise it could be doing do_refill in its thread while we are doing
+          it here.
+       */
 
-int
-AudioExportSpecification::process (nframes_t nframes)
-{
-       float* float_buffer = 0;
-       uint32_t chn;
-       uint32_t x;
-       uint32_t i;
-       sf_count_t written;
-       char errbuf[256];
-       nframes_t to_write = 0;
-       int cnt = 0;
-       
-       do {
-
-               /* now do sample rate conversion */
-       
-               if (sample_rate != frame_rate) {
-               
-                       int err;
-               
-                       src_data.output_frames = out_samples_max / channels;
-                       src_data.end_of_input = ((pos + nframes) >= end_frame);
-                       src_data.data_out = dataF2;
-
-                       if (leftover_frames > 0) {
-
-                               /* input data will be in leftoverF rather than dataF */
-
-                               src_data.data_in = leftoverF;
-
-                               if (cnt == 0) {
-                                       
-                                       /* first time, append new data from dataF into the leftoverF buffer */
-
-                                       memcpy (leftoverF + (leftover_frames * channels), dataF, nframes * channels * sizeof(float));
-                                       src_data.input_frames = nframes + leftover_frames;
-                               } else {
-                                       
-                                       /* otherwise, just use whatever is still left in leftoverF; the contents
-                                          were adjusted using memmove() right after the last SRC call (see
-                                          below)
-                                       */
-
-                                       src_data.input_frames = leftover_frames;
-                               }
-                                       
-                       } else {
-
-                               src_data.data_in = dataF;
-                               src_data.input_frames = nframes;
+       _butler->wait_until_finished ();
 
-                       }
+       /* get everyone to the right position */
 
-                       ++cnt;
+       {
+               boost::shared_ptr<RouteList> rl = routes.reader();
 
-                       if ((err = src_process (src_state, &src_data)) != 0) {
-                               error << string_compose (_("an error occured during sample rate conversion: %1"),
-                                                 src_strerror (err))
+               for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+                       boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+                       if (tr && tr->seek (position, true)) {
+                               error << string_compose (_("%1: cannot seek to %2 for export"),
+                                                 (*i)->name(), position)
                                      << endmsg;
                                return -1;
                        }
-               
-                       to_write = src_data.output_frames_gen;
-                       leftover_frames = src_data.input_frames - src_data.input_frames_used;
-
-                       if (leftover_frames > 0) {
-                               if (leftover_frames > max_leftover_frames) {
-                                       error << _("warning, leftover frames overflowed, glitches might occur in output") << endmsg;
-                                       leftover_frames = max_leftover_frames;
-                               }
-                               memmove (leftoverF, (char *) (src_data.data_in + (src_data.input_frames_used * channels)),
-                                        leftover_frames * channels * sizeof(float));
-                       }
-
-                       float_buffer = dataF2;
-               
-               } else {
-
-                       /* no SRC, keep it simple */
-               
-                       to_write = nframes;
-                       leftover_frames = 0;
-                       float_buffer = dataF;
-               }
-       
-               if (output_data) {
-                       memset (output_data, 0, sample_bytes * to_write * channels);
-               }
-       
-               switch (data_width) {
-               case 8:
-               case 16:
-               case 24:
-                       for (chn = 0; chn < channels; ++chn) { 
-                               gdither_runf (dither, chn, to_write, float_buffer, output_data);
-                       }
-                       break;
-               
-               case 32:
-                       for (chn = 0; chn < channels; ++chn) {
-                       
-                               int *ob = (int *) output_data;
-                               const double int_max = (float) INT_MAX;
-                               const double int_min = (float) INT_MIN;
-                       
-                               for (x = 0; x < to_write; ++x) {
-                                       i = chn + (x * channels);
-                               
-                                       if (float_buffer[i] > 1.0f) {
-                                               ob[i] = INT_MAX;
-                                       } else if (float_buffer[i] < -1.0f) {
-                                               ob[i] = INT_MIN;
-                                       } else {
-                                               if (float_buffer[i] >= 0.0f) {
-                                                       ob[i] = lrintf (int_max * float_buffer[i]);
-                                               } else {
-                                                       ob[i] = - lrintf (int_min * float_buffer[i]);
-                                               }
-                                       }
-                               }
-                       }
-                       break;
-               
-               default:
-                       for (x = 0; x < to_write * channels; ++x) {
-                               if (float_buffer[x] > 1.0f) {
-                                       float_buffer[x] = 1.0f;
-                               } else if (float_buffer[x] < -1.0f) {
-                                       float_buffer[x] = -1.0f;
-                               } 
-                       }
-                       break;
-               }
-       
-               /* and export to disk */
-       
-               switch (data_width) {
-               case 8:
-                       /* XXXX no way to deliver 8 bit audio to libsndfile */
-                       written = to_write;
-                       break;
-               
-               case 16:
-                       written = sf_writef_short (out, (short*) output_data, to_write);
-                       break;
-               
-               case 24:
-               case 32:
-                       written = sf_writef_int (out, (int*) output_data, to_write);
-                       break;
-               
-               default:
-                       written = sf_writef_float (out, float_buffer, to_write);
-                       break;
-               }
-       
-               if ((nframes_t) written != to_write) {
-                       sf_error_str (out, errbuf, sizeof (errbuf) - 1);
-                       error << string_compose(_("Export: could not write data to output file (%1)"), errbuf) << endmsg;
-                       return -1;
                }
+       }
 
+       /* we just did the core part of a locate() call above, but
+          for the sake of any GUI, put the _transport_frame in
+          the right place too.
+       */
 
-       } while (leftover_frames >= nframes);
+       _transport_frame = position;
+       export_status->stop = false;
 
-       return 0;
-}
+       /* get transport ready. note how this is calling butler functions
+          from a non-butler thread. we waited for the butler to stop
+          what it was doing earlier in Session::pre_export() and nothing
+          since then has re-awakened it.
+        */
 
-int
-Session::start_audio_export (AudioExportSpecification& spec)
-{
-       if (spec.prepare (current_block_size, frame_rate())) {
+       /* we are ready to go ... */
+
+       if (!_engine.connected()) {
                return -1;
        }
 
-       spec.pos = spec.start_frame;
-       spec.end_frame = spec.end_frame;
-       spec.total_frames = spec.end_frame - spec.start_frame;
-       spec.running = true; 
-       spec.do_freewheel = false; /* force a call to ::prepare_to_export() before proceeding to normal operation */
-
-       spec.freewheel_connection = _engine.Freewheel.connect (sigc::bind (mem_fun (*this, &Session::process_export), &spec));
-
-       return _engine.freewheel (true);
-}
+       _engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1));
 
-int
-Session::stop_audio_export (AudioExportSpecification& spec)
-{
-       /* don't stop freewheeling but do stop paying attention to it for now */
-
-       spec.freewheel_connection.disconnect ();
-       spec.clear (); /* resets running/stop etc */
-
-       return 0;
+       if (_realtime_export) {
+               Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+               _export_rolling = true;
+               process_function = &Session::process_export_fw;
+               return 0;
+       } else {
+               _export_rolling = true;
+               return _engine.freewheel (true);
+       }
 }
 
-int 
-Session::prepare_to_export (AudioExportSpecification& spec)
+void
+Session::process_export (pframes_t nframes)
 {
-       int ret = -1;
-
-       wait_till_butler_finished ();
-
-       /* take everyone out of awrite to avoid disasters */
-
-       {
-               boost::shared_ptr<RouteList> r = routes.reader ();
-
-               for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-                       (*i)->protect_automation ();
-               }
+       if (_export_rolling && export_status->stop) {
+               stop_audio_export ();
        }
 
-       /* get everyone to the right position */
-
-       {
-               boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
-
-               for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
-                       if ((*i)-> seek (spec.start_frame, true)) {
-                               error << string_compose (_("%1: cannot seek to %2 for export"),
-                                                 (*i)->name(), spec.start_frame)
-                                     << endmsg;
-                               goto out;
-                       }
+       if (_export_rolling) {
+               if (!_realtime_export)  {
+                       /* make sure we've caught up with disk i/o, since
+                        * we're running faster than realtime c/o JACK.
+                        */
+                       _butler->wait_until_finished ();
                }
-       }
 
-       /* make sure we are actually rolling */
+               /* do the usual stuff */
 
-       if (get_record_enabled()) {
-               disable_record (false);
+               process_without_events (nframes);
+       } else if (_realtime_export) {
+               fail_roll (nframes); // somehow we need to silence _ALL_ output buffers
        }
 
-       _exporting = true;
-       
-       /* no slaving */
-
-       post_export_slave = Config->get_slave_source ();
-       post_export_position = _transport_frame;
-
-       Config->set_slave_source (None);
-
-       /* get transport ready */
-
-       set_transport_speed (1.0, false);
-       butler_transport_work ();
-       g_atomic_int_set (&butler_should_do_transport_work, 0);
-       post_transport ();
-
-       /* we are ready to go ... */
+       try {
+               /* handle export - XXX what about error handling? */
 
-       ret = 0;
+               ProcessExport (nframes);
 
-  out:
-       return ret;
+       } catch (std::exception & e) {
+               error << string_compose (_("Export ended unexpectedly: %1"), e.what()) << endmsg;
+               export_status->abort (true);
+       }
 }
 
-int
-Session::process_export (nframes_t nframes, AudioExportSpecification* spec)
+void
+Session::process_export_fw (pframes_t nframes)
 {
-       uint32_t chn;
-       uint32_t x;
-       int ret = -1;
-       nframes_t this_nframes;
+       const bool need_buffers = _engine.freewheeling ();
+       if (_export_preroll > 0) {
 
-       /* This is not required to be RT-safe because we are running while freewheeling */
-
-       if (spec->do_freewheel == false) {
-               
-               /* first time in export function: get set up */
-
-               if (prepare_to_export (*spec)) {
-                       spec->running = false;
-                       spec->status = -1;
-                       return -1;
+               if (need_buffers) {
+                       _engine.main_thread()->get_buffers ();
+               }
+               fail_roll (nframes);
+               if (need_buffers) {
+                       _engine.main_thread()->drop_buffers ();
                }
-               
-               spec->do_freewheel = true;
-       }
-
-       if (!_exporting) {
-               /* finished, but still freewheeling */
-               process_without_events (nframes);
-               return 0;
-       }
-               
-       if (!spec->running || spec->stop || (this_nframes = min ((spec->end_frame - spec->pos), nframes)) == 0) {
-               process_without_events (nframes);
-               return stop_audio_export (*spec);
-       }
 
-       /* make sure we've caught up with disk i/o, since
-          we're running faster than realtime c/o JACK.
-       */
+               _export_preroll -= std::min ((framepos_t)nframes, _export_preroll);
 
-       wait_till_butler_finished ();
-       
-       /* do the usual stuff */
-       
-       process_without_events (nframes);
+               if (_export_preroll > 0) {
+                       // clear out buffers (reverb tails etc).
+                       return;
+               }
 
-       /* and now export the results */
+               set_transport_speed (1.0, 0, false);
+               butler_transport_work ();
+               g_atomic_int_set (&_butler->should_do_transport_work, 0);
+               post_transport ();
 
-       nframes = this_nframes;
+               return;
+       }
 
-       memset (spec->dataF, 0, sizeof (spec->dataF[0]) * nframes * spec->channels);
+       if (_export_latency > 0) {
+               framepos_t remain = std::min ((framepos_t)nframes, _export_latency);
 
-       /* foreach output channel ... */
-       
-       for (chn = 0; chn < spec->channels; ++chn) {
-               
-               AudioExportPortMap::iterator mi = spec->port_map.find (chn);
-               
-               if (mi == spec->port_map.end()) {
-                       /* no ports exported to this channel */
-                       continue;
+               if (need_buffers) {
+                       _engine.main_thread()->get_buffers ();
+               }
+               process_without_events (remain);
+               if (need_buffers) {
+                       _engine.main_thread()->drop_buffers ();
                }
-               
-               vector<PortChannelPair>& mapped_ports ((*mi).second);
-               
-               for (vector<PortChannelPair>::iterator t = mapped_ports.begin(); t != mapped_ports.end(); ++t) {
-                       
-                       /* OK, this port's output is supposed to appear on this channel 
-                        */
 
-                       Port* port = (*t).first;
-                       Sample* port_buffer = port->get_buffer (nframes);
+               _export_latency -= remain;
+               nframes -= remain;
 
-                       /* now interleave the data from the channel into the float buffer */
-                               
-                       for (x = 0; x < nframes; ++x) {
-                               spec->dataF[chn+(x*spec->channels)] += (float) port_buffer[x];
-                       }
+               if (nframes == 0) {
+                       return;
                }
        }
 
-       if (spec->process (nframes)) {
-               goto out;
+       if (need_buffers) {
+               _engine.main_thread()->get_buffers ();
        }
-       
-       spec->pos += nframes;
-       spec->progress = 1.0 - (((float) spec->end_frame - spec->pos) / spec->total_frames);
-
-       /* and we're good to go */
-
-       ret = 0;
-
-  out: 
-       if (ret) {
-               sf_close (spec->out);
-               spec->out = 0;
-               unlink (spec->path.c_str());
-               spec->running = false;
-               spec->status = ret;
-               _exporting = false;
+       process_export (nframes);
+       if (need_buffers) {
+               _engine.main_thread()->drop_buffers ();
        }
 
-       return ret;
+       return;
 }
 
-void
-Session::finalize_audio_export ()
+int
+Session::stop_audio_export ()
 {
-       _engine.freewheel (false);
-       _exporting = false;
-
        /* can't use stop_transport() here because we need
           an immediate halt and don't require all the declick
           stuff that stop_transport() implements.
        */
 
-       realtime_stop (true);
-       schedule_butler_transport_work ();
+       realtime_stop (true, true);
+       _export_rolling = false;
+       _butler->schedule_transport_work ();
+
+       return 0;
+}
+
+void
+Session::finalize_audio_export ()
+{
+       _exporting = false;
+
+       if (_export_rolling) {
+               stop_audio_export ();
+       }
+
+       /* Clean up */
+
+       if (_realtime_export) {
+               Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+               process_function = &Session::process_with_events;
+       }
+       _engine.freewheel (false);
+       export_freewheel_connection.disconnect();
+
+       _mmc->enable_send (_pre_export_mmc_enabled);
+
+       /* maybe write CUE/TOC */
+
+       export_handler.reset();
+       export_status.reset();
 
        /* restart slaving */
 
-       if (post_export_slave != None) {
-               Config->set_slave_source (post_export_slave);
+       if (post_export_sync) {
+               config.set_external_sync (true);
        } else {
-               locate (post_export_position, false, false, false);
+               locate (post_export_position, false, false, false, false, false);
        }
 }