All #include statements that include a header that is a part of a library bundled with ardour MUST use quotes, not angle brackets. Do this: #include "ardour/types.h" NOT this: #include <ardour/types.h> Rationale: This is best practice in general, to ensure we include the local version and not the system version. That quotes mean "local" (in some sense) and angle brackets mean "system" (in some sense) is a ubiquitous convention and IIRC right in the C spec somewhere. More pragmatically, this is required by (my) waf (stuff) for dependencies to work correctly. That is: !!! FAILURE TO DO THIS CAN RESULT IN BROKEN BUILDS !!! Failure to comply is punishable by death by torture. :) P.S. It's not that dramatic in all cases, but this (in combination with some GCC flags specific to the include type) is the best way I have found to be absolutely 100% positive the local ones are being used (and we definitely want to be absolutely 100% positive on that one). git-svn-id: svn://localhost/ardour2/branches/3.0@4655 d708f5d6-7413-0410-9779-e7cbd77b26cf
930 lines
22 KiB
C++
930 lines
22 KiB
C++
/*
|
|
Copyright (C) 2000 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.
|
|
|
|
*/
|
|
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <float.h>
|
|
#include <utime.h>
|
|
#include <cerrno>
|
|
#include <ctime>
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include <glibmm/fileutils.h>
|
|
#include <glibmm/miscutils.h>
|
|
|
|
#include "pbd/xml++.h"
|
|
#include "pbd/pthread_utils.h"
|
|
|
|
#include "ardour/audiosource.h"
|
|
#include "ardour/cycle_timer.h"
|
|
#include "ardour/session.h"
|
|
#include "ardour/transient_detector.h"
|
|
#include "ardour/runtime_functions.h"
|
|
|
|
#include "i18n.h"
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
using Glib::ustring;
|
|
|
|
bool AudioSource::_build_missing_peakfiles = false;
|
|
bool AudioSource::_build_peakfiles = false;
|
|
|
|
#define _FPP 256
|
|
|
|
AudioSource::AudioSource (Session& s, ustring name)
|
|
: Source (s, DataType::AUDIO, name)
|
|
, _length (0)
|
|
{
|
|
_peaks_built = false;
|
|
_peak_byte_max = 0;
|
|
peakfile = -1;
|
|
_read_data_count = 0;
|
|
_write_data_count = 0;
|
|
peak_leftover_cnt = 0;
|
|
peak_leftover_size = 0;
|
|
peak_leftovers = 0;
|
|
}
|
|
|
|
AudioSource::AudioSource (Session& s, const XMLNode& node)
|
|
: Source (s, node)
|
|
, _length (0)
|
|
{
|
|
|
|
_peaks_built = false;
|
|
_peak_byte_max = 0;
|
|
peakfile = -1;
|
|
_read_data_count = 0;
|
|
_write_data_count = 0;
|
|
peak_leftover_cnt = 0;
|
|
peak_leftover_size = 0;
|
|
peak_leftovers = 0;
|
|
|
|
if (set_state (node)) {
|
|
throw failed_constructor();
|
|
}
|
|
}
|
|
|
|
AudioSource::~AudioSource ()
|
|
{
|
|
/* shouldn't happen but make sure we don't leak file descriptors anyway */
|
|
|
|
if (peak_leftover_cnt) {
|
|
cerr << "AudioSource destroyed with leftover peak data pending" << endl;
|
|
}
|
|
|
|
if (peakfile >= 0) {
|
|
::close (peakfile);
|
|
}
|
|
|
|
delete [] peak_leftovers;
|
|
}
|
|
|
|
XMLNode&
|
|
AudioSource::get_state ()
|
|
{
|
|
XMLNode& node (Source::get_state());
|
|
|
|
if (_captured_for.length()) {
|
|
node.add_property ("captured-for", _captured_for);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
int
|
|
AudioSource::set_state (const XMLNode& node)
|
|
{
|
|
const XMLProperty* prop;
|
|
|
|
if ((prop = node.property ("captured-for")) != 0) {
|
|
_captured_for = prop->value();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
sframes_t
|
|
AudioSource::length (sframes_t pos) const
|
|
{
|
|
return _length;
|
|
}
|
|
|
|
void
|
|
AudioSource::update_length (sframes_t pos, sframes_t cnt)
|
|
{
|
|
if (pos + cnt > _length) {
|
|
_length = pos + cnt;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
PEAK FILE STUFF
|
|
***********************************************************************/
|
|
|
|
bool
|
|
AudioSource::peaks_ready (sigc::slot<void> the_slot, sigc::connection& conn) const
|
|
{
|
|
bool ret;
|
|
Glib::Mutex::Lock lm (_peaks_ready_lock);
|
|
|
|
/* check to see if the peak data is ready. if not
|
|
connect the slot while still holding the lock.
|
|
*/
|
|
|
|
if (!(ret = _peaks_built)) {
|
|
conn = PeaksReady.connect (the_slot);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
AudioSource::touch_peakfile ()
|
|
{
|
|
struct stat statbuf;
|
|
|
|
if (stat (peakpath.c_str(), &statbuf) != 0 || statbuf.st_size == 0) {
|
|
return;
|
|
}
|
|
|
|
struct utimbuf tbuf;
|
|
|
|
tbuf.actime = statbuf.st_atime;
|
|
tbuf.modtime = time ((time_t) 0);
|
|
|
|
utime (peakpath.c_str(), &tbuf);
|
|
}
|
|
|
|
int
|
|
AudioSource::rename_peakfile (ustring newpath)
|
|
{
|
|
/* caller must hold _lock */
|
|
|
|
ustring oldpath = peakpath;
|
|
|
|
if (access (oldpath.c_str(), F_OK) == 0) {
|
|
if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
|
|
error << string_compose (_("cannot rename peakfile for %1 from %2 to %3 (%4)"), _name, oldpath, newpath, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
peakpath = newpath;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
AudioSource::initialize_peakfile (bool newfile, ustring audio_path)
|
|
{
|
|
struct stat statbuf;
|
|
|
|
peakpath = peak_path (audio_path);
|
|
|
|
/* if the peak file should be there, but isn't .... */
|
|
|
|
if (!newfile && !Glib::file_test (peakpath.c_str(), Glib::FILE_TEST_EXISTS)) {
|
|
peakpath = find_broken_peakfile (peakpath, audio_path);
|
|
}
|
|
|
|
if (stat (peakpath.c_str(), &statbuf)) {
|
|
if (errno != ENOENT) {
|
|
/* it exists in the peaks dir, but there is some kind of error */
|
|
|
|
error << string_compose(_("AudioSource: cannot stat peakfile \"%1\""), peakpath) << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
/* peakfile does not exist */
|
|
|
|
_peaks_built = false;
|
|
|
|
} else {
|
|
|
|
/* we found it in the peaks dir, so check it out */
|
|
|
|
if (statbuf.st_size == 0 || ((nframes_t) statbuf.st_size < ((length(_timeline_position) / _FPP) * sizeof (PeakData)))) {
|
|
// empty
|
|
_peaks_built = false;
|
|
} else {
|
|
// Check if the audio file has changed since the peakfile was built.
|
|
struct stat stat_file;
|
|
int err = stat (audio_path.c_str(), &stat_file);
|
|
|
|
if (err) {
|
|
_peaks_built = false;
|
|
_peak_byte_max = 0;
|
|
} else {
|
|
|
|
/* allow 6 seconds slop on checking peak vs. file times because of various
|
|
disk action "races"
|
|
*/
|
|
|
|
if (stat_file.st_mtime > statbuf.st_mtime && (stat_file.st_mtime - statbuf.st_mtime > 6)) {
|
|
_peaks_built = false;
|
|
_peak_byte_max = 0;
|
|
} else {
|
|
_peaks_built = true;
|
|
_peak_byte_max = statbuf.st_size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!newfile && !_peaks_built && _build_missing_peakfiles && _build_peakfiles) {
|
|
build_peaks_from_scratch ();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
nframes_t
|
|
AudioSource::read (Sample *dst, sframes_t start, nframes_t cnt, int channel) const
|
|
{
|
|
Glib::Mutex::Lock lm (_lock);
|
|
return read_unlocked (dst, start, cnt);
|
|
}
|
|
|
|
nframes_t
|
|
AudioSource::write (Sample *dst, nframes_t cnt)
|
|
{
|
|
Glib::Mutex::Lock lm (_lock);
|
|
return write_unlocked (dst, cnt);
|
|
}
|
|
|
|
int
|
|
AudioSource::read_peaks (PeakData *peaks, nframes_t npeaks, sframes_t start, nframes_t cnt, double samples_per_visual_peak) const
|
|
{
|
|
return read_peaks_with_fpp (peaks, npeaks, start, cnt, samples_per_visual_peak, _FPP);
|
|
}
|
|
|
|
int
|
|
AudioSource::read_peaks_with_fpp (PeakData *peaks, nframes_t npeaks, sframes_t start, nframes_t cnt,
|
|
double samples_per_visual_peak, nframes_t samples_per_file_peak) const
|
|
{
|
|
Glib::Mutex::Lock lm (_lock);
|
|
double scale;
|
|
double expected_peaks;
|
|
PeakData::PeakDatum xmax;
|
|
PeakData::PeakDatum xmin;
|
|
int32_t to_read;
|
|
uint32_t nread;
|
|
nframes_t zero_fill = 0;
|
|
int ret = -1;
|
|
PeakData* staging = 0;
|
|
Sample* raw_staging = 0;
|
|
int _peakfile = -1;
|
|
|
|
expected_peaks = (cnt / (double) samples_per_file_peak);
|
|
scale = npeaks/expected_peaks;
|
|
|
|
#undef DEBUG_READ_PEAKS
|
|
#ifdef DEBUG_READ_PEAKS
|
|
cerr << "======>RP: npeaks = " << npeaks
|
|
<< " start = " << start
|
|
<< " cnt = " << cnt
|
|
<< " len = " << _length
|
|
<< " samples_per_visual_peak =" << samples_per_visual_peak
|
|
<< " expected was " << expected_peaks << " ... scale = " << scale
|
|
<< " PD ptr = " << peaks
|
|
<<endl;
|
|
|
|
#endif
|
|
|
|
/* fix for near-end-of-file conditions */
|
|
|
|
if (cnt > _length - start) {
|
|
// cerr << "too close to end @ " << _length << " given " << start << " + " << cnt << endl;
|
|
cnt = _length - start;
|
|
nframes_t old = npeaks;
|
|
npeaks = min ((nframes_t) floor (cnt / samples_per_visual_peak), npeaks);
|
|
zero_fill = old - npeaks;
|
|
}
|
|
|
|
// cerr << "actual npeaks = " << npeaks << " zf = " << zero_fill << endl;
|
|
|
|
if (npeaks == cnt) {
|
|
|
|
#ifdef DEBUG_READ_PEAKS
|
|
cerr << "RAW DATA\n";
|
|
#endif
|
|
/* no scaling at all, just get the sample data and duplicate it for
|
|
both max and min peak values.
|
|
*/
|
|
|
|
Sample* raw_staging = new Sample[cnt];
|
|
|
|
if (read_unlocked (raw_staging, start, cnt) != cnt) {
|
|
error << _("cannot read sample data for unscaled peak computation") << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
for (nframes_t i = 0; i < npeaks; ++i) {
|
|
peaks[i].max = raw_staging[i];
|
|
peaks[i].min = raw_staging[i];
|
|
}
|
|
|
|
delete [] raw_staging;
|
|
return 0;
|
|
}
|
|
|
|
if (scale == 1.0) {
|
|
|
|
off_t first_peak_byte = (start / samples_per_file_peak) * sizeof (PeakData);
|
|
|
|
/* open, read, close */
|
|
|
|
if ((_peakfile = ::open (peakpath.c_str(), O_RDONLY, 0664)) < 0) {
|
|
error << string_compose(_("AudioSource: cannot open peakpath (a) \"%1\" (%2)"), peakpath, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
#ifdef DEBUG_READ_PEAKS
|
|
cerr << "DIRECT PEAKS\n";
|
|
#endif
|
|
|
|
nread = ::pread (_peakfile, peaks, sizeof (PeakData)* npeaks, first_peak_byte);
|
|
close (_peakfile);
|
|
|
|
if (nread != sizeof (PeakData) * npeaks) {
|
|
cerr << "AudioSource["
|
|
<< _name
|
|
<< "]: cannot read peaks from peakfile! (read only "
|
|
<< nread
|
|
<< " not "
|
|
<< npeaks
|
|
<< "at sample "
|
|
<< start
|
|
<< " = byte "
|
|
<< first_peak_byte
|
|
<< ')'
|
|
<< endl;
|
|
return -1;
|
|
}
|
|
|
|
if (zero_fill) {
|
|
memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
nframes_t tnp;
|
|
|
|
if (scale < 1.0) {
|
|
|
|
#ifdef DEBUG_READ_PEAKS
|
|
cerr << "DOWNSAMPLE\n";
|
|
#endif
|
|
/* the caller wants:
|
|
|
|
- more frames-per-peak (lower resolution) than the peakfile, or to put it another way,
|
|
- less peaks than the peakfile holds for the same range
|
|
|
|
So, read a block into a staging area, and then downsample from there.
|
|
|
|
to avoid confusion, I'll refer to the requested peaks as visual_peaks and the peakfile peaks as stored_peaks
|
|
*/
|
|
|
|
const uint32_t chunksize = (uint32_t) min (expected_peaks, 65536.0);
|
|
|
|
staging = new PeakData[chunksize];
|
|
|
|
/* compute the rounded up frame position */
|
|
|
|
nframes_t current_frame = start;
|
|
nframes_t current_stored_peak = (nframes_t) ceil (current_frame / (double) samples_per_file_peak);
|
|
uint32_t next_visual_peak = (uint32_t) ceil (current_frame / samples_per_visual_peak);
|
|
double next_visual_peak_frame = next_visual_peak * samples_per_visual_peak;
|
|
uint32_t stored_peak_before_next_visual_peak = (nframes_t) next_visual_peak_frame / samples_per_file_peak;
|
|
uint32_t nvisual_peaks = 0;
|
|
uint32_t stored_peaks_read = 0;
|
|
uint32_t i = 0;
|
|
|
|
/* handle the case where the initial visual peak is on a pixel boundary */
|
|
|
|
current_stored_peak = min (current_stored_peak, stored_peak_before_next_visual_peak);
|
|
|
|
/* open ... close during out: handling */
|
|
|
|
if ((_peakfile = ::open (peakpath.c_str(), O_RDONLY, 0664)) < 0) {
|
|
error << string_compose(_("AudioSource: cannot open peakpath (b) \"%1\" (%2)"), peakpath, strerror (errno)) << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
while (nvisual_peaks < npeaks) {
|
|
|
|
if (i == stored_peaks_read) {
|
|
|
|
uint32_t start_byte = current_stored_peak * sizeof(PeakData);
|
|
tnp = min ((nframes_t)(_length/samples_per_file_peak - current_stored_peak), (nframes_t) expected_peaks);
|
|
to_read = min (chunksize, tnp);
|
|
|
|
#ifdef DEBUG_READ_PEAKS
|
|
cerr << "read " << sizeof (PeakData) * to_read << " from peakfile @ " << start_byte << endl;
|
|
#endif
|
|
|
|
if ((nread = ::pread (_peakfile, staging, sizeof (PeakData) * to_read, start_byte))
|
|
!= sizeof (PeakData) * to_read) {
|
|
|
|
off_t fend = lseek (_peakfile, 0, SEEK_END);
|
|
|
|
cerr << "AudioSource["
|
|
<< _name
|
|
<< "]: cannot read peak data from peakfile ("
|
|
<< (nread / sizeof(PeakData))
|
|
<< " peaks instead of "
|
|
<< to_read
|
|
<< ") ("
|
|
<< strerror (errno)
|
|
<< ')'
|
|
<< " at start_byte = " << start_byte
|
|
<< " _length = " << _length << " versus len = " << fend
|
|
<< " expected maxpeaks = " << (_length - current_frame)/samples_per_file_peak
|
|
<< " npeaks was " << npeaks
|
|
<< endl;
|
|
goto out;
|
|
}
|
|
|
|
i = 0;
|
|
stored_peaks_read = nread / sizeof(PeakData);
|
|
}
|
|
|
|
xmax = -1.0;
|
|
xmin = 1.0;
|
|
|
|
while ((i < stored_peaks_read) && (current_stored_peak <= stored_peak_before_next_visual_peak)) {
|
|
|
|
xmax = max (xmax, staging[i].max);
|
|
xmin = min (xmin, staging[i].min);
|
|
++i;
|
|
++current_stored_peak;
|
|
--expected_peaks;
|
|
}
|
|
|
|
peaks[nvisual_peaks].max = xmax;
|
|
peaks[nvisual_peaks].min = xmin;
|
|
++nvisual_peaks;
|
|
++next_visual_peak;
|
|
|
|
//next_visual_peak_frame = min ((next_visual_peak * samples_per_visual_peak), (next_visual_peak_frame+samples_per_visual_peak) );
|
|
next_visual_peak_frame = min ((double) start+cnt, (next_visual_peak_frame+samples_per_visual_peak) );
|
|
stored_peak_before_next_visual_peak = (uint32_t) next_visual_peak_frame / samples_per_file_peak;
|
|
}
|
|
|
|
if (zero_fill) {
|
|
memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
} else {
|
|
|
|
#ifdef DEBUG_READ_PEAKS
|
|
cerr << "UPSAMPLE\n";
|
|
#endif
|
|
/* the caller wants
|
|
|
|
- less frames-per-peak (more resolution)
|
|
- more peaks than stored in the Peakfile
|
|
|
|
So, fetch data from the raw source, and generate peak
|
|
data on the fly.
|
|
*/
|
|
|
|
nframes_t frames_read = 0;
|
|
nframes_t current_frame = start;
|
|
nframes_t i = 0;
|
|
nframes_t nvisual_peaks = 0;
|
|
nframes_t chunksize = (nframes_t) min (cnt, (nframes_t) 4096);
|
|
raw_staging = new Sample[chunksize];
|
|
|
|
nframes_t frame_pos = start;
|
|
double pixel_pos = floor (frame_pos / samples_per_visual_peak);
|
|
double next_pixel_pos = ceil (frame_pos / samples_per_visual_peak);
|
|
double pixels_per_frame = 1.0 / samples_per_visual_peak;
|
|
|
|
xmin = 1.0;
|
|
xmax = -1.0;
|
|
|
|
while (nvisual_peaks < npeaks) {
|
|
|
|
if (i == frames_read) {
|
|
|
|
to_read = min (chunksize, nframes_t(_length - current_frame));
|
|
|
|
if (to_read == 0) {
|
|
/* XXX ARGH .. out by one error ... need to figure out why this happens
|
|
and fix it rather than do this band-aid move.
|
|
*/
|
|
zero_fill = npeaks - nvisual_peaks;
|
|
break;
|
|
}
|
|
|
|
if ((frames_read = read_unlocked (raw_staging, current_frame, to_read)) == 0) {
|
|
error << string_compose(_("AudioSource[%1]: peak read - cannot read %2 samples at offset %3 of %4 (%5)"),
|
|
_name, to_read, current_frame, _length, strerror (errno))
|
|
<< endmsg;
|
|
goto out;
|
|
}
|
|
|
|
i = 0;
|
|
}
|
|
|
|
xmax = max (xmax, raw_staging[i]);
|
|
xmin = min (xmin, raw_staging[i]);
|
|
++i;
|
|
++current_frame;
|
|
pixel_pos += pixels_per_frame;
|
|
|
|
if (pixel_pos >= next_pixel_pos) {
|
|
|
|
peaks[nvisual_peaks].max = xmax;
|
|
peaks[nvisual_peaks].min = xmin;
|
|
++nvisual_peaks;
|
|
xmin = 1.0;
|
|
xmax = -1.0;
|
|
|
|
next_pixel_pos = ceil (pixel_pos + 0.5);
|
|
}
|
|
}
|
|
|
|
if (zero_fill) {
|
|
memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
|
|
}
|
|
|
|
ret = 0;
|
|
}
|
|
|
|
out:
|
|
if (_peakfile >= 0) {
|
|
close (_peakfile);
|
|
}
|
|
|
|
delete [] staging;
|
|
delete [] raw_staging;
|
|
|
|
#ifdef DEBUG_READ_PEAKS
|
|
cerr << "RP DONE\n";
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
#undef DEBUG_PEAK_BUILD
|
|
|
|
int
|
|
AudioSource::build_peaks_from_scratch ()
|
|
{
|
|
nframes_t current_frame;
|
|
nframes_t cnt;
|
|
Sample* buf = 0;
|
|
nframes_t frames_read;
|
|
nframes_t frames_to_read;
|
|
const nframes_t bufsize = 65536; // 256kB per disk read for mono data is about ideal
|
|
|
|
int ret = -1;
|
|
|
|
{
|
|
/* hold lock while building peaks */
|
|
|
|
Glib::Mutex::Lock lp (_lock);
|
|
|
|
if (prepare_for_peakfile_writes ()) {
|
|
goto out;
|
|
}
|
|
|
|
current_frame = 0;
|
|
cnt = _length;
|
|
_peaks_built = false;
|
|
buf = new Sample[bufsize];
|
|
|
|
while (cnt) {
|
|
|
|
frames_to_read = min (bufsize, cnt);
|
|
|
|
if ((frames_read = read_unlocked (buf, current_frame, frames_to_read)) != frames_to_read) {
|
|
error << string_compose(_("%1: could not write read raw data for peak computation (%2)"), _name, strerror (errno)) << endmsg;
|
|
done_with_peakfile_writes (false);
|
|
goto out;
|
|
}
|
|
|
|
if (compute_and_write_peaks (buf, current_frame, frames_read, true, false, _FPP)) {
|
|
break;
|
|
}
|
|
|
|
current_frame += frames_read;
|
|
cnt -= frames_read;
|
|
}
|
|
|
|
if (cnt == 0) {
|
|
/* success */
|
|
truncate_peakfile();
|
|
}
|
|
|
|
done_with_peakfile_writes ((cnt == 0));
|
|
}
|
|
|
|
{
|
|
Glib::Mutex::Lock lm (_peaks_ready_lock);
|
|
|
|
if (_peaks_built) {
|
|
PeaksReady (); /* EMIT SIGNAL */
|
|
ret = 0;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (ret) {
|
|
unlink (peakpath.c_str());
|
|
}
|
|
|
|
delete [] buf;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
AudioSource::prepare_for_peakfile_writes ()
|
|
{
|
|
if ((peakfile = ::open (peakpath.c_str(), O_RDWR|O_CREAT, 0664)) < 0) {
|
|
error << string_compose(_("AudioSource: cannot open peakpath (c) \"%1\" (%2)"), peakpath, strerror (errno)) << endmsg;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
AudioSource::done_with_peakfile_writes (bool done)
|
|
{
|
|
if (peak_leftover_cnt) {
|
|
compute_and_write_peaks (0, 0, 0, true, false, _FPP);
|
|
}
|
|
|
|
if (done) {
|
|
_peaks_built = true;
|
|
}
|
|
|
|
if (peakfile >= 0) {
|
|
close (peakfile);
|
|
peakfile = -1;
|
|
}
|
|
}
|
|
|
|
int
|
|
AudioSource::compute_and_write_peaks (Sample* buf, sframes_t first_frame, nframes_t cnt,
|
|
bool force, bool intermediate_peaks_ready)
|
|
{
|
|
return compute_and_write_peaks (buf, first_frame, cnt, force, intermediate_peaks_ready, _FPP);
|
|
}
|
|
|
|
int
|
|
AudioSource::compute_and_write_peaks (Sample* buf, sframes_t first_frame, nframes_t cnt,
|
|
bool force, bool intermediate_peaks_ready, nframes_t fpp)
|
|
{
|
|
Sample* buf2 = 0;
|
|
nframes_t to_do;
|
|
uint32_t peaks_computed;
|
|
PeakData* peakbuf = 0;
|
|
int ret = -1;
|
|
nframes_t current_frame;
|
|
nframes_t frames_done;
|
|
const size_t blocksize = (128 * 1024);
|
|
off_t first_peak_byte;
|
|
|
|
if (peakfile < 0) {
|
|
prepare_for_peakfile_writes ();
|
|
}
|
|
|
|
restart:
|
|
if (peak_leftover_cnt) {
|
|
|
|
if (first_frame != peak_leftover_frame + peak_leftover_cnt) {
|
|
|
|
/* uh-oh, ::seek() since the last ::compute_and_write_peaks(),
|
|
and we have leftovers. flush a single peak (since the leftovers
|
|
never represent more than that, and restart.
|
|
*/
|
|
|
|
PeakData x;
|
|
|
|
x.min = peak_leftovers[0];
|
|
x.max = peak_leftovers[0];
|
|
|
|
off_t byte = (peak_leftover_frame / fpp) * sizeof (PeakData);
|
|
|
|
if (::pwrite (peakfile, &x, sizeof (PeakData), byte) != sizeof (PeakData)) {
|
|
error << string_compose(_("%1: could not write peak file data (%2)"), _name, strerror (errno)) << endmsg;
|
|
goto out;
|
|
}
|
|
|
|
_peak_byte_max = max (_peak_byte_max, (off_t) (byte + sizeof(PeakData)));
|
|
|
|
{
|
|
Glib::Mutex::Lock lm (_peaks_ready_lock);
|
|
PeakRangeReady (peak_leftover_frame, peak_leftover_cnt); /* EMIT SIGNAL */
|
|
if (intermediate_peaks_ready) {
|
|
PeaksReady (); /* EMIT SIGNAL */
|
|
}
|
|
}
|
|
|
|
/* left overs are done */
|
|
|
|
peak_leftover_cnt = 0;
|
|
goto restart;
|
|
}
|
|
|
|
/* else ... had leftovers, but they immediately preceed the new data, so just
|
|
merge them and compute.
|
|
*/
|
|
|
|
/* make a new contiguous buffer containing leftovers and the new stuff */
|
|
|
|
to_do = cnt + peak_leftover_cnt;
|
|
buf2 = new Sample[to_do];
|
|
|
|
/* the remnants */
|
|
memcpy (buf2, peak_leftovers, peak_leftover_cnt * sizeof (Sample));
|
|
|
|
/* the new stuff */
|
|
memcpy (buf2+peak_leftover_cnt, buf, cnt * sizeof (Sample));
|
|
|
|
/* no more leftovers */
|
|
peak_leftover_cnt = 0;
|
|
|
|
/* use the temporary buffer */
|
|
buf = buf2;
|
|
|
|
/* make sure that when we write into the peakfile, we startup where we left off */
|
|
|
|
first_frame = peak_leftover_frame;
|
|
|
|
} else {
|
|
to_do = cnt;
|
|
}
|
|
|
|
peakbuf = new PeakData[(to_do/fpp)+1];
|
|
peaks_computed = 0;
|
|
current_frame = first_frame;
|
|
frames_done = 0;
|
|
|
|
while (to_do) {
|
|
|
|
/* if some frames were passed in (i.e. we're not flushing leftovers)
|
|
and there are less than fpp to do, save them till
|
|
next time
|
|
*/
|
|
|
|
if (force && (to_do < fpp)) {
|
|
/* keep the left overs around for next time */
|
|
|
|
if (peak_leftover_size < to_do) {
|
|
delete [] peak_leftovers;
|
|
peak_leftovers = new Sample[to_do];
|
|
peak_leftover_size = to_do;
|
|
}
|
|
memcpy (peak_leftovers, buf, to_do * sizeof (Sample));
|
|
peak_leftover_cnt = to_do;
|
|
peak_leftover_frame = current_frame;
|
|
|
|
/* done for now */
|
|
|
|
break;
|
|
}
|
|
|
|
nframes_t this_time = min (fpp, to_do);
|
|
|
|
peakbuf[peaks_computed].max = buf[0];
|
|
peakbuf[peaks_computed].min = buf[0];
|
|
|
|
ARDOUR::find_peaks (buf+1, this_time-1, &peakbuf[peaks_computed].min, &peakbuf[peaks_computed].max);
|
|
|
|
peaks_computed++;
|
|
buf += this_time;
|
|
to_do -= this_time;
|
|
frames_done += this_time;
|
|
current_frame += this_time;
|
|
}
|
|
|
|
first_peak_byte = (first_frame / fpp) * sizeof (PeakData);
|
|
|
|
if (can_truncate_peaks()) {
|
|
|
|
/* on some filesystems (ext3, at least) this helps to reduce fragmentation of
|
|
the peakfiles. its not guaranteed to do so, and even on ext3 (as of december 2006)
|
|
it does not cause single-extent allocation even for peakfiles of
|
|
less than BLOCKSIZE bytes. only call ftruncate if we'll make the file larger.
|
|
*/
|
|
|
|
off_t endpos = lseek (peakfile, 0, SEEK_END);
|
|
off_t target_length = blocksize * ((first_peak_byte + blocksize + 1) / blocksize);
|
|
|
|
if (endpos < target_length) {
|
|
ftruncate (peakfile, target_length);
|
|
/* error doesn't actually matter though, so continue on without testing */
|
|
}
|
|
}
|
|
|
|
if (::pwrite (peakfile, peakbuf, sizeof (PeakData) * peaks_computed, first_peak_byte) != (ssize_t) (sizeof (PeakData) * peaks_computed)) {
|
|
error << string_compose(_("%1: could not write peak file data (%2)"), _name, strerror (errno)) << endmsg;
|
|
goto out;
|
|
}
|
|
|
|
_peak_byte_max = max (_peak_byte_max, (off_t) (first_peak_byte + sizeof(PeakData)*peaks_computed));
|
|
|
|
if (frames_done) {
|
|
Glib::Mutex::Lock lm (_peaks_ready_lock);
|
|
PeakRangeReady (first_frame, frames_done); /* EMIT SIGNAL */
|
|
if (intermediate_peaks_ready) {
|
|
PeaksReady (); /* EMIT SIGNAL */
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
delete [] peakbuf;
|
|
delete [] buf2;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
AudioSource::truncate_peakfile ()
|
|
{
|
|
if (peakfile < 0) {
|
|
error << string_compose (_("programming error: %1"), "AudioSource::truncate_peakfile() called without open peakfile descriptor")
|
|
<< endmsg;
|
|
return;
|
|
}
|
|
|
|
/* truncate the peakfile down to its natural length if necessary */
|
|
|
|
off_t end = lseek (peakfile, 0, SEEK_END);
|
|
|
|
if (end > _peak_byte_max) {
|
|
ftruncate (peakfile, _peak_byte_max);
|
|
}
|
|
}
|
|
|
|
bool
|
|
AudioSource::file_changed (ustring path)
|
|
{
|
|
struct stat stat_file;
|
|
struct stat stat_peak;
|
|
|
|
int e1 = stat (path.c_str(), &stat_file);
|
|
int e2 = stat (peak_path(path).c_str(), &stat_peak);
|
|
|
|
if (!e1 && !e2 && stat_file.st_mtime > stat_peak.st_mtime){
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
nframes_t
|
|
AudioSource::available_peaks (double zoom_factor) const
|
|
{
|
|
if (zoom_factor < _FPP) {
|
|
return length(_timeline_position); // peak data will come from the audio file
|
|
}
|
|
|
|
/* peak data comes from peakfile, but the filesize might not represent
|
|
the valid data due to ftruncate optimizations, so use _peak_byte_max state.
|
|
XXX - there might be some atomicity issues here, we should probably add a lock,
|
|
but _peak_byte_max only monotonically increases after initialization.
|
|
*/
|
|
|
|
off_t end = _peak_byte_max;
|
|
|
|
return (end/sizeof(PeakData)) * _FPP;
|
|
}
|
|
|