# HG changeset patch # User Chris Cannam # Date 1190987798 0 # Node ID c022976d18e8859e03c3c37b64e789fb8319b7cf # Parent 2b6c99b607f15c8f114da4a2fe248c23a3dbb001 * Merge from sv-match-alignment branch (excluding alignment-specific document). - add aggregate wave model (not yet complete enough to be added as a true model in a layer, but there's potential) - add play solo mode - add alignment model -- unused in plain SV - fix two plugin leaks - add m3u playlist support (opens all files at once, potentially hazardous) - fix retrieval of pre-encoded URLs - add ability to resample audio files on import, so as to match rates with other files previously loaded; add preference for same - add preliminary support in transform code for range and rate of transform input - reorganise preferences dialog, move dark-background option to preferences, add option for temporary directory location diff -r 2b6c99b607f1 -r c022976d18e8 base/Preferences.cpp --- a/base/Preferences.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/base/Preferences.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -40,7 +40,9 @@ m_propertyBoxLayout(VerticallyStacked), m_windowType(HanningWindow), m_resampleQuality(1), - m_omitRecentTemps(true) + m_omitRecentTemps(true), + m_tempDirRoot(""), + m_resampleOnLoad(true) { QSettings settings; settings.beginGroup("Preferences"); @@ -52,6 +54,13 @@ m_windowType = WindowType (settings.value("window-type", int(HanningWindow)).toInt()); m_resampleQuality = settings.value("resample-quality", 1).toInt(); + m_resampleOnLoad = settings.value("resample-on-load", true).toBool(); + m_backgroundMode = BackgroundMode + (settings.value("background-mode", int(BackgroundFromTheme)).toInt()); + settings.endGroup(); + + settings.beginGroup("TempDirectory"); + m_tempDirRoot = settings.value("create-in", "$HOME").toString(); settings.endGroup(); } @@ -69,6 +78,9 @@ props.push_back("Window Type"); props.push_back("Resample Quality"); props.push_back("Omit Temporaries from Recent Files"); + props.push_back("Resample On Load"); + props.push_back("Temporary Directory Root"); + props.push_back("Background Mode"); return props; } @@ -91,7 +103,16 @@ return tr("Playback resampler type"); } if (name == "Omit Temporaries from Recent Files") { - return tr("Omit Temporaries from Recent Files"); + return tr("Omit temporaries from Recent Files menu"); + } + if (name == "Resample On Load") { + return tr("Resample mismatching files on import"); + } + if (name == "Temporary Directory Root") { + return tr("Location for cache file directory"); + } + if (name == "Background Mode") { + return tr("Background colour preference"); } return name; } @@ -117,6 +138,16 @@ if (name == "Omit Temporaries from Recent Files") { return ToggleProperty; } + if (name == "Resample On Load") { + return ToggleProperty; + } + if (name == "Temporary Directory Root") { + // It's an arbitrary string, we don't have a set of values for this + return InvalidProperty; + } + if (name == "Background Mode") { + return ValueProperty; + } return InvalidProperty; } @@ -158,6 +189,13 @@ if (deflt) *deflt = 1; } + if (name == "Background Mode") { + if (min) *min = 0; + if (max) *max = 2; + if (deflt) *deflt = 0; + return int(m_backgroundMode); + } + return 0; } @@ -196,6 +234,13 @@ case SpectrogramZeroPadded: return tr("Zero pad FFT - slow but clear"); } } + if (name == "Background Mode") { + switch (value) { + case BackgroundFromTheme: return tr("Follow desktop theme"); + case DarkBackground: return tr("Dark background"); + case LightBackground: return tr("Light background"); + } + } return ""; } @@ -227,6 +272,8 @@ setResampleQuality(value); } else if (name == "Omit Temporaries from Recent Files") { setOmitTempsFromRecentFiles(value ? true : false); + } else if (name == "Background Mode") { + setBackgroundMode(BackgroundMode(value)); } } @@ -311,3 +358,49 @@ emit propertyChanged("Omit Temporaries from Recent Files"); } } + +void +Preferences::setTemporaryDirectoryRoot(QString root) +{ + if (root == QDir::home().absolutePath()) { + root = "$HOME"; + } + if (m_tempDirRoot != root) { + m_tempDirRoot = root; + QSettings settings; + settings.beginGroup("TempDirectory"); + settings.setValue("create-in", root); + settings.endGroup(); + emit propertyChanged("Temporary Directory Root"); + } +} + +void +Preferences::setResampleOnLoad(bool resample) +{ + if (m_resampleOnLoad != resample) { + m_resampleOnLoad = resample; + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("resample-on-load", resample); + settings.endGroup(); + emit propertyChanged("Resample On Load"); + } +} + +void +Preferences::setBackgroundMode(BackgroundMode mode) +{ + if (m_backgroundMode != mode) { + + m_backgroundMode = mode; + + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("background-mode", int(mode)); + settings.endGroup(); + emit propertyChanged("Background Mode"); + } +} + + diff -r 2b6c99b607f1 -r c022976d18e8 base/Preferences.h --- a/base/Preferences.h Fri Sep 21 09:13:11 2007 +0000 +++ b/base/Preferences.h Fri Sep 28 13:56:38 2007 +0000 @@ -56,6 +56,17 @@ bool getOmitTempsFromRecentFiles() const { return m_omitRecentTemps; } + QString getTemporaryDirectoryRoot() const { return m_tempDirRoot; } + + bool getResampleOnLoad() const { return m_resampleOnLoad; } + + enum BackgroundMode { + BackgroundFromTheme, + DarkBackground, + LightBackground + }; + BackgroundMode getBackgroundMode() const { return m_backgroundMode; } + public slots: virtual void setProperty(const PropertyName &, int); @@ -65,6 +76,9 @@ void setWindowType(WindowType type); void setResampleQuality(int quality); void setOmitTempsFromRecentFiles(bool omit); + void setTemporaryDirectoryRoot(QString tempDirRoot); + void setResampleOnLoad(bool); + void setBackgroundMode(BackgroundMode mode); private: Preferences(); // may throw DirectoryCreationFailed @@ -78,6 +92,9 @@ WindowType m_windowType; int m_resampleQuality; bool m_omitRecentTemps; + QString m_tempDirRoot; + bool m_resampleOnLoad; + BackgroundMode m_backgroundMode; }; #endif diff -r 2b6c99b607f1 -r c022976d18e8 base/Resampler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Resampler.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,186 @@ +/* -*- 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 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. +*/ + +/* + This is a modified version of a source file from the + Rubber Band audio timestretcher library. + This file copyright 2007 Chris Cannam. +*/ + +#include "Resampler.h" + +#include +#include + +#include + +#include + +class Resampler::D +{ +public: + D(Quality quality, size_t channels, size_t chunkSize); + ~D(); + + size_t resample(float **in, float **out, + size_t incount, float ratio, + bool final); + + size_t resampleInterleaved(float *in, float *out, + size_t incount, float ratio, + bool final); + + void reset(); + +protected: + SRC_STATE *m_src; + float *m_iin; + float *m_iout; + size_t m_channels; + size_t m_iinsize; + size_t m_ioutsize; +}; + +Resampler::D::D(Quality quality, size_t channels, size_t chunkSize) : + m_src(0), + m_iin(0), + m_iout(0), + m_channels(channels), + m_iinsize(0), + m_ioutsize(0) +{ + int err = 0; + m_src = src_new(quality == Best ? SRC_SINC_BEST_QUALITY : + quality == Fastest ? SRC_LINEAR : + SRC_SINC_FASTEST, + channels, &err); + + //!!! check err, throw + + if (chunkSize > 0 && m_channels > 1) { + //!!! alignment? + m_iinsize = chunkSize * m_channels; + m_ioutsize = chunkSize * m_channels * 2; + m_iin = (float *)malloc(m_iinsize * sizeof(float)); + m_iout = (float *)malloc(m_ioutsize * sizeof(float)); + } +} + +Resampler::D::~D() +{ + src_delete(m_src); + if (m_iinsize > 0) { + free(m_iin); + } + if (m_ioutsize > 0) { + free(m_iout); + } +} + +size_t +Resampler::D::resample(float **in, float **out, + size_t incount, float ratio, + bool final) +{ + if (m_channels == 1) { + return resampleInterleaved(*in, *out, incount, ratio, final); + } + + size_t outcount = lrintf(ceilf(incount * ratio)); + + if (incount * m_channels > m_iinsize) { + m_iinsize = incount * m_channels; + m_iin = (float *)realloc(m_iin, m_iinsize * sizeof(float)); + } + if (outcount * m_channels > m_ioutsize) { + m_ioutsize = outcount * m_channels; + m_iout = (float *)realloc(m_iout, m_ioutsize * sizeof(float)); + } + for (size_t i = 0; i < incount; ++i) { + for (size_t c = 0; c < m_channels; ++c) { + m_iin[i * m_channels + c] = in[c][i]; + } + } + + size_t gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final); + + for (size_t i = 0; i < gen; ++i) { + for (size_t c = 0; c < m_channels; ++c) { + out[c][i] = m_iout[i * m_channels + c]; + } + } + + return gen; +} + +size_t +Resampler::D::resampleInterleaved(float *in, float *out, + size_t incount, float ratio, + bool final) +{ + SRC_DATA data; + + size_t outcount = lrintf(ceilf(incount * ratio)); + + data.data_in = in; + data.data_out = out; + data.input_frames = incount; + data.output_frames = outcount; + data.src_ratio = ratio; + data.end_of_input = (final ? 1 : 0); + + int err = src_process(m_src, &data); + + //!!! check err, respond appropriately + + return data.output_frames_gen; +} + +void +Resampler::D::reset() +{ + src_reset(m_src); +} + +Resampler::Resampler(Quality quality, size_t channels, size_t chunkSize) +{ + m_d = new D(quality, channels, chunkSize); +} + +Resampler::~Resampler() +{ + delete m_d; +} + +size_t +Resampler::resample(float **in, float **out, + size_t incount, float ratio, + bool final) +{ + return m_d->resample(in, out, incount, ratio, final); +} + +size_t +Resampler::resampleInterleaved(float *in, float *out, + size_t incount, float ratio, + bool final) +{ + return m_d->resampleInterleaved(in, out, incount, ratio, final); +} + +void +Resampler::reset() +{ + m_d->reset(); +} + diff -r 2b6c99b607f1 -r c022976d18e8 base/Resampler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Resampler.h Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,49 @@ +/* -*- 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 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. +*/ + +/* + This is a modified version of a source file from the + Rubber Band audio timestretcher library. + This file copyright 2007 Chris Cannam. +*/ + +#ifndef _RESAMPLER_H_ +#define _RESAMPLER_H_ + +#include + +class Resampler +{ +public: + enum Quality { Best, FastestTolerable, Fastest }; + + Resampler(Quality quality, size_t channels, size_t chunkSize = 0); + ~Resampler(); + + size_t resample(float **in, float **out, + size_t incount, float ratio, + bool final = false); + + size_t resampleInterleaved(float *in, float *out, + size_t incount, float ratio, + bool final = false); + + void reset(); + +protected: + class D; + D *m_d; +}; + +#endif diff -r 2b6c99b607f1 -r c022976d18e8 base/Serialiser.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Serialiser.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,43 @@ +/* -*- 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 2007 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 "Serialiser.h" + +QMutex +Serialiser::m_mapMutex; + +std::map +Serialiser::m_mutexMap; + +Serialiser::Serialiser(QString id) : + m_id(id) +{ + m_mapMutex.lock(); + + if (m_mutexMap.find(m_id) == m_mutexMap.end()) { + m_mutexMap[m_id] = new QMutex; + } + + m_mutexMap[m_id]->lock(); + m_mapMutex.unlock(); +} + +Serialiser::~Serialiser() +{ + m_mutexMap[m_id]->unlock(); +} + + + diff -r 2b6c99b607f1 -r c022976d18e8 base/Serialiser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Serialiser.h Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,36 @@ +/* -*- 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 2007 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 _SERIALISER_H_ +#define _SERIALISER_H_ + +#include +#include + +#include + +class Serialiser +{ +public: + Serialiser(QString id); + ~Serialiser(); + +protected: + QString m_id; + static QMutex m_mapMutex; + static std::map m_mutexMap; +}; + +#endif diff -r 2b6c99b607f1 -r c022976d18e8 base/base.pro --- a/base/base.pro Fri Sep 21 09:13:11 2007 +0000 +++ b/base/base.pro Fri Sep 28 13:56:38 2007 +0000 @@ -31,10 +31,12 @@ RangeMapper.h \ RealTime.h \ RecentFiles.h \ + Resampler.h \ ResizeableBitset.h \ RingBuffer.h \ Scavenger.h \ Selection.h \ + Serialiser.h \ StorageAdviser.h \ TempDirectory.h \ TextAbbrev.h \ @@ -60,7 +62,9 @@ RangeMapper.cpp \ RealTime.cpp \ RecentFiles.cpp \ + Resampler.cpp \ Selection.cpp \ + Serialiser.cpp \ StorageAdviser.cpp \ TempDirectory.cpp \ TextAbbrev.cpp \ diff -r 2b6c99b607f1 -r c022976d18e8 data/data.pro --- a/data/data.pro Fri Sep 21 09:13:11 2007 +0000 +++ b/data/data.pro Fri Sep 28 13:56:38 2007 +0000 @@ -29,14 +29,19 @@ fileio/DataFileReaderFactory.h \ fileio/FileFinder.h \ fileio/FileReadThread.h \ + fileio/MatchFileReader.h \ fileio/MatrixFile.h \ fileio/MIDIFileReader.h \ fileio/MP3FileReader.h \ fileio/OggVorbisFileReader.h \ + fileio/PlaylistFileReader.h \ fileio/QuickTimeFileReader.h \ fileio/RemoteFile.h \ + fileio/ResamplingWavFileReader.h \ fileio/WavFileReader.h \ fileio/WavFileWriter.h \ + model/AggregateWaveModel.h \ + model/AlignmentModel.h \ model/DenseThreeDimensionalModel.h \ model/DenseTimeValueModel.h \ model/EditableDenseThreeDimensionalModel.h \ @@ -66,14 +71,19 @@ fileio/DataFileReaderFactory.cpp \ fileio/FileFinder.cpp \ fileio/FileReadThread.cpp \ + fileio/MatchFileReader.cpp \ fileio/MatrixFile.cpp \ fileio/MIDIFileReader.cpp \ fileio/MP3FileReader.cpp \ fileio/OggVorbisFileReader.cpp \ + fileio/PlaylistFileReader.cpp \ fileio/QuickTimeFileReader.cpp \ fileio/RemoteFile.cpp \ + fileio/ResamplingWavFileReader.cpp \ fileio/WavFileReader.cpp \ fileio/WavFileWriter.cpp \ + model/AggregateWaveModel.cpp \ + model/AlignmentModel.cpp \ model/DenseTimeValueModel.cpp \ model/EditableDenseThreeDimensionalModel.cpp \ model/FFTModel.cpp \ @@ -81,5 +91,6 @@ model/NoteModel.cpp \ model/PowerOfSqrtTwoZoomConstraint.cpp \ model/PowerOfTwoZoomConstraint.cpp \ + model/RangeSummarisableTimeValueModel.cpp \ model/WaveFileModel.cpp \ model/WritableWaveFileModel.cpp diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/AudioFileReader.h --- a/data/fileio/AudioFileReader.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/AudioFileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -33,6 +33,7 @@ size_t getFrameCount() const { return m_frameCount; } size_t getChannelCount() const { return m_channelCount; } size_t getSampleRate() const { return m_sampleRate; } + size_t getNativeRate() const { return m_sampleRate; } // if resampled /** * Return the title of the work in the audio file, if known. This diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/AudioFileReaderFactory.cpp --- a/data/fileio/AudioFileReaderFactory.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/AudioFileReaderFactory.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -16,6 +16,7 @@ #include "AudioFileReaderFactory.h" #include "WavFileReader.h" +#include "ResamplingWavFileReader.h" #include "OggVorbisFileReader.h" #include "MP3FileReader.h" #include "QuickTimeFileReader.h" @@ -53,10 +54,12 @@ } AudioFileReader * -AudioFileReaderFactory::createReader(QString path) +AudioFileReaderFactory::createReader(QString path, size_t targetRate) { QString err; + std::cerr << "AudioFileReaderFactory::createReader(\"" << path.toStdString() << "\"): Requested rate: " << targetRate << std::endl; + AudioFileReader *reader = 0; // First try to construct a preferred reader based on the @@ -69,6 +72,20 @@ WavFileReader::getSupportedExtensions(extensions); if (extensions.find(ext) != extensions.end()) { reader = new WavFileReader(path); + + if (targetRate != 0 && + reader->isOK() && + reader->getSampleRate() != targetRate) { + + std::cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", creating resampling reader" << std::endl; + + delete reader; + reader = new ResamplingWavFileReader + (path, + ResamplingWavFileReader::ResampleThreaded, + ResamplingWavFileReader::CacheInTemporaryFile, + targetRate); + } } #ifdef HAVE_OGGZ @@ -80,7 +97,8 @@ reader = new OggVorbisFileReader (path, OggVorbisFileReader::DecodeThreaded, - OggVorbisFileReader::CacheInTemporaryFile); + OggVorbisFileReader::CacheInTemporaryFile, + targetRate); } } #endif @@ -94,7 +112,8 @@ reader = new MP3FileReader (path, MP3FileReader::DecodeThreaded, - MP3FileReader::CacheInTemporaryFile); + MP3FileReader::CacheInTemporaryFile, + targetRate); } } #endif @@ -107,7 +126,8 @@ reader = new QuickTimeFileReader (path, QuickTimeFileReader::DecodeThreaded, - QuickTimeFileReader::CacheInTemporaryFile); + QuickTimeFileReader::CacheInTemporaryFile, + targetRate); } } #endif @@ -128,7 +148,21 @@ } reader = new WavFileReader(path); + + if (targetRate != 0 && + reader->isOK() && + reader->getSampleRate() != targetRate) { + + delete reader; + reader = new ResamplingWavFileReader + (path, + ResamplingWavFileReader::ResampleThreaded, + ResamplingWavFileReader::CacheInTemporaryFile, + targetRate); + } + if (reader->isOK()) return reader; + if (reader->getError() != "") { std::cerr << "AudioFileReaderFactory: WAV file reader error: \"" << reader->getError().toStdString() << "\"" << std::endl; diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/AudioFileReaderFactory.h --- a/data/fileio/AudioFileReaderFactory.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/AudioFileReaderFactory.h Fri Sep 28 13:56:38 2007 +0000 @@ -34,9 +34,15 @@ * Return an audio file reader initialised to the file at the * given path, or NULL if no suitable reader for this path is * available or the file cannot be opened. + * + * If targetRate is non-zero, the file will be resampled to that + * rate (transparently). You can query reader->getNativeRate() + * if you want to find out whether the file is being resampled + * or not. + * * Caller owns the returned object and must delete it after use. */ - static AudioFileReader *createReader(QString path); + static AudioFileReader *createReader(QString path, size_t targetRate = 0); }; #endif diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/CodedAudioFileReader.cpp --- a/data/fileio/CodedAudioFileReader.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/CodedAudioFileReader.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -4,7 +4,7 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam. + This file copyright 2006-2007 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 @@ -19,35 +19,70 @@ #include "base/TempDirectory.h" #include "base/Exceptions.h" #include "base/Profiler.h" +#include "base/Serialiser.h" +#include "base/Resampler.h" #include #include #include -CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode) : +CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode, + size_t targetRate) : m_cacheMode(cacheMode), m_initialised(false), + m_serialiser(0), + m_fileRate(0), m_cacheFileWritePtr(0), m_cacheFileReader(0), m_cacheWriteBuffer(0), m_cacheWriteBufferIndex(0), - m_cacheWriteBufferSize(16384) + m_cacheWriteBufferSize(16384), + m_resampler(0), + m_resampleBuffer(0) { + std::cerr << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << std::endl; + + m_frameCount = 0; + m_sampleRate = targetRate; } CodedAudioFileReader::~CodedAudioFileReader() { QMutexLocker locker(&m_cacheMutex); + endSerialised(); + if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr); - if (m_cacheFileReader) delete m_cacheFileReader; - if (m_cacheWriteBuffer) delete[] m_cacheWriteBuffer; + + delete m_cacheFileReader; + delete[] m_cacheWriteBuffer; if (m_cacheFileName != "") { if (!QFile(m_cacheFileName).remove()) { std::cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName.toStdString() << "\"" << std::endl; } } + + delete m_resampler; + delete[] m_resampleBuffer; +} + +void +CodedAudioFileReader::startSerialised(QString id) +{ +// std::cerr << "CodedAudioFileReader::startSerialised(" << id.toStdString() << ")" << std::endl; + + delete m_serialiser; + m_serialiser = new Serialiser(id); +} + +void +CodedAudioFileReader::endSerialised() +{ +// std::cerr << "CodedAudioFileReader::endSerialised" << std::endl; + + delete m_serialiser; + m_serialiser = 0; } void @@ -55,11 +90,30 @@ { QMutexLocker locker(&m_cacheMutex); + std::cerr << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << std::endl; + + if (m_fileRate == 0) { + std::cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << std::endl; + m_fileRate = 48000; // got to have something + } + if (m_sampleRate == 0) { + m_sampleRate = m_fileRate; + } + if (m_fileRate != m_sampleRate) { + std::cerr << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << std::endl; + m_resampler = new Resampler(Resampler::FastestTolerable, + m_channelCount, + m_cacheWriteBufferSize); + float ratio = float(m_sampleRate) / float(m_fileRate); + m_resampleBuffer = new float + [lrintf(ceilf(m_cacheWriteBufferSize * m_channelCount * ratio))]; + } + + m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount]; + m_cacheWriteBufferIndex = 0; + if (m_cacheMode == CacheInTemporaryFile) { - m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount]; - m_cacheWriteBufferIndex = 0; - try { QDir dir(TempDirectory::getInstance()->getPath()); m_cacheFileName = dir.filePath(QString("decoded_%1.wav") @@ -68,17 +122,21 @@ SF_INFO fileInfo; fileInfo.samplerate = m_sampleRate; fileInfo.channels = m_channelCount; - fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + + // No point in writing 24-bit or float; generally this + // class is used for decoding files that have come from a + // 16 bit source or that decode to only 16 bits anyway. + fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(), SFM_WRITE, &fileInfo); if (m_cacheFileWritePtr) { - //!!! really want to do this now only if we're in a - //threaded mode -- creating the reader later if we're - //not threaded -- but we don't have access to that - //information here + // Ideally we would do this now only if we were in a + // threaded mode -- creating the reader later if we're + // not threaded -- but we don't have access to that + // information here m_cacheFileReader = new WavFileReader(m_cacheFileName); @@ -89,6 +147,7 @@ m_cacheMode = CacheInMemory; sf_close(m_cacheFileWritePtr); } + } else { 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; m_cacheMode = CacheInMemory; @@ -108,26 +167,82 @@ } void -CodedAudioFileReader::addSampleToDecodeCache(float sample) +CodedAudioFileReader::addSamplesToDecodeCache(float **samples, size_t nframes) { QMutexLocker locker(&m_cacheMutex); if (!m_initialised) return; - switch (m_cacheMode) { + for (size_t i = 0; i < nframes; ++i) { + + for (size_t c = 0; c < m_channelCount; ++c) { - case CacheInTemporaryFile: + float sample = samples[c][i]; + + m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; + if (m_cacheWriteBufferIndex == + m_cacheWriteBufferSize * m_channelCount) { + + pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false); + m_cacheWriteBufferIndex = 0; + } + + if (m_cacheWriteBufferIndex % 10240 == 0 && + m_cacheFileReader) { + m_cacheFileReader->updateFrameCount(); + } + } + } +} + +void +CodedAudioFileReader::addSamplesToDecodeCache(float *samples, size_t nframes) +{ + QMutexLocker locker(&m_cacheMutex); + + if (!m_initialised) return; + + for (size_t i = 0; i < nframes; ++i) { + + for (size_t c = 0; c < m_channelCount; ++c) { + + float sample = samples[i * m_channelCount + c]; + + m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; + + if (m_cacheWriteBufferIndex == + m_cacheWriteBufferSize * m_channelCount) { + + pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false); + m_cacheWriteBufferIndex = 0; + } + + if (m_cacheWriteBufferIndex % 10240 == 0 && + m_cacheFileReader) { + m_cacheFileReader->updateFrameCount(); + } + } + } +} + +void +CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples) +{ + QMutexLocker locker(&m_cacheMutex); + + if (!m_initialised) return; + + for (size_t i = 0; i < samples.size(); ++i) { + + float sample = samples[i]; + m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; if (m_cacheWriteBufferIndex == m_cacheWriteBufferSize * m_channelCount) { - //!!! check for return value! out of disk space, etc! - sf_writef_float(m_cacheFileWritePtr, - m_cacheWriteBuffer, - m_cacheWriteBufferSize); - + pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false); m_cacheWriteBufferIndex = 0; } @@ -135,11 +250,6 @@ m_cacheFileReader) { m_cacheFileReader->updateFrameCount(); } - break; - - case CacheInMemory: - m_data.push_back(sample); - break; } } @@ -155,41 +265,63 @@ return; } + if (m_cacheWriteBufferIndex > 0) { + //!!! check for return value! out of disk space, etc! + pushBuffer(m_cacheWriteBuffer, + m_cacheWriteBufferIndex / m_channelCount, + true); + } + + delete[] m_cacheWriteBuffer; + m_cacheWriteBuffer = 0; + + delete[] m_resampleBuffer; + m_resampleBuffer = 0; + + delete m_resampler; + m_resampler = 0; + + if (m_cacheMode == CacheInTemporaryFile) { + sf_close(m_cacheFileWritePtr); + m_cacheFileWritePtr = 0; + if (m_cacheFileReader) m_cacheFileReader->updateFrameCount(); + } +} + +void +CodedAudioFileReader::pushBuffer(float *buffer, size_t sz, bool final) +{ + if (m_resampler) { + + float ratio = float(m_sampleRate) / float(m_fileRate); + + if (ratio != 1.f) { + size_t out = m_resampler->resampleInterleaved + (buffer, + m_resampleBuffer, + sz, + ratio, + final); + + buffer = m_resampleBuffer; + sz = out; + } + } + + m_frameCount += sz; + switch (m_cacheMode) { case CacheInTemporaryFile: - - if (m_cacheWriteBufferIndex > 0) { - //!!! check for return value! out of disk space, etc! - sf_writef_float(m_cacheFileWritePtr, - m_cacheWriteBuffer, - m_cacheWriteBufferIndex / m_channelCount); - } - - if (m_cacheWriteBuffer) { - delete[] m_cacheWriteBuffer; - m_cacheWriteBuffer = 0; - } - - m_cacheWriteBufferIndex = 0; - - sf_close(m_cacheFileWritePtr); - m_cacheFileWritePtr = 0; - - m_cacheFileReader->updateFrameCount(); -/* - m_cacheFileReader = new WavFileReader(m_cacheFileName); - - if (!m_cacheFileReader->isOK()) { - std::cerr << "ERROR: CodedAudioFileReader::finishDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError().toStdString() << std::endl; - delete m_cacheFileReader; - m_cacheFileReader = 0; - }*/ - + //!!! check for return value! out of disk space, etc! + sf_writef_float(m_cacheFileWritePtr, buffer, sz); break; case CacheInMemory: - // nothing to do + for (size_t s = 0; s < sz; ++s) { + m_data.push_back(buffer[sz]); + } + MUNLOCK_SAMPLEBLOCK(m_data); break; } } diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/CodedAudioFileReader.h --- a/data/fileio/CodedAudioFileReader.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/CodedAudioFileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -22,6 +22,8 @@ #include class WavFileReader; +class Serialiser; +class Resampler; class CodedAudioFileReader : public AudioFileReader { @@ -36,18 +38,31 @@ virtual void getInterleavedFrames(size_t start, size_t count, SampleBlock &frames) const; + virtual size_t getNativeRate() const { return m_fileRate; } + protected: - CodedAudioFileReader(CacheMode cacheMode); + CodedAudioFileReader(CacheMode cacheMode, size_t targetRate); void initialiseDecodeCache(); // samplerate, channels must have been set - void addSampleToDecodeCache(float sample); + void addSamplesToDecodeCache(float **samples, size_t nframes); + void addSamplesToDecodeCache(float *samplesInterleaved, size_t nframes); + void addSamplesToDecodeCache(const SampleBlock &interleaved); void finishDecodeCache(); bool isDecodeCacheInitialised() const { return m_initialised; } + void startSerialised(QString id); + void endSerialised(); + +private: + void pushBuffer(float *interleaved, size_t sz, bool final); + +protected: QMutex m_cacheMutex; CacheMode m_cacheMode; SampleBlock m_data; bool m_initialised; + Serialiser *m_serialiser; + size_t m_fileRate; QString m_cacheFileName; SNDFILE *m_cacheFileWritePtr; @@ -55,6 +70,9 @@ float *m_cacheWriteBuffer; size_t m_cacheWriteBufferIndex; size_t m_cacheWriteBufferSize; // frames + + Resampler *m_resampler; + float *m_resampleBuffer; }; #endif diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/MP3FileReader.cpp --- a/data/fileio/MP3FileReader.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/MP3FileReader.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -34,18 +34,17 @@ #include #include -MP3FileReader::MP3FileReader(QString path, DecodeMode decodeMode, CacheMode mode) : - CodedAudioFileReader(mode), +MP3FileReader::MP3FileReader(QString path, DecodeMode decodeMode, + CacheMode mode, size_t targetRate) : + CodedAudioFileReader(mode, targetRate), m_path(path), m_decodeThread(0) { - m_frameCount = 0; m_channelCount = 0; - m_sampleRate = 0; + m_fileRate = 0; m_fileSize = 0; m_bitrateNum = 0; m_bitrateDenom = 0; - m_frameCount = 0; m_cancelled = false; m_completion = 0; m_done = false; @@ -70,6 +69,8 @@ } m_filebuffer = 0; + m_samplebuffer = 0; + m_samplebuffersize = 0; try { m_filebuffer = new unsigned char[m_fileSize]; @@ -226,11 +227,21 @@ delete[] m_reader->m_filebuffer; m_reader->m_filebuffer = 0; - + + if (m_reader->m_samplebuffer) { + for (size_t c = 0; c < m_reader->m_channelCount; ++c) { + delete[] m_reader->m_samplebuffer[c]; + } + delete[] m_reader->m_samplebuffer; + m_reader->m_samplebuffer = 0; + } + if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache(); m_reader->m_done = true; m_reader->m_completion = 100; + + m_reader->endSerialised(); } bool @@ -287,8 +298,17 @@ if (frames < 1) return MAD_FLOW_CONTINUE; if (m_channelCount == 0) { + + m_fileRate = pcm->samplerate; m_channelCount = channels; - m_sampleRate = pcm->samplerate; + + initialiseDecodeCache(); + + if (m_cacheMode == CacheInTemporaryFile) { + m_completion = 1; + std::cerr << "MP3FileReader::accept: channel count " << m_channelCount << ", file rate " << m_fileRate << ", about to start serialised section" << std::endl; + startSerialised("MP3FileReader::Decode"); + } } if (m_bitrateDenom > 0) { @@ -315,33 +335,41 @@ if (m_cancelled) return MAD_FLOW_STOP; - m_frameCount += frames; - if (!isDecodeCacheInitialised()) { initialiseDecodeCache(); } - for (int i = 0; i < frames; ++i) { + if (m_samplebuffersize < frames) { + if (!m_samplebuffer) { + m_samplebuffer = new float *[channels]; + for (int c = 0; c < channels; ++c) { + m_samplebuffer[c] = 0; + } + } + for (int c = 0; c < channels; ++c) { + delete[] m_samplebuffer[c]; + m_samplebuffer[c] = new float[frames]; + } + m_samplebuffersize = frames; + } - for (int ch = 0; ch < channels; ++ch) { + int activeChannels = int(sizeof(pcm->samples) / sizeof(pcm->samples[0])); + + for (int ch = 0; ch < channels; ++ch) { + + for (int i = 0; i < frames; ++i) { + mad_fixed_t sample = 0; - if (ch < int(sizeof(pcm->samples) / sizeof(pcm->samples[0]))) { + if (ch < activeChannels) { sample = pcm->samples[ch][i]; } float fsample = float(sample) / float(MAD_F_ONE); - addSampleToDecodeCache(fsample); - } - - if (! (i & 0xffff)) { - // periodically munlock to ensure we don't exhaust real memory - // if running with memory locked down - MUNLOCK_SAMPLEBLOCK(m_data); + + m_samplebuffer[ch][i] = fsample; } } - if (frames > 0) { - MUNLOCK_SAMPLEBLOCK(m_data); - } + addSamplesToDecodeCache(m_samplebuffer, frames); return MAD_FLOW_CONTINUE; } diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/MP3FileReader.h --- a/data/fileio/MP3FileReader.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/MP3FileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -35,7 +35,10 @@ DecodeThreaded // decode in a background thread after construction }; - MP3FileReader(QString path, DecodeMode decodeMode, CacheMode cacheMode); + MP3FileReader(QString path, + DecodeMode decodeMode, + CacheMode cacheMode, + size_t targetRate = 0); virtual ~MP3FileReader(); virtual QString getError() const { return m_error; } @@ -61,6 +64,8 @@ bool m_done; unsigned char *m_filebuffer; + float **m_samplebuffer; + size_t m_samplebuffersize; QProgressDialog *m_progress; bool m_cancelled; diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/MatchFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MatchFileReader.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,183 @@ +/* -*- 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 2007 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 "MatchFileReader.h" + +#include +#include + +#include +#include + +Alignment::Alignment() : + thisHopTime(0.0), + refHopTime(0.0) +{ +} + +double +Alignment::fromReference(double t) const +{ + int ri = lrint(t / refHopTime); + int index = search(refIndex, ri); + return thisIndex[index] * thisHopTime; +} + +double +Alignment::toReference(double t) const +{ + int ti = lrint(t / thisHopTime); + int index = search(thisIndex, ti); + return refIndex[index] * refHopTime; +} + +int +Alignment::search(const FrameArray &arr, int val) const +{ + int len = arr.size(); + int max = len - 1; + int min = 0; + while (max > min) { + int mid = (max + min) / 2; + if (val > arr[mid]) { + min = mid + 1; + } else { + max = mid; + } + } // max = MIN_j (arr[j] >= val) i.e. the first equal or next highest + while ((max + 1 < len) && (arr[max + 1] == val)) { + max++; + } + return (min + max) / 2; +} + +MatchFileReader::MatchFileReader(QString path) : + m_file(0) +{ + m_file = new QFile(path); + bool good = false; + + if (!m_file->exists()) { + m_error = QFile::tr("File \"%1\" does not exist").arg(path); + } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) { + m_error = QFile::tr("Failed to open file \"%1\"").arg(path); + } else { + good = true; + } + + if (!good) { + delete m_file; + m_file = 0; + } +} + +MatchFileReader::~MatchFileReader() +{ + if (m_file) { + std::cerr << "MatchFileReader::MatchFileReader: Closing file" << std::endl; + m_file->close(); + } + delete m_file; +} + +bool +MatchFileReader::isOK() const +{ + return (m_file != 0); +} + +QString +MatchFileReader::getError() const +{ + return m_error; +} + +Alignment +MatchFileReader::load() const +{ + Alignment alignment; + + if (!m_file) return alignment; + + QTextStream in(m_file); + +/* +File: /home/studio/match-test/mahler-3-boulez-5.wav +Marks: -1 +FixedPoints: true 0 +0 +0 +0 +0 +File: /home/studio/match-test/mahler-3-haitink-5.wav +Marks: 0 +FixedPoints: true 0 +0.02 +0.02 +12836 +*/ + + int fileCount = 0; + int state = 0; + int count = 0; + + while (!in.atEnd()) { + + QString line = in.readLine().trimmed(); + if (line.startsWith("File: ")) { + ++fileCount; + continue; + } + if (fileCount != 2) continue; + if (line.startsWith("Marks:") || line.startsWith("FixedPoints:")) { + continue; + } + + switch (state) { + case 0: + alignment.thisHopTime = line.toDouble(); + break; + case 1: + alignment.refHopTime = line.toDouble(); + break; + case 2: + count = line.toInt(); + break; + case 3: + alignment.thisIndex.push_back(line.toInt()); + break; + case 4: + alignment.refIndex.push_back(line.toInt()); + break; + } + + if (state < 3) ++state; + else if (state == 3 && alignment.thisIndex.size() == count) ++state; + } + + if (alignment.thisHopTime == 0.0) { + std::cerr << "ERROR in Match file: this hop time == 0, using 0.01 instead" << std::endl; + alignment.thisHopTime = 0.01; + } + + if (alignment.refHopTime == 0.0) { + std::cerr << "ERROR in Match file: ref hop time == 0, using 0.01 instead" << std::endl; + alignment.refHopTime = 0.01; + } + + std::cerr << "MatchFileReader: this hop = " << alignment.thisHopTime << ", ref hop = " << alignment.refHopTime << ", this index count = " << alignment.thisIndex.size() << ", ref index count = " << alignment.refIndex.size() << std::endl; + + return alignment; +} diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/MatchFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MatchFileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,67 @@ +/* -*- 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 2007 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 _MATCH_FILE_READER_H_ +#define _MATCH_FILE_READER_H_ + +#include +#include + +class QFile; +class Model; + +class Alignment +{ +public: + Alignment(); + + typedef std::vector FrameArray; + + double thisHopTime; + double refHopTime; + + FrameArray thisIndex; + FrameArray refIndex; + + double fromReference(double) const; + double toReference(double) const; + + //!!! blah + void setMainModel(Model *m) { m_mainModel = m; } + bool isMainModel(Model *m) const { return m == m_mainModel; } + + int search(const FrameArray &arr, int val) const; + +protected: + Model *m_mainModel; +}; + +class MatchFileReader +{ +public: + MatchFileReader(QString path); + virtual ~MatchFileReader(); + + virtual bool isOK() const; + virtual QString getError() const; + virtual Alignment load() const; + +protected: + QFile *m_file; + QString m_error; +}; + +#endif + diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/OggVorbisFileReader.cpp --- a/data/fileio/OggVorbisFileReader.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/OggVorbisFileReader.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -34,8 +34,9 @@ OggVorbisFileReader::OggVorbisFileReader(QString path, DecodeMode decodeMode, - CacheMode mode) : - CodedAudioFileReader(mode), + CacheMode mode, + size_t targetRate) : + CodedAudioFileReader(mode, targetRate), m_path(path), m_progress(0), m_fileSize(0), @@ -45,9 +46,8 @@ m_completion(0), m_decodeThread(0) { - m_frameCount = 0; m_channelCount = 0; - m_sampleRate = 0; + m_fileRate = 0; std::cerr << "OggVorbisFileReader::OggVorbisFileReader(" << path.toLocal8Bit().data() << "): now have " << (++instances) << " instances" << std::endl; @@ -111,6 +111,11 @@ void OggVorbisFileReader::DecodeThread::run() { + if (m_reader->m_cacheMode == CacheInTemporaryFile) { + m_reader->m_completion = 1; + m_reader->startSerialised("OggVorbisFileReader::Decode"); + } + while (oggz_read(m_reader->m_oggz, 1024) > 0); fish_sound_delete(m_reader->m_fishSound); @@ -120,6 +125,8 @@ if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache(); m_reader->m_completion = 100; + + m_reader->endSerialised(); } int @@ -175,22 +182,13 @@ FishSoundInfo fsinfo; fish_sound_command(fs, FISH_SOUND_GET_INFO, &fsinfo, sizeof(FishSoundInfo)); + reader->m_fileRate = fsinfo.samplerate; reader->m_channelCount = fsinfo.channels; - reader->m_sampleRate = fsinfo.samplerate; reader->initialiseDecodeCache(); } if (nframes > 0) { - - reader->m_frameCount += nframes; - - for (long i = 0; i < nframes; ++i) { - for (size_t c = 0; c < reader->m_channelCount; ++c) { - reader->addSampleToDecodeCache(frames[c][i]); - } - } - - MUNLOCK_SAMPLEBLOCK(reader->m_data); + reader->addSamplesToDecodeCache(frames, nframes); } if (reader->m_cancelled) return 1; diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/OggVorbisFileReader.h --- a/data/fileio/OggVorbisFileReader.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/OggVorbisFileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -37,8 +37,10 @@ DecodeThreaded // decode in a background thread after construction }; - OggVorbisFileReader(QString path, DecodeMode decodeMode, - CacheMode cacheMode); + OggVorbisFileReader(QString path, + DecodeMode decodeMode, + CacheMode cacheMode, + size_t targetRate = 0); virtual ~OggVorbisFileReader(); virtual QString getError() const { return m_error; } diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/PlaylistFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/PlaylistFileReader.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,94 @@ +/* -*- 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 2007 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 "PlaylistFileReader.h" + +#include +#include +#include + +PlaylistFileReader::PlaylistFileReader(QString path) +{ + m_file = new QFile(path); + bool good = false; + + if (!m_file->exists()) { + m_error = QFile::tr("File \"%1\" does not exist").arg(path); + } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) { + m_error = QFile::tr("Failed to open file \"%1\"").arg(path); + } else { + good = true; + } + + if (!good) { + delete m_file; + m_file = 0; + } +} + +PlaylistFileReader::~PlaylistFileReader() +{ + if (m_file) m_file->close(); + delete m_file; +} + +bool +PlaylistFileReader::isOK() const +{ + return (m_file != 0); +} + +QString +PlaylistFileReader::getError() const +{ + return m_error; +} + +PlaylistFileReader::Playlist +PlaylistFileReader::load() const +{ + if (!m_file) return Playlist(); + + QTextStream in(m_file); + in.seek(0); + + Playlist playlist; + + while (!in.atEnd()) { + + // cope with old-style Mac line endings (c.f. CSVFileReader) + // as well as DOS/Unix style + + QString chunk = in.readLine(); + QStringList lines = chunk.split('\r', QString::SkipEmptyParts); + + for (size_t li = 0; li < lines.size(); ++li) { + + QString line = lines[li]; + + if (line.startsWith("#")) continue; + + playlist.push_back(line); + } + } + + return playlist; +} + +void +PlaylistFileReader::getSupportedExtensions(std::set &extensions) +{ + extensions.insert("m3u"); +} diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/PlaylistFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/PlaylistFileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,45 @@ +/* -*- 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 2007 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 _PLAYLIST_FILE_READER_H_ +#define _PLAYLIST_FILE_READER_H_ + +#include + +#include +#include + +class QFile; + +class PlaylistFileReader +{ +public: + typedef std::vector Playlist; + + PlaylistFileReader(QString path); + virtual ~PlaylistFileReader(); + + virtual bool isOK() const; + virtual QString getError() const; + virtual Playlist load() const; + + static void getSupportedExtensions(std::set &extensions); + +protected: + QFile *m_file; + QString m_error; +}; + +#endif diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/QuickTimeFileReader.cpp --- a/data/fileio/QuickTimeFileReader.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/QuickTimeFileReader.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -50,8 +50,9 @@ QuickTimeFileReader::QuickTimeFileReader(QString path, DecodeMode decodeMode, - CacheMode mode) : - CodedAudioFileReader(mode), + CacheMode mode, + size_t targetRate) : + CodedAudioFileReader(mode, targetRate), m_path(path), m_d(new D), m_progress(0), @@ -59,9 +60,8 @@ m_completion(0), m_decodeThread(0) { - m_frameCount = 0; m_channelCount = 0; - m_sampleRate = 0; + m_fileRate = 0; Profiler profiler("QuickTimeFileReader::QuickTimeFileReader", true); @@ -181,9 +181,9 @@ } m_channelCount = m_d->asbd.mChannelsPerFrame; - m_sampleRate = m_d->asbd.mSampleRate; + m_fileRate = m_d->asbd.mSampleRate; - std::cerr << "QuickTime: " << m_channelCount << " channels, " << m_sampleRate << " kHz" << std::endl; + std::cerr << "QuickTime: " << m_channelCount << " channels, " << m_fileRate << " kHz" << std::endl; m_d->asbd.mFormatFlags = kAudioFormatFlagIsFloat | @@ -236,12 +236,8 @@ // std::cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << std::endl; - m_frameCount += framesRead; - // QuickTime buffers are interleaved unless specified otherwise - for (UInt32 i = 0; i < framesRead * m_channelCount; ++i) { - addSampleToDecodeCache(m_d->data[i]); - } + addSamplesToDecodeCache(m_d->data, framesRead); if (framesRead < m_d->blockSize) break; } @@ -288,6 +284,11 @@ void QuickTimeFileReader::DecodeThread::run() { + if (m_reader->m_cacheMode == CacheInTemporaryFile) { + m_reader->m_completion = 1; + m_reader->startSerialised("QuickTimeFileReader::Decode"); + } + while (1) { UInt32 framesRead = m_reader->m_d->blockSize; @@ -301,12 +302,8 @@ break; } - m_reader->m_frameCount += framesRead; - // QuickTime buffers are interleaved unless specified otherwise - for (UInt32 i = 0; i < framesRead * m_reader->m_channelCount; ++i) { - m_reader->addSampleToDecodeCache(m_reader->m_d->data[i]); - } + addSamplesToDecodeCache(m_d->data, framesRead); if (framesRead < m_reader->m_d->blockSize) break; } @@ -319,6 +316,7 @@ } m_reader->m_completion = 100; + m_reader->endSerialised(); } void @@ -339,3 +337,4 @@ } #endif + diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/QuickTimeFileReader.h --- a/data/fileio/QuickTimeFileReader.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/fileio/QuickTimeFileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -37,8 +37,10 @@ DecodeThreaded // decode in a background thread after construction }; - QuickTimeFileReader(QString path, DecodeMode decodeMode, - CacheMode cacheMode); + QuickTimeFileReader(QString path, + DecodeMode decodeMode, + CacheMode cacheMode, + size_t targetRate = 0); virtual ~QuickTimeFileReader(); virtual QString getError() const { return m_error; } diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/ResamplingWavFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/ResamplingWavFileReader.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,170 @@ +/* -*- 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 2007 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 "ResamplingWavFileReader.h" + +#include "WavFileReader.h" +#include "base/Profiler.h" + +#include +#include +#include + +ResamplingWavFileReader::ResamplingWavFileReader(QString path, + ResampleMode resampleMode, + CacheMode mode, + size_t targetRate) : + CodedAudioFileReader(mode, targetRate), + m_path(path), + m_cancelled(false), + m_processed(0), + m_completion(0), + m_original(0), + m_progress(0), + m_decodeThread(0) +{ + m_channelCount = 0; + m_fileRate = 0; + + std::cerr << "ResamplingWavFileReader::ResamplingWavFileReader(\"" + << path.toStdString() << "\"): rate " << targetRate << std::endl; + + Profiler profiler("ResamplingWavFileReader::ResamplingWavFileReader", true); + + m_original = new WavFileReader(path); + if (!m_original->isOK()) { + m_error = m_original->getError(); + return; + } + + m_channelCount = m_original->getChannelCount(); + m_fileRate = m_original->getSampleRate(); + + initialiseDecodeCache(); + + if (resampleMode == ResampleAtOnce) { + + m_progress = new QProgressDialog + (QObject::tr("Resampling %1...").arg(QFileInfo(path).fileName()), + QObject::tr("Stop"), 0, 100); + m_progress->hide(); + + size_t blockSize = 16384; + size_t total = m_original->getFrameCount(); + + SampleBlock block; + + for (size_t i = 0; i < total; i += blockSize) { + + size_t count = blockSize; + if (i + count > total) count = total - i; + + m_original->getInterleavedFrames(i, count, block); + addBlock(block); + + if (m_cancelled) break; + } + + if (isDecodeCacheInitialised()) finishDecodeCache(); + + delete m_original; + m_original = 0; + + delete m_progress; + m_progress = 0; + + } else { + + m_decodeThread = new DecodeThread(this); + m_decodeThread->start(); + } +} + +ResamplingWavFileReader::~ResamplingWavFileReader() +{ + if (m_decodeThread) { + m_cancelled = true; + m_decodeThread->wait(); + delete m_decodeThread; + } + + delete m_original; +} + +void +ResamplingWavFileReader::DecodeThread::run() +{ + if (m_reader->m_cacheMode == CacheInTemporaryFile) { + m_reader->startSerialised("ResamplingWavFileReader::Decode"); + } + + size_t blockSize = 16384; + size_t total = m_reader->m_original->getFrameCount(); + + SampleBlock block; + + for (size_t i = 0; i < total; i += blockSize) { + + size_t count = blockSize; + if (i + count > total) count = total - i; + + m_reader->m_original->getInterleavedFrames(i, count, block); + m_reader->addBlock(block); + + if (m_reader->m_cancelled) break; + } + + if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache(); + m_reader->m_completion = 100; + + m_reader->endSerialised(); + + delete m_reader->m_original; + m_reader->m_original = 0; +} + +void +ResamplingWavFileReader::addBlock(const SampleBlock &frames) +{ + addSamplesToDecodeCache(frames); + + m_processed += frames.size(); + + int progress = lrint((float(m_processed) * 100) / + float(m_original->getFrameCount())); + + if (progress > 99) progress = 99; + m_completion = progress; + + if (m_progress) { + if (progress > m_progress->value()) { + m_progress->setValue(progress); + m_progress->show(); + m_progress->raise(); + qApp->processEvents(); + if (m_progress->wasCanceled()) { + m_cancelled = true; + } + } + } +} + +void +ResamplingWavFileReader::getSupportedExtensions(std::set &extensions) +{ + WavFileReader::getSupportedExtensions(extensions); +} + + diff -r 2b6c99b607f1 -r c022976d18e8 data/fileio/ResamplingWavFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/ResamplingWavFileReader.h Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,78 @@ +/* -*- 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 2007 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 _RESAMPLING_WAV_FILE_READER_H_ +#define _RESAMPLING_WAV_FILE_READER_H_ + +#include "CodedAudioFileReader.h" + +#include "base/Thread.h" + +#include + +class WavFileReader; +class QProgressDialog; + +class ResamplingWavFileReader : public CodedAudioFileReader +{ +public: + enum ResampleMode { + ResampleAtOnce, // resample the file on construction, with progress dialog + ResampleThreaded // resample in a background thread after construction + }; + + ResamplingWavFileReader(QString path, + ResampleMode resampleMode, + CacheMode cacheMode, + size_t targetRate = 0); + virtual ~ResamplingWavFileReader(); + + virtual QString getError() const { return m_error; } + + static void getSupportedExtensions(std::set &extensions); + + virtual int getDecodeCompletion() const { return m_completion; } + + virtual bool isUpdating() const { + return m_decodeThread && m_decodeThread->isRunning(); + } + +protected: + QString m_path; + QString m_error; + bool m_cancelled; + size_t m_processed; + int m_completion; + + WavFileReader *m_original; + QProgressDialog *m_progress; + + void addBlock(const SampleBlock &frames); + + class DecodeThread : public Thread + { + public: + DecodeThread(ResamplingWavFileReader *reader) : m_reader(reader) { } + virtual void run(); + + protected: + ResamplingWavFileReader *m_reader; + }; + + DecodeThread *m_decodeThread; +}; + +#endif + diff -r 2b6c99b607f1 -r c022976d18e8 data/model/AggregateWaveModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/AggregateWaveModel.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,228 @@ +/* -*- 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 2007 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 "AggregateWaveModel.h" + +#include + +PowerOfSqrtTwoZoomConstraint +AggregateWaveModel::m_zoomConstraint; + +AggregateWaveModel::AggregateWaveModel(ChannelSpecList channelSpecs) : + m_components(channelSpecs) +{ + for (ChannelSpecList::const_iterator i = channelSpecs.begin(); + i != channelSpecs.end(); ++i) { + if (i->model->getSampleRate() != + channelSpecs.begin()->model->getSampleRate()) { + std::cerr << "AggregateWaveModel::AggregateWaveModel: WARNING: Component models do not all have the same sample rate" << std::endl; + break; + } + } +} + +AggregateWaveModel::~AggregateWaveModel() +{ +} + +bool +AggregateWaveModel::isOK() const +{ + for (ChannelSpecList::const_iterator i = m_components.begin(); + i != m_components.end(); ++i) { + if (!i->model->isOK()) return false; + } + return true; +} + +bool +AggregateWaveModel::isReady(int *completion) const +{ + if (completion) *completion = 100; + bool ready = true; + for (ChannelSpecList::const_iterator i = m_components.begin(); + i != m_components.end(); ++i) { + int completionHere = 100; + if (!i->model->isReady(&completionHere)) ready = false; + if (completion && completionHere < *completion) { + *completion = completionHere; + } + } + return ready; +} + +size_t +AggregateWaveModel::getFrameCount() const +{ + size_t count = 0; + + for (ChannelSpecList::const_iterator i = m_components.begin(); + i != m_components.end(); ++i) { + size_t thisCount = i->model->getEndFrame() - i->model->getStartFrame(); + if (thisCount > count) count = thisCount; + } + + return count; +} + +size_t +AggregateWaveModel::getChannelCount() const +{ + return m_components.size(); +} + +size_t +AggregateWaveModel::getSampleRate() const +{ + if (m_components.empty()) return 0; + return m_components.begin()->model->getSampleRate(); +} + +Model * +AggregateWaveModel::clone() const +{ + return new AggregateWaveModel(m_components); +} + +size_t +AggregateWaveModel::getValues(int channel, size_t start, size_t end, + float *buffer) const +{ + int ch0 = channel, ch1 = channel; + bool mixing = false; + if (channel == -1) { + ch0 = 0; + ch1 = getChannelCount()-1; + mixing = true; + } + + float *readbuf = buffer; + if (mixing) { + readbuf = new float[end - start]; + for (size_t i = 0; i < end - start; ++i) { + buffer[i] = 0.f; + } + } + + size_t sz = end - start; + + for (int c = ch0; c <= ch1; ++c) { + size_t szHere = + m_components[c].model->getValues(m_components[c].channel, + start, end, + readbuf); + if (szHere < sz) sz = szHere; + if (mixing) { + for (size_t i = 0; i < end - start; ++i) { + buffer[i] += readbuf[i]; + } + } + } + + if (mixing) delete[] readbuf; + return sz; +} + +size_t +AggregateWaveModel::getValues(int channel, size_t start, size_t end, + double *buffer) const +{ + int ch0 = channel, ch1 = channel; + bool mixing = false; + if (channel == -1) { + ch0 = 0; + ch1 = getChannelCount()-1; + mixing = true; + } + + double *readbuf = buffer; + if (mixing) { + readbuf = new double[end - start]; + for (size_t i = 0; i < end - start; ++i) { + buffer[i] = 0.f; + } + } + + size_t sz = end - start; + + for (int c = ch0; c <= ch1; ++c) { + size_t szHere = + m_components[c].model->getValues(m_components[c].channel, + start, end, + readbuf); + if (szHere < sz) sz = szHere; + if (mixing) { + for (size_t i = 0; i < end - start; ++i) { + buffer[i] += readbuf[i]; + } + } + } + + if (mixing) delete[] readbuf; + return sz; +} + +void +AggregateWaveModel::getRanges(size_t channel, size_t start, size_t end, + RangeBlock &ranges, size_t &blockSize) const +{ + //!!! complete +} + +AggregateWaveModel::Range +AggregateWaveModel::getRange(size_t channel, size_t start, size_t end) const +{ + //!!! complete + return Range(); +} + +size_t +AggregateWaveModel::getComponentCount() const +{ + return m_components.size(); +} + +AggregateWaveModel::ModelChannelSpec +AggregateWaveModel::getComponent(size_t c) const +{ + return m_components[c]; +} + +void +AggregateWaveModel::componentModelChanged() +{ + emit modelChanged(); +} + +void +AggregateWaveModel::componentModelChanged(size_t start, size_t end) +{ + emit modelChanged(start, end); +} + +void +AggregateWaveModel::componentModelCompletionChanged() +{ + emit completionChanged(); +} + +void +AggregateWaveModel::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + //!!! complete +} + diff -r 2b6c99b607f1 -r c022976d18e8 data/model/AggregateWaveModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/AggregateWaveModel.h Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,94 @@ +/* -*- 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 2007 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 _AGGREGATE_WAVE_MODEL_H_ +#define _AGGREGATE_WAVE_MODEL_H_ + +#include "RangeSummarisableTimeValueModel.h" +#include "PowerOfSqrtTwoZoomConstraint.h" + +#include + +class AggregateWaveModel : public RangeSummarisableTimeValueModel +{ + Q_OBJECT + +public: + struct ModelChannelSpec + { + ModelChannelSpec(RangeSummarisableTimeValueModel *m, int c) : + model(m), channel(c) { } + RangeSummarisableTimeValueModel *model; + int channel; + }; + + typedef std::vector ChannelSpecList; + + AggregateWaveModel(ChannelSpecList channelSpecs); + ~AggregateWaveModel(); + + bool isOK() const; + bool isReady(int *) const; + + size_t getComponentCount() const; + ModelChannelSpec getComponent(size_t c) const; + + const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; } + + size_t getFrameCount() const; + size_t getChannelCount() const; + size_t getSampleRate() const; + + virtual Model *clone() const; + + float getValueMinimum() const { return -1.0f; } + float getValueMaximum() const { return 1.0f; } + + virtual size_t getStartFrame() const { return 0; } + virtual size_t getEndFrame() const { return getFrameCount(); } + + virtual size_t getValues(int channel, size_t start, size_t end, + float *buffer) const; + + virtual size_t getValues(int channel, size_t start, size_t end, + double *buffer) const; + + virtual void getRanges(size_t channel, size_t start, size_t end, + RangeBlock &ranges, + size_t &blockSize) const; + + virtual Range getRange(size_t channel, size_t start, size_t end) const; + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const; + +signals: + void modelChanged(); + void modelChanged(size_t, size_t); + void completionChanged(); + +protected slots: + void componentModelChanged(); + void componentModelChanged(size_t, size_t); + void componentModelCompletionChanged(); + +protected: + ChannelSpecList m_components; + static PowerOfSqrtTwoZoomConstraint m_zoomConstraint; +}; + +#endif + diff -r 2b6c99b607f1 -r c022976d18e8 data/model/AlignmentModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/AlignmentModel.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,210 @@ +/* -*- 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 2007 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 "AlignmentModel.h" + +#include "SparseTimeValueModel.h" + +AlignmentModel::AlignmentModel(Model *reference, + Model *aligned, + Model *inputModel, + SparseTimeValueModel *path) : + m_reference(reference), + m_aligned(aligned), + m_inputModel(inputModel), + m_path(path), + m_reversePath(0), + m_pathComplete(false) +{ + connect(m_path, SIGNAL(modelChanged()), + this, SLOT(pathChanged())); + + connect(m_path, SIGNAL(modelChanged(size_t, size_t)), + this, SLOT(pathChanged(size_t, size_t))); + + connect(m_path, SIGNAL(completionChanged()), + this, SLOT(pathCompletionChanged())); + + constructReversePath(); +} + +AlignmentModel::~AlignmentModel() +{ + delete m_inputModel; + delete m_path; + delete m_reversePath; +} + +bool +AlignmentModel::isOK() const +{ + return m_path->isOK(); +} + +size_t +AlignmentModel::getStartFrame() const +{ + //!!! do we care about distinct rates? + size_t a = m_reference->getStartFrame(); + size_t b = m_aligned->getStartFrame(); + return std::min(a, b); +} + +size_t +AlignmentModel::getEndFrame() const +{ + //!!! do we care about distinct rates? + size_t a = m_reference->getEndFrame(); + size_t b = m_aligned->getEndFrame(); + return std::max(a, b); +} + +size_t +AlignmentModel::getSampleRate() const +{ + return m_reference->getSampleRate(); +} + +Model * +AlignmentModel::clone() const +{ + return new AlignmentModel + (m_reference, m_aligned, + m_inputModel ? m_inputModel->clone() : 0, + m_path ? static_cast(m_path->clone()) : 0); +} + +bool +AlignmentModel::isReady(int *completion) const +{ + return m_path->isReady(completion); +} + +const ZoomConstraint * +AlignmentModel::getZoomConstraint() const +{ + return m_path->getZoomConstraint(); +} + +const Model * +AlignmentModel::getReferenceModel() const +{ + return m_reference; +} + +const Model * +AlignmentModel::getAlignedModel() const +{ + return m_aligned; +} + +size_t +AlignmentModel::toReference(size_t frame) const +{ +// std::cerr << "AlignmentModel::toReference(" << frame << ")" << std::endl; + if (!m_reversePath) constructReversePath(); + return align(m_reversePath, frame); +} + +size_t +AlignmentModel::fromReference(size_t frame) const +{ +// std::cerr << "AlignmentModel::fromReference(" << frame << ")" << std::endl; + return align(m_path, frame); +} + +void +AlignmentModel::pathChanged() +{ +} + +void +AlignmentModel::pathChanged(size_t, size_t) +{ + if (!m_pathComplete) return; + constructReversePath(); +} + +void +AlignmentModel::pathCompletionChanged() +{ + if (!m_pathComplete) { + int completion = 0; + m_path->isReady(&completion); + std::cerr << "AlignmentModel::pathCompletionChanged: completion = " + << completion << std::endl; + m_pathComplete = (completion == 100); //!!! a bit of a hack + if (m_pathComplete) { + constructReversePath(); + delete m_inputModel; + m_inputModel = 0; + } + } + emit completionChanged(); +} + +void +AlignmentModel::constructReversePath() const +{ + if (!m_reversePath) { + m_reversePath = new SparseTimeValueModel + (m_path->getSampleRate(), m_path->getResolution(), false); + } + + m_reversePath->clear(); + + SparseTimeValueModel::PointList points = m_path->getPoints(); + + for (SparseTimeValueModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + long frame = i->frame; + float value = i->value; + long rframe = lrintf(value * m_aligned->getSampleRate()); + float rvalue = (float)frame / (float)m_reference->getSampleRate(); + m_reversePath->addPoint + (SparseTimeValueModel::Point(rframe, rvalue, "")); + } + + std::cerr << "AlignmentModel::constructReversePath: " << m_reversePath->getPointCount() << " points" << std::endl; +} + +size_t +AlignmentModel::align(SparseTimeValueModel *path, size_t frame) const +{ + // The path consists of a series of points, each with x (time) + // equal to the time on the source model and y (value) equal to + // the time on the target model. Times and values are both + // monotonically increasing. + + const SparseTimeValueModel::PointList &points = path->getPoints(); + + if (points.empty()) { +// std::cerr << "AlignmentModel::align: No points" << std::endl; + return frame; + } + + SparseTimeValueModel::Point point(frame); + SparseTimeValueModel::PointList::const_iterator i = points.lower_bound(point); + if (i == points.end()) --i; + float time = i->value; + size_t rv = lrintf(time * getSampleRate()); + + //!!! interpolate! + +// std::cerr << "AlignmentModel::align: rv = " << rv << std::endl; + + return rv; +} + diff -r 2b6c99b607f1 -r c022976d18e8 data/model/AlignmentModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/AlignmentModel.h Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,72 @@ +/* -*- 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 2007 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 _ALIGNMENT_MODEL_H_ +#define _ALIGNMENT_MODEL_H_ + +#include "Model.h" + +class SparseTimeValueModel; + +class AlignmentModel : public Model +{ + Q_OBJECT + +public: + AlignmentModel(Model *reference, + Model *aligned, + Model *inputModel, // probably an AggregateWaveModel; I take ownership + SparseTimeValueModel *path); // I take ownership + ~AlignmentModel(); + + virtual bool isOK() const; + virtual size_t getStartFrame() const; + virtual size_t getEndFrame() const; + virtual size_t getSampleRate() const; + virtual Model *clone() const; + virtual bool isReady(int *completion = 0) const; + virtual const ZoomConstraint *getZoomConstraint() const; + + const Model *getReferenceModel() const; + const Model *getAlignedModel() const; + + size_t toReference(size_t frame) const; + size_t fromReference(size_t frame) const; + +signals: + void modelChanged(); + void modelChanged(size_t startFrame, size_t endFrame); + void completionChanged(); + +protected slots: + void pathChanged(); + void pathChanged(size_t startFrame, size_t endFrame); + void pathCompletionChanged(); + +protected: + Model *m_reference; // I don't own this + Model *m_aligned; // I don't own this + + Model *m_inputModel; // I own this + SparseTimeValueModel *m_path; // I own this + mutable SparseTimeValueModel *m_reversePath; // I own this + bool m_pathComplete; + + void constructReversePath() const; + + size_t align(SparseTimeValueModel *path, size_t frame) const; +}; + +#endif diff -r 2b6c99b607f1 -r c022976d18e8 data/model/FFTModel.cpp --- a/data/model/FFTModel.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/data/model/FFTModel.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -15,6 +15,7 @@ #include "FFTModel.h" #include "DenseTimeValueModel.h" +#include "AggregateWaveModel.h" #include "base/Profiler.h" #include "base/Pitch.h" @@ -34,14 +35,16 @@ m_xshift(0), m_yshift(0) { - m_server = FFTDataServer::getFuzzyInstance(model, - channel, - windowType, - windowSize, - windowIncrement, - fftSize, - polar, - fillFromColumn); + setSourceModel(const_cast(model)); //!!! hmm. + + m_server = getServer(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); if (!m_server) return; // caller should check isOK() @@ -77,6 +80,61 @@ if (m_server) FFTDataServer::releaseInstance(m_server); } +FFTDataServer * +FFTModel::getServer(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) +{ + // Obviously, an FFT model of channel C (where C != -1) of an + // aggregate model is the same as the FFT model of the appropriate + // channel of whichever model that aggregate channel is drawn + // from. We should use that model here, in case we already have + // the data for it or will be wanting the same data again later. + + // If the channel is -1 (i.e. mixture of all channels), then we + // can't do this shortcut unless the aggregate model only has one + // channel or contains exactly all of the channels of a single + // other model. That isn't very likely -- if it were the case, + // why would we be using an aggregate model? + + if (channel >= 0) { + + const AggregateWaveModel *aggregate = + dynamic_cast(model); + + if (aggregate && channel < aggregate->getComponentCount()) { + + AggregateWaveModel::ModelChannelSpec spec = + aggregate->getComponent(channel); + + return getServer(spec.model, + spec.channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); + } + } + + // The normal case + + return FFTDataServer::getFuzzyInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); +} + size_t FFTModel::getSampleRate() const { diff -r 2b6c99b607f1 -r c022976d18e8 data/model/FFTModel.h --- a/data/model/FFTModel.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/model/FFTModel.h Fri Sep 28 13:56:38 2007 +0000 @@ -165,13 +165,17 @@ virtual void resume() { m_server->resume(); } private: - FFTModel(const FFTModel &); + FFTModel(const FFTModel &); // not implemented FFTModel &operator=(const FFTModel &); // not implemented FFTDataServer *m_server; int m_xshift; int m_yshift; + FFTDataServer *getServer(const DenseTimeValueModel *, + int, WindowType, size_t, size_t, size_t, + bool, size_t); + size_t getPeakPickWindowSize(PeakPickType type, size_t sampleRate, size_t bin, float &percentile) const; }; diff -r 2b6c99b607f1 -r c022976d18e8 data/model/Model.h --- a/data/model/Model.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/model/Model.h Fri Sep 28 13:56:38 2007 +0000 @@ -60,6 +60,12 @@ virtual size_t getSampleRate() const = 0; /** + * Return the frame rate of the underlying material, if the model + * itself has already been resampled. + */ + virtual size_t getNativeRate() const { return getSampleRate(); } + + /** * Return a copy of this model. * * If the model is not editable, this may be effectively a shallow @@ -104,6 +110,24 @@ return 0; } + /** + * If this model was derived from another, return the model it was + * derived from. The assumption is that the source model's + * alignment will also apply to this model, unless some other + * property indicates otherwise. + */ + virtual Model *getSourceModel() const { + return m_sourceModel; + } + + /** + * Set the source model for this model. + */ + //!!! No way to handle source model deletion &c yet + virtual void setSourceModel(Model *model) { + m_sourceModel = model; + } + virtual void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const; @@ -135,11 +159,13 @@ void completionChanged(); protected: - Model() { } + Model() : m_sourceModel(0) { } // Not provided. Model(const Model &); Model &operator=(const Model &); + + Model *m_sourceModel; }; #endif diff -r 2b6c99b607f1 -r c022976d18e8 data/model/RangeSummarisableTimeValueModel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/RangeSummarisableTimeValueModel.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -0,0 +1,62 @@ +/* -*- 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 2007 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 "RangeSummarisableTimeValueModel.h" + +#include "AlignmentModel.h" + +#include + +void +RangeSummarisableTimeValueModel::setAlignment(AlignmentModel *alignment) +{ + delete m_alignment; + m_alignment = alignment; + connect(m_alignment, SIGNAL(completionChanged()), + this, SIGNAL(alignmentCompletionChanged())); +} + +const Model * +RangeSummarisableTimeValueModel::getAlignmentReference() const +{ + if (!m_alignment) return 0; + return m_alignment->getReferenceModel(); +} + +size_t +RangeSummarisableTimeValueModel::alignToReference(size_t frame) const +{ + if (!m_alignment) return frame; + return m_alignment->toReference(frame); +} + +size_t +RangeSummarisableTimeValueModel::alignFromReference(size_t refFrame) const +{ + if (!m_alignment) return refFrame; + return m_alignment->fromReference(refFrame); +} + +int +RangeSummarisableTimeValueModel::getAlignmentCompletion() const +{ + std::cerr << "RangeSummarisableTimeValueModel::getAlignmentCompletion" << std::endl; + if (!m_alignment) return 100; + int completion = 0; + (void)m_alignment->isReady(&completion); + std::cerr << " -> " << completion << std::endl; + return completion; +} + diff -r 2b6c99b607f1 -r c022976d18e8 data/model/RangeSummarisableTimeValueModel.h --- a/data/model/RangeSummarisableTimeValueModel.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/model/RangeSummarisableTimeValueModel.h Fri Sep 28 13:56:38 2007 +0000 @@ -4,7 +4,7 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam. + This file copyright 2006-2007 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 @@ -21,6 +21,8 @@ #include "DenseTimeValueModel.h" #include "base/ZoomConstraint.h" +class AlignmentModel; + /** * Base class for models containing dense two-dimensional data (value * against time) that may be meaningfully represented in a zoomed view @@ -33,6 +35,8 @@ Q_OBJECT public: + RangeSummarisableTimeValueModel() : m_alignment(0) { } + struct Range { float min; @@ -68,6 +72,18 @@ * and end frames. */ virtual Range getRange(size_t channel, size_t start, size_t end) const = 0; + + virtual void setAlignment(AlignmentModel *alignment); // I take ownership + virtual const Model *getAlignmentReference() const; + virtual size_t alignToReference(size_t frame) const; + virtual size_t alignFromReference(size_t referenceFrame) const; + virtual int getAlignmentCompletion() const; + +signals: + void alignmentCompletionChanged(); + +protected: + AlignmentModel *m_alignment; }; #endif diff -r 2b6c99b607f1 -r c022976d18e8 data/model/SparseModel.h --- a/data/model/SparseModel.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/model/SparseModel.h Fri Sep 28 13:56:38 2007 +0000 @@ -87,6 +87,11 @@ virtual PointList getPoints(long frame) const; /** + * Get all points. + */ + virtual const PointList &getPoints() const { return m_points; } + + /** * Return all points that share the nearest frame number prior to * the given one at which there are any points. */ @@ -116,6 +121,12 @@ */ virtual void deletePoint(const PointType &point); + virtual bool isReady(int *completion = 0) const { + bool ready = isOK() && (m_completion == 100); + if (completion) *completion = m_completion; + return ready; + } + virtual void setCompletion(int completion); virtual int getCompletion() const { return m_completion; } @@ -505,13 +516,17 @@ void SparseModel::setCompletion(int completion) { -// std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; + std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; if (m_completion != completion) { m_completion = completion; if (completion == 100) { + if (!m_notifyOnAdd) { + emit completionChanged(); + } + m_notifyOnAdd = true; // henceforth emit modelChanged(); diff -r 2b6c99b607f1 -r c022976d18e8 data/model/WaveFileModel.cpp --- a/data/model/WaveFileModel.cpp Fri Sep 21 09:13:11 2007 +0000 +++ b/data/model/WaveFileModel.cpp Fri Sep 28 13:56:38 2007 +0000 @@ -38,7 +38,7 @@ PowerOfSqrtTwoZoomConstraint WaveFileModel::m_zoomConstraint; -WaveFileModel::WaveFileModel(QString path) : +WaveFileModel::WaveFileModel(QString path, size_t targetRate) : m_path(path), m_myReader(true), m_fillThread(0), @@ -46,13 +46,14 @@ m_lastFillExtent(0), m_exiting(false) { - m_reader = AudioFileReaderFactory::createReader(path); + m_reader = AudioFileReaderFactory::createReader(path, targetRate); + if (m_reader) std::cerr << "WaveFileModel::WaveFileModel: reader rate: " << m_reader->getSampleRate() << std::endl; if (m_reader) setObjectName(m_reader->getTitle()); if (objectName() == "") setObjectName(QFileInfo(path).fileName()); if (isOK()) fillCache(); } -WaveFileModel::WaveFileModel(QString path, QString originalLocation) : +WaveFileModel::WaveFileModel(QString path, QString originalLocation, size_t targetRate) : m_path(originalLocation), m_myReader(true), m_fillThread(0), @@ -60,7 +61,8 @@ m_lastFillExtent(0), m_exiting(false) { - m_reader = AudioFileReaderFactory::createReader(path); + m_reader = AudioFileReaderFactory::createReader(path, targetRate); + if (m_reader) std::cerr << "WaveFileModel::WaveFileModel: reader rate: " << m_reader->getSampleRate() << std::endl; if (m_reader) setObjectName(m_reader->getTitle()); if (objectName() == "") setObjectName(QFileInfo(originalLocation).fileName()); if (isOK()) fillCache(); @@ -151,6 +153,15 @@ } size_t +WaveFileModel::getNativeRate() const +{ + if (!m_reader) return 0; + size_t rate = m_reader->getNativeRate(); + if (rate == 0) rate = getSampleRate(); + return rate; +} + +size_t WaveFileModel::getValues(int channel, size_t start, size_t end, float *buffer) const { diff -r 2b6c99b607f1 -r c022976d18e8 data/model/WaveFileModel.h --- a/data/model/WaveFileModel.h Fri Sep 21 09:13:11 2007 +0000 +++ b/data/model/WaveFileModel.h Fri Sep 28 13:56:38 2007 +0000 @@ -32,8 +32,8 @@ Q_OBJECT public: - WaveFileModel(QString path); - WaveFileModel(QString path, QString originalLocation); + WaveFileModel(QString path, size_t targetRate = 0); + WaveFileModel(QString path, QString originalLocation, size_t targetRate = 0); WaveFileModel(QString originalLocation, AudioFileReader *reader); ~WaveFileModel(); @@ -45,6 +45,7 @@ size_t getFrameCount() const; size_t getChannelCount() const; size_t getSampleRate() const; + size_t getNativeRate() const; virtual Model *clone() const;