more file add/remove ops related to sndfile changes

git-svn-id: svn://localhost/ardour2/trunk@591 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis
2006-06-14 21:28:00 +00:00
parent aff241abf1
commit a999381a84
6 changed files with 1881 additions and 313 deletions

View File

@@ -0,0 +1,157 @@
/*
Copyright (C) 2006 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.
*/
#ifndef __ardour_audiofilesource_h__
#define __ardour_audiofilesource_h__
#include <time.h>
#include <ardour/audiosource.h>
namespace ARDOUR {
struct SoundFileInfo {
float samplerate;
uint16_t channels;
int64_t length;
std::string format_name;
};
class AudioFileSource : public AudioSource {
public:
enum Flag {
Writable = 0x1,
CanRename = 0x2,
Broadcast = 0x4,
Removable = 0x8,
RemovableIfEmpty = 0x10,
RemoveAtDestroy = 0x20,
BuildPeaks = 0x40
};
virtual ~AudioFileSource ();
int set_name (string newname, bool destructive);
string path() const { return _path; }
string peak_path (string audio_path);
string old_peak_path (string audio_path);
static void set_peak_dir (string dir) { peak_dir = dir; }
/* factory for an existing but not-used-in-session audio file. this exists
because there maybe multiple back-end derivations of AudioFileSource,
some of which can handle formats that cannot be handled by others.
For example, CoreAudioFileSource can handle MP3 files, which SndFileSource
cannot.
*/
static AudioFileSource* create (string path_plus_channel);
static AudioFileSource* create (const XMLNode&);
static bool get_soundfile_info (string path, SoundFileInfo& _info, string& error);
void set_allow_remove_if_empty (bool yn);
void mark_for_remove();
/* this block of methods do nothing for regular file sources, but are significant
for files used in destructive recording.
*/
virtual jack_nframes_t last_capture_start_frame() const { return 0; }
virtual void mark_capture_start (jack_nframes_t) {}
virtual void mark_capture_end () {}
virtual void clear_capture_marks() {}
virtual int update_header (jack_nframes_t when, struct tm&, time_t) = 0;
virtual int flush_header () = 0;
int move_to_trash (const string trash_dir_name);
static bool is_empty (string path);
void mark_streaming_write_completed ();
void mark_take (string);
string take_id() const { return _take_id; }
static void set_bwf_country_code (string x);
static void set_bwf_organization_code (string x);
static void set_bwf_serial_number (int);
static void set_search_path (string);
static void set_header_position_offset (jack_nframes_t offset, bool negative);
static sigc::signal<void,struct tm*, time_t> HeaderPositionOffsetChanged;
XMLNode& get_state ();
int set_state (const XMLNode&);
/* this should really be protected, but C++ is getting stricter
and creating slots from protected member functions is starting
to cause issues.
*/
void handle_header_position_change (struct tm*, time_t tnow);
protected:
/* constructor to be called for existing external-to-session files */
AudioFileSource (std::string path, Flag flags);
/* constructor to be called for new in-session files */
AudioFileSource (std::string path, Flag flags,
SampleFormat samp_format, HeaderFormat hdr_format);
/* constructor to be called for existing in-session files */
AudioFileSource (const XMLNode&);
int init (string idstr, bool must_exist);
uint16_t channel;
string _path;
Flag _flags;
string _take_id;
bool allow_remove_if_empty;
uint64_t timeline_position;
static string peak_dir;
static string search_path;
static char bwf_country_code[3];
static char bwf_organization_code[4];
static char bwf_serial_number[13];
static uint64_t header_position_offset;
static bool header_position_negative;
virtual void set_timeline_position (jack_nframes_t pos);
virtual void set_header_timeline_position () = 0;
bool find (std::string path, bool must_exist, bool& is_new);
bool removable() const;
bool writable() const { return _flags & Writable; }
};
}; /* namespace ARDOUR */
#endif /* __ardour_audiofilesource_h__ */

View File

@@ -0,0 +1,166 @@
/*
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.
$Id: audio_source.h 486 2006-04-27 09:04:24Z pauld $
*/
#ifndef __ardour_audio_source_h__
#define __ardour_audio_source_h__
#include <list>
#include <vector>
#include <string>
#include <time.h>
#include <glibmm/thread.h>
#include <sigc++/signal.h>
#include <ardour/source.h>
#include <ardour/ardour.h>
#include <ardour/stateful.h>
#include <pbd/xml++.h>
using std::list;
using std::vector;
using std::string;
namespace ARDOUR {
const jack_nframes_t frames_per_peak = 256;
class AudioSource : public Source
{
public:
AudioSource (string name);
AudioSource (const XMLNode&);
virtual ~AudioSource ();
/* returns the number of items in this `audio_source' */
virtual jack_nframes_t length() const {
return _length;
}
virtual jack_nframes_t available_peaks (double zoom) const;
virtual jack_nframes_t read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const;
virtual jack_nframes_t write (Sample *src, jack_nframes_t cnt, char * workbuf);
virtual float sample_rate () const = 0;
virtual void mark_for_remove() = 0;
virtual void mark_streaming_write_completed () {}
void set_captured_for (string str) { _captured_for = str; }
string captured_for() const { return _captured_for; }
uint32_t read_data_count() const { return _read_data_count; }
uint32_t write_data_count() const { return _write_data_count; }
int read_peaks (PeakData *peaks, jack_nframes_t npeaks, jack_nframes_t start, jack_nframes_t cnt, double samples_per_unit) const;
int build_peaks ();
bool peaks_ready (sigc::slot<void>, sigc::connection&) const;
static sigc::signal<void,AudioSource*> AudioSourceCreated;
mutable sigc::signal<void> PeaksReady;
mutable sigc::signal<void,jack_nframes_t,jack_nframes_t> PeakRangeReady;
XMLNode& get_state ();
int set_state (const XMLNode&);
static int start_peak_thread ();
static void stop_peak_thread ();
int rename_peakfile (std::string newpath);
static void set_build_missing_peakfiles (bool yn) {
_build_missing_peakfiles = yn;
}
static void set_build_peakfiles (bool yn) {
_build_peakfiles = yn;
}
protected:
static bool _build_missing_peakfiles;
static bool _build_peakfiles;
bool _peaks_built;
mutable Glib::Mutex _lock;
jack_nframes_t _length;
bool next_peak_clear_should_notify;
string peakpath;
string _captured_for;
mutable uint32_t _read_data_count; // modified in read()
mutable uint32_t _write_data_count; // modified in write()
int initialize_peakfile (bool newfile, string path);
void build_peaks_from_scratch ();
int do_build_peak (jack_nframes_t, jack_nframes_t);
virtual jack_nframes_t read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const = 0;
virtual jack_nframes_t write_unlocked (Sample *dst, jack_nframes_t cnt, char * workbuf) = 0;
virtual string peak_path(string audio_path) = 0;
virtual string old_peak_path(string audio_path) = 0;
void update_length (jack_nframes_t pos, jack_nframes_t cnt);
static pthread_t peak_thread;
static bool have_peak_thread;
static void* peak_thread_work(void*);
static int peak_request_pipe[2];
struct PeakRequest {
enum Type {
Build,
Quit
};
};
static vector<AudioSource*> pending_peak_sources;
static Glib::Mutex* pending_peak_sources_lock;
static void queue_for_peaks (AudioSource&);
static void clear_queue_for_peaks ();
struct PeakBuildRecord {
jack_nframes_t frame;
jack_nframes_t cnt;
PeakBuildRecord (jack_nframes_t f, jack_nframes_t c)
: frame (f), cnt (c) {}
PeakBuildRecord (const PeakBuildRecord& other) {
frame = other.frame;
cnt = other.cnt;
}
};
list<AudioSource::PeakBuildRecord *> pending_peak_builds;
private:
bool file_changed (string path);
};
}
#endif /* __ardour_audio_source_h__ */

View File

@@ -1,67 +0,0 @@
/*
Copyright (C) 2006 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.
*/
#ifndef __external_source_h__
#define __external_source_h__
#include <ardour/source.h>
namespace ARDOUR {
struct SoundFileInfo {
float samplerate;
uint16_t channels;
int64_t length;
std::string format_name;
};
class ExternalSource : public Source {
public:
virtual ~ExternalSource ();
string path() const { return _path; }
virtual jack_nframes_t read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const = 0;
void mark_for_remove() {} // we never remove external sndfiles
string peak_path(string audio_path);
string old_peak_path(string audio_path);
static void set_peak_dir (string dir) { peak_dir = dir; }
static ExternalSource* create (const string& path_plus_channel, bool build_peak = true);
static ExternalSource* create (const XMLNode& node);
static bool get_soundfile_info (string path, SoundFileInfo& _info, string& error);
protected:
ExternalSource (const string& path_plus_channel, bool build_peak = true);
ExternalSource (const XMLNode&);
static string peak_dir;
uint16_t channel;
string _path;
jack_nframes_t read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const;
};
}; /* namespace ARDOUR */
#endif /* __external_source_h__ */

View File

@@ -0,0 +1,661 @@
/*
Copyright (C) 2006 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 <vector>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pbd/mountpoint.h>
#include <pbd/pathscanner.h>
#include <pbd/stl_delete.h>
#include <pbd/strsplit.h>
#include <sndfile.h>
#include <glibmm/miscutils.h>
#include <ardour/audiofilesource.h>
#include <ardour/sndfile_helpers.h>
#include <ardour/sndfilesource.h>
#include <ardour/destructive_filesource.h>
#include <ardour/session.h>
// if these headers come before sigc++ is included
// the parser throws ObjC++ errors. (nil is a keyword)
#ifdef HAVE_COREAUDIO
#include <ardour/coreaudio_source.h>
#include <AudioToolbox/ExtendedAudioFile.h>
#include <AudioToolbox/AudioFormat.h>
#endif // HAVE_COREAUDIO
#include "i18n.h"
using namespace ARDOUR;
string AudioFileSource::peak_dir = "";
string AudioFileSource::search_path;
sigc::signal<void,struct tm*, time_t> AudioFileSource::HeaderPositionOffsetChanged;
bool AudioFileSource::header_position_negative;
uint64_t AudioFileSource::header_position_offset;
char AudioFileSource::bwf_country_code[3] = "US";
char AudioFileSource::bwf_organization_code[4] = "LAS";
char AudioFileSource::bwf_serial_number[13] = "000000000000";
AudioFileSource::AudioFileSource (string idstr, Flag flags)
: AudioSource (idstr), _flags (flags)
{
/* constructor used for existing external to session files. file must exist already */
if (init (idstr, true)) {
throw failed_constructor ();
}
}
AudioFileSource::AudioFileSource (std::string path, Flag flags, SampleFormat samp_format, HeaderFormat hdr_format)
: AudioSource (path), _flags (flags)
{
/* constructor used for new internal-to-session files. file cannot exist */
if (init (path, false)) {
throw failed_constructor ();
}
}
AudioFileSource::AudioFileSource (const XMLNode& node)
: AudioSource (node), _flags (Flag (Writable|CanRename))
{
/* constructor used for existing internal-to-session files. file must exist */
if (set_state (node)) {
throw failed_constructor ();
}
if (init (_name, true)) {
throw failed_constructor ();
}
}
AudioFileSource::~AudioFileSource ()
{
if (removable()) {
unlink (_path.c_str());
unlink (peakpath.c_str());
}
}
bool
AudioFileSource::removable () const
{
return (_flags & Removable) && ((_flags & RemoveAtDestroy) ||
((_flags & RemovableIfEmpty) && is_empty (_path)));
}
int
AudioFileSource::init (string pathstr, bool must_exist)
{
bool is_new = false;
_length = 0;
next_peak_clear_should_notify = false;
if (!find (pathstr, must_exist, is_new)) {
cerr << "cannot find " << pathstr << " with me = " << must_exist << endl;
return -1;
}
if (is_new && must_exist) {
return -1;
}
return 0;
}
string
AudioFileSource::peak_path (string audio_path)
{
return Session::peak_path_from_audio_path (audio_path);
}
string
AudioFileSource::old_peak_path (string audio_path)
{
/* XXX hardly bombproof! fix me */
struct stat stat_file;
struct stat stat_mount;
string mp = mountpoint (audio_path);
stat (audio_path.c_str(), &stat_file);
stat (mp.c_str(), &stat_mount);
char buf[32];
#ifdef __APPLE__
snprintf (buf, sizeof (buf), "%u-%u-%d.peak", stat_mount.st_ino, stat_file.st_ino, channel);
#else
snprintf (buf, sizeof (buf), "%ld-%ld-%d.peak", stat_mount.st_ino, stat_file.st_ino, channel);
#endif
string res = peak_dir;
res += buf;
return res;
}
#ifdef HAVE_COREAUDIO
AudioFileSource*
AudioFileSource::create (const XMLNode& node)
{
AudioFileSource* es = 0;
if (node.property (X_("destructive")) != 0) {
es = new DestructiveFileSource (node);
} else {
try {
es = new CoreAudioSource (node);
}
catch (failed_constructor& err) {
es = new SndFileSource (node);
}
}
return es;
}
#else
AudioFileSource*
AudioFileSource::create (const XMLNode& node)
{
if (node.property (X_("destructive")) != 0) {
return new DestructiveFileSource (node);
} else {
return new SndFileSource (node);
}
}
#endif // HAVE_COREAUDIO
#ifdef HAVE_COREAUDIO
AudioFileSource*
AudioFileSource::create (const string& idstr)
{
AudioFileSource* es = 0;
try {
es = new CoreAudioSource (idstr);
}
catch (failed_constructor& err) {
es = new SndFileSource (idstr);
}
return es;
}
#else
AudioFileSource*
AudioFileSource::create (string idstr)
{
return new SndFileSource (idstr);
}
#endif // HAVE_COREAUDIO
#ifdef HAVE_COREAUDIO
std::string
CFStringRefToStdString(CFStringRef stringRef)
{
CFIndex size =
CFStringGetMaximumSizeForEncoding(CFStringGetLength(stringRef) ,
kCFStringEncodingASCII);
char *buf = new char[size];
std::string result;
if(CFStringGetCString(stringRef, buf, size, kCFStringEncodingASCII)) {
result = buf;
}
delete [] buf;
return result;
}
#endif // HAVE_COREAUDIO
bool
AudioFileSource::get_soundfile_info (string path, SoundFileInfo& _info, string& error_msg)
{
#ifdef HAVE_COREAUDIO
OSStatus err = noErr;
FSRef ref;
ExtAudioFileRef af = 0;
size_t size;
CFStringRef name;
err = FSPathMakeRef ((UInt8*)path.c_str(), &ref, 0);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
err = ExtAudioFileOpen(&ref, &af);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
AudioStreamBasicDescription absd;
memset(&absd, 0, sizeof(absd));
size = sizeof(AudioStreamBasicDescription);
err = ExtAudioFileGetProperty(af,
kExtAudioFileProperty_FileDataFormat, &size, &absd);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
_info.samplerate = absd.mSampleRate;
_info.channels = absd.mChannelsPerFrame;
size = sizeof(_info.length);
err = ExtAudioFileGetProperty(af, kExtAudioFileProperty_FileLengthFrames, &size, &_info.length);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
size = sizeof(CFStringRef);
err = AudioFormatGetProperty(
kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
_info.format_name = CFStringRefToStdString(name);
ExtAudioFileDispose (af);
return true;
libsndfile:
#endif // HAVE_COREAUDIO
SNDFILE *sf;
SF_INFO sf_info;
sf_info.format = 0; // libsndfile says to clear this before sf_open().
if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) {
char errbuf[256];
error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
return false;
}
sf_close (sf);
_info.samplerate = sf_info.samplerate;
_info.channels = sf_info.channels;
_info.length = sf_info.frames;
_info.format_name = string_compose("Format: %1, %2",
sndfile_major_format(sf_info.format),
sndfile_minor_format(sf_info.format));
return true;
}
XMLNode&
AudioFileSource::get_state ()
{
XMLNode& root (AudioSource::get_state());
char buf[16];
snprintf (buf, sizeof (buf), "0x%x", (int)_flags);
root.add_property ("flags", buf);
return root;
}
int
AudioFileSource::set_state (const XMLNode& node)
{
const XMLProperty* prop;
if (AudioSource::set_state (node)) {
return -1;
}
if ((prop = node.property (X_("flags"))) != 0) {
int ival;
sscanf (prop->value().c_str(), "0x%x", &ival);
_flags = Flag (ival);
} else {
_flags = Flag (0);
}
return 0;
}
void
AudioFileSource::mark_for_remove ()
{
if (!writable()) {
return;
}
_flags = Flag (_flags | RemoveAtDestroy);
}
void
AudioFileSource::mark_streaming_write_completed ()
{
if (!writable()) {
return;
}
Glib::Mutex::Lock lm (_lock);
next_peak_clear_should_notify = true;
if (_peaks_built || pending_peak_builds.empty()) {
_peaks_built = true;
PeaksReady (); /* EMIT SIGNAL */
}
}
void
AudioFileSource::mark_take (string id)
{
if (writable()) {
_take_id = id;
}
}
int
AudioFileSource::move_to_trash (const string trash_dir_name)
{
string newpath;
if (!writable()) {
return -1;
}
/* don't move the file across filesystems, just
stick it in the `trash_dir_name' directory
on whichever filesystem it was already on.
*/
newpath = Glib::path_get_dirname (_path);
newpath = Glib::path_get_dirname (newpath);
newpath += '/';
newpath += trash_dir_name;
newpath += '/';
newpath += Glib::path_get_basename (_path);
if (access (newpath.c_str(), F_OK) == 0) {
/* the new path already exists, try versioning */
char buf[PATH_MAX+1];
int version = 1;
string newpath_v;
snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
newpath_v = buf;
while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
newpath_v = buf;
}
if (version == 999) {
error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
newpath)
<< endmsg;
} else {
newpath = newpath_v;
}
} else {
/* it doesn't exist, or we can't read it or something */
}
if (::rename (_path.c_str(), newpath.c_str()) != 0) {
error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
_path, newpath, strerror (errno))
<< endmsg;
return -1;
}
if (::unlink (peakpath.c_str()) != 0) {
error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
peakpath, _path, strerror (errno))
<< endmsg;
/* try to back out */
rename (newpath.c_str(), _path.c_str());
return -1;
}
_path = newpath;
peakpath = "";
/* file can not be removed twice, since the operation is not idempotent */
_flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
return 0;
}
bool
AudioFileSource::find (string pathstr, bool must_exist, bool& isnew)
{
string::size_type pos;
bool ret = false;
isnew = false;
/* clean up PATH:CHANNEL notation so that we are looking for the correct path */
if ((pos = pathstr.find_last_of (':')) == string::npos) {
pathstr = pathstr;
} else {
pathstr = pathstr.substr (0, pos);
}
if (pathstr[0] != '/') {
/* non-absolute pathname: find pathstr in search path */
vector<string> dirs;
int cnt;
string fullpath;
string keeppath;
if (search_path.length() == 0) {
error << _("FileSource: search path not set") << endmsg;
goto out;
}
split (search_path, dirs, ':');
cnt = 0;
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
fullpath = *i;
if (fullpath[fullpath.length()-1] != '/') {
fullpath += '/';
}
fullpath += pathstr;
if (access (fullpath.c_str(), R_OK) == 0) {
keeppath = fullpath;
++cnt;
}
}
if (cnt > 1) {
error << string_compose (_("FileSource: \"%1\" is ambigous when searching %2\n\t"), pathstr, search_path) << endmsg;
goto out;
} else if (cnt == 0) {
if (must_exist) {
error << string_compose(_("Filesource: cannot find required file (%1): while searching %2"), pathstr, search_path) << endmsg;
goto out;
} else {
isnew = true;
}
}
_name = pathstr;
_path = keeppath;
ret = true;
} else {
/* external files and/or very very old style sessions include full paths */
_path = pathstr;
_name = pathstr.substr (pathstr.find_last_of ('/') + 1);
if (access (_path.c_str(), R_OK) != 0) {
/* file does not exist or we cannot read it */
if (must_exist) {
error << string_compose(_("Filesource: cannot find required file (%1): %2"), _path, strerror (errno)) << endmsg;
goto out;
}
if (errno != ENOENT) {
error << string_compose(_("Filesource: cannot check for existing file (%1): %2"), _path, strerror (errno)) << endmsg;
goto out;
}
/* a new file */
isnew = true;
ret = true;
} else {
/* already exists */
ret = true;
}
}
out:
return ret;
}
void
AudioFileSource::set_search_path (string p)
{
search_path = p;
}
void
AudioFileSource::set_header_position_offset (jack_nframes_t offset, bool negative)
{
time_t tnow;
time (&tnow);
header_position_offset = offset;
header_position_negative = negative;
HeaderPositionOffsetChanged (localtime (&tnow), tnow); /* EMIT SIGNAL */
}
void
AudioFileSource::set_timeline_position (jack_nframes_t pos)
{
timeline_position = pos;
}
void
AudioFileSource::handle_header_position_change (struct tm* now, time_t tnow)
{
/* don't do this if the file has never had its header flushed to disk yet */
if (writable() && _timestamp) {
set_header_timeline_position ();
flush_header ();
}
}
void
AudioFileSource::set_allow_remove_if_empty (bool yn)
{
if (writable()) {
allow_remove_if_empty = yn;
}
}
int
AudioFileSource::set_name (string newname, bool destructive)
{
Glib::Mutex::Lock lm (_lock);
string oldpath = _path;
string newpath = Session::change_audio_path_by_name (oldpath, _name, newname, destructive);
if (newpath.empty()) {
error << string_compose (_("programming error: %1"), "cannot generate a changed audio path") << endmsg;
return -1;
}
if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
error << string_compose (_("cannot rename audio file for %1 to %2"), _name, newpath) << endmsg;
return -1;
}
_name = Glib::path_get_basename (newpath);
_path = newpath;
return rename_peakfile (peak_path (_path));
}
bool
AudioFileSource::is_empty (string path)
{
/* XXX fix me */
return false;
}

897
libs/ardour/audiosource.cc Normal file
View File

@@ -0,0 +1,897 @@
/*
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.
$Id: source.cc 404 2006-03-17 17:39:21Z pauld $
*/
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <float.h>
#include <cerrno>
#include <ctime>
#include <cmath>
#include <iomanip>
#include <algorithm>
#include <pbd/xml++.h>
#include <pbd/pthread_utils.h>
#include <ardour/audiosource.h>
#include "i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace PBD;
sigc::signal<void,AudioSource *> AudioSource::AudioSourceCreated;
pthread_t AudioSource::peak_thread;
bool AudioSource::have_peak_thread = false;
vector<AudioSource*> AudioSource::pending_peak_sources;
Glib::Mutex* AudioSource::pending_peak_sources_lock = 0;
int AudioSource::peak_request_pipe[2];
bool AudioSource::_build_missing_peakfiles = false;
bool AudioSource::_build_peakfiles = false;
AudioSource::AudioSource (string name)
: Source (name)
{
if (pending_peak_sources_lock == 0) {
pending_peak_sources_lock = new Glib::Mutex;
}
_peaks_built = false;
next_peak_clear_should_notify = true;
_read_data_count = 0;
_write_data_count = 0;
}
AudioSource::AudioSource (const XMLNode& node)
: Source (node)
{
if (pending_peak_sources_lock == 0) {
pending_peak_sources_lock = new Glib::Mutex;
}
_peaks_built = false;
next_peak_clear_should_notify = true;
_read_data_count = 0;
_write_data_count = 0;
if (set_state (node)) {
throw failed_constructor();
}
}
AudioSource::~AudioSource ()
{
}
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;
Source::set_state (node);
if ((prop = node.property ("captured-for")) != 0) {
_captured_for = prop->value();
}
return 0;
}
/***********************************************************************
PEAK FILE STUFF
***********************************************************************/
void*
AudioSource::peak_thread_work (void* arg)
{
PBD::ThreadCreated (pthread_self(), X_("Peak"));
struct pollfd pfd[1];
if (pending_peak_sources_lock == 0) {
pending_peak_sources_lock = new Glib::Mutex;
}
Glib::Mutex::Lock lm (*pending_peak_sources_lock);
while (true) {
pfd[0].fd = peak_request_pipe[0];
pfd[0].events = POLLIN|POLLERR|POLLHUP;
pending_peak_sources_lock->unlock ();
if (poll (pfd, 1, -1) < 0) {
if (errno == EINTR) {
pending_peak_sources_lock->lock ();
continue;
}
error << string_compose (_("poll on peak request pipe failed (%1)"),
strerror (errno))
<< endmsg;
break;
}
if (pfd[0].revents & ~POLLIN) {
error << _("Error on peak thread request pipe") << endmsg;
break;
}
if (pfd[0].revents & POLLIN) {
char req;
/* empty the pipe of all current requests */
while (1) {
size_t nread = ::read (peak_request_pipe[0], &req, sizeof (req));
if (nread == 1) {
switch ((PeakRequest::Type) req) {
case PeakRequest::Build:
break;
case PeakRequest::Quit:
pthread_exit_pbd (0);
/*NOTREACHED*/
break;
default:
break;
}
} else if (nread == 0) {
break;
} else if (errno == EAGAIN) {
break;
} else {
fatal << _("Error reading from peak request pipe") << endmsg;
/*NOTREACHED*/
}
}
}
pending_peak_sources_lock->lock ();
while (!pending_peak_sources.empty()) {
AudioSource* s = pending_peak_sources.front();
pending_peak_sources.erase (pending_peak_sources.begin());
pending_peak_sources_lock->unlock ();
s->build_peaks();
pending_peak_sources_lock->lock ();
}
}
pthread_exit_pbd (0);
/*NOTREACHED*/
return 0;
}
int
AudioSource::start_peak_thread ()
{
if (!_build_peakfiles) {
return 0;
}
if (pipe (peak_request_pipe)) {
error << string_compose(_("Cannot create transport request signal pipe (%1)"), strerror (errno)) << endmsg;
return -1;
}
if (fcntl (peak_request_pipe[0], F_SETFL, O_NONBLOCK)) {
error << string_compose(_("UI: cannot set O_NONBLOCK on peak request pipe (%1)"), strerror (errno)) << endmsg;
return -1;
}
if (fcntl (peak_request_pipe[1], F_SETFL, O_NONBLOCK)) {
error << string_compose(_("UI: cannot set O_NONBLOCK on peak request pipe (%1)"), strerror (errno)) << endmsg;
return -1;
}
if (pthread_create_and_store ("peak file builder", &peak_thread, 0, peak_thread_work, 0)) {
error << _("AudioSource: could not create peak thread") << endmsg;
return -1;
}
have_peak_thread = true;
return 0;
}
void
AudioSource::stop_peak_thread ()
{
if (!have_peak_thread) {
return;
}
void* status;
char c = (char) PeakRequest::Quit;
::write (peak_request_pipe[1], &c, 1);
pthread_join (peak_thread, &status);
}
void
AudioSource::queue_for_peaks (AudioSource& source)
{
if (have_peak_thread) {
Glib::Mutex::Lock lm (*pending_peak_sources_lock);
source.next_peak_clear_should_notify = true;
if (find (pending_peak_sources.begin(),
pending_peak_sources.end(),
&source) == pending_peak_sources.end()) {
pending_peak_sources.push_back (&source);
}
char c = (char) PeakRequest::Build;
::write (peak_request_pipe[1], &c, 1);
}
}
void AudioSource::clear_queue_for_peaks ()
{
/* this is done to cancel a group of running peak builds */
if (have_peak_thread) {
Glib::Mutex::Lock lm (*pending_peak_sources_lock);
pending_peak_sources.clear ();
}
}
bool
AudioSource::peaks_ready (sigc::slot<void> the_slot, sigc::connection& conn) const
{
bool ret;
Glib::Mutex::Lock lm (_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;
}
int
AudioSource::rename_peakfile (string newpath)
{
/* caller must hold _lock */
string 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, string audio_path)
{
struct stat statbuf;
peakpath = peak_path (audio_path);
/* Nasty band-aid for older sessions that were created before we
used libsndfile for all audio files.
*/
if (!newfile && access (peakpath.c_str(), R_OK) != 0) {
string str = old_peak_path (audio_path);
if (access (str.c_str(), R_OK) == 0) {
peakpath = str;
}
}
if (newfile) {
if (!_build_peakfiles) {
return 0;
}
_peaks_built = false;
} else {
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;
}
} else {
/* we found it in the peaks dir */
}
if (statbuf.st_size == 0) {
_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 && stat_file.st_mtime > statbuf.st_mtime){
_peaks_built = false;
} else {
_peaks_built = true;
}
}
}
if (!newfile && !_peaks_built && _build_missing_peakfiles && _build_peakfiles) {
build_peaks_from_scratch ();
}
return 0;
}
jack_nframes_t
AudioSource::read (Sample *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const
{
Glib::Mutex::Lock lm (_lock);
return read_unlocked (dst, start, cnt, workbuf);
}
jack_nframes_t
AudioSource::write (Sample *dst, jack_nframes_t cnt, char * workbuf)
{
Glib::Mutex::Lock lm (_lock);
return write_unlocked (dst, cnt, workbuf);
}
int
AudioSource::read_peaks (PeakData *peaks, jack_nframes_t npeaks, jack_nframes_t start, jack_nframes_t cnt, double samples_per_visual_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;
jack_nframes_t zero_fill = 0;
int ret = -1;
PeakData* staging = 0;
Sample* raw_staging = 0;
char * workbuf = 0;
int peakfile = -1;
expected_peaks = (cnt / (double) frames_per_peak);
scale = npeaks/expected_peaks;
#if 0
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;
jack_nframes_t old = npeaks;
npeaks = min ((jack_nframes_t) floor (cnt / samples_per_visual_peak), npeaks);
zero_fill = old - npeaks;
}
// cerr << "actual npeaks = " << npeaks << " zf = " << zero_fill << endl;
if (npeaks == cnt) {
// cerr << "RAW DATA\n";
/* 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];
workbuf = new char[cnt*4];
if (read_unlocked (raw_staging, start, cnt, workbuf) != cnt) {
error << _("cannot read sample data for unscaled peak computation") << endmsg;
return -1;
}
for (jack_nframes_t i = 0; i < npeaks; ++i) {
peaks[i].max = raw_staging[i];
peaks[i].min = raw_staging[i];
}
delete [] raw_staging;
delete [] workbuf;
return 0;
}
if (scale == 1.0) {
off_t first_peak_byte = (start / frames_per_peak) * sizeof (PeakData);
/* open, read, close */
if ((peakfile = ::open (peakpath.c_str(), O_RDWR|O_CREAT, 0664)) < 0) {
error << string_compose(_("AudioSource: cannot open peakpath \"%1\" (%2)"), peakpath, strerror (errno)) << endmsg;
return -1;
}
// cerr << "DIRECT PEAKS\n";
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;
}
jack_nframes_t tnp;
if (scale < 1.0) {
// cerr << "DOWNSAMPLE\n";
/* 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, 4096.0);
staging = new PeakData[chunksize];
/* compute the rounded up frame position */
jack_nframes_t current_frame = start;
jack_nframes_t current_stored_peak = (jack_nframes_t) ceil (current_frame / (double) frames_per_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 = (jack_nframes_t) next_visual_peak_frame / frames_per_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_RDWR|O_CREAT, 0664)) < 0) {
error << string_compose(_("AudioSource: cannot open peakpath \"%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 ((_length/frames_per_peak - current_stored_peak), (jack_nframes_t) expected_peaks);
to_read = min (chunksize, tnp);
off_t fend = lseek (peakfile, 0, SEEK_END);
if ((nread = ::pread (peakfile, staging, sizeof (PeakData) * to_read, start_byte))
!= sizeof (PeakData) * to_read) {
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)/frames_per_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 / frames_per_peak;
}
if (zero_fill) {
memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
}
ret = 0;
} else {
// cerr << "UPSAMPLE\n";
/* 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.
*/
jack_nframes_t frames_read = 0;
jack_nframes_t current_frame = start;
jack_nframes_t i = 0;
jack_nframes_t nvisual_peaks = 0;
jack_nframes_t chunksize = (jack_nframes_t) min (cnt, (jack_nframes_t) 4096);
raw_staging = new Sample[chunksize];
workbuf = new char[chunksize *4];
jack_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, (_length - current_frame));
if ((frames_read = read_unlocked (raw_staging, current_frame, to_read, workbuf)) < 0) {
error << string_compose(_("AudioSource[%1]: peak read - cannot read %2 samples at offset %3")
, _name, to_read, current_frame)
<< 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);
}
if (staging) {
delete [] staging;
}
if (raw_staging) {
delete [] raw_staging;
}
if (workbuf) {
delete [] workbuf;
}
return ret;
}
#undef DEBUG_PEAK_BUILD
int
AudioSource::build_peaks ()
{
vector<PeakBuildRecord*> built;
int status = -1;
bool pr_signal = false;
list<PeakBuildRecord*> copy;
{
Glib::Mutex::Lock lm (_lock);
copy = pending_peak_builds;
pending_peak_builds.clear ();
}
#ifdef DEBUG_PEAK_BUILD
cerr << "build peaks with " << copy.size() << " requests pending\n";
#endif
for (list<PeakBuildRecord *>::iterator i = copy.begin(); i != copy.end(); ++i) {
if ((status = do_build_peak ((*i)->frame, (*i)->cnt)) != 0) {
unlink (peakpath.c_str());
break;
}
built.push_back (new PeakBuildRecord (*(*i)));
delete *i;
}
{
Glib::Mutex::Lock lm (_lock);
if (status == 0) {
_peaks_built = true;
if (next_peak_clear_should_notify) {
next_peak_clear_should_notify = false;
pr_signal = true;
}
}
}
if (status == 0) {
for (vector<PeakBuildRecord *>::iterator i = built.begin(); i != built.end(); ++i) {
PeakRangeReady ((*i)->frame, (*i)->cnt); /* EMIT SIGNAL */
delete *i;
}
if (pr_signal) {
PeaksReady (); /* EMIT SIGNAL */
}
}
return status;
}
int
AudioSource::do_build_peak (jack_nframes_t first_frame, jack_nframes_t cnt)
{
jack_nframes_t current_frame;
Sample buf[frames_per_peak];
Sample xmin, xmax;
uint32_t peaki;
PeakData* peakbuf;
char * workbuf = 0;
jack_nframes_t frames_read;
jack_nframes_t frames_to_read;
off_t first_peak_byte;
int peakfile = -1;
int ret = -1;
#ifdef DEBUG_PEAK_BUILD
cerr << pthread_self() << ": " << _name << ": building peaks for " << first_frame << " to " << first_frame + cnt - 1 << endl;
#endif
first_peak_byte = (first_frame / frames_per_peak) * sizeof (PeakData);
#ifdef DEBUG_PEAK_BUILD
cerr << "seeking to " << first_peak_byte << " before writing new peak data\n";
#endif
current_frame = first_frame;
peakbuf = new PeakData[(cnt/frames_per_peak)+1];
peaki = 0;
workbuf = new char[max(frames_per_peak, cnt) * 4];
if ((peakfile = ::open (peakpath.c_str(), O_RDWR|O_CREAT, 0664)) < 0) {
error << string_compose(_("AudioSource: cannot open peakpath \"%1\" (%2)"), peakpath, strerror (errno)) << endmsg;
return -1;
}
while (cnt) {
frames_to_read = min (frames_per_peak, cnt);
if ((frames_read = read_unlocked (buf, current_frame, frames_to_read, workbuf)) != frames_to_read) {
error << string_compose(_("%1: could not write read raw data for peak computation (%2)"), _name, strerror (errno)) << endmsg;
goto out;
}
xmin = buf[0];
xmax = buf[0];
for (jack_nframes_t n = 1; n < frames_read; ++n) {
xmax = max (xmax, buf[n]);
xmin = min (xmin, buf[n]);
// if (current_frame < frames_read) {
// cerr << "sample = " << buf[n] << " max = " << xmax << " min = " << xmin << " max of 2 = " << max (xmax, buf[n]) << endl;
// }
}
peakbuf[peaki].max = xmax;
peakbuf[peaki].min = xmin;
peaki++;
current_frame += frames_read;
cnt -= frames_read;
}
if (::pwrite (peakfile, peakbuf, sizeof (PeakData) * peaki, first_peak_byte) != (ssize_t) (sizeof (PeakData) * peaki)) {
error << string_compose(_("%1: could not write peak file data (%2)"), _name, strerror (errno)) << endmsg;
goto out;
}
ret = 0;
out:
delete [] peakbuf;
if (peakfile >= 0) {
close (peakfile);
}
if (workbuf)
delete [] workbuf;
return ret;
}
void
AudioSource::build_peaks_from_scratch ()
{
Glib::Mutex::Lock lp (_lock);
next_peak_clear_should_notify = true;
pending_peak_builds.push_back (new PeakBuildRecord (0, _length));
queue_for_peaks (*this);
}
bool
AudioSource::file_changed (string 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;
}
}
jack_nframes_t
AudioSource::available_peaks (double zoom_factor) const
{
int peakfile;
off_t end;
if (zoom_factor < frames_per_peak) {
return length(); // peak data will come from the audio file
}
/* peak data comes from peakfile */
if ((peakfile = ::open (peakpath.c_str(), O_RDONLY)) < 0) {
error << string_compose(_("AudioSource: cannot open peakpath \"%1\" (%2)"), peakpath, strerror (errno)) << endmsg;
return 0;
}
{
Glib::Mutex::Lock lm (_lock);
end = lseek (peakfile, 0, SEEK_END);
}
close (peakfile);
return (end/sizeof(PeakData)) * frames_per_peak;
}
void
AudioSource::update_length (jack_nframes_t pos, jack_nframes_t cnt)
{
if (pos + cnt > _length) {
_length = pos+cnt;
}
}

View File

@@ -1,246 +0,0 @@
/*
Copyright (C) 2006 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 <sys/time.h>
#include <sndfile.h>
#include <pbd/mountpoint.h>
#include <ardour/externalsource.h>
#include <ardour/sndfilesource.h>
#include <ardour/sndfile_helpers.h>
// if these headers come before sigc++ is included
// the parser throws ObjC++ errors. (nil is a keyword)
#ifdef HAVE_COREAUDIO
#include <ardour/coreaudio_source.h>
#include <AudioToolbox/ExtendedAudioFile.h>
#include <AudioToolbox/AudioFormat.h>
#endif // HAVE_COREAUDIO
#include "i18n.h"
using namespace ARDOUR;
string ExternalSource::peak_dir = "";
ExternalSource::ExternalSource (const XMLNode& node)
: Source (node)
{
}
ExternalSource::ExternalSource (const string& idstr, bool build_peak)
: Source(build_peak)
{
}
ExternalSource::~ExternalSource ()
{
}
jack_nframes_t
ExternalSource::read_unlocked (Sample *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const
{
return read (dst, start, cnt, workbuf);
}
string
ExternalSource::peak_path (string audio_path)
{
/* XXX hardly bombproof! fix me */
struct stat stat_file;
struct stat stat_mount;
string mp = mountpoint (audio_path);
stat (audio_path.c_str(), &stat_file);
stat (mp.c_str(), &stat_mount);
char buf[32];
#ifdef __APPLE__
snprintf (buf, sizeof (buf), "%u-%u-%d.peak", stat_mount.st_ino, stat_file.st_ino, channel);
#else
snprintf (buf, sizeof (buf), "%ld-%ld-%d.peak", stat_mount.st_ino, stat_file.st_ino, channel);
#endif
string res = peak_dir;
res += buf;
return res;
}
#ifdef HAVE_COREAUDIO
ExternalSource*
ExternalSource::create (const XMLNode& node)
{
ExternalSource* es = 0;
try {
es = new CoreAudioSource (node);
}
catch (failed_constructor& err) {
es = new SndFileSource (node);
}
es = new SndFileSource (node);
return es;
}
#else
ExternalSource*
ExternalSource::create (const XMLNode& node)
{
return new SndFileSource (node);
}
#endif // HAVE_COREAUDIO
#ifdef HAVE_COREAUDIO
ExternalSource*
ExternalSource::create (const string& idstr, bool build_peak)
{
ExternalSource* es = 0;
try {
es = new CoreAudioSource (idstr, build_peak);
}
catch (failed_constructor& err) {
es = new SndFileSource (idstr, build_peak);
}
return es;
}
#else
ExternalSource*
ExternalSource::create (const string& idstr, bool build_peak)
{
return new SndFileSource (idstr, build_peak);
}
#endif // HAVE_COREAUDIO
#ifdef HAVE_COREAUDIO
std::string
CFStringRefToStdString(CFStringRef stringRef)
{
CFIndex size =
CFStringGetMaximumSizeForEncoding(CFStringGetLength(stringRef) ,
kCFStringEncodingASCII);
char *buf = new char[size];
std::string result;
if(CFStringGetCString(stringRef, buf, size, kCFStringEncodingASCII)) {
result = buf;
}
delete [] buf;
return result;
}
#endif // HAVE_COREAUDIO
bool
ExternalSource::get_soundfile_info (string path, SoundFileInfo& _info, string& error_msg)
{
#ifdef HAVE_COREAUDIO
OSStatus err = noErr;
FSRef ref;
ExtAudioFileRef af = 0;
size_t size;
CFStringRef name;
err = FSPathMakeRef ((UInt8*)path.c_str(), &ref, 0);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
err = ExtAudioFileOpen(&ref, &af);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
AudioStreamBasicDescription absd;
memset(&absd, 0, sizeof(absd));
size = sizeof(AudioStreamBasicDescription);
err = ExtAudioFileGetProperty(af,
kExtAudioFileProperty_FileDataFormat, &size, &absd);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
_info.samplerate = absd.mSampleRate;
_info.channels = absd.mChannelsPerFrame;
size = sizeof(_info.length);
err = ExtAudioFileGetProperty(af, kExtAudioFileProperty_FileLengthFrames, &size, &_info.length);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
size = sizeof(CFStringRef);
err = AudioFormatGetProperty(
kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name);
if (err != noErr) {
ExtAudioFileDispose (af);
goto libsndfile;
}
_info.format_name = CFStringRefToStdString(name);
ExtAudioFileDispose (af);
return true;
libsndfile:
#endif // HAVE_COREAUDIO
SNDFILE *sf;
SF_INFO sf_info;
sf_info.format = 0; // libsndfile says to clear this before sf_open().
if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) {
char errbuf[256];
error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
return false;
}
sf_close (sf);
_info.samplerate = sf_info.samplerate;
_info.channels = sf_info.channels;
_info.length = sf_info.frames;
_info.format_name = string_compose("Format: %1, %2",
sndfile_major_format(sf_info.format),
sndfile_minor_format(sf_info.format));
return true;
}