// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- /* Rosegarden A sequencer and musical notation editor. This program is Copyright 2000-2008 Guillaume Laurent , Chris Cannam , Richard Bown 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 #include #include #include #include // for new recording file #include // sprintf #include #include #if (__GNUC__ < 3) #include #define stringstream strstream #else #include #endif #include #include #include #include #include #include #include #include #include #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, TQT_SIGNAL(setProgress(int)), this, TQT_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::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::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() throw (BadAudioPathException) { 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::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::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::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") .tqarg(TQDateTime::tqcurrentDateTime().toString("yyyyMMdd-hhmmss")) .tqarg(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 AudioFileManager::createRecordingAudioFiles(unsigned int n) { std::vector 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") .tqarg(prefix) .tqarg(sourceBase.c_str()) .tqarg(TQDateTime::tqcurrentDateTime().toString("yyyyMMdd-hhmmss")) .tqarg(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").tqarg(url.prettyURL())); TQString localPath = ""; if (!KIO::NetAccess::download(url, localPath)) { KMessageBox::error(0, i18n("Cannot download file %1").tqarg(url.prettyURL())); throw SoundFile::BadSoundFileException(url.prettyURL().ascii()); } AudioFileId id = 0; try { id = importFile(localPath.ascii(), sampleRate); } catch (BadAudioPathException ape) { KIO::NetAccess::removeTempFile(localPath); throw ape; } catch (SoundFile::BadSoundFileException bse) { KIO::NetAccess::removeTempFile(localPath); throw bse; } return id; } bool AudioFileManager::fileNeedsConversion(const std::string &fileName, int sampleRate) { KProcess *proc = new KProcess(); *proc << "rosegarden-audiofile-importer"; if (sampleRate > 0) { *proc << "-r"; *proc << TQString("%1").tqarg(sampleRate); } *proc << "-w"; *proc << fileName.c_str(); proc->start(KProcess::Block, KProcess::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; KProcess *proc = new KProcess(); *proc << "rosegarden-audiofile-importer"; if (sampleRate > 0) { *proc << "-r"; *proc << TQString("%1").tqarg(sampleRate); } *proc << "-w"; *proc << fileName.c_str(); proc->start(KProcess::Block, KProcess::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") .tqarg(sourceBase) .tqarg(TQDateTime::tqcurrentDateTime().toString("yyyyMMdd-hhmmss")) .tqarg(newId + 1); if (TQFile(m_audioPath.c_str() + targetName).exists()) { targetName = ""; ++newId; } } m_importProcess = new KProcess; *m_importProcess << "rosegarden-audiofile-importer"; if (sampleRate > 0) { *m_importProcess << "-r"; *m_importProcess << TQString("%1").tqarg(sampleRate); } *m_importProcess << "-c"; *m_importProcess << fileName.c_str(); *m_importProcess << (m_audioPath.c_str() + targetName); m_importProcess->start(KProcess::NotifyOnExit, KProcess::NoCommunication); while (m_importProcess->isRunning()) { kapp->tqprocessEvents(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::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 << "" << std::endl; audioFiles << " " << std::endl; std::string fileName; std::vector::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 << "