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@148: #include <iostream>
Chris@148: #include <QDir>
Chris@263: #include <QMutexLocker>
Chris@148: 
Chris@297: CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode,
Chris@297:                                            size_t targetRate) :
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@297:     m_resampleBuffer(0)
Chris@148: {
Chris@327: //    std::cerr << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << std::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@297:     delete m_cacheFileReader;
Chris@297:     delete[] m_cacheWriteBuffer;
Chris@148: 
Chris@148:     if (m_cacheFileName != "") {
Chris@290:         if (!QFile(m_cacheFileName).remove()) {
Chris@290:             std::cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName.toStdString() << "\"" << std::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@297: //    std::cerr << "CodedAudioFileReader::startSerialised(" << id.toStdString() << ")" << std::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@297: //    std::cerr << "CodedAudioFileReader::endSerialised" << std::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@297:     std::cerr << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << std::endl;
Chris@297: 
Chris@297:     if (m_fileRate == 0) {
Chris@297:         std::cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << std::endl;
Chris@297:         m_fileRate = 48000; // got to have something
Chris@297:     }
Chris@297:     if (m_sampleRate == 0) {
Chris@297:         m_sampleRate = m_fileRate;
Chris@297:     }
Chris@297:     if (m_fileRate != m_sampleRate) {
Chris@297:         std::cerr << "CodedAudioFileReader: resampling " << m_fileRate << " -> " <<  m_sampleRate << std::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@297:             [lrintf(ceilf(m_cacheWriteBufferSize * m_channelCount * ratio))];
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@290:                     std::cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError().toStdString() << std::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@290:                 std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName.toStdString() << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << std::endl;
Chris@148:                 m_cacheMode = CacheInMemory;
Chris@148:             }
Chris@265: 
Chris@148:         } catch (DirectoryCreationFailed f) {
Chris@148:             std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << std::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@148:         std::cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << std::endl;
Chris@148:         return;
Chris@148:     }
Chris@148: 
Chris@297:     if (m_cacheWriteBufferIndex > 0) {
Chris@297:         //!!! check for return value! out of disk space, etc!
Chris@297:         pushBuffer(m_cacheWriteBuffer,
Chris@297:                    m_cacheWriteBufferIndex / m_channelCount,
Chris@297:                    true);
Chris@297:     }        
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@322:     float max = 1.0;
Chris@322:     size_t count = sz * m_channelCount;
Chris@318: 
Chris@297:     if (m_resampler) {
Chris@297:         
Chris@297:         float ratio = float(m_sampleRate) / float(m_fileRate);
Chris@297: 
Chris@297:         if (ratio != 1.f) {
Chris@322: 
Chris@297:             size_t out = m_resampler->resampleInterleaved
Chris@297:                 (buffer,
Chris@297:                  m_resampleBuffer,
Chris@297:                  sz,
Chris@297:                  ratio,
Chris@297:                  final);
Chris@297: 
Chris@297:             buffer = m_resampleBuffer;
Chris@297:             sz = out;
Chris@322:             count = sz * m_channelCount;
Chris@297:         }
Chris@322:     }
Chris@318: 
Chris@322:     for (size_t i = 0; i < count; ++i) {
Chris@322:         if (buffer[i] >  max) buffer[i] =  max;
Chris@322:     }
Chris@322:     for (size_t i = 0; i < count; ++i) {
Chris@322:         if (buffer[i] < -max) buffer[i] = -max;
Chris@297:     }
Chris@297: 
Chris@297:     m_frameCount += sz;
Chris@297: 
Chris@148:     switch (m_cacheMode) {
Chris@148: 
Chris@148:     case CacheInTemporaryFile:
Chris@297:         //!!! check for return value! out of disk space, etc!
Chris@297:         sf_writef_float(m_cacheFileWritePtr, buffer, sz);
Chris@148:         break;
Chris@148: 
Chris@148:     case CacheInMemory:
Chris@322:         for (size_t s = 0; s < count; ++s) {
Chris@322:             m_data.push_back(buffer[count]);
Chris@297:         }
Chris@297: 	MUNLOCK_SAMPLEBLOCK(m_data);
Chris@148:         break;
Chris@148:     }
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@263:     //!!! we want to ensure this doesn't require a lock -- at the
Chris@263:     // moment it does need one, but it doesn't have one...
Chris@263: 
Chris@265:     if (!m_initialised) {
Chris@265:         std::cerr << "CodedAudioFileReader::getInterleavedFrames: not initialised" << std::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@148: 
Chris@148:         // slownessabounds
Chris@148: 
Chris@148:         for (size_t i = start; i < start + count; ++i) {
Chris@148:             for (size_t ch = 0; ch < m_channelCount; ++ch) {
Chris@148:                 size_t index = i * m_channelCount + ch;
Chris@148:                 if (index >= m_data.size()) return;
Chris@148:                 frames.push_back(m_data[index]);
Chris@148:             }
Chris@148:         }
Chris@148:     }
Chris@148:     }
Chris@148: }
Chris@148: