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:
157
libs/ardour/ardour/audiofilesource.h
Normal file
157
libs/ardour/ardour/audiofilesource.h
Normal 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__ */
|
||||
|
||||
166
libs/ardour/ardour/audiosource.h
Normal file
166
libs/ardour/ardour/audiosource.h
Normal 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__ */
|
||||
@@ -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__ */
|
||||
|
||||
661
libs/ardour/audiofilesource.cc
Normal file
661
libs/ardour/audiofilesource.cc
Normal 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
897
libs/ardour/audiosource.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user