Mercurial > hg > svcore
changeset 1124:efea94b04d5a 3.0-integration
Merge from branch recording
author | Chris Cannam |
---|---|
date | Thu, 20 Aug 2015 13:15:19 +0100 (2015-08-20) |
parents | fbc43d5dc3d1 (current diff) 133747edd76c (diff) |
children | 2c43f9904068 |
files | data/model/ReadOnlyWaveFileModel.cpp data/model/ReadOnlyWaveFileModel.h data/model/WaveFileModel.cpp data/model/WaveFileModel.h data/model/WritableWaveFileModel.cpp data/model/WritableWaveFileModel.h svcore.pro |
diffstat | 8 files changed, 901 insertions(+), 809 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/ReadOnlyWaveFileModel.cpp Thu Aug 20 13:15:19 2015 +0100 @@ -0,0 +1,721 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + 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 "ReadOnlyWaveFileModel.h" + +#include "fileio/AudioFileReader.h" +#include "fileio/AudioFileReaderFactory.h" + +#include "system/System.h" + +#include "base/Preferences.h" + +#include <QFileInfo> +#include <QTextStream> + +#include <iostream> +#include <unistd.h> +#include <cmath> +#include <sndfile.h> + +#include <cassert> + +using namespace std; + +//#define DEBUG_WAVE_FILE_MODEL 1 + +PowerOfSqrtTwoZoomConstraint +ReadOnlyWaveFileModel::m_zoomConstraint; + +ReadOnlyWaveFileModel::ReadOnlyWaveFileModel(FileSource source, sv_samplerate_t targetRate) : + m_source(source), + m_path(source.getLocation()), + m_reader(0), + m_myReader(true), + m_startFrame(0), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false), + m_lastDirectReadStart(0), + m_lastDirectReadCount(0) +{ + m_source.waitForData(); + if (m_source.isOK()) { + bool normalise = Preferences::getInstance()->getNormaliseAudio(); + m_reader = AudioFileReaderFactory::createThreadingReader + (m_source, targetRate, normalise); + if (m_reader) { + SVDEBUG << "ReadOnlyWaveFileModel::ReadOnlyWaveFileModel: reader rate: " + << m_reader->getSampleRate() << endl; + } + } + if (m_reader) setObjectName(m_reader->getTitle()); + if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); + if (isOK()) fillCache(); +} + +ReadOnlyWaveFileModel::ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader) : + m_source(source), + m_path(source.getLocation()), + m_reader(0), + m_myReader(false), + m_startFrame(0), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false) +{ + m_reader = reader; + if (m_reader) setObjectName(m_reader->getTitle()); + if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); + fillCache(); +} + +ReadOnlyWaveFileModel::~ReadOnlyWaveFileModel() +{ + m_exiting = true; + if (m_fillThread) m_fillThread->wait(); + if (m_myReader) delete m_reader; + m_reader = 0; +} + +bool +ReadOnlyWaveFileModel::isOK() const +{ + return m_reader && m_reader->isOK(); +} + +bool +ReadOnlyWaveFileModel::isReady(int *completion) const +{ + bool ready = (isOK() && (m_fillThread == 0)); + double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame()); + static int prevCompletion = 0; + if (completion) { + *completion = int(c * 100.0 + 0.01); + if (m_reader) { + int decodeCompletion = m_reader->getDecodeCompletion(); + if (decodeCompletion < 90) *completion = decodeCompletion; + else *completion = min(*completion, decodeCompletion); + } + if (*completion != 0 && + *completion != 100 && + prevCompletion != 0 && + prevCompletion > *completion) { + // just to avoid completion going backwards + *completion = prevCompletion; + } + prevCompletion = *completion; + } +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl; +#endif + return ready; +} + +sv_frame_t +ReadOnlyWaveFileModel::getFrameCount() const +{ + if (!m_reader) return 0; + return m_reader->getFrameCount(); +} + +int +ReadOnlyWaveFileModel::getChannelCount() const +{ + if (!m_reader) return 0; + return m_reader->getChannelCount(); +} + +sv_samplerate_t +ReadOnlyWaveFileModel::getSampleRate() const +{ + if (!m_reader) return 0; + return m_reader->getSampleRate(); +} + +sv_samplerate_t +ReadOnlyWaveFileModel::getNativeRate() const +{ + if (!m_reader) return 0; + sv_samplerate_t rate = m_reader->getNativeRate(); + if (rate == 0) rate = getSampleRate(); + return rate; +} + +QString +ReadOnlyWaveFileModel::getTitle() const +{ + QString title; + if (m_reader) title = m_reader->getTitle(); + if (title == "") title = objectName(); + return title; +} + +QString +ReadOnlyWaveFileModel::getMaker() const +{ + if (m_reader) return m_reader->getMaker(); + return ""; +} + +QString +ReadOnlyWaveFileModel::getLocation() const +{ + if (m_reader) return m_reader->getLocation(); + return ""; +} + +QString +ReadOnlyWaveFileModel::getLocalFilename() const +{ + if (m_reader) return m_reader->getLocalFilename(); + return ""; +} + +vector<float> +ReadOnlyWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const +{ + // Read directly from the file. This is used for e.g. audio + // playback or input to transforms. + +#ifdef DEBUG_WAVE_FILE_MODEL + cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl; +#endif + + int channels = getChannelCount(); + + if (channel >= channels) { + cerr << "ERROR: WaveFileModel::getData: channel (" + << channel << ") >= channel count (" << channels << ")" + << endl; + return {}; + } + + if (!m_reader || !m_reader->isOK() || count == 0) { + return {}; + } + + if (start >= m_startFrame) { + start -= m_startFrame; + } else { + if (count <= m_startFrame - start) { + return {}; + } else { + count -= (m_startFrame - start); + start = 0; + } + } + + vector<float> interleaved = m_reader->getInterleavedFrames(start, count); + if (channels == 1) return interleaved; + + sv_frame_t obtained = interleaved.size() / channels; + + vector<float> result(obtained, 0.f); + + if (channel != -1) { + // get a single channel + for (int i = 0; i < obtained; ++i) { + result[i] = interleaved[i * channels + channel]; + } + } else { + // channel == -1, mix down all channels + for (int c = 0; c < channels; ++c) { + for (int i = 0; i < obtained; ++i) { + result[i] += interleaved[i * channels + c]; + } + } + } + + return result; +} + +vector<vector<float>> +ReadOnlyWaveFileModel::getMultiChannelData(int fromchannel, int tochannel, + sv_frame_t start, sv_frame_t count) const +{ + // Read directly from the file. This is used for e.g. audio + // playback or input to transforms. + +#ifdef DEBUG_WAVE_FILE_MODEL + cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl; +#endif + + int channels = getChannelCount(); + + if (fromchannel > tochannel) { + cerr << "ERROR: ReadOnlyWaveFileModel::getData: fromchannel (" + << fromchannel << ") > tochannel (" << tochannel << ")" + << endl; + return {}; + } + + if (tochannel >= channels) { + cerr << "ERROR: ReadOnlyWaveFileModel::getData: tochannel (" + << tochannel << ") >= channel count (" << channels << ")" + << endl; + return {}; + } + + if (!m_reader || !m_reader->isOK() || count == 0) { + return {}; + } + + int reqchannels = (tochannel - fromchannel) + 1; + + if (start >= m_startFrame) { + start -= m_startFrame; + } else { + if (count <= m_startFrame - start) { + return {}; + } else { + count -= (m_startFrame - start); + start = 0; + } + } + + vector<float> interleaved = m_reader->getInterleavedFrames(start, count); + if (channels == 1) return { interleaved }; + + sv_frame_t obtained = interleaved.size() / channels; + vector<vector<float>> result(reqchannels, vector<float>(obtained, 0.f)); + + for (int c = fromchannel; c <= tochannel; ++c) { + int destc = c - fromchannel; + for (int i = 0; i < obtained; ++i) { + result[destc][i] = interleaved[i * channels + c]; + } + } + + return result; +} + +int +ReadOnlyWaveFileModel::getSummaryBlockSize(int desired) const +{ + int cacheType = 0; + int power = m_zoomConstraint.getMinCachePower(); + int roundedBlockSize = m_zoomConstraint.getNearestBlockSize + (desired, cacheType, power, ZoomConstraint::RoundDown); + if (cacheType != 0 && cacheType != 1) { + // We will be reading directly from file, so can satisfy any + // blocksize requirement + return desired; + } else { + return roundedBlockSize; + } +} + +void +ReadOnlyWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count, + RangeBlock &ranges, int &blockSize) const +{ + ranges.clear(); + if (!isOK()) return; + ranges.reserve((count / blockSize) + 1); + + if (start > m_startFrame) start -= m_startFrame; + else if (count <= m_startFrame - start) return; + else { + count -= (m_startFrame - start); + start = 0; + } + + int cacheType = 0; + int power = m_zoomConstraint.getMinCachePower(); + int roundedBlockSize = m_zoomConstraint.getNearestBlockSize + (blockSize, cacheType, power, ZoomConstraint::RoundDown); + + int channels = getChannelCount(); + + if (cacheType != 0 && cacheType != 1) { + + // We need to read directly from the file. We haven't got + // this cached. Hope the requested area is small. This is + // not optimal -- we'll end up reading the same frames twice + // for stereo files, in two separate calls to this method. + // We could fairly trivially handle this for most cases that + // matter by putting a single cache in getInterleavedFrames + // for short queries. + + m_directReadMutex.lock(); + + if (m_lastDirectReadStart != start || + m_lastDirectReadCount != count || + m_directRead.empty()) { + + m_directRead = m_reader->getInterleavedFrames(start, count); + m_lastDirectReadStart = start; + m_lastDirectReadCount = count; + } + + float max = 0.0, min = 0.0, total = 0.0; + sv_frame_t i = 0, got = 0; + + while (i < count) { + + sv_frame_t index = i * channels + channel; + if (index >= (sv_frame_t)m_directRead.size()) break; + + float sample = m_directRead[index]; + if (sample > max || got == 0) max = sample; + if (sample < min || got == 0) min = sample; + total += fabsf(sample); + + ++i; + ++got; + + if (got == blockSize) { + ranges.push_back(Range(min, max, total / float(got))); + min = max = total = 0.0f; + got = 0; + } + } + + m_directReadMutex.unlock(); + + if (got > 0) { + ranges.push_back(Range(min, max, total / float(got))); + } + + return; + + } else { + + QMutexLocker locker(&m_mutex); + + const RangeBlock &cache = m_cache[cacheType]; + + blockSize = roundedBlockSize; + + sv_frame_t cacheBlock, div; + + if (cacheType == 0) { + cacheBlock = (1 << m_zoomConstraint.getMinCachePower()); + div = (1 << power) / cacheBlock; + } else { + cacheBlock = sv_frame_t((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01); + div = sv_frame_t(((1 << power) * sqrt(2.) + 0.01) / double(cacheBlock)); + } + + sv_frame_t startIndex = start / cacheBlock; + sv_frame_t endIndex = (start + count) / cacheBlock; + + float max = 0.0, min = 0.0, total = 0.0; + sv_frame_t i = 0, got = 0; + +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl; +#endif + + for (i = 0; i <= endIndex - startIndex; ) { + + sv_frame_t index = (i + startIndex) * channels + channel; + if (index >= (sv_frame_t)cache.size()) break; + + const Range &range = cache[index]; + if (range.max() > max || got == 0) max = range.max(); + if (range.min() < min || got == 0) min = range.min(); + total += range.absmean(); + + ++i; + ++got; + + if (got == div) { + ranges.push_back(Range(min, max, total / float(got))); + min = max = total = 0.0f; + got = 0; + } + } + + if (got > 0) { + ranges.push_back(Range(min, max, total / float(got))); + } + } + +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "returning " << ranges.size() << " ranges" << endl; +#endif + return; +} + +ReadOnlyWaveFileModel::Range +ReadOnlyWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const +{ + Range range; + if (!isOK()) return range; + + if (start > m_startFrame) start -= m_startFrame; + else if (count <= m_startFrame - start) return range; + else { + count -= (m_startFrame - start); + start = 0; + } + + int blockSize; + for (blockSize = 1; blockSize <= count; blockSize *= 2); + if (blockSize > 1) blockSize /= 2; + + bool first = false; + + sv_frame_t blockStart = (start / blockSize) * blockSize; + sv_frame_t blockEnd = ((start + count) / blockSize) * blockSize; + + if (blockStart < start) blockStart += blockSize; + + if (blockEnd > blockStart) { + RangeBlock ranges; + getSummaries(channel, blockStart, blockEnd - blockStart, ranges, blockSize); + for (int i = 0; i < (int)ranges.size(); ++i) { + if (first || ranges[i].min() < range.min()) range.setMin(ranges[i].min()); + if (first || ranges[i].max() > range.max()) range.setMax(ranges[i].max()); + if (first || ranges[i].absmean() < range.absmean()) range.setAbsmean(ranges[i].absmean()); + first = false; + } + } + + if (blockStart > start) { + Range startRange = getSummary(channel, start, blockStart - start); + range.setMin(min(range.min(), startRange.min())); + range.setMax(max(range.max(), startRange.max())); + range.setAbsmean(min(range.absmean(), startRange.absmean())); + } + + if (blockEnd < start + count) { + Range endRange = getSummary(channel, blockEnd, start + count - blockEnd); + range.setMin(min(range.min(), endRange.min())); + range.setMax(max(range.max(), endRange.max())); + range.setAbsmean(min(range.absmean(), endRange.absmean())); + } + + return range; +} + +void +ReadOnlyWaveFileModel::fillCache() +{ + m_mutex.lock(); + + m_updateTimer = new QTimer(this); + connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); + m_updateTimer->start(100); + + m_fillThread = new RangeCacheFillThread(*this); + connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled())); + + m_mutex.unlock(); + m_fillThread->start(); + +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::fillCache: started fill thread" << endl; +#endif +} + +void +ReadOnlyWaveFileModel::fillTimerTimedOut() +{ + if (m_fillThread) { + sv_frame_t fillExtent = m_fillThread->getFillExtent(); +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl; +#endif + if (fillExtent > m_lastFillExtent) { + emit modelChangedWithin(m_lastFillExtent, fillExtent); + m_lastFillExtent = fillExtent; + } + } else { +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: no thread" << endl; +#endif + emit modelChanged(); + } +} + +void +ReadOnlyWaveFileModel::cacheFilled() +{ + m_mutex.lock(); + delete m_fillThread; + m_fillThread = 0; + delete m_updateTimer; + m_updateTimer = 0; + m_mutex.unlock(); + if (getEndFrame() > m_lastFillExtent) { + emit modelChangedWithin(m_lastFillExtent, getEndFrame()); + } + emit modelChanged(); + emit ready(); +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::cacheFilled" << endl; +#endif +} + +void +ReadOnlyWaveFileModel::RangeCacheFillThread::run() +{ + int cacheBlockSize[2]; + cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower()); + cacheBlockSize[1] = (int((1 << m_model.m_zoomConstraint.getMinCachePower()) * + sqrt(2.) + 0.01)); + + sv_frame_t frame = 0; + const sv_frame_t readBlockSize = 16384; + vector<float> block; + + if (!m_model.isOK()) return; + + int channels = m_model.getChannelCount(); + bool updating = m_model.m_reader->isUpdating(); + + if (updating) { + while (channels == 0 && !m_model.m_exiting) { +// SVDEBUG << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl; + sleep(1); + channels = m_model.getChannelCount(); + } + } + + Range *range = new Range[2 * channels]; + float *means = new float[2 * channels]; + int count[2]; + count[0] = count[1] = 0; + for (int i = 0; i < 2 * channels; ++i) { + means[i] = 0.f; + } + + bool first = true; + + while (first || updating) { + + updating = m_model.m_reader->isUpdating(); + m_frameCount = m_model.getFrameCount(); + +// SVDEBUG << "ReadOnlyWaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl; + + while (frame < m_frameCount) { + +// SVDEBUG << "ReadOnlyWaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl; + + if (updating && (frame + readBlockSize > m_frameCount)) break; + + block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize); + +// cerr << "block is " << block.size() << endl; + + for (sv_frame_t i = 0; i < readBlockSize; ++i) { + + if (channels * i + channels > (int)block.size()) break; + + for (int ch = 0; ch < channels; ++ch) { + + sv_frame_t index = channels * i + ch; + float sample = block[index]; + + for (int cacheType = 0; cacheType < 2; ++cacheType) { // cache type + + sv_frame_t rangeIndex = ch * 2 + cacheType; + range[rangeIndex].sample(sample); + means[rangeIndex] += fabsf(sample); + } + } + + //!!! this looks like a ludicrous way to do synchronisation + QMutexLocker locker(&m_model.m_mutex); + + for (int cacheType = 0; cacheType < 2; ++cacheType) { + + if (++count[cacheType] == cacheBlockSize[cacheType]) { + + for (int ch = 0; ch < int(channels); ++ch) { + int rangeIndex = ch * 2 + cacheType; + means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); + range[rangeIndex].setAbsmean(means[rangeIndex]); + m_model.m_cache[cacheType].push_back(range[rangeIndex]); + range[rangeIndex] = Range(); + means[rangeIndex] = 0.f; + } + + count[cacheType] = 0; + } + } + + ++frame; + } + + if (m_model.m_exiting) break; + + m_fillExtent = frame; + } + +// cerr << "ReadOnlyWaveFileModel: inner loop ended" << endl; + + first = false; + if (m_model.m_exiting) break; + if (updating) { +// cerr << "sleeping..." << endl; + sleep(1); + } + } + + if (!m_model.m_exiting) { + + QMutexLocker locker(&m_model.m_mutex); + + for (int cacheType = 0; cacheType < 2; ++cacheType) { + + if (count[cacheType] > 0) { + + for (int ch = 0; ch < int(channels); ++ch) { + int rangeIndex = ch * 2 + cacheType; + means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); + range[rangeIndex].setAbsmean(means[rangeIndex]); + m_model.m_cache[cacheType].push_back(range[rangeIndex]); + range[rangeIndex] = Range(); + means[rangeIndex] = 0.f; + } + + count[cacheType] = 0; + } + + const Range &rr = *m_model.m_cache[cacheType].begin(); + MUNLOCK(&rr, m_model.m_cache[cacheType].capacity() * sizeof(Range)); + } + } + + delete[] means; + delete[] range; + + m_fillExtent = m_frameCount; + +#ifdef DEBUG_WAVE_FILE_MODEL + for (int cacheType = 0; cacheType < 2; ++cacheType) { + cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl; + } +#endif +} + +void +ReadOnlyWaveFileModel::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + Model::toXml(out, indent, + QString("type=\"wavefile\" file=\"%1\" %2") + .arg(encodeEntities(m_path)).arg(extraAttributes)); +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/ReadOnlyWaveFileModel.h Thu Aug 20 13:15:19 2015 +0100 @@ -0,0 +1,131 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam and QMUL. + + 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. +*/ + +#ifndef READ_ONLY_WAVE_FILE_MODEL_H +#define READ_ONLY_WAVE_FILE_MODEL_H + +#include "WaveFileModel.h" + +#include "base/Thread.h" +#include <QMutex> +#include <QTimer> + +#include "data/fileio/FileSource.h" + +#include "RangeSummarisableTimeValueModel.h" +#include "PowerOfSqrtTwoZoomConstraint.h" + +#include <stdlib.h> + +class AudioFileReader; + +class ReadOnlyWaveFileModel : public WaveFileModel +{ + Q_OBJECT + +public: + ReadOnlyWaveFileModel(FileSource source, sv_samplerate_t targetRate = 0); + ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader); + ~ReadOnlyWaveFileModel(); + + bool isOK() const; + bool isReady(int *) const; + + const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; } + + sv_frame_t getFrameCount() const; + int getChannelCount() const; + sv_samplerate_t getSampleRate() const; + sv_samplerate_t getNativeRate() const; + + QString getTitle() const; + QString getMaker() const; + QString getLocation() const; + + QString getLocalFilename() const; + + float getValueMinimum() const { return -1.0f; } + float getValueMaximum() const { return 1.0f; } + + virtual sv_frame_t getStartFrame() const { return m_startFrame; } + virtual sv_frame_t getEndFrame() const { return m_startFrame + getFrameCount(); } + + void setStartFrame(sv_frame_t startFrame) { m_startFrame = startFrame; } + + virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const; + + virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; + + virtual int getSummaryBlockSize(int desired) const; + + virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count, + RangeBlock &ranges, + int &blockSize) const; + + virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const; + + QString getTypeName() const { return tr("Wave File"); } + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const; + +protected slots: + void fillTimerTimedOut(); + void cacheFilled(); + +protected: + void initialize(); + + class RangeCacheFillThread : public Thread + { + public: + RangeCacheFillThread(ReadOnlyWaveFileModel &model) : + m_model(model), m_fillExtent(0), + m_frameCount(model.getFrameCount()) { } + + sv_frame_t getFillExtent() const { return m_fillExtent; } + virtual void run(); + + protected: + ReadOnlyWaveFileModel &m_model; + sv_frame_t m_fillExtent; + sv_frame_t m_frameCount; + }; + + void fillCache(); + + FileSource m_source; + QString m_path; + AudioFileReader *m_reader; + bool m_myReader; + + sv_frame_t m_startFrame; + + RangeBlock m_cache[2]; // interleaved at two base resolutions + mutable QMutex m_mutex; + RangeCacheFillThread *m_fillThread; + QTimer *m_updateTimer; + sv_frame_t m_lastFillExtent; + bool m_exiting; + static PowerOfSqrtTwoZoomConstraint m_zoomConstraint; + + mutable std::vector<float> m_directRead; + mutable sv_frame_t m_lastDirectReadStart; + mutable sv_frame_t m_lastDirectReadCount; + mutable QMutex m_directReadMutex; +}; + +#endif
--- a/data/model/WaveFileModel.cpp Tue Aug 04 16:39:40 2015 +0100 +++ b/data/model/WaveFileModel.cpp Thu Aug 20 13:15:19 2015 +0100 @@ -15,707 +15,7 @@ #include "WaveFileModel.h" -#include "fileio/AudioFileReader.h" -#include "fileio/AudioFileReaderFactory.h" - -#include "system/System.h" - -#include "base/Preferences.h" - -#include <QFileInfo> -#include <QTextStream> - -#include <iostream> -#include <unistd.h> -#include <cmath> -#include <sndfile.h> - -#include <cassert> - -using namespace std; - -//#define DEBUG_WAVE_FILE_MODEL 1 - -PowerOfSqrtTwoZoomConstraint -WaveFileModel::m_zoomConstraint; - -WaveFileModel::WaveFileModel(FileSource source, sv_samplerate_t targetRate) : - m_source(source), - m_path(source.getLocation()), - m_reader(0), - m_myReader(true), - m_startFrame(0), - m_fillThread(0), - m_updateTimer(0), - m_lastFillExtent(0), - m_exiting(false), - m_lastDirectReadStart(0), - m_lastDirectReadCount(0) +WaveFileModel::~WaveFileModel() { - m_source.waitForData(); - if (m_source.isOK()) { - bool normalise = Preferences::getInstance()->getNormaliseAudio(); - m_reader = AudioFileReaderFactory::createThreadingReader - (m_source, targetRate, normalise); - if (m_reader) { - SVDEBUG << "WaveFileModel::WaveFileModel: reader rate: " - << m_reader->getSampleRate() << endl; - } - } - if (m_reader) setObjectName(m_reader->getTitle()); - if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); - if (isOK()) fillCache(); } -WaveFileModel::WaveFileModel(FileSource source, AudioFileReader *reader) : - m_source(source), - m_path(source.getLocation()), - m_reader(0), - m_myReader(false), - m_startFrame(0), - m_fillThread(0), - m_updateTimer(0), - m_lastFillExtent(0), - m_exiting(false) -{ - m_reader = reader; - if (m_reader) setObjectName(m_reader->getTitle()); - if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); - fillCache(); -} - -WaveFileModel::~WaveFileModel() -{ - m_exiting = true; - if (m_fillThread) m_fillThread->wait(); - if (m_myReader) delete m_reader; - m_reader = 0; -} - -bool -WaveFileModel::isOK() const -{ - return m_reader && m_reader->isOK(); -} - -bool -WaveFileModel::isReady(int *completion) const -{ - bool ready = (isOK() && (m_fillThread == 0)); - double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame()); - static int prevCompletion = 0; - if (completion) { - *completion = int(c * 100.0 + 0.01); - if (m_reader) { - int decodeCompletion = m_reader->getDecodeCompletion(); - if (decodeCompletion < 90) *completion = decodeCompletion; - else *completion = min(*completion, decodeCompletion); - } - if (*completion != 0 && - *completion != 100 && - prevCompletion != 0 && - prevCompletion > *completion) { - // just to avoid completion going backwards - *completion = prevCompletion; - } - prevCompletion = *completion; - } -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl; -#endif - return ready; -} - -sv_frame_t -WaveFileModel::getFrameCount() const -{ - if (!m_reader) return 0; - return m_reader->getFrameCount(); -} - -int -WaveFileModel::getChannelCount() const -{ - if (!m_reader) return 0; - return m_reader->getChannelCount(); -} - -sv_samplerate_t -WaveFileModel::getSampleRate() const -{ - if (!m_reader) return 0; - return m_reader->getSampleRate(); -} - -sv_samplerate_t -WaveFileModel::getNativeRate() const -{ - if (!m_reader) return 0; - sv_samplerate_t rate = m_reader->getNativeRate(); - if (rate == 0) rate = getSampleRate(); - return rate; -} - -QString -WaveFileModel::getTitle() const -{ - QString title; - if (m_reader) title = m_reader->getTitle(); - if (title == "") title = objectName(); - return title; -} - -QString -WaveFileModel::getMaker() const -{ - if (m_reader) return m_reader->getMaker(); - return ""; -} - -QString -WaveFileModel::getLocation() const -{ - if (m_reader) return m_reader->getLocation(); - return ""; -} - -QString -WaveFileModel::getLocalFilename() const -{ - if (m_reader) return m_reader->getLocalFilename(); - return ""; -} - -vector<float> -WaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const -{ - // Read directly from the file. This is used for e.g. audio - // playback or input to transforms. - -#ifdef DEBUG_WAVE_FILE_MODEL - cout << "WaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl; -#endif - - int channels = getChannelCount(); - - if (channel >= channels) { - cerr << "ERROR: WaveFileModel::getData: channel (" - << channel << ") >= channel count (" << channels << ")" - << endl; - return {}; - } - - if (!m_reader || !m_reader->isOK() || count == 0) { - return {}; - } - - if (start >= m_startFrame) { - start -= m_startFrame; - } else { - if (count <= m_startFrame - start) { - return {}; - } else { - count -= (m_startFrame - start); - start = 0; - } - } - - vector<float> interleaved = m_reader->getInterleavedFrames(start, count); - if (channels == 1) return interleaved; - - sv_frame_t obtained = interleaved.size() / channels; - - vector<float> result(obtained, 0.f); - - if (channel != -1) { - // get a single channel - for (int i = 0; i < obtained; ++i) { - result[i] = interleaved[i * channels + channel]; - } - } else { - // channel == -1, mix down all channels - for (int c = 0; c < channels; ++c) { - for (int i = 0; i < obtained; ++i) { - result[i] += interleaved[i * channels + c]; - } - } - } - - return result; -} - -vector<vector<float>> -WaveFileModel::getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count) const -{ - // Read directly from the file. This is used for e.g. audio - // playback or input to transforms. - -#ifdef DEBUG_WAVE_FILE_MODEL - cout << "WaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl; -#endif - - int channels = getChannelCount(); - - if (fromchannel > tochannel) { - cerr << "ERROR: WaveFileModel::getData: fromchannel (" - << fromchannel << ") > tochannel (" << tochannel << ")" - << endl; - return {}; - } - - if (tochannel >= channels) { - cerr << "ERROR: WaveFileModel::getData: tochannel (" - << tochannel << ") >= channel count (" << channels << ")" - << endl; - return {}; - } - - if (!m_reader || !m_reader->isOK() || count == 0) { - return {}; - } - - int reqchannels = (tochannel - fromchannel) + 1; - - if (start >= m_startFrame) { - start -= m_startFrame; - } else { - if (count <= m_startFrame - start) { - return {}; - } else { - count -= (m_startFrame - start); - start = 0; - } - } - - vector<float> interleaved = m_reader->getInterleavedFrames(start, count); - if (channels == 1) return { interleaved }; - - sv_frame_t obtained = interleaved.size() / channels; - vector<vector<float>> result(reqchannels, vector<float>(obtained, 0.f)); - - for (int c = fromchannel; c <= tochannel; ++c) { - int destc = c - fromchannel; - for (int i = 0; i < obtained; ++i) { - result[destc][i] = interleaved[i * channels + c]; - } - } - - return result; -} - -int -WaveFileModel::getSummaryBlockSize(int desired) const -{ - int cacheType = 0; - int power = m_zoomConstraint.getMinCachePower(); - int roundedBlockSize = m_zoomConstraint.getNearestBlockSize - (desired, cacheType, power, ZoomConstraint::RoundDown); - if (cacheType != 0 && cacheType != 1) { - // We will be reading directly from file, so can satisfy any - // blocksize requirement - return desired; - } else { - return roundedBlockSize; - } -} - -void -WaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count, - RangeBlock &ranges, int &blockSize) const -{ - ranges.clear(); - if (!isOK()) return; - ranges.reserve((count / blockSize) + 1); - - if (start > m_startFrame) start -= m_startFrame; - else if (count <= m_startFrame - start) return; - else { - count -= (m_startFrame - start); - start = 0; - } - - int cacheType = 0; - int power = m_zoomConstraint.getMinCachePower(); - int roundedBlockSize = m_zoomConstraint.getNearestBlockSize - (blockSize, cacheType, power, ZoomConstraint::RoundDown); - - int channels = getChannelCount(); - - if (cacheType != 0 && cacheType != 1) { - - // We need to read directly from the file. We haven't got - // this cached. Hope the requested area is small. This is - // not optimal -- we'll end up reading the same frames twice - // for stereo files, in two separate calls to this method. - // We could fairly trivially handle this for most cases that - // matter by putting a single cache in getInterleavedFrames - // for short queries. - - m_directReadMutex.lock(); - - if (m_lastDirectReadStart != start || - m_lastDirectReadCount != count || - m_directRead.empty()) { - - m_directRead = m_reader->getInterleavedFrames(start, count); - m_lastDirectReadStart = start; - m_lastDirectReadCount = count; - } - - float max = 0.0, min = 0.0, total = 0.0; - sv_frame_t i = 0, got = 0; - - while (i < count) { - - sv_frame_t index = i * channels + channel; - if (index >= (sv_frame_t)m_directRead.size()) break; - - float sample = m_directRead[index]; - if (sample > max || got == 0) max = sample; - if (sample < min || got == 0) min = sample; - total += fabsf(sample); - - ++i; - ++got; - - if (got == blockSize) { - ranges.push_back(Range(min, max, total / float(got))); - min = max = total = 0.0f; - got = 0; - } - } - - m_directReadMutex.unlock(); - - if (got > 0) { - ranges.push_back(Range(min, max, total / float(got))); - } - - return; - - } else { - - QMutexLocker locker(&m_mutex); - - const RangeBlock &cache = m_cache[cacheType]; - - blockSize = roundedBlockSize; - - sv_frame_t cacheBlock, div; - - if (cacheType == 0) { - cacheBlock = (1 << m_zoomConstraint.getMinCachePower()); - div = (1 << power) / cacheBlock; - } else { - cacheBlock = sv_frame_t((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01); - div = sv_frame_t(((1 << power) * sqrt(2.) + 0.01) / double(cacheBlock)); - } - - sv_frame_t startIndex = start / cacheBlock; - sv_frame_t endIndex = (start + count) / cacheBlock; - - float max = 0.0, min = 0.0, total = 0.0; - sv_frame_t i = 0, got = 0; - -#ifdef DEBUG_WAVE_FILE_MODEL - cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl; -#endif - - for (i = 0; i <= endIndex - startIndex; ) { - - sv_frame_t index = (i + startIndex) * channels + channel; - if (index >= (sv_frame_t)cache.size()) break; - - const Range &range = cache[index]; - if (range.max() > max || got == 0) max = range.max(); - if (range.min() < min || got == 0) min = range.min(); - total += range.absmean(); - - ++i; - ++got; - - if (got == div) { - ranges.push_back(Range(min, max, total / float(got))); - min = max = total = 0.0f; - got = 0; - } - } - - if (got > 0) { - ranges.push_back(Range(min, max, total / float(got))); - } - } - -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "returning " << ranges.size() << " ranges" << endl; -#endif - return; -} - -WaveFileModel::Range -WaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const -{ - Range range; - if (!isOK()) return range; - - if (start > m_startFrame) start -= m_startFrame; - else if (count <= m_startFrame - start) return range; - else { - count -= (m_startFrame - start); - start = 0; - } - - int blockSize; - for (blockSize = 1; blockSize <= count; blockSize *= 2); - if (blockSize > 1) blockSize /= 2; - - bool first = false; - - sv_frame_t blockStart = (start / blockSize) * blockSize; - sv_frame_t blockEnd = ((start + count) / blockSize) * blockSize; - - if (blockStart < start) blockStart += blockSize; - - if (blockEnd > blockStart) { - RangeBlock ranges; - getSummaries(channel, blockStart, blockEnd - blockStart, ranges, blockSize); - for (int i = 0; i < (int)ranges.size(); ++i) { - if (first || ranges[i].min() < range.min()) range.setMin(ranges[i].min()); - if (first || ranges[i].max() > range.max()) range.setMax(ranges[i].max()); - if (first || ranges[i].absmean() < range.absmean()) range.setAbsmean(ranges[i].absmean()); - first = false; - } - } - - if (blockStart > start) { - Range startRange = getSummary(channel, start, blockStart - start); - range.setMin(min(range.min(), startRange.min())); - range.setMax(max(range.max(), startRange.max())); - range.setAbsmean(min(range.absmean(), startRange.absmean())); - } - - if (blockEnd < start + count) { - Range endRange = getSummary(channel, blockEnd, start + count - blockEnd); - range.setMin(min(range.min(), endRange.min())); - range.setMax(max(range.max(), endRange.max())); - range.setAbsmean(min(range.absmean(), endRange.absmean())); - } - - return range; -} - -void -WaveFileModel::fillCache() -{ - m_mutex.lock(); - - m_updateTimer = new QTimer(this); - connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); - m_updateTimer->start(100); - - m_fillThread = new RangeCacheFillThread(*this); - connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled())); - - m_mutex.unlock(); - m_fillThread->start(); - -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::fillCache: started fill thread" << endl; -#endif -} - -void -WaveFileModel::fillTimerTimedOut() -{ - if (m_fillThread) { - sv_frame_t fillExtent = m_fillThread->getFillExtent(); -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl; -#endif - if (fillExtent > m_lastFillExtent) { - emit modelChangedWithin(m_lastFillExtent, fillExtent); - m_lastFillExtent = fillExtent; - } - } else { -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::fillTimerTimedOut: no thread" << endl; -#endif - emit modelChanged(); - } -} - -void -WaveFileModel::cacheFilled() -{ - m_mutex.lock(); - delete m_fillThread; - m_fillThread = 0; - delete m_updateTimer; - m_updateTimer = 0; - m_mutex.unlock(); - if (getEndFrame() > m_lastFillExtent) { - emit modelChangedWithin(m_lastFillExtent, getEndFrame()); - } - emit modelChanged(); - emit ready(); -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::cacheFilled" << endl; -#endif -} - -void -WaveFileModel::RangeCacheFillThread::run() -{ - int cacheBlockSize[2]; - cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower()); - cacheBlockSize[1] = (int((1 << m_model.m_zoomConstraint.getMinCachePower()) * - sqrt(2.) + 0.01)); - - sv_frame_t frame = 0; - const sv_frame_t readBlockSize = 16384; - vector<float> block; - - if (!m_model.isOK()) return; - - int channels = m_model.getChannelCount(); - bool updating = m_model.m_reader->isUpdating(); - - if (updating) { - while (channels == 0 && !m_model.m_exiting) { -// SVDEBUG << "WaveFileModel::fill: Waiting for channels..." << endl; - sleep(1); - channels = m_model.getChannelCount(); - } - } - - Range *range = new Range[2 * channels]; - float *means = new float[2 * channels]; - int count[2]; - count[0] = count[1] = 0; - for (int i = 0; i < 2 * channels; ++i) { - means[i] = 0.f; - } - - bool first = true; - - while (first || updating) { - - updating = m_model.m_reader->isUpdating(); - m_frameCount = m_model.getFrameCount(); - -// SVDEBUG << "WaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl; - - while (frame < m_frameCount) { - -// SVDEBUG << "WaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl; - - if (updating && (frame + readBlockSize > m_frameCount)) break; - - block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize); - -// cerr << "block is " << block.size() << endl; - - for (sv_frame_t i = 0; i < readBlockSize; ++i) { - - if (channels * i + channels > (int)block.size()) break; - - for (int ch = 0; ch < channels; ++ch) { - - sv_frame_t index = channels * i + ch; - float sample = block[index]; - - for (int cacheType = 0; cacheType < 2; ++cacheType) { // cache type - - sv_frame_t rangeIndex = ch * 2 + cacheType; - range[rangeIndex].sample(sample); - means[rangeIndex] += fabsf(sample); - } - } - - //!!! this looks like a ludicrous way to do synchronisation - QMutexLocker locker(&m_model.m_mutex); - - for (int cacheType = 0; cacheType < 2; ++cacheType) { - - if (++count[cacheType] == cacheBlockSize[cacheType]) { - - for (int ch = 0; ch < int(channels); ++ch) { - int rangeIndex = ch * 2 + cacheType; - means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); - range[rangeIndex].setAbsmean(means[rangeIndex]); - m_model.m_cache[cacheType].push_back(range[rangeIndex]); - range[rangeIndex] = Range(); - means[rangeIndex] = 0.f; - } - - count[cacheType] = 0; - } - } - - ++frame; - } - - if (m_model.m_exiting) break; - - m_fillExtent = frame; - } - -// cerr << "WaveFileModel: inner loop ended" << endl; - - first = false; - if (m_model.m_exiting) break; - if (updating) { -// cerr << "sleeping..." << endl; - sleep(1); - } - } - - if (!m_model.m_exiting) { - - QMutexLocker locker(&m_model.m_mutex); - - for (int cacheType = 0; cacheType < 2; ++cacheType) { - - if (count[cacheType] > 0) { - - for (int ch = 0; ch < int(channels); ++ch) { - int rangeIndex = ch * 2 + cacheType; - means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); - range[rangeIndex].setAbsmean(means[rangeIndex]); - m_model.m_cache[cacheType].push_back(range[rangeIndex]); - range[rangeIndex] = Range(); - means[rangeIndex] = 0.f; - } - - count[cacheType] = 0; - } - - const Range &rr = *m_model.m_cache[cacheType].begin(); - MUNLOCK(&rr, m_model.m_cache[cacheType].capacity() * sizeof(Range)); - } - } - - delete[] means; - delete[] range; - - m_fillExtent = m_frameCount; - -#ifdef DEBUG_WAVE_FILE_MODEL - for (int cacheType = 0; cacheType < 2; ++cacheType) { - cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl; - } -#endif -} - -void -WaveFileModel::toXml(QTextStream &out, - QString indent, - QString extraAttributes) const -{ - Model::toXml(out, indent, - QString("type=\"wavefile\" file=\"%1\" %2") - .arg(encodeEntities(m_path)).arg(extraAttributes)); -} - -
--- a/data/model/WaveFileModel.h Tue Aug 04 16:39:40 2015 +0100 +++ b/data/model/WaveFileModel.h Thu Aug 20 13:15:19 2015 +0100 @@ -13,117 +13,36 @@ COPYING included with this distribution for more information. */ -#ifndef _WAVE_FILE_MODEL_H_ -#define _WAVE_FILE_MODEL_H_ - -#include "base/Thread.h" -#include <QMutex> -#include <QTimer> - -#include "data/fileio/FileSource.h" +#ifndef WAVE_FILE_MODEL_H +#define WAVE_FILE_MODEL_H #include "RangeSummarisableTimeValueModel.h" -#include "PowerOfSqrtTwoZoomConstraint.h" #include <stdlib.h> -class AudioFileReader; - class WaveFileModel : public RangeSummarisableTimeValueModel { Q_OBJECT public: - WaveFileModel(FileSource source, sv_samplerate_t targetRate = 0); - WaveFileModel(FileSource source, AudioFileReader *reader); - ~WaveFileModel(); + virtual ~WaveFileModel(); - bool isOK() const; - bool isReady(int *) const; + virtual sv_frame_t getFrameCount() const = 0; + virtual int getChannelCount() const = 0; + virtual sv_samplerate_t getSampleRate() const = 0; + virtual sv_samplerate_t getNativeRate() const = 0; - const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; } + virtual QString getTitle() const = 0; + virtual QString getMaker() const = 0; + virtual QString getLocation() const = 0; - sv_frame_t getFrameCount() const; - int getChannelCount() const; - sv_samplerate_t getSampleRate() const; - sv_samplerate_t getNativeRate() const; + virtual sv_frame_t getStartFrame() const = 0; + virtual sv_frame_t getEndFrame() const = 0; - QString getTitle() const; - QString getMaker() const; - QString getLocation() const; + virtual void setStartFrame(sv_frame_t startFrame) = 0; - QString getLocalFilename() const; - - float getValueMinimum() const { return -1.0f; } - float getValueMaximum() const { return 1.0f; } - - virtual sv_frame_t getStartFrame() const { return m_startFrame; } - virtual sv_frame_t getEndFrame() const { return m_startFrame + getFrameCount(); } - - void setStartFrame(sv_frame_t startFrame) { m_startFrame = startFrame; } - - virtual std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const; - - virtual std::vector<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; - - virtual int getSummaryBlockSize(int desired) const; - - virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count, - RangeBlock &ranges, - int &blockSize) const; - - virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const; - - QString getTypeName() const { return tr("Wave File"); } - - virtual void toXml(QTextStream &out, - QString indent = "", - QString extraAttributes = "") const; - -protected slots: - void fillTimerTimedOut(); - void cacheFilled(); - protected: - void initialize(); - - class RangeCacheFillThread : public Thread - { - public: - RangeCacheFillThread(WaveFileModel &model) : - m_model(model), m_fillExtent(0), - m_frameCount(model.getFrameCount()) { } - - sv_frame_t getFillExtent() const { return m_fillExtent; } - virtual void run(); - - protected: - WaveFileModel &m_model; - sv_frame_t m_fillExtent; - sv_frame_t m_frameCount; - }; - - void fillCache(); - - FileSource m_source; - QString m_path; - AudioFileReader *m_reader; - bool m_myReader; - - sv_frame_t m_startFrame; - - RangeBlock m_cache[2]; // interleaved at two base resolutions - mutable QMutex m_mutex; - RangeCacheFillThread *m_fillThread; - QTimer *m_updateTimer; - sv_frame_t m_lastFillExtent; - bool m_exiting; - static PowerOfSqrtTwoZoomConstraint m_zoomConstraint; - - mutable std::vector<float> m_directRead; - mutable sv_frame_t m_lastDirectReadStart; - mutable sv_frame_t m_lastDirectReadCount; - mutable QMutex m_directReadMutex; + WaveFileModel() { } // only accessible from subclasses }; #endif
--- a/data/model/WritableWaveFileModel.cpp Tue Aug 04 16:39:40 2015 +0100 +++ b/data/model/WritableWaveFileModel.cpp Thu Aug 20 13:15:19 2015 +0100 @@ -15,6 +15,8 @@ #include "WritableWaveFileModel.h" +#include "ReadOnlyWaveFileModel.h" + #include "base/TempDirectory.h" #include "base/Exceptions.h" @@ -76,7 +78,7 @@ return; } - m_model = new WaveFileModel(source, m_reader); + m_model = new ReadOnlyWaveFileModel(source, m_reader); if (!m_model->isOK()) { cerr << "WritableWaveFileModel: Error in creating wave file model" << endl; delete m_model; @@ -215,15 +217,16 @@ QString indent, QString extraAttributes) const { - // We don't actually write the data to XML. We just write a brief - // description of the model. Any code that uses this class is - // going to need to be aware that it will have to make separate - // arrangements for the audio file itself. + // The assumption here is that the underlying wave file has + // already been saved somewhere (its location is available through + // getLocation()) and that the code that uses this class is + // dealing with the problem of making sure it remains available. + // We just write this out as if it were a normal wave file. Model::toXml (out, indent, - QString("type=\"writablewavefile\" file=\"%1\" channels=\"%2\" %3") + QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2") .arg(encodeEntities(m_writer->getPath())) - .arg(m_model->getChannelCount()).arg(extraAttributes)); + .arg(extraAttributes)); }
--- a/data/model/WritableWaveFileModel.h Tue Aug 04 16:39:40 2015 +0100 +++ b/data/model/WritableWaveFileModel.h Thu Aug 20 13:15:19 2015 +0100 @@ -13,15 +13,17 @@ COPYING included with this distribution for more information. */ -#ifndef _WRITABLE_WAVE_FILE_MODEL_H_ -#define _WRITABLE_WAVE_FILE_MODEL_H_ +#ifndef WRITABLE_WAVE_FILE_MODEL_H +#define WRITABLE_WAVE_FILE_MODEL_H #include "WaveFileModel.h" +#include "ReadOnlyWaveFileModel.h" +#include "PowerOfSqrtTwoZoomConstraint.h" class WavFileWriter; class WavFileReader; -class WritableWaveFileModel : public RangeSummarisableTimeValueModel +class WritableWaveFileModel : public WaveFileModel { Q_OBJECT @@ -51,6 +53,20 @@ sv_frame_t getFrameCount() const; int getChannelCount() const { return m_channels; } sv_samplerate_t getSampleRate() const { return m_sampleRate; } + sv_samplerate_t getNativeRate() const { return m_sampleRate; } + + QString getTitle() const { + if (m_model) return m_model->getTitle(); + else return ""; + } + QString getMaker() const { + if (m_model) return m_model->getMaker(); + else return ""; + } + QString getLocation() const { + if (m_model) return m_model->getLocation(); + else return ""; + } float getValueMinimum() const { return -1.0f; } float getValueMaximum() const { return 1.0f; } @@ -78,7 +94,7 @@ QString extraAttributes = "") const; protected: - WaveFileModel *m_model; + ReadOnlyWaveFileModel *m_model; WavFileWriter *m_writer; WavFileReader *m_reader; sv_samplerate_t m_sampleRate;
--- a/rdf/RDFImporter.cpp Tue Aug 04 16:39:40 2015 +0100 +++ b/rdf/RDFImporter.cpp Thu Aug 20 13:15:19 2015 +0100 @@ -30,7 +30,7 @@ #include "data/model/NoteModel.h" #include "data/model/TextModel.h" #include "data/model/RegionModel.h" -#include "data/model/WaveFileModel.h" +#include "data/model/ReadOnlyWaveFileModel.h" #include "data/fileio/FileSource.h" #include "data/fileio/CachedFile.h" @@ -270,7 +270,7 @@ reporter->setMessage(RDFImporter::tr("Importing audio referenced in RDF...")); } fs->waitForData(); - WaveFileModel *newModel = new WaveFileModel(*fs, m_sampleRate); + ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(*fs, m_sampleRate); if (newModel->isOK()) { cerr << "Successfully created wave file model from source at \"" << source << "\"" << endl; models.push_back(newModel);
--- a/svcore.pro Tue Aug 04 16:39:40 2015 +0100 +++ b/svcore.pro Thu Aug 20 13:15:19 2015 +0100 @@ -174,6 +174,7 @@ data/model/TabularModel.h \ data/model/TextModel.h \ data/model/WaveFileModel.h \ + data/model/ReadOnlyWaveFileModel.h \ data/model/WritableWaveFileModel.h \ data/osc/OSCMessage.h \ data/osc/OSCQueue.h @@ -215,6 +216,7 @@ data/model/PowerOfTwoZoomConstraint.cpp \ data/model/RangeSummarisableTimeValueModel.cpp \ data/model/WaveFileModel.cpp \ + data/model/ReadOnlyWaveFileModel.cpp \ data/model/WritableWaveFileModel.cpp \ data/osc/OSCMessage.cpp \ data/osc/OSCQueue.cpp