You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1252 lines
32 KiB
1252 lines
32 KiB
/*
|
|
Rosegarden
|
|
A sequencer and musical notation editor.
|
|
|
|
This program is Copyright 2000-2008
|
|
Guillaume Laurent <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <bownie@bownie.com>
|
|
|
|
The moral right of the authors to claim authorship of this work
|
|
has been asserted.
|
|
|
|
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. See the file
|
|
COPYING included with this distribution for more information.
|
|
*/
|
|
|
|
|
|
#include <iostream>
|
|
#include <tdeapplication.h>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <dirent.h> // for new recording file
|
|
#include <cstdio> // sprintf
|
|
#include <cstdlib>
|
|
#include <pthread.h>
|
|
#include <sstream>
|
|
|
|
|
|
#include <kapp.h>
|
|
#include <tdelocale.h>
|
|
#include <kprocess.h>
|
|
#include <tdeio/netaccess.h>
|
|
#include <tdemessagebox.h>
|
|
|
|
#include <tqpixmap.h>
|
|
#include <tqpainter.h>
|
|
#include <tqdatetime.h>
|
|
#include <tqfile.h>
|
|
|
|
#include "AudioFile.h"
|
|
#include "AudioFileManager.h"
|
|
#include "WAVAudioFile.h"
|
|
#include "BWFAudioFile.h"
|
|
#include "MP3AudioFile.h"
|
|
#include "misc/Strings.h"
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
static pthread_mutex_t _audioFileManagerLock;
|
|
|
|
class MutexLock
|
|
{
|
|
public:
|
|
MutexLock(pthread_mutex_t *mutex) : m_mutex(mutex)
|
|
{
|
|
pthread_mutex_lock(m_mutex);
|
|
}
|
|
~MutexLock()
|
|
{
|
|
pthread_mutex_unlock(m_mutex);
|
|
}
|
|
private:
|
|
pthread_mutex_t *m_mutex;
|
|
};
|
|
|
|
AudioFileManager::AudioFileManager() :
|
|
m_importProcess(0),
|
|
m_expectedSampleRate(0)
|
|
{
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init(&attr);
|
|
#ifdef HAVE_PTHREAD_MUTEX_RECURSIVE
|
|
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
#else
|
|
#ifdef PTHREAD_MUTEX_RECURSIVE
|
|
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
#else
|
|
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
|
|
#endif
|
|
#endif
|
|
|
|
pthread_mutex_init(&_audioFileManagerLock, &attr);
|
|
|
|
// Set this through the set method so that the tilde gets
|
|
// shaken out.
|
|
//
|
|
setAudioPath("~/rosegarden");
|
|
|
|
// Retransmit progress
|
|
//
|
|
connect(&m_peakManager, TQ_SIGNAL(setProgress(int)),
|
|
this, TQ_SIGNAL(setProgress(int)));
|
|
}
|
|
|
|
AudioFileManager::~AudioFileManager()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
// Add a file from an absolute path
|
|
//
|
|
AudioFileId
|
|
AudioFileManager::addFile(const std::string &filePath)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
TQString ext;
|
|
|
|
if (filePath.length() > 3) {
|
|
ext = TQString(filePath.substr(filePath.length() - 3, 3).c_str()).lower();
|
|
}
|
|
|
|
// Check for file existing already in manager by path
|
|
//
|
|
int check = fileExists(filePath);
|
|
if (check != -1) {
|
|
return AudioFileId(check);
|
|
}
|
|
|
|
// prepare for audio file
|
|
AudioFile *aF = 0;
|
|
AudioFileId id = getFirstUnusedID();
|
|
|
|
if (ext == "wav") {
|
|
// identify file type
|
|
AudioFileType subType = RIFFAudioFile::identifySubType(filePath);
|
|
|
|
if (subType == BWF) {
|
|
#ifdef DEBUG_AUDIOFILEMANAGER
|
|
std::cout << "FOUND BWF" << std::endl;
|
|
#endif
|
|
|
|
try {
|
|
aF = new BWFAudioFile(id, getShortFilename(filePath), filePath);
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
} else if (subType == WAV) {
|
|
try {
|
|
aF = new WAVAudioFile(id, getShortFilename(filePath), filePath);
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
}
|
|
|
|
// Ensure we have a valid file handle
|
|
//
|
|
if (aF == 0) {
|
|
std::cerr << "AudioFileManager: Unknown WAV audio file subtype in " << filePath << std::endl;
|
|
throw BadAudioPathException(filePath, __FILE__, __LINE__);
|
|
}
|
|
|
|
// Add file type on extension
|
|
try {
|
|
if (aF->open() == false) {
|
|
delete aF;
|
|
std::cerr << "AudioFileManager: Malformed audio file in " << filePath << std::endl;
|
|
throw BadAudioPathException(filePath, __FILE__, __LINE__);
|
|
}
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
}
|
|
#ifdef HAVE_LIBMAD
|
|
else if (ext == "mp3") {
|
|
try {
|
|
aF = new MP3AudioFile(id, getShortFilename(filePath), filePath);
|
|
|
|
if (aF->open() == false) {
|
|
delete aF;
|
|
std::cerr << "AudioFileManager: Malformed mp3 audio file in " << filePath << std::endl;
|
|
throw BadAudioPathException(filePath, __FILE__, __LINE__);
|
|
}
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
}
|
|
#endif // HAVE_LIBMAD
|
|
else {
|
|
std::cerr << "AudioFileManager: Unsupported audio file extension in " << filePath << std::endl;
|
|
throw BadAudioPathException(filePath, __FILE__, __LINE__);
|
|
}
|
|
|
|
if (aF) {
|
|
m_audioFiles.push_back(aF);
|
|
return id;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Convert long filename to shorter version
|
|
std::string
|
|
AudioFileManager::getShortFilename(const std::string &fileName)
|
|
{
|
|
std::string rS = fileName;
|
|
unsigned int pos = rS.find_last_of("/");
|
|
|
|
if (pos > 0 && ( pos + 1 ) < rS.length())
|
|
rS = rS.substr(pos + 1, rS.length());
|
|
|
|
return rS;
|
|
}
|
|
|
|
// Turn a long path into a directory ending with a slash
|
|
//
|
|
std::string
|
|
AudioFileManager::getDirectory(const std::string &path)
|
|
{
|
|
std::string rS = path;
|
|
unsigned int pos = rS.find_last_of("/");
|
|
|
|
if (pos > 0 && ( pos + 1 ) < rS.length())
|
|
rS = rS.substr(0, pos + 1);
|
|
|
|
return rS;
|
|
}
|
|
|
|
|
|
// Create a new AudioFile with unique ID and label - insert from
|
|
// our RG4 file
|
|
//
|
|
AudioFileId
|
|
AudioFileManager::insertFile(const std::string &name,
|
|
const std::string &fileName)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
// first try to expand any beginning tilde
|
|
//
|
|
std::string foundFileName = substituteTildeForHome(fileName);
|
|
|
|
// If we've expanded and we can't find the file
|
|
// then try to find it in audio file directory.
|
|
//
|
|
TQFileInfo info(foundFileName.c_str());
|
|
if (!info.exists())
|
|
foundFileName = getFileInPath(foundFileName);
|
|
|
|
#ifdef DEBUG_AUDIOFILEMANAGER_INSERT_FILE
|
|
|
|
std::cout << "AudioFileManager::insertFile - "
|
|
<< "expanded fileName = \""
|
|
<< foundFileName << "\"" << std::endl;
|
|
#endif
|
|
|
|
// bail if we haven't found any reasonable filename
|
|
if (foundFileName == "")
|
|
return false;
|
|
|
|
AudioFileId id = getFirstUnusedID();
|
|
|
|
WAVAudioFile *aF = 0;
|
|
|
|
try {
|
|
|
|
aF = new WAVAudioFile(id, name, foundFileName);
|
|
|
|
// if we don't recognise the file then don't insert it
|
|
//
|
|
if (aF->open() == false) {
|
|
delete aF;
|
|
std::cerr << "AudioFileManager::insertFile - don't recognise file type in " << foundFileName << std::endl;
|
|
throw BadAudioPathException(foundFileName, __FILE__, __LINE__);
|
|
}
|
|
m_audioFiles.push_back(aF);
|
|
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
bool
|
|
AudioFileManager::removeFile(AudioFileId id)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
|
|
for (it = m_audioFiles.begin();
|
|
it != m_audioFiles.end();
|
|
++it) {
|
|
if ((*it)->getId() == id) {
|
|
m_peakManager.removeAudioFile(*it);
|
|
m_recordedAudioFiles.erase(*it);
|
|
m_derivedAudioFiles.erase(*it);
|
|
delete(*it);
|
|
m_audioFiles.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
AudioFileId
|
|
AudioFileManager::getFirstUnusedID()
|
|
{
|
|
AudioFileId rI = 0;
|
|
|
|
if (m_audioFiles.size() == 0)
|
|
return rI;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
|
|
for (it = m_audioFiles.begin();
|
|
it != m_audioFiles.end();
|
|
++it) {
|
|
if (rI < (*it)->getId())
|
|
rI = (*it)->getId();
|
|
}
|
|
|
|
rI++;
|
|
|
|
return rI;
|
|
}
|
|
|
|
bool
|
|
AudioFileManager::insertFile(const std::string &name,
|
|
const std::string &fileName,
|
|
AudioFileId id)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
// first try to expany any beginning tilde
|
|
std::string foundFileName = substituteTildeForHome(fileName);
|
|
|
|
// If we've expanded and we can't find the file
|
|
// then try to find it in audio file directory.
|
|
//
|
|
TQFileInfo info(foundFileName.c_str());
|
|
if (!info.exists())
|
|
foundFileName = getFileInPath(foundFileName);
|
|
|
|
#ifdef DEBUG_AUDIOFILEMANAGER_INSERT_FILE
|
|
|
|
std::cout << "AudioFileManager::insertFile - "
|
|
<< "expanded fileName = \""
|
|
<< foundFileName << "\"" << std::endl;
|
|
#endif
|
|
|
|
// If no joy here then we can't find this file
|
|
if (foundFileName == "")
|
|
return false;
|
|
|
|
// make sure we don't have a file of this ID hanging around already
|
|
removeFile(id);
|
|
|
|
// and insert
|
|
WAVAudioFile *aF = 0;
|
|
|
|
try {
|
|
|
|
aF = new WAVAudioFile(id, name, foundFileName);
|
|
|
|
// Test the file
|
|
if (aF->open() == false) {
|
|
delete aF;
|
|
return false;
|
|
}
|
|
|
|
m_audioFiles.push_back(aF);
|
|
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Add a given path to our sample search path
|
|
//
|
|
void
|
|
AudioFileManager::setAudioPath(const std::string &path)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::string hPath = path;
|
|
|
|
// add a trailing / if we don't have one
|
|
//
|
|
if (hPath[hPath.size() - 1] != '/')
|
|
hPath += std::string("/");
|
|
|
|
// get the home directory
|
|
if (hPath[0] == '~') {
|
|
hPath.erase(0, 1);
|
|
hPath = std::string(getenv("HOME")) + hPath;
|
|
}
|
|
|
|
m_audioPath = hPath;
|
|
|
|
}
|
|
|
|
void
|
|
AudioFileManager::testAudioPath()
|
|
{
|
|
TQFileInfo info(m_audioPath.c_str());
|
|
if (!(info.exists() && info.isDir() && !info.isRelative() &&
|
|
info.isWritable() && info.isReadable()))
|
|
throw BadAudioPathException(m_audioPath.data());
|
|
}
|
|
|
|
|
|
// See if we can find a given file in our search path
|
|
// return the first occurence of a match or the empty
|
|
// std::string if no match.
|
|
//
|
|
std::string
|
|
AudioFileManager::getFileInPath(const std::string &file)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
TQFileInfo info(file.c_str());
|
|
|
|
if (info.exists())
|
|
return file;
|
|
|
|
// Build the search filename from the audio path and
|
|
// the file basename.
|
|
//
|
|
TQString searchFile = TQString(m_audioPath.c_str()) + info.fileName();
|
|
TQFileInfo searchInfo(searchFile);
|
|
|
|
if (searchInfo.exists())
|
|
return searchFile.latin1();
|
|
|
|
std::cout << "AudioFileManager::getFileInPath - "
|
|
<< "searchInfo = " << searchFile.ascii() << std::endl;
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
// Check for file path existence
|
|
//
|
|
int
|
|
AudioFileManager::fileExists(const std::string &path)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
|
|
for (it = m_audioFiles.begin();
|
|
it != m_audioFiles.end();
|
|
++it) {
|
|
if ((*it)->getFilename() == path)
|
|
return (*it)->getId();
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
// Does a specific file id exist on the manager?
|
|
//
|
|
bool
|
|
AudioFileManager::fileExists(AudioFileId id)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
|
|
for (it = m_audioFiles.begin();
|
|
it != m_audioFiles.end();
|
|
++it) {
|
|
if ((*it)->getId() == id)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
void
|
|
AudioFileManager::clear()
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
|
|
for (it = m_audioFiles.begin();
|
|
it != m_audioFiles.end();
|
|
++it) {
|
|
m_recordedAudioFiles.erase(*it);
|
|
m_derivedAudioFiles.erase(*it);
|
|
delete(*it);
|
|
}
|
|
|
|
m_audioFiles.erase(m_audioFiles.begin(), m_audioFiles.end());
|
|
|
|
// Clear the PeakFileManager too
|
|
//
|
|
m_peakManager.clear();
|
|
}
|
|
|
|
AudioFile *
|
|
AudioFileManager::createRecordingAudioFile()
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
AudioFileId newId = getFirstUnusedID();
|
|
TQString fileName = "";
|
|
|
|
while (fileName == "") {
|
|
|
|
fileName = TQString("rg-%1-%2.wav")
|
|
.arg(TQDateTime::currentDateTime().toString("yyyyMMdd-hhmmss"))
|
|
.arg(newId + 1);
|
|
|
|
if (TQFile(m_audioPath.c_str() + fileName).exists()) {
|
|
fileName = "";
|
|
++newId;
|
|
}
|
|
}
|
|
|
|
// insert file into vector
|
|
WAVAudioFile *aF = 0;
|
|
|
|
try {
|
|
aF = new WAVAudioFile(newId, fileName.ascii(), (m_audioPath.c_str() + fileName).ascii());
|
|
m_audioFiles.push_back(aF);
|
|
m_recordedAudioFiles.insert(aF);
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
|
|
return aF;
|
|
}
|
|
|
|
std::vector<std::string>
|
|
AudioFileManager::createRecordingAudioFiles(unsigned int n)
|
|
{
|
|
std::vector<std::string> v;
|
|
for (unsigned int i = 0; i < n; ++i) {
|
|
AudioFile *af = createRecordingAudioFile();
|
|
if (af)
|
|
v.push_back(m_audioPath + af->getFilename().data());
|
|
// !af should not happen, and we have no good recovery if it does
|
|
}
|
|
return v;
|
|
}
|
|
|
|
bool
|
|
AudioFileManager::wasAudioFileRecentlyRecorded(AudioFileId id)
|
|
{
|
|
AudioFile *file = getAudioFile(id);
|
|
if (file)
|
|
return (m_recordedAudioFiles.find(file) !=
|
|
m_recordedAudioFiles.end());
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
AudioFileManager::wasAudioFileRecentlyDerived(AudioFileId id)
|
|
{
|
|
AudioFile *file = getAudioFile(id);
|
|
if (file)
|
|
return (m_derivedAudioFiles.find(file) !=
|
|
m_derivedAudioFiles.end());
|
|
return false;
|
|
}
|
|
|
|
void
|
|
AudioFileManager::resetRecentlyCreatedFiles()
|
|
{
|
|
m_recordedAudioFiles.clear();
|
|
m_derivedAudioFiles.clear();
|
|
}
|
|
|
|
AudioFile *
|
|
AudioFileManager::createDerivedAudioFile(AudioFileId source,
|
|
const char *prefix)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock);
|
|
|
|
AudioFile *sourceFile = getAudioFile(source);
|
|
if (!sourceFile) return 0;
|
|
|
|
AudioFileId newId = getFirstUnusedID();
|
|
TQString fileName = "";
|
|
|
|
std::string sourceBase = sourceFile->getShortFilename();
|
|
if (sourceBase.length() > 4 && sourceBase.substr(0, 3) == "rg-") {
|
|
sourceBase = sourceBase.substr(3);
|
|
}
|
|
if (sourceBase.length() > 15) sourceBase = sourceBase.substr(0, 15);
|
|
|
|
while (fileName == "") {
|
|
|
|
fileName = TQString("%1-%2-%3-%4.wav")
|
|
.arg(prefix)
|
|
.arg(sourceBase.c_str())
|
|
.arg(TQDateTime::currentDateTime().toString("yyyyMMdd-hhmmss"))
|
|
.arg(newId + 1);
|
|
|
|
if (TQFile(m_audioPath.c_str() + fileName).exists()) {
|
|
fileName = "";
|
|
++newId;
|
|
}
|
|
}
|
|
|
|
// insert file into vector
|
|
WAVAudioFile *aF = 0;
|
|
|
|
try {
|
|
aF = new WAVAudioFile(newId,
|
|
fileName.ascii(),
|
|
(m_audioPath.c_str() + fileName).ascii());
|
|
m_audioFiles.push_back(aF);
|
|
m_derivedAudioFiles.insert(aF);
|
|
} catch (SoundFile::BadSoundFileException e) {
|
|
delete aF;
|
|
throw BadAudioPathException(e);
|
|
}
|
|
|
|
return aF;
|
|
}
|
|
|
|
AudioFileId
|
|
AudioFileManager::importURL(const KURL &url, int sampleRate)
|
|
{
|
|
if (url.isLocalFile()) return importFile(url.path().ascii(), sampleRate);
|
|
|
|
std::cerr << "AudioFileManager::importURL("<< url.prettyURL().ascii() << ", " << sampleRate << ")" << std::endl;
|
|
|
|
emit setOperationName(i18n("Downloading file %1").arg(url.prettyURL()));
|
|
|
|
TQString localPath = "";
|
|
if (!TDEIO::NetAccess::download(url, localPath)) {
|
|
KMessageBox::error(0, i18n("Cannot download file %1").arg(url.prettyURL()));
|
|
throw SoundFile::BadSoundFileException(url.prettyURL().ascii());
|
|
}
|
|
|
|
AudioFileId id = 0;
|
|
|
|
try {
|
|
id = importFile(localPath.ascii(), sampleRate);
|
|
} catch (BadAudioPathException ape) {
|
|
TDEIO::NetAccess::removeTempFile(localPath);
|
|
throw ape;
|
|
} catch (SoundFile::BadSoundFileException bse) {
|
|
TDEIO::NetAccess::removeTempFile(localPath);
|
|
throw bse;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
bool
|
|
AudioFileManager::fileNeedsConversion(const std::string &fileName,
|
|
int sampleRate)
|
|
{
|
|
TDEProcess *proc = new TDEProcess();
|
|
*proc << "rosegarden-audiofile-importer";
|
|
if (sampleRate > 0) {
|
|
*proc << "-r";
|
|
*proc << TQString("%1").arg(sampleRate);
|
|
}
|
|
*proc << "-w";
|
|
*proc << fileName.c_str();
|
|
|
|
proc->start(TDEProcess::Block, TDEProcess::NoCommunication);
|
|
|
|
int es = proc->exitStatus();
|
|
delete proc;
|
|
|
|
if (es == 0 || es == 1) { // 1 == "other error" -- wouldn't be able to convert
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
AudioFileId
|
|
AudioFileManager::importFile(const std::string &fileName, int sampleRate)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock);
|
|
|
|
std::cerr << "AudioFileManager::importFile("<< fileName << ", " << sampleRate << ")" << std::endl;
|
|
|
|
TDEProcess *proc = new TDEProcess();
|
|
*proc << "rosegarden-audiofile-importer";
|
|
if (sampleRate > 0) {
|
|
*proc << "-r";
|
|
*proc << TQString("%1").arg(sampleRate);
|
|
}
|
|
*proc << "-w";
|
|
*proc << fileName.c_str();
|
|
|
|
proc->start(TDEProcess::Block, TDEProcess::NoCommunication);
|
|
|
|
int es = proc->exitStatus();
|
|
delete proc;
|
|
|
|
if (es == 0) {
|
|
AudioFileId id = addFile(fileName);
|
|
m_expectedSampleRate = sampleRate;
|
|
return id;
|
|
}
|
|
|
|
if (es == 2) {
|
|
emit setOperationName(i18n("Converting audio file..."));
|
|
} else if (es == 3) {
|
|
emit setOperationName(i18n("Resampling audio file..."));
|
|
} else if (es == 4) {
|
|
emit setOperationName(i18n("Converting and resampling audio file..."));
|
|
} else {
|
|
emit setOperationName(i18n("Importing audio file..."));
|
|
}
|
|
|
|
AudioFileId newId = getFirstUnusedID();
|
|
TQString targetName = "";
|
|
|
|
TQString sourceBase = TQFileInfo(fileName.c_str()).baseName();
|
|
if (sourceBase.length() > 3 && sourceBase.startsWith("rg-")) {
|
|
sourceBase = sourceBase.right(sourceBase.length() - 3);
|
|
}
|
|
if (sourceBase.length() > 15) sourceBase = sourceBase.left(15);
|
|
|
|
while (targetName == "") {
|
|
|
|
targetName = TQString("conv-%2-%3-%4.wav")
|
|
.arg(sourceBase)
|
|
.arg(TQDateTime::currentDateTime().toString("yyyyMMdd-hhmmss"))
|
|
.arg(newId + 1);
|
|
|
|
if (TQFile(m_audioPath.c_str() + targetName).exists()) {
|
|
targetName = "";
|
|
++newId;
|
|
}
|
|
}
|
|
|
|
m_importProcess = new TDEProcess;
|
|
|
|
*m_importProcess << "rosegarden-audiofile-importer";
|
|
if (sampleRate > 0) {
|
|
*m_importProcess << "-r";
|
|
*m_importProcess << TQString("%1").arg(sampleRate);
|
|
}
|
|
*m_importProcess << "-c";
|
|
*m_importProcess << fileName.c_str();
|
|
*m_importProcess << (m_audioPath.c_str() + targetName);
|
|
|
|
m_importProcess->start(TDEProcess::NotifyOnExit, TDEProcess::NoCommunication);
|
|
|
|
while (m_importProcess->isRunning()) {
|
|
kapp->processEvents(100);
|
|
}
|
|
|
|
if (!m_importProcess->normalExit()) {
|
|
// interrupted
|
|
throw SoundFile::BadSoundFileException(fileName, "Import cancelled");
|
|
}
|
|
|
|
es = m_importProcess->exitStatus();
|
|
delete m_importProcess;
|
|
m_importProcess = 0;
|
|
|
|
if (es) {
|
|
std::cerr << "audio file importer failed" << std::endl;
|
|
throw SoundFile::BadSoundFileException(fileName, i18n("Failed to convert or resample audio file on import").ascii());
|
|
} else {
|
|
std::cerr << "audio file importer succeeded" << std::endl;
|
|
}
|
|
|
|
// insert file into vector
|
|
WAVAudioFile *aF = 0;
|
|
|
|
aF = new WAVAudioFile(newId,
|
|
targetName.ascii(),
|
|
(m_audioPath.c_str() + targetName).ascii());
|
|
m_audioFiles.push_back(aF);
|
|
m_derivedAudioFiles.insert(aF);
|
|
// Don't catch SoundFile::BadSoundFileException
|
|
|
|
m_expectedSampleRate = sampleRate;
|
|
|
|
return aF->getId();
|
|
}
|
|
|
|
void
|
|
AudioFileManager::slotStopImport()
|
|
{
|
|
if (m_importProcess) {
|
|
m_importProcess->kill(SIGTERM);
|
|
sleep(1);
|
|
m_importProcess->kill(SIGKILL);
|
|
}
|
|
}
|
|
|
|
AudioFile*
|
|
AudioFileManager::getLastAudioFile()
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::vector<AudioFile*>::iterator it = m_audioFiles.begin();
|
|
AudioFile* audioFile = 0;
|
|
|
|
while (it != m_audioFiles.end()) {
|
|
audioFile = (*it);
|
|
it++;
|
|
}
|
|
|
|
return audioFile;
|
|
}
|
|
|
|
std::string
|
|
AudioFileManager::substituteHomeForTilde(const std::string &path)
|
|
{
|
|
std::string rS = path;
|
|
std::string homePath = std::string(getenv("HOME"));
|
|
|
|
// if path length is less than homePath then just return unchanged
|
|
if (rS.length() < homePath.length())
|
|
return rS;
|
|
|
|
// if the first section matches the path then substitute
|
|
if (rS.substr(0, homePath.length()) == homePath) {
|
|
rS.erase(0, homePath.length());
|
|
rS = "~" + rS;
|
|
}
|
|
|
|
return rS;
|
|
}
|
|
|
|
std::string
|
|
AudioFileManager::substituteTildeForHome(const std::string &path)
|
|
{
|
|
std::string rS = path;
|
|
std::string homePath = std::string(getenv("HOME"));
|
|
|
|
if (rS.substr(0, 2) == std::string("~/")) {
|
|
rS.erase(0, 1); // erase tilde and prepend HOME env
|
|
rS = homePath + rS;
|
|
}
|
|
|
|
return rS;
|
|
}
|
|
|
|
|
|
|
|
// Export audio files and assorted bits and bobs - make sure
|
|
// that we store the files in a format that's user independent
|
|
// so that people can pack up and swap their songs (including
|
|
// audio files) and shift them about easily.
|
|
//
|
|
std::string
|
|
AudioFileManager::toXmlString()
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::stringstream audioFiles;
|
|
std::string audioPath = substituteHomeForTilde(m_audioPath);
|
|
|
|
audioFiles << "<audiofiles";
|
|
if (m_expectedSampleRate != 0) {
|
|
audioFiles << " expectedRate=\"" << m_expectedSampleRate << "\"";
|
|
}
|
|
audioFiles << ">" << std::endl;
|
|
audioFiles << " <audioPath value=\""
|
|
<< audioPath << "\"/>" << std::endl;
|
|
|
|
std::string fileName;
|
|
std::vector<AudioFile*>::iterator it;
|
|
|
|
for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
|
|
fileName = (*it)->getFilename();
|
|
|
|
// attempt two substitutions - If the prefix to the filename
|
|
// is the same as the audio path then we can dock the prefix
|
|
// as it'll be added again next time. If the path doesn't
|
|
// have the audio path in it but has our home directory in it
|
|
// then swap this out for a tilde '~'
|
|
//
|
|
#ifdef DEBUG_AUDIOFILEMANAGER
|
|
|
|
std::cout << "DIR = " << getDirectory(fileName) << " : "
|
|
" PATH = " << m_audioPath << std::endl;
|
|
#endif
|
|
|
|
if (getDirectory(fileName) == m_audioPath)
|
|
fileName = getShortFilename(fileName);
|
|
else
|
|
fileName = substituteHomeForTilde(fileName);
|
|
|
|
audioFiles << " <audio id=\""
|
|
<< (*it)->getId()
|
|
<< "\" file=\""
|
|
<< fileName
|
|
<< "\" label=\""
|
|
<< encode((*it)->getName())
|
|
<< "\"/>" << std::endl;
|
|
}
|
|
|
|
audioFiles << "</audiofiles>" << std::endl;
|
|
|
|
#if (__GNUC__ < 3)
|
|
|
|
audioFiles << std::ends;
|
|
#else
|
|
|
|
audioFiles << std::endl;
|
|
#endif
|
|
|
|
return audioFiles.str();
|
|
}
|
|
|
|
// Generate preview peak files or peak chunks according
|
|
// to file type.
|
|
//
|
|
void
|
|
AudioFileManager::generatePreviews()
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
#ifdef DEBUG_AUDIOFILEMANAGER
|
|
|
|
std::cout << "AudioFileManager::generatePreviews - "
|
|
<< "for " << m_audioFiles.size() << " files"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
|
|
// Generate peaks if we need to
|
|
//
|
|
std::vector<AudioFile*>::iterator it;
|
|
for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
|
|
if (!m_peakManager.hasValidPeaks(*it))
|
|
m_peakManager.generatePeaks(*it, 1);
|
|
}
|
|
}
|
|
|
|
// Attempt to stop a preview
|
|
//
|
|
void
|
|
AudioFileManager::slotStopPreview()
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock);
|
|
m_peakManager.stopPreview();
|
|
}
|
|
|
|
|
|
// Generate a preview for a specific audio file - say if
|
|
// one has just been added to the AudioFileManager.
|
|
// Also used for generating previews if the file has been
|
|
// modified.
|
|
//
|
|
bool
|
|
AudioFileManager::generatePreview(AudioFileId id)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
AudioFile *audioFile = getAudioFile(id);
|
|
|
|
if (audioFile == 0)
|
|
return false;
|
|
|
|
if (!m_peakManager.hasValidPeaks(audioFile))
|
|
m_peakManager.generatePeaks(audioFile, 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
AudioFile*
|
|
AudioFileManager::getAudioFile(AudioFileId id)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
|
|
for (it = m_audioFiles.begin();
|
|
it != m_audioFiles.end();
|
|
it++) {
|
|
if ((*it)->getId() == id)
|
|
return (*it);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::vector<float>
|
|
AudioFileManager::getPreview(AudioFileId id,
|
|
const RealTime &startTime,
|
|
const RealTime &endTime,
|
|
int width,
|
|
bool withMinima)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
AudioFile *audioFile = getAudioFile(id);
|
|
|
|
if (audioFile == 0) {
|
|
return std::vector<float>();
|
|
}
|
|
|
|
if (!m_peakManager.hasValidPeaks(audioFile)) {
|
|
std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
|
|
throw PeakFileManager::BadPeakFileException
|
|
(audioFile->getFilename(), __FILE__, __LINE__);
|
|
}
|
|
|
|
return m_peakManager.getPreview(audioFile,
|
|
startTime,
|
|
endTime,
|
|
width,
|
|
withMinima);
|
|
}
|
|
|
|
void
|
|
AudioFileManager::drawPreview(AudioFileId id,
|
|
const RealTime &startTime,
|
|
const RealTime &endTime,
|
|
TQPixmap *pixmap)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
AudioFile *audioFile = getAudioFile(id);
|
|
if (!audioFile)
|
|
return ;
|
|
|
|
if (!m_peakManager.hasValidPeaks(audioFile)) {
|
|
std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
|
|
throw PeakFileManager::BadPeakFileException
|
|
(audioFile->getFilename(), __FILE__, __LINE__);
|
|
}
|
|
|
|
std::vector<float> values = m_peakManager.getPreview
|
|
(audioFile,
|
|
startTime,
|
|
endTime,
|
|
pixmap->width(),
|
|
false);
|
|
|
|
TQPainter painter(pixmap);
|
|
pixmap->fill(kapp->palette().color(TQPalette::Active,
|
|
TQColorGroup::Base));
|
|
painter.setPen(kapp->palette().color(TQPalette::Active,
|
|
TQColorGroup::Dark));
|
|
|
|
if (values.size() == 0) {
|
|
#ifdef DEBUG_AUDIOFILEMANAGER
|
|
std::cerr << "AudioFileManager::drawPreview - "
|
|
<< "no preview values returned!" << std::endl;
|
|
#endif
|
|
|
|
return ;
|
|
}
|
|
|
|
float yStep = pixmap->height() / 2;
|
|
int channels = audioFile->getChannels();
|
|
float ch1Value, ch2Value;
|
|
|
|
if (channels == 0) {
|
|
#ifdef DEBUG_AUDIOFILEMANAGER
|
|
std::cerr << "AudioFileManager::drawPreview - "
|
|
<< "no channels in audio file!" << std::endl;
|
|
#endif
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
// Render pixmap
|
|
//
|
|
for (int i = 0; i < pixmap->width(); i++) {
|
|
// Always get two values for our pixmap no matter how many
|
|
// channels in AudioFile as that's all we can display.
|
|
//
|
|
if (channels == 1) {
|
|
ch1Value = values[i];
|
|
ch2Value = values[i];
|
|
} else {
|
|
ch1Value = values[i * channels];
|
|
ch2Value = values[i * channels + 1];
|
|
}
|
|
|
|
painter.drawLine(i, static_cast<int>(yStep - ch1Value * yStep),
|
|
i, static_cast<int>(yStep + ch2Value * yStep));
|
|
}
|
|
}
|
|
|
|
void
|
|
AudioFileManager::drawHighlightedPreview(AudioFileId id,
|
|
const RealTime &startTime,
|
|
const RealTime &endTime,
|
|
const RealTime &highlightStart,
|
|
const RealTime &highlightEnd,
|
|
TQPixmap *pixmap)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
AudioFile *audioFile = getAudioFile(id);
|
|
if (!audioFile)
|
|
return ;
|
|
|
|
if (!m_peakManager.hasValidPeaks(audioFile)) {
|
|
std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
|
|
throw PeakFileManager::BadPeakFileException
|
|
(audioFile->getFilename(), __FILE__, __LINE__);
|
|
}
|
|
|
|
std::vector<float> values = m_peakManager.getPreview
|
|
(audioFile,
|
|
startTime,
|
|
endTime,
|
|
pixmap->width(),
|
|
false);
|
|
|
|
int startWidth = (int)(double(pixmap->width()) * (highlightStart /
|
|
(endTime - startTime)));
|
|
int endWidth = (int)(double(pixmap->width()) * (highlightEnd /
|
|
(endTime - startTime)));
|
|
|
|
TQPainter painter(pixmap);
|
|
pixmap->fill(kapp->palette().color(TQPalette::Active,
|
|
TQColorGroup::Base));
|
|
|
|
float yStep = pixmap->height() / 2;
|
|
int channels = audioFile->getChannels();
|
|
float ch1Value, ch2Value;
|
|
|
|
// Render pixmap
|
|
//
|
|
for (int i = 0; i < pixmap->width(); ++i) {
|
|
if ((i * channels + (channels - 1)) >= int(values.size()))
|
|
break;
|
|
|
|
// Always get two values for our pixmap no matter how many
|
|
// channels in AudioFile as that's all we can display.
|
|
//
|
|
if (channels == 1) {
|
|
ch1Value = values[i];
|
|
ch2Value = values[i];
|
|
} else {
|
|
ch1Value = values[i * channels];
|
|
ch2Value = values[i * channels + 1];
|
|
}
|
|
|
|
if (i < startWidth || i > endWidth)
|
|
painter.setPen(kapp->palette().color(TQPalette::Active,
|
|
TQColorGroup::Mid));
|
|
else
|
|
painter.setPen(kapp->palette().color(TQPalette::Active,
|
|
TQColorGroup::Dark));
|
|
|
|
painter.drawLine(i, static_cast<int>(yStep - ch1Value * yStep),
|
|
i, static_cast<int>(yStep + ch2Value * yStep));
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
AudioFileManager::print()
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
#ifdef DEBUG_AUDIOFILEMANAGER
|
|
|
|
std::cout << "AudioFileManager - " << m_audioFiles.size() << " entr";
|
|
|
|
if (m_audioFiles.size() == 1)
|
|
std::cout << "y";
|
|
else
|
|
std::cout << "ies";
|
|
|
|
std::cout << std::endl << std::endl;
|
|
|
|
std::vector<AudioFile*>::iterator it;
|
|
for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
|
|
std::cout << (*it)->getId() << " : " << (*it)->getName()
|
|
<< " : \"" << (*it)->getFilename() << "\"" << std::endl;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::vector<SplitPointPair>
|
|
AudioFileManager::getSplitPoints(AudioFileId id,
|
|
const RealTime &startTime,
|
|
const RealTime &endTime,
|
|
int threshold,
|
|
const RealTime &minTime)
|
|
{
|
|
MutexLock lock (&_audioFileManagerLock)
|
|
;
|
|
|
|
AudioFile *audioFile = getAudioFile(id);
|
|
|
|
if (audioFile == 0)
|
|
return std::vector<SplitPointPair>();
|
|
|
|
return m_peakManager.getSplitPoints(audioFile,
|
|
startTime,
|
|
endTime,
|
|
threshold,
|
|
minTime);
|
|
}
|
|
|
|
std::set<int>
|
|
AudioFileManager::getActualSampleRates() const
|
|
{
|
|
std::set<int> rates;
|
|
|
|
for (std::vector<AudioFile *>::const_iterator i = m_audioFiles.begin();
|
|
i != m_audioFiles.end(); ++i) {
|
|
|
|
unsigned int sr = (*i)->getSampleRate();
|
|
if (sr != 0) rates.insert(int(sr));
|
|
}
|
|
|
|
return rates;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#include "AudioFileManager.moc"
|