13
0
livetrax/libs/ardour/session_export.cc
Jesse Chappell bd21c474e5 committed RWlock fixes to libardour. added hw monitoring fixes from nick_m. minor alsa midi fix and update rate settings.
git-svn-id: svn://localhost/trunk/ardour2@244 d708f5d6-7413-0410-9779-e7cbd77b26cf
2006-01-06 04:59:17 +00:00

641 lines
13 KiB
C++

/*
Copyright (C) 1999-2002 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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
$Id$
*/
/* 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 <unistd.h>
#include <inttypes.h>
#include <float.h>
#include <sigc++/bind.h>
#include <pbd/error.h>
#include <pbd/lockmonitor.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/diskstream.h>
#include <ardour/panner.h>
#include "i18n.h"
using namespace std;
using namespace ARDOUR;
//using namespace sigc;
static int
convert_spec_to_info (AudioExportSpecification& spec, SF_INFO& sfinfo)
{
if (spec.path.length() == 0) {
error << _("Export: no output file specified") << endmsg;
return -1;
}
/* 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 ();
}
void
AudioExportSpecification::init ()
{
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 (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 ();
}
int
AudioExportSpecification::prepare (jack_nframes_t blocksize, jack_nframes_t frate)
{
char errbuf[256];
GDitherSize dither_size;
frame_rate = frate;
if (channels == 0) {
error << _("illegal frame range in export specification") << endmsg;
return -1;
}
if (start_frame >= end_frame) {
error << _("illegal frame range in export specification") << endmsg;
return -1;
}
if ((data_width = sndfile_data_width(format)) == 0) {
error << _("Bad data width size. Report me!") << endmsg;
return -1;
}
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;
}
if (convert_spec_to_info (*this, sfinfo)) {
return -1;
}
/* 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;
}
dataF = new float[blocksize * channels];
if (sample_rate != frame_rate) {
int err;
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 = (jack_nframes_t) ceil (blocksize * src_data.src_ratio * channels);
dataF2 = new float[out_samples_max];
max_leftover_frames = 4 * blocksize;
leftoverF = new float[max_leftover_frames * channels];
leftover_frames = 0;
} else {
out_samples_max = blocksize * channels;
}
dither = gdither_new (dither_type, channels, dither_size, data_width);
/* allocate buffers where dithering and output will occur */
switch (data_width) {
case 8:
sample_bytes = 1;
break;
case 16:
sample_bytes = 2;
break;
case 24:
case 32:
sample_bytes = 4;
break;
default:
sample_bytes = 0; // float format
break;
}
if (sample_bytes) {
output_data = (void*) malloc (sample_bytes * out_samples_max);
}
return 0;
}
int
AudioExportSpecification::process (jack_nframes_t nframes)
{
float* float_buffer = 0;
uint32_t chn;
uint32_t x;
uint32_t i;
sf_count_t written;
char errbuf[256];
jack_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;
}
++cnt;
if ((err = src_process (src_state, &src_data)) != 0) {
error << string_compose (_("an error occured during sample rate conversion: %1"),
src_strerror (err))
<< 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 ((jack_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;
}
} while (leftover_frames >= nframes);
return 0;
}
int
Session::start_audio_export (AudioExportSpecification& spec)
{
int ret;
if (spec.prepare (current_block_size, frame_rate())) {
return -1;
}
spec.pos = spec.start_frame;
spec.end_frame = spec.end_frame;
spec.total_frames = spec.end_frame - spec.start_frame;
spec.freewheel_connection = _engine.Freewheel.connect (sigc::bind (mem_fun (*this, &Session::process_export), &spec));
if ((ret = _engine.freewheel (true)) == 0) {
spec.running = true;
spec.do_freewheel = false;
}
return ret;
}
int
Session::stop_audio_export (AudioExportSpecification& spec)
{
/* 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 ();
/* restart slaving */
if (post_export_slave != None) {
set_slave_source (post_export_slave, post_export_position);
} else {
locate (post_export_position, false, false, false);
}
spec.clear ();
_exporting = false;
spec.running = false;
return 0;
}
int
Session::prepare_to_export (AudioExportSpecification& spec)
{
int ret = -1;
wait_till_butler_finished ();
/* take everyone out of awrite to avoid disasters */
{
RWLockMonitor lm (route_lock, false, __LINE__, __FILE__);
for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
(*i)->protect_automation ();
}
}
/* get everyone to the right position */
{
RWLockMonitor lm (diskstream_lock, false, __LINE__, __FILE__);
for (DiskStreamList::iterator i = diskstreams.begin(); i != diskstreams.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;
}
}
}
/* make sure we are actually rolling */
if (get_record_enabled()) {
disable_record ();
}
_exporting = true;
/* no slaving */
post_export_slave = _slave_type;
post_export_position = _transport_frame;
set_slave_source (None, 0);
/* get transport ready */
set_transport_speed (1.0, false);
butler_transport_work ();
atomic_set (&butler_should_do_transport_work, 0);
post_transport ();
/* we are ready to go ... */
ret = 0;
out:
return ret;
}
int
Session::process_export (jack_nframes_t nframes, AudioExportSpecification* spec)
{
uint32_t chn;
uint32_t x;
int ret = -1;
jack_nframes_t this_nframes;
/* 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;
}
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.
*/
wait_till_butler_finished ();
/* do the usual stuff */
process_without_events (nframes);
/* and now export the results */
nframes = this_nframes;
memset (spec->dataF, 0, sizeof (spec->dataF[0]) * nframes * spec->channels);
/* 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;
}
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);
/* 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 (spec->process (nframes)) {
goto out;
}
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;
}
return ret;
}