diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/sound/PlayableAudioFile.cpp | |
download | rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip |
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
Diffstat (limited to 'src/sound/PlayableAudioFile.cpp')
-rw-r--r-- | src/sound/PlayableAudioFile.cpp | 1086 |
1 files changed, 1086 insertions, 0 deletions
diff --git a/src/sound/PlayableAudioFile.cpp b/src/sound/PlayableAudioFile.cpp new file mode 100644 index 0000000..b5ddcf7 --- /dev/null +++ b/src/sound/PlayableAudioFile.cpp @@ -0,0 +1,1086 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + 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 "PlayableAudioFile.h" +#include <cassert> + +namespace Rosegarden +{ + +//#define DEBUG_RING_BUFFER_POOL 1 +//#define DEBUG_PLAYABLE 1 +//#define DEBUG_PLAYABLE_READ 1 + +class RingBufferPool +{ +public: + typedef float sample_t; + + RingBufferPool(size_t bufferSize); + virtual ~RingBufferPool(); + + /** + * Set the default size for buffers. Buffers currently allocated + * will not be resized until they are returned. + */ + void setBufferSize(size_t n); + + size_t getBufferSize() const + { + return m_bufferSize; + } + + /** + * Discard or create buffers as necessary so as to have n buffers + * in the pool. This will not discard any buffers that are + * currently allocated, so if more than n are allocated, more than + * n will remain. + */ + void setPoolSize(size_t n); + + size_t getPoolSize() const + { + return m_buffers.size(); + } + + /** + * Return true if n buffers available, false otherwise. + */ + bool getBuffers(size_t n, RingBuffer<sample_t> **buffers); + + /** + * Return a buffer to the pool. + */ + void returnBuffer(RingBuffer<sample_t> *buffer); + +protected: + // Want to avoid memory allocation if possible when marking a buffer + // unallocated or allocated, so we use a single container for all + + typedef std::pair<RingBuffer<sample_t> *, bool> AllocPair; + typedef std::vector<AllocPair> AllocList; + AllocList m_buffers; + + size_t m_bufferSize; + size_t m_available; + + pthread_mutex_t m_lock; +}; + + +RingBufferPool::RingBufferPool(size_t bufferSize) : + m_bufferSize(bufferSize), + m_available(0) +{ + pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER; + memcpy(&m_lock, &initialisingMutex, sizeof(pthread_mutex_t)); +} + +RingBufferPool::~RingBufferPool() +{ + size_t allocatedCount = 0; + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { + if (i->second) + ++allocatedCount; + } + + if (allocatedCount > 0) { + std::cerr << "WARNING: RingBufferPool::~RingBufferPool: deleting pool with " << allocatedCount << " allocated buffers" << std::endl; + } + + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { + delete i->first; + } + + m_buffers.clear(); + + pthread_mutex_destroy(&m_lock); +} + +void +RingBufferPool::setBufferSize(size_t n) +{ + if (m_bufferSize == n) + return ; + + pthread_mutex_lock(&m_lock); + +#ifdef DEBUG_RING_BUFFER_POOL + + std::cerr << "RingBufferPool::setBufferSize: from " << m_bufferSize + << " to " << n << std::endl; + int c = 0; +#endif + + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { + if (!i->second) { + delete i->first; + i->first = new RingBuffer<sample_t>(n); +#ifdef DEBUG_RING_BUFFER_POOL + + std::cerr << "Resized buffer " << c++ << std::endl; +#endif + + } else { +#ifdef DEBUG_RING_BUFFER_POOL + std::cerr << "Buffer " << c++ << " is already in use, resizing in place" << std::endl; +#endif + + i->first->resize(n); + } + } + + m_bufferSize = n; + pthread_mutex_unlock(&m_lock); +} + +void +RingBufferPool::setPoolSize(size_t n) +{ + pthread_mutex_lock(&m_lock); + +#ifdef DEBUG_RING_BUFFER_POOL + + std::cerr << "RingBufferPool::setPoolSize: from " << m_buffers.size() + << " to " << n << std::endl; +#endif + + size_t allocatedCount = 0, count = 0; + + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { + if (i->second) + ++allocatedCount; + ++count; + } + + if (count > n) { + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ) { + if (!i->second) { + delete i->first; + m_buffers.erase(i); + if (--count == n) + break; + } else { + ++i; + } + } + } + + while (count < n) { + m_buffers.push_back(AllocPair(new RingBuffer<sample_t>(m_bufferSize), + false)); + ++count; + } + + m_available = std::max(allocatedCount, n) - allocatedCount; + +#ifdef DEBUG_RING_BUFFER_POOL + + std::cerr << "RingBufferPool::setPoolSize: have " << m_buffers.size() + << " buffers (" << allocatedCount << " allocated, " << m_available << " available)" << std::endl; +#endif + + pthread_mutex_unlock(&m_lock); +} + +bool +RingBufferPool::getBuffers(size_t n, RingBuffer<sample_t> **buffers) +{ + pthread_mutex_lock(&m_lock); + + size_t count = 0; + + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { + if (!i->second && ++count == n) + break; + } + + if (count < n) { +#ifdef DEBUG_RING_BUFFER_POOL + std::cerr << "RingBufferPool::getBuffers(" << n << "): not available (in pool of " << m_buffers.size() << "), resizing" << std::endl; +#endif + + AllocList newBuffers; + + while (count < n) { + for (size_t i = 0; i < m_buffers.size(); ++i) { + newBuffers.push_back(m_buffers[i]); + } + for (size_t i = 0; i < m_buffers.size(); ++i) { + newBuffers.push_back(AllocPair(new RingBuffer<sample_t>(m_bufferSize), + false)); + } + count += m_buffers.size(); + m_available += m_buffers.size(); + } + + m_buffers = newBuffers; + } + + count = 0; + +#ifdef DEBUG_RING_BUFFER_POOL + + std::cerr << "RingBufferPool::getBuffers(" << n << "): available" << std::endl; +#endif + + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { + if (!i->second) { + i->second = true; + i->first->reset(); + i->first->mlock(); + buffers[count] = i->first; + --m_available; + if (++count == n) + break; + } + } + +#ifdef DEBUG_RING_BUFFER_POOL + std::cerr << "RingBufferPool::getBuffers: " << m_available << " remain in pool of " << m_buffers.size() << std::endl; +#endif + + pthread_mutex_unlock(&m_lock); + return true; +} + +void +RingBufferPool::returnBuffer(RingBuffer<sample_t> *buffer) +{ + pthread_mutex_lock(&m_lock); + +#ifdef DEBUG_RING_BUFFER_POOL + + std::cerr << "RingBufferPool::returnBuffer" << std::endl; +#endif + + buffer->munlock(); + + for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { + if (i->first == buffer) { + i->second = false; + ++m_available; + if (buffer->getSize() != m_bufferSize) { + delete buffer; + i->first = new RingBuffer<sample_t>(m_bufferSize); + } + } + } + +#ifdef DEBUG_RING_BUFFER_POOL + std::cerr << "RingBufferPool::returnBuffer: " << m_available << " remain in pool of " << m_buffers.size() << std::endl; +#endif + + pthread_mutex_unlock(&m_lock); +} + + +AudioCache PlayableAudioFile::m_smallFileCache; + +std::vector<PlayableAudioFile::sample_t *> PlayableAudioFile::m_workBuffers; +size_t PlayableAudioFile::m_workBufferSize = 0; + +char *PlayableAudioFile::m_rawFileBuffer; +size_t PlayableAudioFile::m_rawFileBufferSize = 0; + +RingBufferPool *PlayableAudioFile::m_ringBufferPool = 0; + +size_t PlayableAudioFile::m_xfadeFrames = 30; + +PlayableAudioFile::PlayableAudioFile(InstrumentId instrumentId, + AudioFile *audioFile, + const RealTime &startTime, + const RealTime &startIndex, + const RealTime &duration, + size_t bufferSize, + size_t smallFileSize, + int targetChannels, + int targetSampleRate) : + m_startTime(startTime), + m_startIndex(startIndex), + m_duration(duration), + m_file(0), + m_audioFile(audioFile), + m_instrumentId(instrumentId), + m_targetChannels(targetChannels), + m_targetSampleRate(targetSampleRate), + m_fileEnded(false), + m_firstRead(true), + m_runtimeSegmentId( -1), + m_isSmallFile(false), + m_currentScanPoint(RealTime::zeroTime), + m_smallFileScanFrame(0), + m_autoFade(false), + m_fadeInTime(RealTime::zeroTime), + m_fadeOutTime(RealTime::zeroTime) +{ +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::PlayableAudioFile - creating " << this << " for instrument " << instrumentId << " with file " << (m_audioFile ? m_audioFile->getShortFilename() : "(none)") << std::endl; +#endif + + if (!m_ringBufferPool) { + //!!! Problematic -- how do we deal with different playable audio + // files requiring different buffer sizes? That shouldn't be the + // usual case, but it's not unthinkable. + m_ringBufferPool = new RingBufferPool(bufferSize); + } else { + m_ringBufferPool->setBufferSize + (std::max(bufferSize, m_ringBufferPool->getBufferSize())); + } + + initialise(bufferSize, smallFileSize); +} + + +void +PlayableAudioFile::setRingBufferPoolSizes(size_t n, size_t nframes) +{ + if (!m_ringBufferPool) { + m_ringBufferPool = new RingBufferPool(nframes); + } else { + m_ringBufferPool->setBufferSize + (std::max(nframes, m_ringBufferPool->getBufferSize())); + } + m_ringBufferPool->setPoolSize(n); +} + + +void +PlayableAudioFile::initialise(size_t bufferSize, size_t smallFileSize) +{ +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::initialise() " << this << std::endl; +#endif + + checkSmallFileCache(smallFileSize); + + if (!m_isSmallFile) { + + m_file = new std::ifstream(m_audioFile->getFilename().c_str(), + std::ios::in | std::ios::binary); + + if (!*m_file) { + std::cerr << "ERROR: PlayableAudioFile::initialise: Failed to open audio file " << m_audioFile->getFilename() << std::endl; + delete m_file; + m_file = 0; + } + } + + // Scan to the beginning of the data chunk we need + // +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::initialise - scanning to " << m_startIndex << std::endl; +#endif + + if (m_file) { + scanTo(m_startIndex); + } else { + m_fileEnded = false; + m_currentScanPoint = m_startIndex; + m_smallFileScanFrame = RealTime::realTime2Frame + (m_currentScanPoint, m_audioFile->getSampleRate()); + } + +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::initialise: buffer size is " << bufferSize << " frames, file size is " << m_audioFile->getSize() << std::endl; +#endif + + if (m_targetChannels <= 0) + m_targetChannels = m_audioFile->getChannels(); + if (m_targetSampleRate <= 0) + m_targetSampleRate = m_audioFile->getSampleRate(); + + m_ringBuffers = new RingBuffer<sample_t> *[m_targetChannels]; + for (int ch = 0; ch < m_targetChannels; ++ch) { + m_ringBuffers[ch] = 0; + } +} + +PlayableAudioFile::~PlayableAudioFile() +{ + if (m_file) { + m_file->close(); + delete m_file; + } + + returnRingBuffers(); + delete[] m_ringBuffers; + m_ringBuffers = 0; + + if (m_isSmallFile) { + m_smallFileCache.decrementReference(m_audioFile); + } + +#ifdef DEBUG_PLAYABLE + // std::cerr << "PlayableAudioFile::~PlayableAudioFile - destroying - " << this << std::endl; +#endif +} + +void +PlayableAudioFile::returnRingBuffers() +{ + for (int i = 0; i < m_targetChannels; ++i) { + if (m_ringBuffers[i]) { + m_ringBufferPool->returnBuffer(m_ringBuffers[i]); + m_ringBuffers[i] = 0; + } + } +} + +bool +PlayableAudioFile::scanTo(const RealTime &time) +{ +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::scanTo(" << time << ")" << std::endl; +#endif + + m_fileEnded = false; // until we know otherwise -- this flag is an + // optimisation, not a reliable record + + bool ok = false; + + if (m_isSmallFile) { + + m_currentScanPoint = time; + m_smallFileScanFrame = RealTime::realTime2Frame + (time, m_audioFile->getSampleRate()); +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "... maps to frame " << m_smallFileScanFrame << std::endl; +#endif + ok = true; + + } else { + + ok = m_audioFile->scanTo(m_file, time); + if (ok) { + m_currentScanPoint = time; + } + } + +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::scanTo(" << time << "): set m_currentScanPoint to " << m_currentScanPoint << std::endl; +#endif + + m_firstRead = true; // so we know to xfade in + + return ok; +} + + +size_t +PlayableAudioFile::getSampleFramesAvailable() +{ + size_t actual = 0; + + if (m_isSmallFile) { + size_t cchannels; + size_t cframes; + (void)m_smallFileCache.getData(m_audioFile, cchannels, cframes); + if (cframes > m_smallFileScanFrame) + return cframes - m_smallFileScanFrame; + else + return 0; + } + + for (int ch = 0; ch < m_targetChannels; ++ch) { + if (!m_ringBuffers[ch]) + return 0; + size_t thisChannel = m_ringBuffers[ch]->getReadSpace(); + if (ch == 0 || thisChannel < actual) + actual = thisChannel; + } + +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile(" << (m_audioFile ? m_audioFile->getShortFilename() : "(none)") << " " << this << ")::getSampleFramesAvailable: have " << actual << std::endl; +#endif + + return actual; +} + +size_t +PlayableAudioFile::addSamples(std::vector<sample_t *> &destination, + size_t channels, size_t nframes, size_t offset) +{ +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): channels " << channels << ", my target channels " << m_targetChannels << std::endl; +#endif + + if (!m_isSmallFile) { + + size_t qty = 0; + bool done = m_fileEnded; + + for (int ch = 0; ch < int(channels) && ch < m_targetChannels; ++ch) { + if (!m_ringBuffers[ch]) + return 0; //!!! fatal + size_t here = m_ringBuffers[ch]->readAdding(destination[ch] + offset, nframes); + if (ch == 0 || here < qty) + qty = here; + if (done && (m_ringBuffers[ch]->getReadSpace() > 0)) + done = false; + } + + for (int ch = channels; ch < m_targetChannels; ++ch) { + m_ringBuffers[ch]->skip(nframes); + } + + if (done) { +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): reached end, returning buffers" << std::endl; +#endif + + returnRingBuffers(); + } + +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): returning " << qty << " frames (at least " << (m_ringBuffers[0] ? m_ringBuffers[0]->getReadSpace() : 0) << " remaining)" << std::endl; +#endif + + return qty; + + } else { + + size_t cchannels; + size_t cframes; + float **cached = m_smallFileCache.getData(m_audioFile, cchannels, cframes); + + if (!cached) { + std::cerr << "WARNING: PlayableAudioFile::addSamples: Failed to find small file in cache" << std::endl; + m_isSmallFile = false; + } else { + + size_t scanFrame = m_smallFileScanFrame; + + if (scanFrame >= cframes) { + m_fileEnded = true; + return 0; + } + + size_t endFrame = scanFrame + nframes; + size_t n = nframes; + + if (endFrame >= cframes) { + m_fileEnded = true; + n = cframes - scanFrame; + } + +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::addSamples: it's a small file: want frames " << scanFrame << " to " << endFrame << " of " << cframes << std::endl; +#endif + + size_t xfadeIn = (m_firstRead ? m_xfadeFrames : 0); + size_t xfadeOut = (m_fileEnded ? m_xfadeFrames : 0); + + // all this could be neater! + + if (channels == 1 && cchannels == 2) { // mix + for (size_t i = 0; i < n; ++i) { + sample_t v = + cached[0][scanFrame + i] + + cached[1][scanFrame + i]; + // if ((i + 1) < xfadeIn) + // v = (v * (i + 1)) / xfadeIn; + //if ((n - i) < xfadeOut) + // v = (v * (n - i)) / xfadeOut; + destination[0][i + offset] += v; + } + } else { + for (size_t ch = 0; ch < channels; ++ch) { + int sch = ch; + if (ch >= cchannels) { + if (channels == 2 && cchannels == 1) + sch = 0; + else + break; + } else { + for (size_t i = 0; i < n; ++i) { + sample_t v = cached[sch][scanFrame + i]; + // if ((i + 1) < xfadeIn) + // v = (v * (i + 1)) / xfadeIn; + //if ((n - i) < xfadeOut) + // v = (v * (n - i)) / xfadeOut; + destination[ch][i + offset] += v; + } + } + } + } + + m_smallFileScanFrame += nframes; + m_currentScanPoint = m_currentScanPoint + + RealTime::frame2RealTime(nframes, m_targetSampleRate); + return nframes; + } + } + + return 0; +} + +void +PlayableAudioFile::checkSmallFileCache(size_t smallFileSize) +{ + if (m_smallFileCache.has(m_audioFile)) { + +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::checkSmallFileCache: Found file in small file cache" << std::endl; +#endif + + m_smallFileCache.incrementReference(m_audioFile); + m_isSmallFile = true; + + } else if (m_audioFile->getSize() <= smallFileSize) { + + std::ifstream file(m_audioFile->getFilename().c_str(), + std::ios::in | std::ios::binary); + + if (!file) { + std::cerr << "ERROR: PlayableAudioFile::checkSmallFileCache: Failed to open audio file " << m_audioFile->getFilename() << std::endl; + return ; + } + +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::checkSmallFileCache: Adding file to small file cache" << std::endl; +#endif + + // We always encache files with their original number of + // channels (because they might be called for in any channel + // configuration subsequently) but with the current sample + // rate, not their original one. + + m_audioFile->scanTo(&file, RealTime::zeroTime); + + size_t reqd = m_audioFile->getSize() / m_audioFile->getBytesPerFrame(); + unsigned char *buffer = new unsigned char[m_audioFile->getSize()]; + size_t obtained = m_audioFile->getSampleFrames(&file, (char *)buffer, reqd); + +// std::cerr <<"obtained=" << obtained << std::endl; + + size_t nch = getSourceChannels(); + size_t nframes = obtained; + if (int(getSourceSampleRate()) != m_targetSampleRate) { +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::checkSmallFileCache: Resampling badly from " << getSourceSampleRate() << " to " << m_targetSampleRate << std::endl; +#endif + nframes = size_t(float(nframes) * float(m_targetSampleRate) / + float(getSourceSampleRate())); + } + + std::vector<sample_t *> samples; + for (size_t ch = 0; ch < nch; ++ch) { + samples.push_back(new sample_t[nframes]); + } + + if (!m_audioFile->decode(buffer, + obtained * m_audioFile->getBytesPerFrame(), + m_targetSampleRate, + nch, + nframes, + samples)) { + std::cerr << "PlayableAudioFile::checkSmallFileCache: failed to decode file" << std::endl; + } else { + sample_t **toCache = new sample_t * [nch]; + for (size_t ch = 0; ch < nch; ++ch) { + toCache[ch] = samples[ch]; + } + m_smallFileCache.addData(m_audioFile, nch, nframes, toCache); + m_isSmallFile = true; + } + + delete[] buffer; + + file.close(); + } + + if (m_isSmallFile) { + if (m_file) { + m_file->close(); + delete m_file; + m_file = 0; + } + } +} + + +void +PlayableAudioFile::fillBuffers() +{ +#ifdef DEBUG_PLAYABLE + if (m_audioFile) { + std::cerr << "PlayableAudioFile(" << m_audioFile->getShortFilename() << ")::fillBuffers() [async] -- scanning to " << m_startIndex << std::endl; + } else { + std::cerr << "PlayableAudioFile::fillBuffers() [async] -- scanning to " << m_startIndex << std::endl; + } +#endif + + if (!m_isSmallFile && (!m_file || !*m_file)) { + m_file = new std::ifstream(m_audioFile->getFilename().c_str(), + std::ios::in | std::ios::binary); + if (!*m_file) { + std::cerr << "ERROR: PlayableAudioFile::fillBuffers: Failed to open audio file " << m_audioFile->getFilename() << std::endl; + delete m_file; + m_file = 0; + return ; + } + } + + scanTo(m_startIndex); + updateBuffers(); +} + +void +PlayableAudioFile::clearBuffers() +{ + returnRingBuffers(); +} + +bool +PlayableAudioFile::fillBuffers(const RealTime ¤tTime) +{ +#ifdef DEBUG_PLAYABLE + if (!m_isSmallFile) { + if (m_audioFile) { + std::cerr << "PlayableAudioFile(" << m_audioFile->getShortFilename() << " " << this << ")::fillBuffers(" << currentTime << "):\n my start time " << m_startTime << ", start index " << m_startIndex << ", duration " << m_duration << std::endl; + } else { + std::cerr << "PlayableAudioFile::fillBuffers(" << currentTime << "): my start time " << m_startTime << ", start index " << m_startIndex << ", duration " << m_duration << std::endl; + } + } +#endif + + if (currentTime > m_startTime + m_duration) { + +#ifdef DEBUG_PLAYABLE + std::cerr << "PlayableAudioFile::fillBuffers: seeking past end, returning buffers" << std::endl; +#endif + + returnRingBuffers(); + return true; + } + + if (!m_isSmallFile && (!m_file || !*m_file)) { + m_file = new std::ifstream(m_audioFile->getFilename().c_str(), + std::ios::in | std::ios::binary); + if (!*m_file) { + std::cerr << "ERROR: PlayableAudioFile::fillBuffers: Failed to open audio file " << m_audioFile->getFilename() << std::endl; + delete m_file; + m_file = 0; + return false; + } + scanTo(m_startIndex); + } + + RealTime scanTime = m_startIndex; + + if (currentTime > m_startTime) { + scanTime = m_startIndex + currentTime - m_startTime; + } + + // size_t scanFrames = RealTime::realTime2Frame + // (scanTime, + // m_isSmallFile ? m_targetSampleRate : m_audioFile->getSampleRate()); + + if (scanTime != m_currentScanPoint) { + scanTo(scanTime); + } + + if (!m_isSmallFile) { + for (int i = 0; i < m_targetChannels; ++i) { + if (m_ringBuffers[i]) + m_ringBuffers[i]->reset(); + } + updateBuffers(); + } + + return true; +} + +bool +PlayableAudioFile::updateBuffers() +{ + if (m_isSmallFile) + return false; + if (!m_file) + return false; + + if (m_fileEnded) { +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::updateBuffers: at end of file already" << std::endl; +#endif + + return false; + } + + if (!m_ringBuffers[0]) { + + if (m_targetChannels < 0) { + std::cerr << "WARNING: PlayableAudioFile::updateBuffers: m_targetChannels < 0, can't allocate ring buffers" << std::endl; + return false; + } + + // need a buffer: can we get one? + if (!m_ringBufferPool->getBuffers(m_targetChannels, m_ringBuffers)) { + std::cerr << "WARNING: PlayableAudioFile::updateBuffers: no ring buffers available" << std::endl; + return false; + } + } + + size_t nframes = 0; + + for (int ch = 0; ch < m_targetChannels; ++ch) { + if (!m_ringBuffers[ch]) + continue; + size_t writeSpace = m_ringBuffers[ch]->getWriteSpace(); + if (ch == 0 || writeSpace < nframes) + nframes = writeSpace; + } + + if (nframes == 0) { +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::updateBuffers: frames == 0, ignoring" << std::endl; +#endif + + return false; + } + +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "PlayableAudioFile::updateBuffers: want " << nframes << " frames" << std::endl; +#endif + + + RealTime block = RealTime::frame2RealTime(nframes, m_targetSampleRate); + if (m_currentScanPoint + block >= m_startIndex + m_duration) { + block = m_startIndex + m_duration - m_currentScanPoint; + if (block <= RealTime::zeroTime) + nframes = 0; + else + nframes = RealTime::realTime2Frame(block, m_targetSampleRate); + m_fileEnded = true; + } + + size_t fileFrames = nframes; + if (m_targetSampleRate != int(getSourceSampleRate())) { + fileFrames = size_t(float(fileFrames) * float(getSourceSampleRate()) / + float(m_targetSampleRate)); + } + +#ifdef DEBUG_PLAYABLE_READ + std::cerr << "Want " << fileFrames << " (" << block << ") from file (" << (m_duration + m_startIndex - m_currentScanPoint - block) << " to go)" << std::endl; +#endif + + //!!! need to be doing this in initialise, want to avoid allocations here + if ((getBytesPerFrame() * fileFrames) > m_rawFileBufferSize) { + delete[] m_rawFileBuffer; + m_rawFileBufferSize = getBytesPerFrame() * fileFrames; +#ifdef DEBUG_PLAYABLE_READ + + std::cerr << "Expanding raw file buffer to " << m_rawFileBufferSize << " chars" << std::endl; +#endif + + m_rawFileBuffer = new char[m_rawFileBufferSize]; + } + + size_t obtained = + m_audioFile->getSampleFrames(m_file, m_rawFileBuffer, fileFrames); + + if (obtained < fileFrames || m_file->eof()) { + m_fileEnded = true; + } + +#ifdef DEBUG_PLAYABLE + std::cerr << "requested " << fileFrames << " frames from file for " << nframes << " frames, got " << obtained << " frames" << std::endl; +#endif + + if (nframes > m_workBufferSize) { + + for (size_t i = 0; i < m_workBuffers.size(); ++i) { + delete[] m_workBuffers[i]; + } + + m_workBuffers.clear(); + m_workBufferSize = nframes; +#ifdef DEBUG_PLAYABLE_READ + + std::cerr << "Expanding work buffer to " << m_workBufferSize << " frames" << std::endl; +#endif + + for (int i = 0; i < m_targetChannels; ++i) { + m_workBuffers.push_back(new sample_t[m_workBufferSize]); + } + + } else { + + while (m_targetChannels > m_workBuffers.size()) { + m_workBuffers.push_back(new sample_t[m_workBufferSize]); + } + } + + if (m_audioFile->decode((const unsigned char *)m_rawFileBuffer, + obtained * getBytesPerFrame(), + m_targetSampleRate, + m_targetChannels, + nframes, + m_workBuffers, + false)) { + + /*!!! No -- GUI and notification side of things isn't up to this yet, + so comment it out just in case + + if (m_autoFade) { + + if (m_currentScanPoint < m_startIndex + m_fadeInTime) { + + size_t fadeSamples = + RealTime::realTime2Frame(m_fadeInTime, getTargetSampleRate()); + size_t originSamples = + RealTime::realTime2Frame(m_currentScanPoint - m_startIndex, + getTargetSampleRate()); + + for (size_t i = 0; i < nframes; ++i) { + if (i + originSamples > fadeSamples) { + break; + } + float gain = float(i + originSamples) / float(fadeSamples); + for (int ch = 0; ch < m_targetChannels; ++ch) { + m_workBuffers[ch][i] *= gain; + } + } + } + + if (m_currentScanPoint + block > + m_startIndex + m_duration - m_fadeOutTime) { + + size_t fadeSamples = + RealTime::realTime2Frame(m_fadeOutTime, getTargetSampleRate()); + size_t originSamples = // counting from end + RealTime::realTime2Frame + (m_startIndex + m_duration - m_currentScanPoint, + getTargetSampleRate()); + + for (size_t i = 0; i < nframes; ++i) { + float gain = 1.0; + if (originSamples < i) gain = 0.0; + else { + size_t fromEnd = originSamples - i; + if (fromEnd < fadeSamples) { + gain = float(fromEnd) / float(fadeSamples); + } + } + for (int ch = 0; ch < m_targetChannels; ++ch) { + m_workBuffers[ch][i] *= gain; + } + } + } + } + */ + + m_currentScanPoint = m_currentScanPoint + block; + + for (int ch = 0; ch < m_targetChannels; ++ch) { + + if (m_firstRead || m_fileEnded) { + float xfade = std::min(m_xfadeFrames, nframes); + if (m_firstRead) { + for (size_t i = 0; i < xfade; ++i) { + m_workBuffers[ch][i] *= float(i + 1) / xfade; + } + } + if (m_fileEnded) { + for (size_t i = 0; i < xfade; ++i) { + m_workBuffers[ch][nframes - i - 1] *= + float(i + 1) / xfade; + } + } + } + + if (m_ringBuffers[ch]) { + m_ringBuffers[ch]->write(m_workBuffers[ch], nframes); + } + } + } + + m_firstRead = false; + + if (obtained < fileFrames) { + if (m_file) { + m_file->close(); + delete m_file; + m_file = 0; + } + } + + return true; +} + + +// How many channels in the base AudioFile? +// +unsigned int +PlayableAudioFile::getSourceChannels() +{ + if (m_audioFile) { + return m_audioFile->getChannels(); + } + return 0; +} + +unsigned int +PlayableAudioFile::getTargetChannels() +{ + return m_targetChannels; +} + +unsigned int +PlayableAudioFile::getBytesPerFrame() +{ + if (m_audioFile) { + return m_audioFile->getBytesPerFrame(); + } + return 0; +} + +unsigned int +PlayableAudioFile::getSourceSampleRate() +{ + if (m_audioFile) { + return m_audioFile->getSampleRate(); + } + return 0; +} + +unsigned int +PlayableAudioFile::getTargetSampleRate() +{ + return m_targetSampleRate; +} + + +// How many bits per sample in the base AudioFile? +// +unsigned int +PlayableAudioFile::getBitsPerSample() +{ + if (m_audioFile) { + return m_audioFile->getBitsPerSample(); + } + return 0; +} + + +} + |