Chris@148: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@148: 
Chris@148: /*
Chris@148:     Sonic Visualiser
Chris@148:     An audio file viewer and annotation editor.
Chris@148:     Centre for Digital Music, Queen Mary, University of London.
Chris@297:     This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@148:     
Chris@148:     This program is free software; you can redistribute it and/or
Chris@148:     modify it under the terms of the GNU General Public License as
Chris@148:     published by the Free Software Foundation; either version 2 of the
Chris@148:     License, or (at your option) any later version.  See the file
Chris@148:     COPYING included with this distribution for more information.
Chris@148: */
Chris@148: 
Chris@148: #include "CodedAudioFileReader.h"
Chris@148: 
Chris@148: #include "WavFileReader.h"
Chris@148: #include "base/TempDirectory.h"
Chris@148: #include "base/Exceptions.h"
Chris@192: #include "base/Profiler.h"
Chris@297: #include "base/Serialiser.h"
Chris@297: #include "base/Resampler.h"
Chris@148: 
Chris@723: #include <stdint.h>
Chris@148: #include <iostream>
Chris@148: #include <QDir>
Chris@263: #include <QMutexLocker>
Chris@148: 
Chris@297: CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode,
Chris@920:                                            size_t targetRate,
Chris@920:                                            bool normalised) :
Chris@148:     m_cacheMode(cacheMode),
Chris@148:     m_initialised(false),
Chris@297:     m_serialiser(0),
Chris@297:     m_fileRate(0),
Chris@148:     m_cacheFileWritePtr(0),
Chris@148:     m_cacheFileReader(0),
Chris@148:     m_cacheWriteBuffer(0),
Chris@148:     m_cacheWriteBufferIndex(0),
Chris@297:     m_cacheWriteBufferSize(16384),
Chris@297:     m_resampler(0),
Chris@757:     m_resampleBuffer(0),
Chris@920:     m_fileFrameCount(0),
Chris@920:     m_normalised(normalised),
Chris@920:     m_max(0.f),
Chris@920:     m_gain(1.f)
Chris@148: {
Chris@922:     SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << ", normalised = " << normalised << endl;
Chris@297: 
Chris@297:     m_frameCount = 0;
Chris@297:     m_sampleRate = targetRate;
Chris@148: }
Chris@148: 
Chris@148: CodedAudioFileReader::~CodedAudioFileReader()
Chris@148: {
Chris@263:     QMutexLocker locker(&m_cacheMutex);
Chris@263: 
Chris@297:     endSerialised();
Chris@297: 
Chris@148:     if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
Chris@297: 
Chris@742:     SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
Chris@532: 
Chris@297:     delete m_cacheFileReader;
Chris@297:     delete[] m_cacheWriteBuffer;
Chris@148: 
Chris@148:     if (m_cacheFileName != "") {
Chris@290:         if (!QFile(m_cacheFileName).remove()) {
Chris@843:             cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << endl;
Chris@148:         }
Chris@148:     }
Chris@297: 
Chris@297:     delete m_resampler;
Chris@297:     delete[] m_resampleBuffer;
Chris@297: }
Chris@297: 
Chris@297: void
Chris@297: CodedAudioFileReader::startSerialised(QString id)
Chris@297: {
Chris@742:     SVDEBUG << "CodedAudioFileReader::startSerialised(" << id << ")" << endl;
Chris@297: 
Chris@297:     delete m_serialiser;
Chris@297:     m_serialiser = new Serialiser(id);
Chris@297: }
Chris@297: 
Chris@297: void
Chris@297: CodedAudioFileReader::endSerialised()
Chris@297: {
Chris@844:     SVDEBUG << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId() : "(none)") << endl;
Chris@297: 
Chris@297:     delete m_serialiser;
Chris@297:     m_serialiser = 0;
Chris@148: }
Chris@148: 
Chris@148: void
Chris@148: CodedAudioFileReader::initialiseDecodeCache()
Chris@148: {
Chris@263:     QMutexLocker locker(&m_cacheMutex);
Chris@263: 
Chris@742:     SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << endl;
Chris@297: 
Chris@297:     if (m_fileRate == 0) {
Chris@843:         cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl;
Chris@754:         throw FileOperationFailed("(coded file)", "File sample rate unknown (bug in subclass implementation?)");
Chris@297:     }
Chris@297:     if (m_sampleRate == 0) {
Chris@297:         m_sampleRate = m_fileRate;
Chris@690:         SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << endl;
Chris@297:     }
Chris@297:     if (m_fileRate != m_sampleRate) {
Chris@757:         SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " <<  m_sampleRate << endl;
Chris@297:         m_resampler = new Resampler(Resampler::FastestTolerable,
Chris@297:                                     m_channelCount,
Chris@297:                                     m_cacheWriteBufferSize);
Chris@297:         float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297:         m_resampleBuffer = new float
Chris@398:             [lrintf(ceilf(m_cacheWriteBufferSize * m_channelCount * ratio + 1))];
Chris@297:     }
Chris@297: 
Chris@297:     m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
Chris@297:     m_cacheWriteBufferIndex = 0;
Chris@297: 
Chris@148:     if (m_cacheMode == CacheInTemporaryFile) {
Chris@148: 
Chris@148:         try {
Chris@148:             QDir dir(TempDirectory::getInstance()->getPath());
Chris@148:             m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
Chris@290:                                            .arg((intptr_t)this));
Chris@148: 
Chris@148:             SF_INFO fileInfo;
Chris@148:             fileInfo.samplerate = m_sampleRate;
Chris@148:             fileInfo.channels = m_channelCount;
Chris@297: 
Chris@297:             // No point in writing 24-bit or float; generally this
Chris@297:             // class is used for decoding files that have come from a
Chris@297:             // 16 bit source or that decode to only 16 bits anyway.
Chris@297:             fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
Chris@148:     
Chris@290:             m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148:                                           SFM_WRITE, &fileInfo);
Chris@148: 
Chris@265:             if (m_cacheFileWritePtr) {
Chris@265: 
Chris@297:                 // Ideally we would do this now only if we were in a
Chris@297:                 // threaded mode -- creating the reader later if we're
Chris@297:                 // not threaded -- but we don't have access to that
Chris@297:                 // information here
Chris@265: 
Chris@265:                 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265: 
Chris@265:                 if (!m_cacheFileReader->isOK()) {
Chris@843:                     cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl;
Chris@265:                     delete m_cacheFileReader;
Chris@265:                     m_cacheFileReader = 0;
Chris@265:                     m_cacheMode = CacheInMemory;
Chris@265:                     sf_close(m_cacheFileWritePtr);
Chris@265:                 }
Chris@297: 
Chris@265:             } else {
Chris@843:                 cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << endl;
Chris@148:                 m_cacheMode = CacheInMemory;
Chris@148:             }
Chris@265: 
Chris@148:         } catch (DirectoryCreationFailed f) {
Chris@843:             cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
Chris@148:             m_cacheMode = CacheInMemory;
Chris@148:         }
Chris@148:     }
Chris@148: 
Chris@148:     if (m_cacheMode == CacheInMemory) {
Chris@148:         m_data.clear();
Chris@148:     }
Chris@148: 
Chris@148:     m_initialised = true;
Chris@148: }
Chris@148: 
Chris@148: void
Chris@297: CodedAudioFileReader::addSamplesToDecodeCache(float **samples, size_t nframes)
Chris@148: {
Chris@263:     QMutexLocker locker(&m_cacheMutex);
Chris@263: 
Chris@148:     if (!m_initialised) return;
Chris@148: 
Chris@297:     for (size_t i = 0; i < nframes; ++i) {
Chris@297:         
Chris@297:         for (size_t c = 0; c < m_channelCount; ++c) {
Chris@148: 
Chris@297:             float sample = samples[c][i];
Chris@297:         
Chris@297:             m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148: 
Chris@297:             if (m_cacheWriteBufferIndex ==
Chris@297:                 m_cacheWriteBufferSize * m_channelCount) {
Chris@297: 
Chris@297:                 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297:                 m_cacheWriteBufferIndex = 0;
Chris@297:             }
Chris@297: 
Chris@297:             if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297:                 m_cacheFileReader) {
Chris@297:                 m_cacheFileReader->updateFrameCount();
Chris@297:             }
Chris@297:         }
Chris@297:     }
Chris@297: }
Chris@297: 
Chris@297: void
Chris@297: CodedAudioFileReader::addSamplesToDecodeCache(float *samples, size_t nframes)
Chris@297: {
Chris@297:     QMutexLocker locker(&m_cacheMutex);
Chris@297: 
Chris@297:     if (!m_initialised) return;
Chris@297: 
Chris@297:     for (size_t i = 0; i < nframes; ++i) {
Chris@297:         
Chris@297:         for (size_t c = 0; c < m_channelCount; ++c) {
Chris@297: 
Chris@297:             float sample = samples[i * m_channelCount + c];
Chris@297:         
Chris@297:             m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297: 
Chris@297:             if (m_cacheWriteBufferIndex ==
Chris@297:                 m_cacheWriteBufferSize * m_channelCount) {
Chris@297: 
Chris@297:                 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297:                 m_cacheWriteBufferIndex = 0;
Chris@297:             }
Chris@297: 
Chris@297:             if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297:                 m_cacheFileReader) {
Chris@297:                 m_cacheFileReader->updateFrameCount();
Chris@297:             }
Chris@297:         }
Chris@297:     }
Chris@297: }
Chris@297: 
Chris@297: void
Chris@297: CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples)
Chris@297: {
Chris@297:     QMutexLocker locker(&m_cacheMutex);
Chris@297: 
Chris@297:     if (!m_initialised) return;
Chris@297: 
Chris@297:     for (size_t i = 0; i < samples.size(); ++i) {
Chris@297: 
Chris@297:         float sample = samples[i];
Chris@297:         
Chris@148:         m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148: 
Chris@148:         if (m_cacheWriteBufferIndex ==
Chris@148:             m_cacheWriteBufferSize * m_channelCount) {
Chris@148: 
Chris@297:             pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148:             m_cacheWriteBufferIndex = 0;
Chris@266:         }
Chris@265: 
Chris@266:         if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266:             m_cacheFileReader) {
Chris@266:             m_cacheFileReader->updateFrameCount();
Chris@148:         }
Chris@148:     }
Chris@148: }
Chris@148: 
Chris@148: void
Chris@148: CodedAudioFileReader::finishDecodeCache()
Chris@148: {
Chris@263:     QMutexLocker locker(&m_cacheMutex);
Chris@263: 
Chris@192:     Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192: 
Chris@148:     if (!m_initialised) {
Chris@843:         cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl;
Chris@148:         return;
Chris@148:     }
Chris@148: 
Chris@920:     pushBuffer(m_cacheWriteBuffer,
Chris@920:                m_cacheWriteBufferIndex / m_channelCount,
Chris@920:                true);
Chris@297: 
Chris@297:     delete[] m_cacheWriteBuffer;
Chris@297:     m_cacheWriteBuffer = 0;
Chris@297: 
Chris@297:     delete[] m_resampleBuffer;
Chris@297:     m_resampleBuffer = 0;
Chris@297: 
Chris@297:     delete m_resampler;
Chris@297:     m_resampler = 0;
Chris@297: 
Chris@297:     if (m_cacheMode == CacheInTemporaryFile) {
Chris@297:         sf_close(m_cacheFileWritePtr);
Chris@297:         m_cacheFileWritePtr = 0;
Chris@297:         if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@297:     }
Chris@297: }
Chris@297: 
Chris@297: void
Chris@297: CodedAudioFileReader::pushBuffer(float *buffer, size_t sz, bool final)
Chris@297: {
Chris@757:     m_fileFrameCount += sz;
Chris@757: 
Chris@757:     float ratio = 1.f;
Chris@758:     if (m_resampler && m_fileRate != 0) {
Chris@758:         ratio = float(m_sampleRate) / float(m_fileRate);
Chris@758:     }
Chris@758:         
Chris@758:     if (ratio != 1.f) {
Chris@758:         pushBufferResampling(buffer, sz, ratio, final);
Chris@758:     } else {
Chris@758:         pushBufferNonResampling(buffer, sz);
Chris@758:     }
Chris@758: }
Chris@757: 
Chris@758: void
Chris@758: CodedAudioFileReader::pushBufferNonResampling(float *buffer, size_t sz)
Chris@758: {
Chris@920:     float clip = 1.0;
Chris@758:     size_t count = sz * m_channelCount;
Chris@318: 
Chris@920:     if (m_normalised) {
Chris@920:         for (size_t i = 0; i < count; ++i) {
Chris@920:             float v = fabsf(buffer[i]);
Chris@920:             if (v > m_max) {
Chris@920:                 m_max = v;
Chris@920:                 m_gain = 1.f / m_max;
Chris@920:             }
Chris@920:         }
Chris@920:     } else {
Chris@920:         for (size_t i = 0; i < count; ++i) {
Chris@920:             if (buffer[i] >  clip) buffer[i] =  clip;
Chris@920:         }
Chris@920:         for (size_t i = 0; i < count; ++i) {
Chris@920:             if (buffer[i] < -clip) buffer[i] = -clip;
Chris@920:         }
Chris@297:     }
Chris@297: 
Chris@297:     m_frameCount += sz;
Chris@297: 
Chris@148:     switch (m_cacheMode) {
Chris@148: 
Chris@148:     case CacheInTemporaryFile:
Chris@544:         if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
Chris@544:             sf_close(m_cacheFileWritePtr);
Chris@544:             m_cacheFileWritePtr = 0;
Chris@544:             throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
Chris@544:         }
Chris@148:         break;
Chris@148: 
Chris@148:     case CacheInMemory:
Chris@543:         m_dataLock.lockForWrite();
Chris@322:         for (size_t s = 0; s < count; ++s) {
Chris@543:             m_data.push_back(buffer[s]);
Chris@297:         }
Chris@297: 	MUNLOCK_SAMPLEBLOCK(m_data);
Chris@543:         m_dataLock.unlock();
Chris@148:         break;
Chris@148:     }
Chris@758: }
Chris@757: 
Chris@758: void
Chris@758: CodedAudioFileReader::pushBufferResampling(float *buffer, size_t sz,
Chris@758:                                            float ratio, bool final)
Chris@758: {
Chris@759:     SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl;
Chris@757: 
Chris@759:     if (sz > 0) {
Chris@759: 
Chris@759:         size_t out = m_resampler->resampleInterleaved
Chris@759:             (buffer,
Chris@759:              m_resampleBuffer,
Chris@759:              sz,
Chris@759:              ratio,
Chris@759:              false);
Chris@759: 
Chris@759:         pushBufferNonResampling(m_resampleBuffer, out);
Chris@759:     }
Chris@757: 
Chris@758:     if (final) {
Chris@758: 
Chris@758:         size_t padFrames = 1;
Chris@758:         if (m_frameCount / ratio < m_fileFrameCount) {
Chris@758:             padFrames = m_fileFrameCount - (m_frameCount / ratio) + 1;
Chris@757:         }
Chris@758: 
Chris@758:         size_t padSamples = padFrames * m_channelCount;
Chris@758: 
Chris@759:         SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << m_frameCount / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl;
Chris@758: 
Chris@758:         float *padding = new float[padSamples];
Chris@758:         for (int i = 0; i < padSamples; ++i) padding[i] = 0.f;
Chris@758: 
Chris@759:         size_t out = m_resampler->resampleInterleaved
Chris@758:             (padding,
Chris@758:              m_resampleBuffer,
Chris@758:              padFrames,
Chris@758:              ratio,
Chris@758:              true);
Chris@758: 
Chris@759:         if (m_frameCount + out > int(m_fileFrameCount * ratio)) {
Chris@759:             out = int(m_fileFrameCount * ratio) - m_frameCount;
Chris@759:         }
Chris@759: 
Chris@758:         pushBufferNonResampling(m_resampleBuffer, out);
Chris@758:         delete[] padding;
Chris@757:     }
Chris@148: }
Chris@148: 
Chris@148: void
Chris@148: CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count,
Chris@148:                                            SampleBlock &frames) const
Chris@148: {
Chris@543:     // Lock is only required in CacheInMemory mode (the cache file
Chris@543:     // reader is expected to be thread safe and manage its own
Chris@543:     // locking)
Chris@263: 
Chris@265:     if (!m_initialised) {
Chris@690:         SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
Chris@265:         return;
Chris@265:     }
Chris@148: 
Chris@148:     switch (m_cacheMode) {
Chris@148: 
Chris@148:     case CacheInTemporaryFile:
Chris@148:         if (m_cacheFileReader) {
Chris@148:             m_cacheFileReader->getInterleavedFrames(start, count, frames);
Chris@148:         }
Chris@148:         break;
Chris@148: 
Chris@148:     case CacheInMemory:
Chris@148:     {
Chris@148:         frames.clear();
Chris@148:         if (!isOK()) return;
Chris@148:         if (count == 0) return;
Chris@543:         frames.reserve(count * m_channelCount);
Chris@148: 
Chris@543:         size_t idx = start * m_channelCount;
Chris@543:         size_t i = 0;
Chris@148: 
Chris@543:         m_dataLock.lockForRead();
Chris@543:         while (i < count * m_channelCount && idx < m_data.size()) {
Chris@543:             frames.push_back(m_data[idx]);
Chris@543:             ++idx;
Chris@148:         }
Chris@543:         m_dataLock.unlock();
Chris@148:     }
Chris@148:     }
Chris@920: 
Chris@920:     if (m_normalised) {
Chris@920:         for (int i = 0; i < (int)(count * m_channelCount); ++i) {
Chris@920:             frames[i] *= m_gain;
Chris@920:         }
Chris@920:     }
Chris@148: }
Chris@148: