From 145364a8af6a1fec06556221e66d4b724a62fc9a Mon Sep 17 00:00:00 2001 From: tpearson Date: Mon, 1 Mar 2010 18:37:05 +0000 Subject: Added old abandoned KDE3 version of the RoseGarden MIDI tool git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- src/sound/AudioFileManager.cpp | 1257 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1257 insertions(+) create mode 100644 src/sound/AudioFileManager.cpp (limited to 'src/sound/AudioFileManager.cpp') diff --git a/src/sound/AudioFileManager.cpp b/src/sound/AudioFileManager.cpp new file mode 100644 index 0000000..93be26c --- /dev/null +++ b/src/sound/AudioFileManager.cpp @@ -0,0 +1,1257 @@ +// -*- 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, SIGNAL(setProgress(int)), + this, SIGNAL(setProgress(int))); +} + +AudioFileManager::~AudioFileManager() +{ + clear(); +} + +// Add a file from an absolute path +// +AudioFileId +AudioFileManager::addFile(const std::string &filePath) +{ + MutexLock lock (&_audioFileManagerLock) + ; + + QString ext; + + if (filePath.length() > 3) { + ext = QString(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. + // + QFileInfo 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. + // + QFileInfo 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) +{ + QFileInfo 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) + ; + + QFileInfo info(file.c_str()); + + if (info.exists()) + return file; + + // Build the search filename from the audio path and + // the file basename. + // + QString searchFile = QString(m_audioPath.c_str()) + info.fileName(); + QFileInfo searchInfo(searchFile); + + if (searchInfo.exists()) + return searchFile.latin1(); + + std::cout << "AudioFileManager::getFileInPath - " + << "searchInfo = " << searchFile << 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(); + QString fileName = ""; + + while (fileName == "") { + + fileName = QString("rg-%1-%2.wav") + .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")) + .arg(newId + 1); + + if (QFile(m_audioPath.c_str() + fileName).exists()) { + fileName = ""; + ++newId; + } + } + + // insert file into vector + WAVAudioFile *aF = 0; + + try { + aF = new WAVAudioFile(newId, fileName.data(), m_audioPath + fileName.data()); + 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(); + QString 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 = QString("%1-%2-%3-%4.wav") + .arg(prefix) + .arg(sourceBase) + .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")) + .arg(newId + 1); + + if (QFile(m_audioPath.c_str() + fileName).exists()) { + fileName = ""; + ++newId; + } + } + + // insert file into vector + WAVAudioFile *aF = 0; + + try { + aF = new WAVAudioFile(newId, + fileName.data(), + m_audioPath + fileName.data()); + 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(), sampleRate); + + std::cerr << "AudioFileManager::importURL("<< url.prettyURL() << ", " << sampleRate << ")" << std::endl; + + emit setOperationName(i18n("Downloading file %1").arg(url.prettyURL())); + + QString localPath = ""; + if (!KIO::NetAccess::download(url, localPath)) { + KMessageBox::error(0, i18n("Cannot download file %1").arg(url.prettyURL())); + throw SoundFile::BadSoundFileException(url.prettyURL()); + } + + AudioFileId id = 0; + + try { + id = importFile(localPath.data(), 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 << QString("%1").arg(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 << QString("%1").arg(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(); + QString targetName = ""; + + QString sourceBase = QFileInfo(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 = QString("conv-%2-%3-%4.wav") + .arg(sourceBase) + .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")) + .arg(newId + 1); + + if (QFile(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 << QString("%1").arg(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->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")); + } else { + std::cerr << "audio file importer succeeded" << std::endl; + } + + // insert file into vector + WAVAudioFile *aF = 0; + + aF = new WAVAudioFile(newId, + targetName.data(), + m_audioPath + targetName.data()); + 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 << "