summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG15
-rw-r--r--README.md6
-rw-r--r--bqaudiostream/COPYING2
-rw-r--r--bqaudiostream/Makefile3
-rw-r--r--bqaudiostream/README.md2
-rw-r--r--bqaudiostream/bqaudiostream/AudioReadStream.h85
-rw-r--r--bqaudiostream/bqaudiostream/AudioReadStreamFactory.h6
-rw-r--r--bqaudiostream/bqaudiostream/AudioWriteStream.h2
-rw-r--r--bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h16
-rw-r--r--bqaudiostream/bqaudiostream/Exceptions.h2
-rw-r--r--bqaudiostream/src/AudioReadStream.cpp25
-rw-r--r--bqaudiostream/src/AudioReadStreamFactory.cpp21
-rw-r--r--bqaudiostream/src/AudioStreamExceptions.cpp2
-rw-r--r--bqaudiostream/src/AudioWriteStreamFactory.cpp10
-rw-r--r--bqaudiostream/src/CoreAudioReadStream.cpp38
-rw-r--r--bqaudiostream/src/CoreAudioReadStream.h2
-rw-r--r--bqaudiostream/src/CoreAudioWriteStream.cpp2
-rw-r--r--bqaudiostream/src/CoreAudioWriteStream.h2
-rw-r--r--bqaudiostream/src/MediaFoundationReadStream.cpp2
-rw-r--r--bqaudiostream/src/MediaFoundationReadStream.h2
-rw-r--r--bqaudiostream/src/MiniMP3ReadStream.cpp7
-rw-r--r--bqaudiostream/src/OggVorbisReadStream.cpp2
-rw-r--r--bqaudiostream/src/OggVorbisReadStream.h2
-rw-r--r--bqaudiostream/src/OpusReadStream.cpp9
-rw-r--r--bqaudiostream/src/OpusReadStream.h2
-rw-r--r--bqaudiostream/src/OpusWriteStream.cpp53
-rw-r--r--bqaudiostream/src/SimpleWavFileReadStream.cpp32
-rw-r--r--bqaudiostream/src/SimpleWavFileReadStream.h2
-rw-r--r--bqaudiostream/src/SimpleWavFileWriteStream.cpp38
-rw-r--r--bqaudiostream/src/SimpleWavFileWriteStream.h3
-rw-r--r--bqaudiostream/src/WavFileReadStream.cpp17
-rw-r--r--bqaudiostream/src/WavFileReadStream.h3
-rw-r--r--bqaudiostream/src/WavFileWriteStream.cpp2
-rw-r--r--bqaudiostream/src/WavFileWriteStream.h2
-rw-r--r--bqaudiostream/test/TestAudioStreamRead.h5
-rw-r--r--bqaudiostream/test/TestSimpleWavRead.h37
-rw-r--r--bqaudiostream/test/TestWavSeek.h121
-rw-r--r--bqaudiostream/test/main.cpp7
-rw-r--r--bqaudiostream/test/test.pro2
-rw-r--r--bqaudiostream/test/testfiles/20samplesbin0 -> 84 bytes
-rw-r--r--bqaudiostream/test/testfiles/8000-1-8bin0 -> 16044 bytes
-rw-r--r--bqfft/.build.yml3
-rw-r--r--bqfft/Makefile1
-rw-r--r--bqfft/README.md39
-rw-r--r--bqfft/build/Makefile.linux.sleef11
-rw-r--r--bqfft/src/FFT.cpp371
-rw-r--r--bqfft/test/TestFFT.cpp9
-rw-r--r--bqvec/bqvec/Allocators.h8
-rw-r--r--bqvec/bqvec/VectorOpsComplex.h127
-rw-r--r--bqvec/src/VectorOpsComplex.cpp54
-rw-r--r--bqvec/test/Timings.cpp332
-rw-r--r--debian/changelog14
-rw-r--r--debian/control4
-rw-r--r--debian/copyright2
-rw-r--r--debian/copyright_hints21
-rw-r--r--debian/patches/01-libserd.patch2
-rw-r--r--deploy/linux/deb-skeleton/DEBIAN/control2
-rw-r--r--deploy/linux/docker/Dockerfile_appimage.in7
-rw-r--r--deploy/linux/docker/Dockerfile_deb.in7
-rwxr-xr-xdeploy/macos/notarize.sh8
-rw-r--r--main/MainWindow.cpp1
-rw-r--r--main/PreferencesDialog.cpp20
-rw-r--r--main/PreferencesDialog.h2
-rw-r--r--main/main.cpp20
-rw-r--r--meson.build5
-rw-r--r--svapp/audio/AudioCallbackPlaySource.cpp14
-rw-r--r--svapp/audio/TimeStretchWrapper.cpp60
-rw-r--r--svapp/audio/TimeStretchWrapper.h20
-rw-r--r--svcore/base/Pitch.cpp5
-rw-r--r--svcore/base/Preferences.cpp22
-rw-r--r--svcore/base/Preferences.h5
-rw-r--r--svcore/base/UnitDatabase.cpp3
-rw-r--r--svgui/layer/LayerGeometryProvider.h10
-rw-r--r--svgui/layer/SliceLayer.cpp35
-rw-r--r--svgui/layer/SliceLayer.h1
-rw-r--r--svgui/layer/SpectrumLayer.cpp13
-rw-r--r--svgui/layer/WaveformLayer.cpp54
-rw-r--r--svgui/view/View.h4
-rw-r--r--svgui/view/ViewProxy.h3
79 files changed, 1441 insertions, 469 deletions
diff --git a/CHANGELOG b/CHANGELOG
index be84d41..fa3a97c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,18 @@
+Changes in Sonic Visualiser v4.5.1 (7 Nov 2022) since the previous release 4.5:
+
+ - Update to use Rubber Band v3 with its higher quality timestretcher
+ (the older, lower-CPU one is still available in preferences). This
+ means the build requires rubberband-3.0.0 or newer, so you may need
+ to install it separately if building on an older system.
+
+ - Fix inability to select Hz as the unit of a layer following import
+
+ - Provisional fix to bailing out on startup when invoked with "Open
+ With..." on Mac
+
+ - Fix nonsense display when showing frequency scaled points with value
+ of 0Hz
+
Changes in Sonic Visualiser v4.5 (31 March 2022) since the previous release 4.4:
diff --git a/README.md b/README.md
index 3ac81da..2890654 100644
--- a/README.md
+++ b/README.md
@@ -47,8 +47,10 @@ Landone, Mathieu Barthet, Dan Stowell, Jesús Corral García, Matthias
Mauch, and Craig Sapp. Special thanks to Professor Mark Sandler for
initiating and supporting the project.
-Sonic Visualiser is currently maintained primarily by Chris Cannam at
-Particular Programs Ltd.
+Sonic Visualiser is currently lightly maintained by Chris Cannam at
+Particular Programs Ltd. There are no major features in development as
+of this release, only bug fixes. If you need something specific and
+have funding available for it, please contact us.
The Sonic Visualiser code is in general
diff --git a/bqaudiostream/COPYING b/bqaudiostream/COPYING
index 89dc84f..d4c2e9a 100644
--- a/bqaudiostream/COPYING
+++ b/bqaudiostream/COPYING
@@ -1,5 +1,5 @@
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/Makefile b/bqaudiostream/Makefile
index e728ce9..c5589cb 100644
--- a/bqaudiostream/Makefile
+++ b/bqaudiostream/Makefile
@@ -15,8 +15,7 @@
# -DHAVE_COREAUDIO * Read various formats using CoreAudio on macOS/iOS
#
# If HAVE_LIBSNDFILE is not defined, a simple built-in Wav file writer
-# will also be provided, as none of the other libraries have write
-# support included here.
+# will also be provided.
AUDIOSTREAM_DEFINES := -DHAVE_LIBSNDFILE -DHAVE_OGGZ -DHAVE_FISHSOUND -DHAVE_OPUS
diff --git a/bqaudiostream/README.md b/bqaudiostream/README.md
index 9bf87b5..840dae1 100644
--- a/bqaudiostream/README.md
+++ b/bqaudiostream/README.md
@@ -16,6 +16,6 @@ C++ standard required: C++98 (does not use C++11)
* See also: [bqfft](https://hg.sr.ht/~breakfastquay/bqfft) [bqaudioio](https://hg.sr.ht/~breakfastquay/bqaudioio)
-Copyright 2007-2021 Particular Programs Ltd. Under a permissive
+Copyright 2007-2022 Particular Programs Ltd. Under a permissive
BSD/MIT-style licence: see the file COPYING for details.
diff --git a/bqaudiostream/bqaudiostream/AudioReadStream.h b/bqaudiostream/bqaudiostream/AudioReadStream.h
index f647854..86b861c 100644
--- a/bqaudiostream/bqaudiostream/AudioReadStream.h
+++ b/bqaudiostream/bqaudiostream/AudioReadStream.h
@@ -6,7 +6,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -53,17 +53,60 @@ class AudioReadStream
public:
virtual ~AudioReadStream();
+ /**
+ * Return any stream error message. Errors are reported by
+ * throwing exceptions; after catching an exception, you may call
+ * getError() to retrieve a more detailed, possibly human-readable
+ * message.
+ */
virtual std::string getError() const { return ""; }
+ /**
+ * Return the number of channels in the stream.
+ */
size_t getChannelCount() const { return m_channelCount; }
- size_t getSampleRate() const { return m_sampleRate; } // source stream rate
- size_t getEstimatedFrameCount() const; // at source stream rate
-
+
+ /**
+ * Return the native sample rate of the stream.
+ */
+ size_t getSampleRate() const { return m_sampleRate; }
+
+ /**
+ * Return true if the audio stream is seekable to a specific frame
+ * position within the source.
+ *
+ * This depends on the format and also on the retrieval sample
+ * rate (see setRetrievalSampleRate() and
+ * getRetrievalSampleRate()), as resampling streams are not
+ * seekable.
+ */
+ bool isSeekable() const;
+
+ /**
+ * Return an estimate of the number of frames in the stream (at
+ * its native sample rate) or zero if the stream can't provide
+ * that information.
+ *
+ * For seekable streams (see isSeekable()) this is guaranteed to
+ * return a true frame count. For other streams it may be
+ * approximate, hence the name.
+ */
+ size_t getEstimatedFrameCount() const;
+
+ /**
+ * Set a sample rate at which audio data should be read from the
+ * stream. The stream will resample if this differs from the
+ * native rate of the stream (reported by getSampleRate()). The
+ * default is to use the native rate of the stream.
+ */
void setRetrievalSampleRate(size_t);
- size_t getRetrievalSampleRate() const;
- virtual std::string getTrackName() const = 0;
- virtual std::string getArtistName() const = 0;
+ /**
+ * Return the sample rate at which audio data will be read from
+ * the stream. The stream will resample if this differs from the
+ * native rate of the stream (reported by getSampleRate()).
+ */
+ size_t getRetrievalSampleRate() const;
/**
* Retrieve \count frames of audio data (that is, \count *
@@ -81,13 +124,41 @@ public:
* May throw InvalidFileFormat if decoding fails.
*/
size_t getInterleavedFrames(size_t count, float *frames);
+
+ /**
+ * Re-seek the stream to the requested audio frame position.
+ * Return true on success, or false if the stream is not seekable
+ * or the requested frame is out of range (beyond the end of the
+ * stream).
+ *
+ * The stream is left in a valid state regardless of whether
+ * seeking succeeds, but its position following a call that
+ * returns false is not defined. If the stream is seekable, it
+ * will be possible to seek it back into range and continue
+ * reading even after a failed seek.
+ */
+ bool seek(size_t frame);
+
+ /**
+ * Return the track name or title, if any was found in the stream,
+ * or an empty string otherwise.
+ */
+ virtual std::string getTrackName() const = 0;
+
+ /**
+ * Return the artist name, if any was found in the stream, or an
+ * empty string otherwise.
+ */
+ virtual std::string getArtistName() const = 0;
protected:
AudioReadStream();
virtual size_t getFrames(size_t count, float *frames) = 0;
+ virtual bool performSeek(size_t) { return false; }
size_t m_channelCount;
size_t m_sampleRate;
size_t m_estimatedFrameCount;
+ bool m_seekable;
private:
int getResampledChunk(int count, float *frames);
diff --git a/bqaudiostream/bqaudiostream/AudioReadStreamFactory.h b/bqaudiostream/bqaudiostream/AudioReadStreamFactory.h
index 07e4c4a..8a6f731 100644
--- a/bqaudiostream/bqaudiostream/AudioReadStreamFactory.h
+++ b/bqaudiostream/bqaudiostream/AudioReadStreamFactory.h
@@ -6,7 +6,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -49,7 +49,9 @@ public:
/**
* Create and return a read stream object for the given audio file
* name, if possible. The file name should be UTF-8 encoded. The
- * audio format will be deduced from the file extension.
+ * audio format will be deduced from the file extension. If the
+ * file has no extension, it will still be opened if it is a
+ * RIFF/WAVE file; for other formats the behaviour is undefined.
*
* May throw FileNotFound, FileOpenFailed,
* AudioReadStream::FileDRMProtected, InvalidFileFormat,
diff --git a/bqaudiostream/bqaudiostream/AudioWriteStream.h b/bqaudiostream/bqaudiostream/AudioWriteStream.h
index 2e77065..65a0f8a 100644
--- a/bqaudiostream/bqaudiostream/AudioWriteStream.h
+++ b/bqaudiostream/bqaudiostream/AudioWriteStream.h
@@ -6,7 +6,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h b/bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h
index 131b056..24eab77 100644
--- a/bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h
+++ b/bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h
@@ -6,7 +6,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -49,8 +49,9 @@ public:
/**
* Create and return a write stream object for the given audio
* file name, if possible. The audio file format will be deduced
- * from the file extension. If the file already exists, it will be
- * silently overwritten.
+ * from the file extension. If the file has no extension, it will
+ * be opened in RIFF/WAVE format. If the file already exists, it
+ * will be silently overwritten.
*
* May throw FailedToWriteFile, FileOperationFailed, or
* UnknownFileType.
@@ -67,8 +68,17 @@ public:
size_t channelCount,
size_t sampleRate);
+ /**
+ * Return a list of the file extensions supported by registered
+ * writers (e.g. "wav", "aiff", "opus").
+ */
static std::vector<std::string> getSupportedFileExtensions();
+ /**
+ * Return true if the extension of the given filename (e.g. "wav"
+ * extension for filename "A.WAV") is supported by a registered
+ * writer.
+ */
static bool isExtensionSupportedFor(std::string fileName);
/**
diff --git a/bqaudiostream/bqaudiostream/Exceptions.h b/bqaudiostream/bqaudiostream/Exceptions.h
index 513de34..663a63b 100644
--- a/bqaudiostream/bqaudiostream/Exceptions.h
+++ b/bqaudiostream/bqaudiostream/Exceptions.h
@@ -6,7 +6,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/AudioReadStream.cpp b/bqaudiostream/src/AudioReadStream.cpp
index 3489adb..1131a19 100644
--- a/bqaudiostream/src/AudioReadStream.cpp
+++ b/bqaudiostream/src/AudioReadStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -47,6 +47,7 @@ AudioReadStream::AudioReadStream() :
m_channelCount(0),
m_sampleRate(0),
m_estimatedFrameCount(0),
+ m_seekable(false),
m_retrievalRate(0),
m_totalFileFrames(0),
m_totalRetrievedFrames(0),
@@ -61,6 +62,19 @@ AudioReadStream::~AudioReadStream()
delete m_resampleBuffer;
}
+bool
+AudioReadStream::isSeekable() const
+{
+ if (m_retrievalRate != 0 &&
+ m_retrievalRate != m_sampleRate) {
+ return false;
+ }
+ if (m_channelCount == 0) {
+ return false;
+ }
+ return m_seekable;
+}
+
size_t
AudioReadStream::getEstimatedFrameCount() const
{
@@ -91,6 +105,15 @@ AudioReadStream::getRetrievalSampleRate() const
else return m_retrievalRate;
}
+bool
+AudioReadStream::seek(size_t frame)
+{
+ if (!isSeekable()) {
+ return false;
+ }
+ return performSeek(frame);
+}
+
size_t
AudioReadStream::getInterleavedFrames(size_t count, float *frames)
{
diff --git a/bqaudiostream/src/AudioReadStreamFactory.cpp b/bqaudiostream/src/AudioReadStreamFactory.cpp
index 08c782c..a1b7698 100644
--- a/bqaudiostream/src/AudioReadStreamFactory.cpp
+++ b/bqaudiostream/src/AudioReadStreamFactory.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -71,6 +71,14 @@ AudioReadStreamFactory::createReadStream(std::string audioFileName)
// more predictable always to use only the reader that has
// registered the extension (if there is one).
+ if (extension == "") {
+ // We explicitly support extension-less files so long as they
+ // are RIFF/WAVE format. (This is in order to support
+ // programmatically generated temporary files created with
+ // e.g. mkstemp.)
+ extension = "wav";
+ }
+
try {
AudioReadStream *stream = f->createFor(extension, audioFileName);
if (!stream) throw UnknownFileType(audioFileName);
@@ -129,6 +137,12 @@ AudioReadStreamFactory::getFileFilter()
// WavFileReadStream uses libsndfile, which is mostly trustworthy
#include "WavFileReadStream.cpp"
+// SimpleWavFileReadStream reads most WAV files. The dedicated
+// WavFileReadStream using libsndfile is better and goes first, but
+// this must come before the other general platform libraries because
+// we don't currently have seek support in those
+#include "SimpleWavFileReadStream.cpp"
+
// OggVorbisReadStream uses the official libraries, which ought to be good
#include "OggVorbisReadStream.cpp"
@@ -146,8 +160,3 @@ AudioReadStreamFactory::getFileFilter()
// practice is not as good as the platform frameworks
#include "MiniMP3ReadStream.cpp"
-// SimpleWavFileReadStream reads most WAV files, but any of the other
-// WAV readers (WavFileReadStream, MediaFoundationReadStream,
-// CoreAudioReadStream) will be more general and more trustworthy
-#include "SimpleWavFileReadStream.cpp"
-
diff --git a/bqaudiostream/src/AudioStreamExceptions.cpp b/bqaudiostream/src/AudioStreamExceptions.cpp
index 30b8197..46c1c53 100644
--- a/bqaudiostream/src/AudioStreamExceptions.cpp
+++ b/bqaudiostream/src/AudioStreamExceptions.cpp
@@ -6,7 +6,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/AudioWriteStreamFactory.cpp b/bqaudiostream/src/AudioWriteStreamFactory.cpp
index 63e5975..6811ef6 100644
--- a/bqaudiostream/src/AudioWriteStreamFactory.cpp
+++ b/bqaudiostream/src/AudioWriteStreamFactory.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -60,6 +60,14 @@ AudioWriteStreamFactory::createWriteStream(std::string audioFileName,
AudioWriteStreamFactoryImpl *f = AudioWriteStreamFactoryImpl::getInstance();
+ if (extension == "") {
+ // We explicitly support extension-less filenames and write
+ // them in RIFF/WAVE format. (This is in order to support
+ // programmatically generated temporary files created with
+ // e.g. mkstemp.)
+ extension = "wav";
+ }
+
try {
AudioWriteStream *stream = f->createFor(extension, target);
if (!stream) throw UnknownFileType(audioFileName);
diff --git a/bqaudiostream/src/CoreAudioReadStream.cpp b/bqaudiostream/src/CoreAudioReadStream.cpp
index eb027a5..73a15bb 100644
--- a/bqaudiostream/src/CoreAudioReadStream.cpp
+++ b/bqaudiostream/src/CoreAudioReadStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -234,6 +234,7 @@ CoreAudioReadStream::CoreAudioReadStream(std::string path) :
noncritical = ExtAudioFileGetProperty
(m_d->file, kExtAudioFileProperty_FileLengthFrames, &propsize, &totalFrames);
if (noncritical == noErr && totalFrames > 0) {
+// std::cerr << "CoreAudioReadStream: estimated frame count " << totalFrames << std::endl;
m_estimatedFrameCount = totalFrames;
}
}
@@ -244,27 +245,38 @@ CoreAudioReadStream::getFrames(size_t count, float *frames)
if (!m_channelCount) return 0;
if (count == 0) return 0;
- m_d->buffer.mBuffers[0].mDataByteSize =
- sizeof(float) * m_channelCount * count;
+ UInt32 obtained = 0;
+
+ while (obtained < count) {
+
+ m_d->buffer.mBuffers[0].mDataByteSize =
+ sizeof(float) * m_channelCount * (count - obtained);
+
+ m_d->buffer.mBuffers[0].mData = frames + m_channelCount * obtained;
- m_d->buffer.mBuffers[0].mData = frames;
+ UInt32 framesRead = count - obtained;
- UInt32 framesRead = count;
+ m_d->err = ExtAudioFileRead(m_d->file, &framesRead, &m_d->buffer);
+ if (m_d->err) {
+ m_error = "CoreAudioReadStream: Error in decoder: code " + codestr(m_d->err);
+ throw InvalidFileFormat(m_path, "error in decoder");
+ }
- m_d->err = ExtAudioFileRead(m_d->file, &framesRead, &m_d->buffer);
- if (m_d->err) {
- m_error = "CoreAudioReadStream: Error in decoder: code " + codestr(m_d->err);
- throw InvalidFileFormat(m_path, "error in decoder");
- }
+// std::cerr << "CoreAudioReadStream::getFrames: " << count << " frames requested across " << m_channelCount << " channel(s), " << framesRead << " frames actually read" << std::endl;
+
+ if (framesRead == 0) {
+ break;
+ }
- // cerr << "CoreAudioReadStream::getFrames: " << count << " frames requested across " << m_channelCount << " channel(s), " << framesRead << " frames actually read" << std::endl;
+ obtained += framesRead;
+ }
- return framesRead;
+ return obtained;
}
CoreAudioReadStream::~CoreAudioReadStream()
{
-// cerr << "CoreAudioReadStream::~CoreAudioReadStream" << std::endl;
+// std::cerr << "CoreAudioReadStream::~CoreAudioReadStream" << std::endl;
if (m_channelCount) {
ExtAudioFileDispose(m_d->file);
diff --git a/bqaudiostream/src/CoreAudioReadStream.h b/bqaudiostream/src/CoreAudioReadStream.h
index 7e67d8d..2d8e360 100644
--- a/bqaudiostream/src/CoreAudioReadStream.h
+++ b/bqaudiostream/src/CoreAudioReadStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/CoreAudioWriteStream.cpp b/bqaudiostream/src/CoreAudioWriteStream.cpp
index 8f48cfc..e3b9d41 100644
--- a/bqaudiostream/src/CoreAudioWriteStream.cpp
+++ b/bqaudiostream/src/CoreAudioWriteStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/CoreAudioWriteStream.h b/bqaudiostream/src/CoreAudioWriteStream.h
index d9b951e..0c3593a 100644
--- a/bqaudiostream/src/CoreAudioWriteStream.h
+++ b/bqaudiostream/src/CoreAudioWriteStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/MediaFoundationReadStream.cpp b/bqaudiostream/src/MediaFoundationReadStream.cpp
index bab1fd8..f051e67 100644
--- a/bqaudiostream/src/MediaFoundationReadStream.cpp
+++ b/bqaudiostream/src/MediaFoundationReadStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/MediaFoundationReadStream.h b/bqaudiostream/src/MediaFoundationReadStream.h
index fa8da1c..da2637d 100644
--- a/bqaudiostream/src/MediaFoundationReadStream.h
+++ b/bqaudiostream/src/MediaFoundationReadStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/MiniMP3ReadStream.cpp b/bqaudiostream/src/MiniMP3ReadStream.cpp
index 200b81d..8eba57a 100644
--- a/bqaudiostream/src/MiniMP3ReadStream.cpp
+++ b/bqaudiostream/src/MiniMP3ReadStream.cpp
@@ -90,7 +90,12 @@ MiniMP3ReadStream::MiniMP3ReadStream(std::string path) :
m_channelCount = m_d->dec.info.channels;
m_sampleRate = m_d->dec.info.hz;
- m_estimatedFrameCount = m_d->dec.samples / m_channelCount;
+
+ if (m_channelCount > 0) {
+ m_estimatedFrameCount = m_d->dec.samples / m_channelCount;
+ } else {
+ m_estimatedFrameCount = 0;
+ }
}
size_t
diff --git a/bqaudiostream/src/OggVorbisReadStream.cpp b/bqaudiostream/src/OggVorbisReadStream.cpp
index b270bb7..9dbc9af 100644
--- a/bqaudiostream/src/OggVorbisReadStream.cpp
+++ b/bqaudiostream/src/OggVorbisReadStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/OggVorbisReadStream.h b/bqaudiostream/src/OggVorbisReadStream.h
index adbf198..55e794e 100644
--- a/bqaudiostream/src/OggVorbisReadStream.h
+++ b/bqaudiostream/src/OggVorbisReadStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/OpusReadStream.cpp b/bqaudiostream/src/OpusReadStream.cpp
index f7fb57b..6b72368 100644
--- a/bqaudiostream/src/OpusReadStream.cpp
+++ b/bqaudiostream/src/OpusReadStream.cpp
@@ -112,8 +112,13 @@ OpusReadStream::OpusReadStream(std::string path) :
m_sampleRate = 48000; // libopusfile always decodes to 48kHz! I like that
ogg_int64_t total = op_pcm_total(m_d->file, -1);
- if (total > 0) {
- m_estimatedFrameCount = total / m_channelCount;
+ if (total > 0 && m_channelCount > 0) {
+ // op_pcm_total appears to return the total number of samples
+ // per channel, or audio frames. So do not divide by the
+ // channel count
+ m_estimatedFrameCount = total;
+ } else {
+ m_estimatedFrameCount = 0;
}
}
diff --git a/bqaudiostream/src/OpusReadStream.h b/bqaudiostream/src/OpusReadStream.h
index 2281095..6ae2af7 100644
--- a/bqaudiostream/src/OpusReadStream.h
+++ b/bqaudiostream/src/OpusReadStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/OpusWriteStream.cpp b/bqaudiostream/src/OpusWriteStream.cpp
index 06ba78e..a6fe410 100644
--- a/bqaudiostream/src/OpusWriteStream.cpp
+++ b/bqaudiostream/src/OpusWriteStream.cpp
@@ -34,6 +34,8 @@
#ifdef HAVE_OPUS
+//#define DEBUG_OPUS_WRITE 1
+
#include "OpusWriteStream.h"
#include <opus/opusenc.h>
@@ -62,17 +64,20 @@ opuswritebuilder(
class OpusWriteStream::D
{
public:
- D() : encoder(0) { }
+ D() : encoder(0), begun(false) { }
OggOpusComments *comments;
OggOpusEnc *encoder;
+ bool begun;
};
OpusWriteStream::OpusWriteStream(Target target) :
AudioWriteStream(target),
m_d(new D)
{
-// cerr << "OpusWriteStream: file is " << getPath() << ", channel count is " << getChannelCount() << ", sample rate " << getSampleRate() << endl;
+#ifdef DEBUG_OPUS_WRITE
+ std::cerr << "OpusWriteStream::OpusWriteStream: file is " << getPath() << ", channel count is " << getChannelCount() << ", sample rate " << getSampleRate() << std::endl;
+#endif
//!!! +windows file encoding?
@@ -85,11 +90,11 @@ OpusWriteStream::OpusWriteStream(Target target) :
&err);
if (err || !m_d->encoder) {
- ostringstream os;
+ std::ostringstream os;
os << "OpusWriteStream: Unable to open file for writing (error code "
<< err << ")";
m_error = os.str();
- cerr << m_error << endl;
+ std::cerr << m_error << std::endl;
m_d->encoder = 0;
throw FailedToWriteFile(getPath());
}
@@ -98,12 +103,24 @@ OpusWriteStream::OpusWriteStream(Target target) :
OpusWriteStream::~OpusWriteStream()
{
if (m_d->encoder) {
-// cerr << "OpusWriteStream::~OpusWriteStream: closing" << endl;
- int err = ope_encoder_drain(m_d->encoder);
- if (err) {
- cerr << "WARNING: ope_encoder_drain failed (error code "
- << err << ")" << endl;
+#ifdef DEBUG_OPUS_WRITE
+ std::cerr << "OpusWriteStream::~OpusWriteStream: closing" << std::endl;
+#endif
+ if (m_d->begun) {
+ int err = ope_encoder_drain(m_d->encoder);
+ if (err) {
+ std::cerr << "WARNING: ope_encoder_drain failed (error code "
+ << err << ")" << std::endl;
+ }
+ } else {
+ // ope_encoder_drain can crash (!) if called without any
+ // data having been previously written - see
+ // https://github.com/xiph/libopusenc/issues/24
+#ifdef DEBUG_OPUS_WRITE
+ std::cerr << "OpusWriteStream::~OpusWriteStream: not draining (nothing has been written)" << std::endl;
+#endif
}
+
ope_encoder_destroy(m_d->encoder);
ope_comments_destroy(m_d->comments);
}
@@ -112,18 +129,30 @@ OpusWriteStream::~OpusWriteStream()
void
OpusWriteStream::putInterleavedFrames(size_t count, const float *frames)
{
- if (count == 0 || !m_d->encoder) return;
+ if (count == 0 || !m_d->encoder) {
+#ifdef DEBUG_OPUS_WRITE
+ std::cerr << "OpusWriteStream::putInterleavedFrames: No encoder!"
+ << std::endl;
+#endif
+ return;
+ }
int err = ope_encoder_write_float(m_d->encoder, frames, count);
if (err) {
- ostringstream os;
+ std::ostringstream os;
os << "OpusWriteStream: Failed to write frames to encoder (error code "
<< err << ")";
m_error = os.str();
- cerr << m_error << endl;
+ std::cerr << m_error << std::endl;
throw FileOperationFailed(getPath(), "encode");
}
+
+ m_d->begun = true;
+
+#ifdef DEBUG_OPUS_WRITE
+ std::cerr << "OpusWriteStream::putInterleavedFrames: wrote " << count << " frames" << std::endl;
+#endif
}
}
diff --git a/bqaudiostream/src/SimpleWavFileReadStream.cpp b/bqaudiostream/src/SimpleWavFileReadStream.cpp
index a9c225d..fe3228a 100644
--- a/bqaudiostream/src/SimpleWavFileReadStream.cpp
+++ b/bqaudiostream/src/SimpleWavFileReadStream.cpp
@@ -59,7 +59,8 @@ SimpleWavFileReadStream::SimpleWavFileReadStream(std::string filename) :
m_file(0),
m_bitDepth(0),
m_dataChunkSize(0),
- m_dataReadOffset(0)
+ m_dataReadOffset(0),
+ m_dataReadStart(0)
{
m_file = new std::ifstream(filename.c_str(),
std::ios::in | std::ios::binary);
@@ -133,6 +134,7 @@ SimpleWavFileReadStream::readHeader()
m_channelCount = channels;
m_sampleRate = sampleRate;
m_bitDepth = bitsPerSample;
+ m_seekable = true;
// we don't use
(void)byteRate;
@@ -143,8 +145,13 @@ SimpleWavFileReadStream::readHeader()
}
m_dataChunkSize = readExpectedChunkSize("data");
- m_estimatedFrameCount = m_dataChunkSize / bytesPerFrame;
+ if (bytesPerFrame > 0) {
+ m_estimatedFrameCount = m_dataChunkSize / bytesPerFrame;
+ } else {
+ m_estimatedFrameCount = 0;
+ }
m_dataReadOffset = 0;
+ m_dataReadStart = m_file->tellg();
}
uint32_t
@@ -212,6 +219,27 @@ SimpleWavFileReadStream::readChunkSizeAfterTag()
return readMandatoryNumber(4);
}
+bool
+SimpleWavFileReadStream::performSeek(size_t frame)
+{
+ int sampleSize = m_bitDepth / 8;
+ int frameSize = sampleSize * m_channelCount;
+
+ size_t target = size_t(m_dataReadStart) + frameSize * frame;
+ if (target > m_dataChunkSize + m_dataReadStart) {
+ return false;
+ }
+
+ m_file->seekg(target, std::ios::beg);
+
+ size_t actual = m_file->tellg();
+ // (In fact I think tellg() always reports whatever you passed to seekg())
+ if (actual != target) return false;
+
+ m_dataReadOffset = uint32_t(actual - m_dataReadStart);
+ return true;
+}
+
size_t
SimpleWavFileReadStream::getFrames(size_t count, float *frames)
{
diff --git a/bqaudiostream/src/SimpleWavFileReadStream.h b/bqaudiostream/src/SimpleWavFileReadStream.h
index 52eff82..66b6e2f 100644
--- a/bqaudiostream/src/SimpleWavFileReadStream.h
+++ b/bqaudiostream/src/SimpleWavFileReadStream.h
@@ -60,6 +60,7 @@ public:
protected:
virtual size_t getFrames(size_t count, float *frames);
+ virtual bool performSeek(size_t frame);
private:
std::string m_path;
@@ -72,6 +73,7 @@ private:
bool m_floatSwap;
uint32_t m_dataChunkSize;
uint32_t m_dataReadOffset;
+ uint32_t m_dataReadStart;
void readHeader();
uint32_t readExpectedChunkSize(std::string tag);
diff --git a/bqaudiostream/src/SimpleWavFileWriteStream.cpp b/bqaudiostream/src/SimpleWavFileWriteStream.cpp
index e8ee0fc..7e4469b 100644
--- a/bqaudiostream/src/SimpleWavFileWriteStream.cpp
+++ b/bqaudiostream/src/SimpleWavFileWriteStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -38,6 +38,7 @@
#include "Exceptions.h"
#include <iostream>
+#include <stdint.h>
using namespace std;
@@ -62,7 +63,7 @@ SimpleWavFileWriteStream::SimpleWavFileWriteStream(Target target) :
m_bitDepth(24),
m_file(0)
{
- m_file = new std::ofstream(getPath().c_str(), ios::out | std::ios::binary);
+ m_file = new std::ofstream(getPath().c_str(), std::ios::out | std::ios::binary);
if (!*m_file) {
delete m_file;
@@ -75,23 +76,37 @@ SimpleWavFileWriteStream::SimpleWavFileWriteStream(Target target) :
writeFormatChunk();
}
+static
+std::string
+int2le(uint32_t value, uint32_t length)
+{
+ std::string r(length, '\0');
+
+ for (uint32_t i = 0; i < length; ++i) {
+ r[i] = (uint8_t)(value & 0xff);
+ value >>= 8;
+ }
+
+ return r;
+}
+
SimpleWavFileWriteStream::~SimpleWavFileWriteStream()
{
if (!m_file) {
return;
}
- m_file->seekp(0, ios::end);
+ m_file->seekp(0, std::ios::end);
uint32_t totalSize = m_file->tellp();
// seek to first length position
- m_file->seekp(4, ios::beg);
+ m_file->seekp(4, std::ios::beg);
// write complete file size minus 8 bytes to here
putBytes(int2le(totalSize - 8, 4));
// reseek from start forward 40
- m_file->seekp(40, ios::beg);
+ m_file->seekp(40, std::ios::beg);
// write the data chunk size to end
putBytes(int2le(totalSize - 44, 4));
@@ -118,19 +133,6 @@ SimpleWavFileWriteStream::putBytes(const uint8_t *buffer, size_t n)
m_file->write((const char *)buffer, n);
}
-std::string
-SimpleWavFileWriteStream::int2le(uint32_t value, uint32_t length)
-{
- std::string r(length, '\0');
-
- for (uint32_t i = 0; i < length; ++i) {
- r[i] = (uint8_t)(value & 0xff);
- value >>= 8;
- }
-
- return r;
-}
-
void
SimpleWavFileWriteStream::writeFormatChunk()
{
diff --git a/bqaudiostream/src/SimpleWavFileWriteStream.h b/bqaudiostream/src/SimpleWavFileWriteStream.h
index ca5514d..84669ee 100644
--- a/bqaudiostream/src/SimpleWavFileWriteStream.h
+++ b/bqaudiostream/src/SimpleWavFileWriteStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -62,7 +62,6 @@ protected:
std::ofstream *m_file;
void writeFormatChunk();
- std::string int2le(uint32_t value, uint32_t length);
void putBytes(std::string);
void putBytes(const unsigned char *, size_t);
};
diff --git a/bqaudiostream/src/WavFileReadStream.cpp b/bqaudiostream/src/WavFileReadStream.cpp
index 4067c05..43fd4d3 100644
--- a/bqaudiostream/src/WavFileReadStream.cpp
+++ b/bqaudiostream/src/WavFileReadStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -120,6 +120,7 @@ WavFileReadStream::WavFileReadStream(std::string path) :
m_channelCount = m_fileInfo.channels;
m_sampleRate = m_fileInfo.samplerate;
+ m_seekable = m_fileInfo.seekable;
if (m_fileInfo.frames > 0) {
m_estimatedFrameCount = m_fileInfo.frames;
@@ -134,7 +135,7 @@ WavFileReadStream::WavFileReadStream(std::string path) :
m_artist = str;
}
- sf_seek(m_file, 0, SEEK_SET);
+ sf_seek(m_file, 0, SF_SEEK_SET);
}
WavFileReadStream::~WavFileReadStream()
@@ -142,13 +143,23 @@ WavFileReadStream::~WavFileReadStream()
if (m_file) sf_close(m_file);
}
+bool
+WavFileReadStream::performSeek(size_t frame)
+{
+ sf_count_t pos = sf_seek(m_file, frame, SF_SEEK_SET);
+ if (pos < 0) return false;
+ if (size_t(pos) != frame) return false;
+ m_offset = frame;
+ return true;
+}
+
size_t
WavFileReadStream::getFrames(size_t count, float *frames)
{
if (!m_file || !m_channelCount) return 0;
if (count == 0) return 0;
- if ((long)m_offset >= m_fileInfo.frames) {
+ if (sf_count_t(m_offset) >= m_fileInfo.frames) {
return 0;
}
diff --git a/bqaudiostream/src/WavFileReadStream.h b/bqaudiostream/src/WavFileReadStream.h
index 515097d..6c87ad0 100644
--- a/bqaudiostream/src/WavFileReadStream.h
+++ b/bqaudiostream/src/WavFileReadStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -62,6 +62,7 @@ public:
protected:
virtual size_t getFrames(size_t count, float *frames);
+ virtual bool performSeek(size_t frame);
SF_INFO m_fileInfo;
SNDFILE *m_file;
diff --git a/bqaudiostream/src/WavFileWriteStream.cpp b/bqaudiostream/src/WavFileWriteStream.cpp
index 5d2d550..3cd2a04 100644
--- a/bqaudiostream/src/WavFileWriteStream.cpp
+++ b/bqaudiostream/src/WavFileWriteStream.cpp
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/src/WavFileWriteStream.h b/bqaudiostream/src/WavFileWriteStream.h
index 86ada11..7cbc090 100644
--- a/bqaudiostream/src/WavFileWriteStream.h
+++ b/bqaudiostream/src/WavFileWriteStream.h
@@ -5,7 +5,7 @@
A small library wrapping various audio file read/write
implementations in C++.
- Copyright 2007-2021 Particular Programs Ltd.
+ Copyright 2007-2022 Particular Programs Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/bqaudiostream/test/TestAudioStreamRead.h b/bqaudiostream/test/TestAudioStreamRead.h
index b5f5fb5..44018f8 100644
--- a/bqaudiostream/test/TestAudioStreamRead.h
+++ b/bqaudiostream/test/TestAudioStreamRead.h
@@ -79,7 +79,10 @@ private slots:
// RATE-CHANNELS.ext (for lossy data)
QStringList fileAndExt = audiofile.split(".");
QStringList bits = fileAndExt[0].split("-");
- QString extension = fileAndExt[1];
+ QString extension;
+ if (fileAndExt.size() > 1) {
+ extension = fileAndExt[1];
+ }
int nominalRate = bits[0].toInt();
int nominalChannels = bits[1].toInt();
int nominalDepth = 16;
diff --git a/bqaudiostream/test/TestSimpleWavRead.h b/bqaudiostream/test/TestSimpleWavRead.h
index be19b97..ddc073e 100644
--- a/bqaudiostream/test/TestSimpleWavRead.h
+++ b/bqaudiostream/test/TestSimpleWavRead.h
@@ -23,6 +23,12 @@ class TestSimpleWavRead : public QObject
return f;
}
+ // Without file extension
+ static const char *testsound_noextension() {
+ static const char *f = "testfiles/20samples";
+ return f;
+ }
+
private slots:
void supported() {
@@ -82,6 +88,37 @@ private slots:
QCOMPARE(n, size_t(10));
delete s;
}
+
+ void open_noextension() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound_noextension());
+ QVERIFY(s);
+ QCOMPARE(s->getError(), std::string());
+ QCOMPARE(s->getChannelCount(), size_t(1));
+ QCOMPARE(s->getSampleRate(), size_t(44100));
+ delete s;
+ }
+
+ void readEnd_noextension() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound_noextension());
+ QVERIFY(s);
+ float frames[20];
+ size_t n = s->getInterleavedFrames(20, frames);
+ QCOMPARE(n, size_t(20));
+ QCOMPARE(frames[17], 0.f);
+ QCOMPARE(frames[18], 0.f);
+ QCOMPARE(frames[19], -1.f);
+ delete s;
+ }
+
+ void resampledLength_noextension() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound_noextension());
+ QVERIFY(s);
+ s->setRetrievalSampleRate(22050);
+ float frames[22];
+ size_t n = s->getInterleavedFrames(22, frames);
+ QCOMPARE(n, size_t(10));
+ delete s;
+ }
};
}
diff --git a/bqaudiostream/test/TestWavSeek.h b/bqaudiostream/test/TestWavSeek.h
new file mode 100644
index 0000000..3efa64e
--- /dev/null
+++ b/bqaudiostream/test/TestWavSeek.h
@@ -0,0 +1,121 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+/* Copyright Chris Cannam - All Rights Reserved */
+
+#ifndef TEST_WAV_SEEK_H
+#define TEST_WAV_SEEK_H
+
+#include <QObject>
+#include <QtTest>
+
+#include "bqaudiostream/AudioReadStreamFactory.h"
+#include "bqaudiostream/AudioReadStream.h"
+
+namespace breakfastquay {
+
+class TestWavSeek : public QObject
+{
+ Q_OBJECT
+
+ // This is a 44.1KHz 16-bit mono WAV file with 20 samples in it,
+ // with a 1 at the start, -1 at the end and 0 elsewhere
+ static const char *testsound() {
+ static const char *f = "testfiles/20samples.wav";
+ return f;
+ }
+
+private slots:
+
+ void seekable() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ QCOMPARE(s->isSeekable(), true);
+ delete s;
+ }
+
+ void resamplingNotSeekable() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ s->setRetrievalSampleRate(22050);
+ QCOMPARE(s->isSeekable(), false);
+ delete s;
+ }
+
+ void seekTrivial() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ QCOMPARE(s->seek(0), true);
+ delete s;
+ }
+
+ void seekBeyondEnd() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ QCOMPARE(s->seek(100), false);
+ delete s;
+ }
+
+ void readTwice() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ float firstPass[22], secondPass[22];
+ size_t n = s->getInterleavedFrames(22, firstPass);
+ QCOMPARE(n, size_t(20));
+ QCOMPARE(s->seek(0), true);
+ size_t m = s->getInterleavedFrames(22, secondPass);
+ QCOMPARE(m, n);
+ for (size_t i = 0; i < n; ++i) {
+ QCOMPARE(firstPass[i], secondPass[i]);
+ }
+ delete s;
+ }
+
+ void unchangingSeeks() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ float frames[20];
+ size_t n = s->getInterleavedFrames(20, frames);
+ QCOMPARE(n, size_t(20));
+ for (size_t i = 0; i < n; ++i) {
+ QCOMPARE(s->seek(i), true);
+ float f(-1.f);
+ QCOMPARE(s->getInterleavedFrames(1, &f), size_t(1));
+ QCOMPARE(f, frames[i]);
+ }
+ delete s;
+ }
+
+ void seekBeyondEndAndBack() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ QCOMPARE(s->seek(100), false);
+ QCOMPARE(s->seek(0), true);
+ float frames[4];
+ size_t n = s->getInterleavedFrames(4, frames);
+ QCOMPARE(n, size_t(4));
+ QCOMPARE(frames[0], 32767.f/32768.f); // 16 bit file, so never quite 1
+ QCOMPARE(frames[1], 0.f);
+ QCOMPARE(frames[2], 0.f);
+ QCOMPARE(frames[3], 0.f);
+ delete s;
+ }
+
+ void seekToEnd() {
+ AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound());
+ QVERIFY(s);
+ QCOMPARE(s->seek(16), true);
+ float frames[20];
+ size_t n = s->getInterleavedFrames(20, frames);
+ QCOMPARE(n, size_t(4));
+ QCOMPARE(frames[0], 0.f);
+ QCOMPARE(frames[1], 0.f);
+ QCOMPARE(frames[2], 0.f);
+ QCOMPARE(frames[3], -1.f);
+ delete s;
+ }
+};
+
+}
+
+#endif
+
+
diff --git a/bqaudiostream/test/main.cpp b/bqaudiostream/test/main.cpp
index 17e9c72..a90ef12 100644
--- a/bqaudiostream/test/main.cpp
+++ b/bqaudiostream/test/main.cpp
@@ -2,6 +2,7 @@
/* Copyright Chris Cannam - All Rights Reserved */
#include "TestSimpleWavRead.h"
+#include "TestWavSeek.h"
#include "TestAudioStreamRead.h"
#include "TestWavReadWrite.h"
#include <QtTest>
@@ -23,6 +24,12 @@ int main(int argc, char *argv[])
}
{
+ breakfastquay::TestWavSeek t;
+ if (QTest::qExec(&t, argc, argv) == 0) ++good;
+ else ++bad;
+ }
+
+ {
breakfastquay::TestAudioStreamRead t;
if (QTest::qExec(&t, argc, argv) == 0) ++good;
else ++bad;
diff --git a/bqaudiostream/test/test.pro b/bqaudiostream/test/test.pro
index c48fb3f..a3b56c0 100644
--- a/bqaudiostream/test/test.pro
+++ b/bqaudiostream/test/test.pro
@@ -15,7 +15,7 @@ LIBS += -L.. -lbqaudiostream -L../../bqresample -lbqresample -L../../bqvec -lbqv
INCLUDEPATH += . .. ../../bqvec ../../bqresample ../../bqthingfactory
DEPENDPATH += . .. ../../bqvec ../../bqresample ../../bqthingfactory
-HEADERS += AudioStreamTestData.h TestAudioStreamRead.h TestSimpleWavRead.h TestWavReadWrite.h
+HEADERS += AudioStreamTestData.h TestAudioStreamRead.h TestSimpleWavRead.h TestWavReadWrite.h TestWavSeek.h
SOURCES += main.cpp
!win32 {
diff --git a/bqaudiostream/test/testfiles/20samples b/bqaudiostream/test/testfiles/20samples
new file mode 100644
index 0000000..8e343fe
--- /dev/null
+++ b/bqaudiostream/test/testfiles/20samples
Binary files differ
diff --git a/bqaudiostream/test/testfiles/8000-1-8 b/bqaudiostream/test/testfiles/8000-1-8
new file mode 100644
index 0000000..c7e9544
--- /dev/null
+++ b/bqaudiostream/test/testfiles/8000-1-8
Binary files differ
diff --git a/bqfft/.build.yml b/bqfft/.build.yml
index 1f679da..ee690e2 100644
--- a/bqfft/.build.yml
+++ b/bqfft/.build.yml
@@ -1,8 +1,9 @@
-image: ubuntu/18.04
+image: ubuntu/20.04
packages:
- libboost-test-dev
- valgrind
- libfftw3-dev
+ - libsleef-dev
- curl
sources:
- hg+https://hg.sr.ht/~breakfastquay/bqfft
diff --git a/bqfft/Makefile b/bqfft/Makefile
index 6b18521..1cfe5fd 100644
--- a/bqfft/Makefile
+++ b/bqfft/Makefile
@@ -7,6 +7,7 @@
# -DHAVE_IPP Intel's Integrated Performance Primitives are available
# -DHAVE_VDSP Apple's Accelerate framework is available
# -DHAVE_FFTW3 The FFTW library is available
+# -DHAVE_SLEEF The SLEEF library is available
# -DHAVE_KISSFFT The KissFFT library is available
# -DUSE_BUILTIN_FFT Compile the built-in FFT code (which is not bad)
#
diff --git a/bqfft/README.md b/bqfft/README.md
index 651dea9..55a94fe 100644
--- a/bqfft/README.md
+++ b/bqfft/README.md
@@ -4,8 +4,8 @@ bqfft
A small library wrapping various FFT implementations for some common
audio processing use cases. Contains a built-in implementation and
-wrappers for FFTW3, KissFFT, Intel IPP, and Apple vDSP. Suitable for
-Windows, Mac, Linux, and mobile platforms.
+wrappers for FFTW3, SLEEF, KissFFT, Intel IPP, and Apple vDSP.
+Suitable for Windows, Mac, Linux, and mobile platforms.
Note this is not a general FFT interface, as it handles only real
signals on the time-domain side.
@@ -15,25 +15,28 @@ that bqfft does not know how to calculate using any of the libraries
that have been compiled in, a simple slow DFT will be used instead. A
warning will be printed to stderr if this happens.
-Of the available libraries, vDSP, IPP, and the built-in implementation
-support power-of-two FFT lengths only, KissFFT supports any multiple
-of two, and FFTW supports any length. You can compile in more than one
-library, so for example if you compile in Accelerate and KissFFT, the
-former will be used for powers of two and the latter for other even
-lengths.
+Of the available libraries, vDSP, IPP, SLEEF, and the built-in
+implementation support power-of-two FFT lengths only, KissFFT supports
+any multiple of two, and FFTW supports any length. You can compile in
+more than one library, so for example if you compile in Accelerate and
+KissFFT, the former will be used for powers of two and the latter for
+other even lengths.
Here are some other pros and cons of the supported libraries:
- * Intel IPP - By far the fastest on actual Intel hardware. Of
- uncertain benefit with other manufacturers. Not available beyond
- x86/amd64, not open source.
+ * Intel IPP - The fastest on actual Intel hardware. Of uncertain
+ benefit with other manufacturers. Not available beyond x86/amd64,
+ not open source.
- * Apple vDSP - Faster than the open source libraries on all Apple
- hardware, and provided with the OS. There is seldom any good reason
- not to use this on Apple platforms.
+ * Apple vDSP - Generally the fastest on all Apple hardware, and
+ provided with the OS. There is seldom any good reason not to use
+ this on Apple platforms.
- * FFTW3 - Fastest open source library and portable, but its bulk and
- GPL licence may be an issue.
+ * SLEEF - Typically very fast, unencumbered, portable, open source
+ vector library; complex and (at the time of writing) rather new.
+
+ * FFTW3 - Fast, open source, and portable, but its bulk and GPL
+ licence may be an issue.
* KissFFT - As used here it is single-precision throughout, so it may
be a good choice for platforms on which double-precision arithmetic
@@ -44,7 +47,7 @@ Here are some other pros and cons of the supported libraries:
* Built-in implementation - Double precision, so more precise than
KissFFT, and faster on typical 64-bit desktop and modern mobile
- hardware. Slower than IPP, vDSP, and FFTW3.
+ hardware. Slower than IPP, vDSP, SLEEF, and FFTW3.
Requires the bqvec library.
@@ -69,5 +72,5 @@ C++ standard required: C++98 (does not use C++11 or newer features)
[![Build status](https://builds.sr.ht/~breakfastquay/bqfft.svg)](https://builds.sr.ht/~breakfastquay/bqfft?)
-Copyright 2007-2021 Particular Programs Ltd. See the file COPYING for
+Copyright 2007-2022 Particular Programs Ltd. See the file COPYING for
(BSD/MIT-style) licence terms.
diff --git a/bqfft/build/Makefile.linux.sleef b/bqfft/build/Makefile.linux.sleef
new file mode 100644
index 0000000..574daab
--- /dev/null
+++ b/bqfft/build/Makefile.linux.sleef
@@ -0,0 +1,11 @@
+
+FFT_DEFINES := -DHAVE_SLEEF
+
+VECTOR_DEFINES :=
+
+ALLOCATOR_DEFINES := -DHAVE_POSIX_MEMALIGN
+
+THIRD_PARTY_INCLUDES := -I/usr/local/include
+THIRD_PARTY_LIBS := -L/usr/local/lib -lsleefdft -lsleef
+
+include build/Makefile.inc
diff --git a/bqfft/src/FFT.cpp b/bqfft/src/FFT.cpp
index 66f9ea0..be9a42b 100644
--- a/bqfft/src/FFT.cpp
+++ b/bqfft/src/FFT.cpp
@@ -63,6 +63,13 @@
#include <fftw3.h>
#endif
+#ifdef HAVE_SLEEF
+extern "C" {
+#include <sleef.h>
+#include <sleefdft.h>
+}
+#endif
+
#ifdef HAVE_VDSP
#include <Accelerate/Accelerate.h>
#endif
@@ -73,6 +80,7 @@
#ifndef HAVE_IPP
#ifndef HAVE_FFTW3
+#ifndef HAVE_SLEEF
#ifndef HAVE_KISSFFT
#ifndef USE_BUILTIN_FFT
#ifndef HAVE_VDSP
@@ -82,6 +90,7 @@
#endif
#endif
#endif
+#endif
#include <cmath>
#include <iostream>
@@ -1437,6 +1446,302 @@ pthread_mutex_t D_FFTW::m_commonMutex = PTHREAD_MUTEX_INITIALIZER;
#endif /* HAVE_FFTW3 */
+#ifdef HAVE_SLEEF
+
+class D_SLEEF : public FFTImpl
+{
+ bool isAligned(const void *ptr) {
+ return ! ((uintptr_t)ptr & 63);
+ }
+
+public:
+ D_SLEEF(int size) :
+ m_fplanf(0), m_fplani(0), m_fbuf(0), m_fpacked(0),
+ m_dplanf(0), m_dplani(0), m_dbuf(0), m_dpacked(0),
+ m_size(size)
+ {
+ }
+
+ ~D_SLEEF() {
+ if (m_fplanf) {
+ SleefDFT_dispose(m_fplanf);
+ SleefDFT_dispose(m_fplani);
+ Sleef_free(m_fbuf);
+ Sleef_free(m_fpacked);
+ }
+ if (m_dplanf) {
+ SleefDFT_dispose(m_dplanf);
+ SleefDFT_dispose(m_dplani);
+ Sleef_free(m_dbuf);
+ Sleef_free(m_dpacked);
+ }
+ }
+
+ int getSize() const {
+ return m_size;
+ }
+
+ FFT::Precisions
+ getSupportedPrecisions() const {
+ return FFT::SinglePrecision | FFT::DoublePrecision;
+ }
+
+ void initFloat() {
+ if (m_fplanf) return;
+
+ m_fbuf = static_cast<float *>
+ (Sleef_malloc(m_size * sizeof(float)));
+ m_fpacked = static_cast<float *>
+ (Sleef_malloc((m_size + 2) * sizeof(float)));
+
+ m_fplanf = SleefDFT_float_init1d
+ (m_size, m_fbuf, m_fpacked,
+ SLEEF_MODE_FORWARD | SLEEF_MODE_REAL | SLEEF_MODE_ESTIMATE);
+
+ m_fplani = SleefDFT_float_init1d
+ (m_size, m_fpacked, m_fbuf,
+ SLEEF_MODE_BACKWARD | SLEEF_MODE_REAL | SLEEF_MODE_ESTIMATE);
+ }
+
+ void initDouble() {
+ if (m_dplanf) return;
+
+ m_dbuf = static_cast<double *>
+ (Sleef_malloc(m_size * sizeof(double)));
+ m_dpacked = static_cast<double *>
+ (Sleef_malloc((m_size + 2) * sizeof(double)));
+
+ m_dplanf = SleefDFT_double_init1d
+ (m_size, m_dbuf, m_dpacked,
+ SLEEF_MODE_FORWARD | SLEEF_MODE_REAL | SLEEF_MODE_ESTIMATE);
+
+ m_dplani = SleefDFT_double_init1d
+ (m_size, m_dpacked, m_dbuf,
+ SLEEF_MODE_BACKWARD | SLEEF_MODE_REAL | SLEEF_MODE_ESTIMATE);
+ }
+
+ void packFloat(const float *BQ_R__ re, const float *BQ_R__ im) {
+ const float *src[2] = { re, im };
+ v_interleave(m_fpacked, src, 2, m_size/2 + 1);
+ }
+
+ void packDouble(const double *BQ_R__ re, const double *BQ_R__ im) {
+ const double *src[2] = { re, im };
+ v_interleave(m_dpacked, src, 2, m_size/2 + 1);
+ }
+
+ void unpackFloat(float *BQ_R__ re, float *BQ_R__ im) {
+ float *dst[2] = { re, im };
+ v_deinterleave(dst, m_fpacked, 2, m_size/2 + 1);
+ }
+
+ void unpackDouble(double *BQ_R__ re, double *BQ_R__ im) {
+ double *dst[2] = { re, im };
+ v_deinterleave(dst, m_dpacked, 2, m_size/2 + 1);
+ }
+
+ void forward(const double *BQ_R__ realIn, double *BQ_R__ realOut, double *BQ_R__ imagOut) {
+ if (!m_dplanf) initDouble();
+ if (isAligned(realIn)) {
+ SleefDFT_double_execute(m_dplanf, realIn, 0);
+ } else {
+ v_copy(m_dbuf, realIn, m_size);
+ SleefDFT_double_execute(m_dplanf, 0, 0);
+ }
+ unpackDouble(realOut, imagOut);
+ }
+
+ void forwardInterleaved(const double *BQ_R__ realIn, double *BQ_R__ complexOut) {
+ if (!m_dplanf) initDouble();
+ if (isAligned(realIn) && isAligned(complexOut)) {
+ SleefDFT_double_execute(m_dplanf, realIn, complexOut);
+ } else {
+ v_copy(m_dbuf, realIn, m_size);
+ SleefDFT_double_execute(m_dplanf, 0, 0);
+ v_copy(complexOut, m_dpacked, m_size + 2);
+ }
+ }
+
+ void forwardPolar(const double *BQ_R__ realIn, double *BQ_R__ magOut, double *BQ_R__ phaseOut) {
+ if (!m_dplanf) initDouble();
+ if (isAligned(realIn)) {
+ SleefDFT_double_execute(m_dplanf, realIn, 0);
+ } else {
+ v_copy(m_dbuf, realIn, m_size);
+ SleefDFT_double_execute(m_dplanf, 0, 0);
+ }
+ v_cartesian_interleaved_to_polar(magOut, phaseOut, m_dpacked, m_size/2+1);
+ }
+
+ void forwardMagnitude(const double *BQ_R__ realIn, double *BQ_R__ magOut) {
+ if (!m_dplanf) initDouble();
+ if (isAligned(realIn)) {
+ SleefDFT_double_execute(m_dplanf, realIn, 0);
+ } else {
+ v_copy(m_dbuf, realIn, m_size);
+ SleefDFT_double_execute(m_dplanf, 0, 0);
+ }
+ v_cartesian_interleaved_to_magnitudes(magOut, m_dpacked, m_size/2+1);
+ }
+
+ void forward(const float *BQ_R__ realIn, float *BQ_R__ realOut, float *BQ_R__ imagOut) {
+ if (!m_fplanf) initFloat();
+ if (isAligned(realIn)) {
+ SleefDFT_float_execute(m_fplanf, realIn, 0);
+ } else {
+ v_copy(m_fbuf, realIn, m_size);
+ SleefDFT_float_execute(m_fplanf, 0, 0);
+ }
+ unpackFloat(realOut, imagOut);
+ }
+
+ void forwardInterleaved(const float *BQ_R__ realIn, float *BQ_R__ complexOut) {
+ if (!m_fplanf) initFloat();
+ if (isAligned(realIn) && isAligned(complexOut)) {
+ SleefDFT_float_execute(m_fplanf, realIn, complexOut);
+ } else {
+ v_copy(m_fbuf, realIn, m_size);
+ SleefDFT_float_execute(m_fplanf, 0, 0);
+ v_copy(complexOut, m_fpacked, m_size + 2);
+ }
+ }
+
+ void forwardPolar(const float *BQ_R__ realIn, float *BQ_R__ magOut, float *BQ_R__ phaseOut) {
+ if (!m_fplanf) initFloat();
+ if (isAligned(realIn)) {
+ SleefDFT_float_execute(m_fplanf, realIn, 0);
+ } else {
+ v_copy(m_fbuf, realIn, m_size);
+ SleefDFT_float_execute(m_fplanf, 0, 0);
+ }
+ v_cartesian_interleaved_to_polar(magOut, phaseOut, m_fpacked, m_size/2+1);
+ }
+
+ void forwardMagnitude(const float *BQ_R__ realIn, float *BQ_R__ magOut) {
+ if (!m_fplanf) initFloat();
+ if (isAligned(realIn)) {
+ SleefDFT_float_execute(m_fplanf, realIn, 0);
+ } else {
+ v_copy(m_fbuf, realIn, m_size);
+ SleefDFT_float_execute(m_fplanf, 0, 0);
+ }
+ v_cartesian_interleaved_to_magnitudes(magOut, m_fpacked, m_size/2+1);
+ }
+
+ void inverse(const double *BQ_R__ realIn, const double *BQ_R__ imagIn, double *BQ_R__ realOut) {
+ if (!m_dplanf) initDouble();
+ packDouble(realIn, imagIn);
+ if (isAligned(realOut)) {
+ SleefDFT_double_execute(m_dplani, 0, realOut);
+ } else {
+ SleefDFT_double_execute(m_dplani, 0, 0);
+ v_copy(realOut, m_dbuf, m_size);
+ }
+ }
+
+ void inverseInterleaved(const double *BQ_R__ complexIn, double *BQ_R__ realOut) {
+ if (!m_dplanf) initDouble();
+ if (isAligned(complexIn) && isAligned(realOut)) {
+ SleefDFT_double_execute(m_dplani, complexIn, realOut);
+ } else {
+ v_copy(m_dpacked, complexIn, m_size + 2);
+ SleefDFT_double_execute(m_dplani, 0, 0);
+ v_copy(realOut, m_dbuf, m_size);
+ }
+ }
+
+ void inversePolar(const double *BQ_R__ magIn, const double *BQ_R__ phaseIn, double *BQ_R__ realOut) {
+ if (!m_dplanf) initDouble();
+ v_polar_to_cartesian_interleaved(m_dpacked, magIn, phaseIn, m_size/2+1);
+ if (isAligned(realOut)) {
+ SleefDFT_double_execute(m_dplani, 0, realOut);
+ } else {
+ SleefDFT_double_execute(m_dplani, 0, 0);
+ v_copy(realOut, m_dbuf, m_size);
+ }
+ }
+
+ void inverseCepstral(const double *BQ_R__ magIn, double *BQ_R__ cepOut) {
+ if (!m_dplanf) initDouble();
+ const int hs = m_size/2;
+ for (int i = 0; i <= hs; ++i) {
+ m_dpacked[i*2] = log(magIn[i] + 0.000001);
+ m_dpacked[i*2+1] = 0.0;
+ }
+ if (isAligned(cepOut)) {
+ SleefDFT_double_execute(m_dplani, 0, cepOut);
+ } else {
+ SleefDFT_double_execute(m_dplani, 0, 0);
+ v_copy(cepOut, m_dbuf, m_size);
+ }
+ }
+
+ void inverse(const float *BQ_R__ realIn, const float *BQ_R__ imagIn, float *BQ_R__ realOut) {
+ if (!m_fplanf) initFloat();
+ packFloat(realIn, imagIn);
+ if (isAligned(realOut)) {
+ SleefDFT_float_execute(m_dplani, 0, realOut);
+ } else {
+ SleefDFT_float_execute(m_fplani, 0, 0);
+ v_copy(realOut, m_fbuf, m_size);
+ }
+ }
+
+ void inverseInterleaved(const float *BQ_R__ complexIn, float *BQ_R__ realOut) {
+ if (!m_fplanf) initFloat();
+ if (isAligned(complexIn) && isAligned(realOut)) {
+ SleefDFT_float_execute(m_fplani, complexIn, realOut);
+ } else {
+ v_copy(m_fpacked, complexIn, m_size + 2);
+ SleefDFT_float_execute(m_fplani, 0, 0);
+ v_copy(realOut, m_fbuf, m_size);
+ }
+ }
+
+ void inversePolar(const float *BQ_R__ magIn, const float *BQ_R__ phaseIn, float *BQ_R__ realOut) {
+ if (!m_fplanf) initFloat();
+ v_polar_to_cartesian_interleaved(m_fpacked, magIn, phaseIn, m_size/2+1);
+ if (isAligned(realOut)) {
+ SleefDFT_float_execute(m_fplani, 0, realOut);
+ } else {
+ SleefDFT_float_execute(m_fplani, 0, 0);
+ v_copy(realOut, m_fbuf, m_size);
+ }
+ }
+
+ void inverseCepstral(const float *BQ_R__ magIn, float *BQ_R__ cepOut) {
+ if (!m_fplanf) initFloat();
+ const int hs = m_size/2;
+ for (int i = 0; i <= hs; ++i) {
+ m_fpacked[i*2] = logf(magIn[i] + 0.000001f);
+ m_fpacked[i*2+1] = 0.0;
+ }
+ if (isAligned(cepOut)) {
+ SleefDFT_float_execute(m_fplani, 0, cepOut);
+ } else {
+ SleefDFT_float_execute(m_fplani, 0, 0);
+ v_copy(cepOut, m_fbuf, m_size);
+ }
+ }
+
+private:
+ SleefDFT *m_fplanf;
+ SleefDFT *m_fplani;
+
+ float *m_fbuf;
+ float *m_fpacked;
+
+ SleefDFT *m_dplanf;
+ SleefDFT *m_dplani;
+
+ double *m_dbuf;
+ double *m_dpacked;
+
+ const int m_size;
+};
+
+#endif /* HAVE_SLEEF */
+
#ifdef HAVE_KISSFFT
class D_KISSFFT : public FFTImpl
@@ -2278,6 +2583,9 @@ getImplementationDetails()
#ifdef HAVE_FFTW3
impls["fftw"] = SizeConstraintNone;
#endif
+#ifdef HAVE_SLEEF
+ impls["sleef"] = SizeConstraintEvenPowerOfTwo;
+#endif
#ifdef HAVE_KISSFFT
impls["kissfft"] = SizeConstraintEven;
#endif
@@ -2322,7 +2630,7 @@ pickImplementation(int size)
}
std::string preference[] = {
- "ipp", "vdsp", "fftw", "builtin", "kissfft"
+ "ipp", "vdsp", "sleef", "fftw", "builtin", "kissfft"
};
for (int i = 0; i < int(sizeof(preference)/sizeof(preference[0])); ++i) {
@@ -2403,6 +2711,10 @@ FFT::FFT(int size, int debugLevel) :
#ifdef HAVE_FFTW3
d = new FFTs::D_FFTW(size);
#endif
+ } else if (impl == "sleef") {
+#ifdef HAVE_SLEEF
+ d = new FFTs::D_SLEEF(size);
+#endif
} else if (impl == "kissfft") {
#ifdef HAVE_KISSFFT
d = new FFTs::D_KISSFFT(size);
@@ -2661,6 +2973,14 @@ FFT::tune()
d->initDouble();
candidates["fftw"] = d;
#endif
+
+#ifdef HAVE_SLEEF
+ os << "Constructing new SLEEF FFT object for size " << size << "..." << std::endl;
+ d = new FFTs::D_SLEEF(size);
+ d->initFloat();
+ d->initDouble();
+ candidates["sleef"] = d;
+#endif
#ifdef HAVE_KISSFFT
os << "Constructing new KISSFFT object for size " << size << "..." << std::endl;
@@ -2705,19 +3025,21 @@ FFT::tune()
int iterations = 500;
os << "Iterations: " << iterations << std::endl;
- double *da = new double[size];
- double *db = new double[size];
- double *dc = new double[size];
- double *dd = new double[size];
- double *di = new double[size + 2];
- double *dj = new double[size + 2];
+ double *da = allocate_and_zero<double>(size);
+ double *db = allocate_and_zero<double>(size);
+ double *dc = allocate_and_zero<double>(size);
+ double *dd = allocate_and_zero<double>(size);
- float *fa = new float[size];
- float *fb = new float[size];
- float *fc = new float[size];
- float *fd = new float[size];
- float *fi = new float[size + 2];
- float *fj = new float[size + 2];
+ double *di = allocate_and_zero<double>(size + 2);
+ double *dj = allocate_and_zero<double>(size + 2);
+
+ float *fa = allocate_and_zero<float>(size);
+ float *fb = allocate_and_zero<float>(size);
+ float *fc = allocate_and_zero<float>(size);
+ float *fd = allocate_and_zero<float>(size);
+
+ float *fi = allocate_and_zero<float>(size + 2);
+ float *fj = allocate_and_zero<float>(size + 2);
for (int type = 0; type < 16; ++type) {
@@ -2846,15 +3168,22 @@ FFT::tune()
wins[low]++;
}
+
+ deallocate(da);
+ deallocate(db);
+ deallocate(dc);
+ deallocate(dd);
+
+ deallocate(di);
+ deallocate(dj);
- delete[] fa;
- delete[] fb;
- delete[] fc;
- delete[] fd;
- delete[] da;
- delete[] db;
- delete[] dc;
- delete[] dd;
+ deallocate(fa);
+ deallocate(fb);
+ deallocate(fc);
+ deallocate(fd);
+
+ deallocate(fi);
+ deallocate(fj);
}
while (!candidates.empty()) {
diff --git a/bqfft/test/TestFFT.cpp b/bqfft/test/TestFFT.cpp
index 33b28b3..f382dfd 100644
--- a/bqfft/test/TestFFT.cpp
+++ b/bqfft/test/TestFFT.cpp
@@ -110,19 +110,16 @@ BOOST_AUTO_TEST_SUITE(TestFFT)
#define ALL_IMPL_AUTO_TEST_CASE(name) \
void performTest_##name (); \
ONE_IMPL_AUTO_TEST_CASE(name, ipp); \
+ ONE_IMPL_AUTO_TEST_CASE(name, vdsp); \
ONE_IMPL_AUTO_TEST_CASE(name, fftw); \
+ ONE_IMPL_AUTO_TEST_CASE(name, sleef); \
ONE_IMPL_AUTO_TEST_CASE(name, kissfft); \
- ONE_IMPL_AUTO_TEST_CASE(name, vdsp); \
- ONE_IMPL_AUTO_TEST_CASE(name, medialib); \
- ONE_IMPL_AUTO_TEST_CASE(name, openmax); \
- ONE_IMPL_AUTO_TEST_CASE(name, sfft); \
ONE_IMPL_AUTO_TEST_CASE(name, builtin); \
ONE_IMPL_AUTO_TEST_CASE(name, dft); \
void performTest_##name ()
std::string all_implementations[] = {
- "ipp", "vdsp", "fftw", "sfft", "openmax",
- "medialib", "kissfft", "builtin", "dft"
+ "ipp", "vdsp", "fftw", "sleef", "kissfft", "builtin", "dft"
};
BOOST_AUTO_TEST_CASE(showImplementations)
diff --git a/bqvec/bqvec/Allocators.h b/bqvec/bqvec/Allocators.h
index 94373e9..ef4627b 100644
--- a/bqvec/bqvec/Allocators.h
+++ b/bqvec/bqvec/Allocators.h
@@ -116,9 +116,9 @@ T *allocate(size_t count)
#else /* !MALLOC_IS_ALIGNED */
// That's the "sufficiently aligned" functions dealt with, the
- // rest need a specific alignment provided to the call. 32-byte
- // alignment is required for at least OpenMAX
- static const int alignment = 32;
+ // rest need a specific alignment provided to the call. 64-byte
+ // alignment is enough for 8x8 double operations
+ static const int alignment = 64;
#ifdef HAVE__ALIGNED_MALLOC
ptr = _aligned_malloc(count * sizeof(T), alignment);
@@ -145,7 +145,7 @@ T *allocate(size_t count)
// Alignment must be a power of two, bigger than the pointer
// size. Stuff the actual malloc'd pointer in just before the
// returned value. This is the least desirable way to do this --
- // the other options below are all better
+ // the other options are all better
size_t allocd = count * sizeof(T) + alignment;
void *buf = malloc(allocd);
if (buf) {
diff --git a/bqvec/bqvec/VectorOpsComplex.h b/bqvec/bqvec/VectorOpsComplex.h
index 1e8c9c4..49dd76a 100644
--- a/bqvec/bqvec/VectorOpsComplex.h
+++ b/bqvec/bqvec/VectorOpsComplex.h
@@ -477,51 +477,6 @@ inline void c_magphase(float *mag, float *phase, float real, float imag)
}
#endif
-#ifndef NO_COMPLEX_TYPES
-
-inline bq_complex_t c_phasor(bq_complex_element_t phase)
-{
- bq_complex_t c;
- c_phasor<bq_complex_element_t>(&c.re, &c.im, phase);
- return c;
-}
-
-inline void c_magphase(bq_complex_element_t *mag, bq_complex_element_t *phase,
- bq_complex_t c)
-{
- c_magphase<bq_complex_element_t>(mag, phase, c.re, c.im);
-}
-
-void v_polar_to_cartesian(bq_complex_t *const BQ_R__ dst,
- const bq_complex_element_t *const BQ_R__ mag,
- const bq_complex_element_t *const BQ_R__ phase,
- const int count);
-
-void v_polar_interleaved_to_cartesian(bq_complex_t *const BQ_R__ dst,
- const bq_complex_element_t *const BQ_R__ src,
- const int count);
-
-void v_cartesian_to_polar(bq_complex_element_t *const BQ_R__ mag,
- bq_complex_element_t *const BQ_R__ phase,
- const bq_complex_t *const BQ_R__ src,
- const int count);
-
-inline void v_cartesian_to_polar_interleaved(bq_complex_element_t *const BQ_R__ dst,
- const bq_complex_t *const BQ_R__ src,
- const int count)
-{
- for (int i = 0; i < count; ++i) {
- c_magphase<bq_complex_element_t>(&dst[i*2], &dst[i*2+1],
- src[i].re, src[i].im);
- }
-}
-
-void v_cartesian_to_magnitudes(bq_complex_element_t *const BQ_R__ mag,
- const bq_complex_t *const BQ_R__ src,
- const int count);
-
-#endif // !NO_COMPLEX_TYPES
-
template<typename S, typename T> // S source, T target
void v_polar_to_cartesian(T *const BQ_R__ real,
T *const BQ_R__ imag,
@@ -812,6 +767,88 @@ inline void v_cartesian_interleaved_to_magnitudes(double *const BQ_R__ mag,
}
#endif
+#ifndef NO_COMPLEX_TYPES
+
+inline bq_complex_t c_phasor(bq_complex_element_t phase)
+{
+ bq_complex_t c;
+ c_phasor<bq_complex_element_t>(&c.re, &c.im, phase);
+ return c;
+}
+
+inline void c_magphase(bq_complex_element_t *mag, bq_complex_element_t *phase,
+ bq_complex_t c)
+{
+ c_magphase<bq_complex_element_t>(mag, phase, c.re, c.im);
+}
+
+inline void v_polar_to_cartesian(bq_complex_t *const BQ_R__ dst,
+ const bq_complex_element_t *const BQ_R__ mag,
+ const bq_complex_element_t *const BQ_R__ phase,
+ const int count)
+{
+ if (sizeof(bq_complex_element_t) == sizeof(float)) {
+ v_polar_to_cartesian_interleaved((float *)dst,
+ (const float *)mag,
+ (const float *)phase,
+ count);
+ } else {
+ v_polar_to_cartesian_interleaved((double *)dst,
+ (const double *)mag,
+ (const double *)phase,
+ count);
+ }
+}
+
+void v_polar_interleaved_to_cartesian(bq_complex_t *const BQ_R__ dst,
+ const bq_complex_element_t *const BQ_R__ src,
+ const int count);
+
+inline void v_cartesian_to_polar(bq_complex_element_t *const BQ_R__ mag,
+ bq_complex_element_t *const BQ_R__ phase,
+ const bq_complex_t *const BQ_R__ src,
+ const int count)
+{
+ if (sizeof(bq_complex_element_t) == sizeof(float)) {
+ v_cartesian_interleaved_to_polar((float *)mag,
+ (float *)phase,
+ (const float *)src,
+ count);
+ } else {
+ v_cartesian_interleaved_to_polar((double *)mag,
+ (double *)phase,
+ (const double *)src,
+ count);
+ }
+}
+
+inline void v_cartesian_to_polar_interleaved(bq_complex_element_t *const BQ_R__ dst,
+ const bq_complex_t *const BQ_R__ src,
+ const int count)
+{
+ for (int i = 0; i < count; ++i) {
+ c_magphase<bq_complex_element_t>(&dst[i*2], &dst[i*2+1],
+ src[i].re, src[i].im);
+ }
+}
+
+inline void v_cartesian_to_magnitudes(bq_complex_element_t *const BQ_R__ mag,
+ const bq_complex_t *const BQ_R__ src,
+ const int count)
+{
+ if (sizeof(bq_complex_element_t) == sizeof(float)) {
+ v_cartesian_interleaved_to_magnitudes((float *)mag,
+ (const float *)src,
+ count);
+ } else {
+ v_cartesian_interleaved_to_magnitudes((double *)mag,
+ (const double *)src,
+ count);
+ }
+}
+
+#endif // !NO_COMPLEX_TYPES
+
}
#endif
diff --git a/bqvec/src/VectorOpsComplex.cpp b/bqvec/src/VectorOpsComplex.cpp
index 44644a5..c69275e 100644
--- a/bqvec/src/VectorOpsComplex.cpp
+++ b/bqvec/src/VectorOpsComplex.cpp
@@ -221,60 +221,6 @@ v_polar_to_cartesian_interleaved_pommier(float *const BQ_R__ dst,
#ifndef NO_COMPLEX_TYPES
-void
-v_polar_to_cartesian(bq_complex_t *const BQ_R__ dst,
- const bq_complex_element_t *const BQ_R__ mag,
- const bq_complex_element_t *const BQ_R__ phase,
- const int count)
-{
- if (sizeof(bq_complex_element_t) == sizeof(float)) {
- v_polar_to_cartesian_interleaved((float *)dst,
- (const float *)mag,
- (const float *)phase,
- count);
- } else {
- v_polar_to_cartesian_interleaved((double *)dst,
- (const double *)mag,
- (const double *)phase,
- count);
- }
-}
-
-void
-v_cartesian_to_polar(bq_complex_element_t *const BQ_R__ mag,
- bq_complex_element_t *const BQ_R__ phase,
- const bq_complex_t *const BQ_R__ src,
- const int count)
-{
- if (sizeof(bq_complex_element_t) == sizeof(float)) {
- v_cartesian_interleaved_to_polar((float *)mag,
- (float *)phase,
- (const float *)src,
- count);
- } else {
- v_cartesian_interleaved_to_polar((double *)mag,
- (double *)phase,
- (const double *)src,
- count);
- }
-}
-
-void
-v_cartesian_to_magnitudes(bq_complex_element_t *const BQ_R__ mag,
- const bq_complex_t *const BQ_R__ src,
- const int count)
-{
- if (sizeof(bq_complex_element_t) == sizeof(float)) {
- v_cartesian_interleaved_to_magnitudes((float *)mag,
- (const float *)src,
- count);
- } else {
- v_cartesian_interleaved_to_magnitudes((double *)mag,
- (const double *)src,
- count);
- }
-}
-
#if defined USE_POMMIER_MATHFUN
//!!! further tests reqd. This is only single precision but it seems
diff --git a/bqvec/test/Timings.cpp b/bqvec/test/Timings.cpp
index 76019b7..20488b6 100644
--- a/bqvec/test/Timings.cpp
+++ b/bqvec/test/Timings.cpp
@@ -1,6 +1,7 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
#include "bqvec/VectorOpsComplex.h"
+#include "bqvec/Allocators.h"
#include <iostream>
#include <cstdlib>
@@ -10,8 +11,6 @@
using namespace std;
using namespace breakfastquay;
-//!!! This is nonsense. TODO: Replace it with sense.
-
#ifdef _WIN32
#define drand48() (-1+2*((float)rand())/RAND_MAX)
#endif
@@ -21,11 +20,12 @@ testMultiply()
{
cerr << "testVectorOps: testing v_multiply complex" << endl;
+ const int iterations = 50000;
const int N = 1024;
- //!!! todo: use aligned allocate(), otherwise results will vary randomly
- bq_complex_t target[N];
- bq_complex_t src1[N];
- bq_complex_t src2[N];
+
+ bq_complex_t *target = allocate<bq_complex_t>(N);
+ bq_complex_t *src1 = allocate<bq_complex_t>(N);
+ bq_complex_t *src2 = allocate<bq_complex_t>(N);
for (int i = 0; i < N; ++i) {
src1[i].re = drand48();
@@ -34,60 +34,55 @@ testMultiply()
src2[i].im = drand48();
}
- double mean, first, last, total = 0;
- for (int i = 0; i < N; ++i) {
- bq_complex_t result;
- c_multiply(result, src1[i], src2[i]);
- if (i == 0) first = result.re;
- if (i == N-1) last = result.im;
- total += result.re;
- total += result.im;
- }
- mean = total / (N*2);
- cerr << "Naive method: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- v_multiply_to(target, src1, src2, N);
- total = 0;
-
- for (int i = 0; i < N; ++i) {
- if (i == 0) first = target[i].re;
- if (i == N-1) last = target[i].im;
- total += target[i].re;
- total += target[i].im;
- }
- mean = total / (N*2);
- cerr << "v_multiply: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- int iterations = 50000;
-// cerr << "Iterations: " << iterations << endl;
-
-// cerr << "CLOCKS_PER_SEC = " << CLOCKS_PER_SEC << endl;
float divisor = float(CLOCKS_PER_SEC) / 1000.f;
-
clock_t start = clock();
+ double first, last, total = 0;
for (int j = 0; j < iterations; ++j) {
- for (int i = 0; i < N; ++i) {
- c_multiply(target[i], src1[i], src2[i]);
- }
+ for (int i = 0; i < N; ++i) {
+ bq_complex_t result;
+ c_multiply(result, src1[i], src2[i]);
+ if (i == 0) first = result.re;
+ if (i == N-1) last = result.im;
+ total += result.re;
+ total += result.im;
+ total += j;
+ }
}
-
+
clock_t end = clock();
- cerr << "Time for naive method: " << float(end - start)/divisor << endl;
+ cerr << "repeated c_multiply: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for repeated c_multiply: " << float(end - start)/divisor
+ << endl;
start = clock();
+ first = last = total = 0;
+
for (int j = 0; j < iterations; ++j) {
v_multiply_to(target, src1, src2, N);
+ for (int i = 0; i < N; ++i) {
+ if (i == 0) first = target[i].re;
+ if (i == N-1) last = target[i].im;
+ total += target[i].re;
+ total += target[i].im;
+ total += j;
+ }
}
-
+
end = clock();
- cerr << "Time for v_multiply: " << float(end - start)/divisor << endl;
+ cerr << "v_multiply_to: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for v_multiply_to: " << float(end - start)/divisor << endl;
+ deallocate(target);
+ deallocate(src1);
+ deallocate(src2);
+
+ cerr << endl;
return true;
}
@@ -96,72 +91,68 @@ testPolarToCart()
{
cerr << "testVectorOps: testing v_polar_to_cartesian" << endl;
+ const int iterations = 50000;
const int N = 1024;
- bq_complex_t target[N];
- bq_complex_element_t mag[N];
- bq_complex_element_t phase[N];
+
+ bq_complex_t *target = allocate<bq_complex_t>(N);
+ bq_complex_element_t *mag = allocate<bq_complex_element_t>(N);
+ bq_complex_element_t *phase = allocate<bq_complex_element_t>(N);
for (int i = 0; i < N; ++i) {
mag[i] = drand48();
phase[i] = (drand48() * M_PI * 2) - M_PI;
}
- double mean, first, last, total = 0;
- for (int i = 0; i < N; ++i) {
- double real = mag[i] * cos(phase[i]);
- double imag = mag[i] * sin(phase[i]);
- if (i == 0) first = real;
- if (i == N-1) last = imag;
- total += real;
- total += imag;
- }
- mean = total / (N*2);
- cerr << "Naive method: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- v_polar_to_cartesian(target, mag, phase, N);
-
- total = 0;
-
- for (int i = 0; i < N; ++i) {
- if (i == 0) first = target[i].re;
- if (i == N-1) last = target[i].im;
- total += target[i].re;
- total += target[i].im;
- }
- mean = total / (N*2);
- cerr << "v_polar_to_cartesian: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- int iterations = 10000;
-// cerr << "Iterations: " << iterations << endl;
-
-// cerr << "CLOCKS_PER_SEC = " << CLOCKS_PER_SEC << endl;
float divisor = float(CLOCKS_PER_SEC) / 1000.f;
-
clock_t start = clock();
+ double first, last, total = 0;
for (int j = 0; j < iterations; ++j) {
- for (int i = 0; i < N; ++i) {
- target[i].re = mag[i] * cos(phase[i]);
- target[i].im = mag[i] * sin(phase[i]);
- }
+ for (int i = 0; i < N; ++i) {
+ double real = mag[i] * cos(phase[i]);
+ double imag = mag[i] * sin(phase[i]);
+ if (i == 0) first = real;
+ if (i == N-1) last = imag;
+ total += real;
+ total += imag;
+ total += j;
+ }
}
-
- clock_t end = clock();
- cerr << "Time for naive method: " << float(end - start)/divisor << endl;
+ clock_t end = clock();
+ cerr << "naive method: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for naive method: " << float(end - start)/divisor
+ << endl;
+
start = clock();
+ first = last = total = 0;
+
for (int j = 0; j < iterations; ++j) {
- v_polar_to_cartesian(target, mag, phase, N);
+ v_polar_to_cartesian(target, mag, phase, N);
+ for (int i = 0; i < N; ++i) {
+ if (i == 0) first = target[i].re;
+ if (i == N-1) last = target[i].im;
+ total += target[i].re;
+ total += target[i].im;
+ total += j;
+ }
}
-
- end = clock();
- cerr << "Time for v_polar_to_cartesian: " << float(end - start)/divisor << endl;
+ end = clock();
+ cerr << "v_polar_to_cartesian: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for v_polar_to_cartesian: " << float(end - start)/divisor
+ << endl;
+
+ deallocate(target);
+ deallocate(mag);
+ deallocate(phase);
+
+ cerr << endl;
return true;
}
@@ -170,71 +161,66 @@ testPolarToCartInterleaved()
{
cerr << "testVectorOps: testing v_polar_interleaved_to_cartesian" << endl;
+ const int iterations = 50000;
const int N = 1024;
- bq_complex_t target[N];
- bq_complex_element_t source[N*2];
+
+ bq_complex_t *target = allocate<bq_complex_t>(N);
+ bq_complex_element_t *source = allocate<bq_complex_element_t>(N*2);
for (int i = 0; i < N; ++i) {
source[i*2] = drand48();
source[i*2+1] = (drand48() * M_PI * 2) - M_PI;
}
- double mean, first, last, total = 0;
- for (int i = 0; i < N; ++i) {
- double real = source[i*2] * cos(source[i*2+1]);
- double imag = source[i*2] * sin(source[i*2+1]);
- if (i == 0) first = real;
- if (i == N-1) last = imag;
- total += real;
- total += imag;
- }
- mean = total / (N*2);
- cerr << "Naive method: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- v_polar_interleaved_to_cartesian(target, source, N);
-
- total = 0;
-
- for (int i = 0; i < N; ++i) {
- if (i == 0) first = target[i].re;
- if (i == N-1) last = target[i].im;
- total += target[i].re;
- total += target[i].im;
- }
- mean = total / (N*2);
- cerr << "v_polar_interleaved_to_cartesian: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- int iterations = 10000;
-// cerr << "Iterations: " << iterations << endl;
-
-// cerr << "CLOCKS_PER_SEC = " << CLOCKS_PER_SEC << endl;
float divisor = float(CLOCKS_PER_SEC) / 1000.f;
-
clock_t start = clock();
+ double first, last, total = 0;
for (int j = 0; j < iterations; ++j) {
- for (int i = 0; i < N; ++i) {
- target[i].re = source[i*2] * cos(source[i*2+1]);
- target[i].im = source[i*2] * sin(source[i*2+1]);
- }
+ for (int i = 0; i < N; ++i) {
+ double real = source[i*2] * cos(source[i*2+1]);
+ double imag = source[i*2] * sin(source[i*2+1]);
+ if (i == 0) first = real;
+ if (i == N-1) last = imag;
+ total += real;
+ total += imag;
+ total += j;
+ }
}
-
- clock_t end = clock();
- cerr << "Time for naive method: " << float(end - start)/divisor << endl;
+ clock_t end = clock();
+ cerr << "naive method: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for naive method: " << float(end - start)/divisor
+ << endl;
+
start = clock();
+ first = last = total = 0;
+
for (int j = 0; j < iterations; ++j) {
- v_polar_interleaved_to_cartesian(target, source, N);
+ v_polar_interleaved_to_cartesian(target, source, N);
+ for (int i = 0; i < N; ++i) {
+ if (i == 0) first = target[i].re;
+ if (i == N-1) last = target[i].im;
+ total += target[i].re;
+ total += target[i].im;
+ total += j;
+ }
}
-
- end = clock();
- cerr << "Time for v_polar_interleaved_to_cartesian: " << float(end - start)/divisor << endl;
+ end = clock();
+ cerr << "v_polar_interleaved_to_cartesian: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for v_polar_interleaved_to_cartesian: " << float(end - start)/divisor
+ << endl;
+
+ deallocate(target);
+ deallocate(source);
+
+ cerr << endl;
return true;
}
@@ -243,77 +229,69 @@ testCartToPolar()
{
cerr << "testVectorOps: testing v_cartesian_to_polar" << endl;
+ const int iterations = 50000;
const int N = 1024;
- bq_complex_t source[N];
- bq_complex_element_t mag[N];
- bq_complex_element_t phase[N];
+
+ bq_complex_t *source = allocate<bq_complex_t>(N);
+ bq_complex_element_t *mag = allocate<bq_complex_element_t>(N);
+ bq_complex_element_t *phase = allocate<bq_complex_element_t>(N);
for (int i = 0; i < N; ++i) {
source[i].re = (drand48() * 2.0) - 1.0;
source[i].im = (drand48() * 2.0) - 1.0;
}
- double mean, first, last, total = 0;
- for (int i = 0; i < N; ++i) {
- double mag = sqrt(source[i].re * source[i].re + source[i].im * source[i].im);
- double phase = atan2(source[i].im, source[i].re);
- if (i == 0) first = mag;
- if (i == N-1) last = phase;
- total += mag;
- total += phase;
- }
- mean = total / (N*2);
- cerr << "Naive method: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- v_cartesian_to_polar(mag, phase, source, N);
-
- total = 0;
-
- for (int i = 0; i < N; ++i) {
- if (i == 0) first = mag[i];
- if (i == N-1) last = phase[i];
- total += mag[i];
- total += phase[i];
- }
- mean = total / (N*2);
- cerr << "v_cartesian_to_polar: mean = " << mean << ", first = " << first
- << ", last = " << last << endl;
-
- int iterations = 10000;
-// cerr << "Iterations: " << iterations << endl;
-
-// cerr << "CLOCKS_PER_SEC = " << CLOCKS_PER_SEC << endl;
float divisor = float(CLOCKS_PER_SEC) / 1000.f;
-
clock_t start = clock();
+ double first, last, total = 0;
for (int j = 0; j < iterations; ++j) {
- for (int i = 0; i < N; ++i) {
- mag[i] = sqrt(source[i].re * source[i].re + source[i].im * source[i].im);
- phase[i] = atan2(source[i].im, source[i].re);
- }
+ for (int i = 0; i < N; ++i) {
+ double mag = sqrt(source[i].re * source[i].re + source[i].im * source[i].im);
+ double phase = atan2(source[i].im, source[i].re);
+ if (i == 0) first = mag;
+ if (i == N-1) last = phase;
+ total += mag;
+ total += j * phase;
+ }
}
clock_t end = clock();
- cerr << "Time for naive method: " << float(end - start)/divisor << endl;
+ cerr << "naive method: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for naive method: " << float(end - start)/divisor << endl;
start = clock();
+ first = last = total = 0;
for (int j = 0; j < iterations; ++j) {
v_cartesian_to_polar(mag, phase, source, N);
+ for (int i = 0; i < N; ++i) {
+ if (i == 0) first = mag[i];
+ if (i == N-1) last = phase[i];
+ total += mag[i];
+ total += j * phase[i];
+ }
}
-
+
end = clock();
- cerr << "Time for v_cartesian_to_polar: " << float(end - start)/divisor << endl;
+ cerr << "v_cartesian_to_polar: first = " << first << ", last = " << last
+ << ", total = " << total << endl;
+ cerr << "time for v_cartesian_to_polar: " << float(end - start)/divisor << endl;
+ deallocate(source);
+ deallocate(mag);
+ deallocate(phase);
+
+ cerr << endl;
return true;
}
int main(int, char **)
{
+ cerr << endl;
if (!testMultiply()) return 1;
if (!testPolarToCart()) return 1;
if (!testPolarToCartInterleaved()) return 1;
diff --git a/debian/changelog b/debian/changelog
index ea323ce..a81e99d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,17 @@
+sonic-visualiser (4.5.1-1) unstable; urgency=medium
+
+ [ IOhannes m zmölnig (Debian/GNU) ]
+ * New upstream version 4.5.1
+ + Refresh patches
+ * Update d/copyright
+ + Bump dates
+ + Regenerate d/copyright_hints
+
+ [ Debian Janitor ]
+ * Remove constraints unnecessary since buster (oldstable)
+
+ -- IOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org> Tue, 15 Nov 2022 00:19:03 +0100
+
sonic-visualiser (4.5-3) unstable; urgency=medium
* d/watch: switch to github API
diff --git a/debian/control b/debian/control
index d5ab556..a99e0e5 100644
--- a/debian/control
+++ b/debian/control
@@ -10,8 +10,8 @@ Build-Depends:
capnproto,
debhelper-compat (= 13),
libbz2-dev,
- libcapnp-dev (>= 0.6.0),
- libdataquay-dev (>= 0.9-3),
+ libcapnp-dev,
+ libdataquay-dev,
libfftw3-dev,
libfishsound1-dev,
libid3tag0-dev,
diff --git a/debian/copyright b/debian/copyright
index b74f946..9fb82e2 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -25,7 +25,7 @@ Files: bqaudioio/*
bqresample/*
bqthingfactory/*
bqvec/*
-Copyright: 2007-2019, Particular Programs Ltd.
+Copyright: 2007-2022, Particular Programs Ltd.
License: Expat
Files: bqaudiostream/test/*.cpp
diff --git a/debian/copyright_hints b/debian/copyright_hints
index a135fec..0aae5cf 100644
--- a/debian/copyright_hints
+++ b/debian/copyright_hints
@@ -421,6 +421,8 @@ Files: COMPILE_linux.md
bqaudioio/build/Makefile.inc
bqaudiostream/Makefile
bqaudiostream/build/Makefile.inc
+ bqaudiostream/test/testfiles/20samples
+ bqaudiostream/test/testfiles/8000-1-8
bqfft/Makefile
bqfft/build/Makefile.inc
bqfft/build/run-platform-tests.sh
@@ -460,6 +462,7 @@ Files: COMPILE_linux.md
debian/patches/series
debian/rules
debian/source/format
+ debian/source/lintian-overrides
debian/watch
deploy/clean-build-and-package
deploy/linux/AppRun
@@ -738,6 +741,7 @@ Files: bqaudioio/COPYING
bqvec/src/VectorOpsComplex.cpp
Copyright: 2007-2020, Particular Programs Ltd.
2007-2021, Particular Programs Ltd.
+ 2007-2022, Particular Programs Ltd.
License: Expat
FIXME
@@ -940,6 +944,7 @@ Files: bqaudiostream/test/AudioStreamTestData.h
bqaudiostream/test/TestAudioStreamRead.h
bqaudiostream/test/TestSimpleWavRead.h
bqaudiostream/test/TestWavReadWrite.h
+ bqaudiostream/test/TestWavSeek.h
bqaudiostream/test/generate.cpp
bqaudiostream/test/main.cpp
Copyright: Chris Cannam -
@@ -1011,6 +1016,7 @@ Files: bqaudioio/README.md
bqfft/README.md
bqvec/README.md
Copyright: 2007-2021, Particular Programs Ltd. See the file COPYING for
+ 2007-2022, Particular Programs Ltd. See the file COPYING for
License: UNKNOWN
FIXME
@@ -1113,6 +1119,7 @@ License: GPL-2+ and/or LGPL
Files: bqaudiostream/README.md
bqthingfactory/README.md
Copyright: 2007-2021, Particular Programs Ltd. Under a permissive
+ 2007-2022, Particular Programs Ltd. Under a permissive
License: UNKNOWN
FIXME
@@ -1148,7 +1155,7 @@ License: BSD-3-clause
FIXME
Files: debian/copyright_hints
-Copyright: 2000-2006, Chris Cannam, Guillaume Laurent,
+Copyright: 2006, Chris Cannam, 2006-2014 QMUL.
License: BSD-3-clause and/or BSL-1.0 and/or Expat and/or GPL-2 and/or LGPL-2.1 and/or Zlib
FIXME
@@ -1179,7 +1186,7 @@ License: Expat
FIXME
Files: debian/copyright
-Copyright: 2000-2020, Chris Cannam, and Queen Mary University of London
+Copyright: 2000-2022, Chris Cannam, and Queen Mary University of London
2008-2016, Queen Mary University of London
2009-2016, Chris Cannam, Particular Programs Ltd
License: Expat and/or GPL-2
@@ -1227,6 +1234,11 @@ Copyright: 2000-2002, Richard W.E. Furse, Paul Barton-Davis,
License: LGPL-2.1+
FIXME
+Files: debian/changelog
+Copyright: Bump copyright dates
+License: UNKNOWN
+ FIXME
+
Files: vamp-plugin-sdk/rdf/README
Copyright: 2008, Centre For Digital Music, Queen Mary, University of London.
License: UNKNOWN
@@ -1255,11 +1267,6 @@ Copyright: 2007-2021, Particular Programs Ltd.
License: UNKNOWN
FIXME
-Files: debian/changelog
-Copyright: entry for non-existing files
-License: UNKNOWN
- FIXME
-
Files: svcore/plugin/api/alsa/README
Copyright: 2005, under the GNU Lesser General
License: UNKNOWN
diff --git a/debian/patches/01-libserd.patch b/debian/patches/01-libserd.patch
index c47fc29..d4bc01e 100644
--- a/debian/patches/01-libserd.patch
+++ b/debian/patches/01-libserd.patch
@@ -9,7 +9,7 @@ Last-Update: 2022-08-05
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
--- sonic-visualiser.orig/meson.build
+++ sonic-visualiser/meson.build
-@@ -1268,6 +1268,7 @@
+@@ -1269,6 +1269,7 @@
dependencies: [
capnp_gen_header_dep,
server_dependencies,
diff --git a/deploy/linux/deb-skeleton/DEBIAN/control b/deploy/linux/deb-skeleton/DEBIAN/control
index 36e15b0..a96d134 100644
--- a/deploy/linux/deb-skeleton/DEBIAN/control
+++ b/deploy/linux/deb-skeleton/DEBIAN/control
@@ -5,7 +5,7 @@ Architecture: amd64
Version: 2.4cc-1
Installed-Size: 6056
Section: contrib/sound
-Depends: libqt5core5a, libsndfile1, libsamplerate0, libfftw3-3, libbz2-1.0, libpulse0, libmad0, libid3tag0, liboggz2, libfishsound1, libasound2, liblo7, liblrdf0, libsord-0-0, libserd-0-0, vamp-plugin-sdk, librubberband2, libc6
+Depends: libqt5core5a, libsndfile1, libsamplerate0, libfftw3-3, libbz2-1.0, libpulse0, libmad0, libid3tag0, liboggz2, libfishsound1, libasound2, liblo7, liblrdf0, libsord-0-0, libserd-0-0, vamp-plugin-sdk, libc6
Description: View and analyse the contents of music audio files
Sonic Visualiser is an application for viewing and analysing the contents
of music audio files. It was developed at the Centre for Digital Music at
diff --git a/deploy/linux/docker/Dockerfile_appimage.in b/deploy/linux/docker/Dockerfile_appimage.in
index 8067a1d..15529b3 100644
--- a/deploy/linux/docker/Dockerfile_appimage.in
+++ b/deploy/linux/docker/Dockerfile_appimage.in
@@ -23,7 +23,6 @@ RUN apt-get update && \
libxml2-utils \
libgl1-mesa-dev \
raptor2-utils \
- librubberband-dev \
git \
mercurial \
curl wget unzip \
@@ -60,6 +59,12 @@ RUN wget https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-li
RUN unzip ninja-linux.zip
RUN ln -s $(pwd)/ninja /usr/bin/ninja
+RUN wget https://breakfastquay.com/files/releases/rubberband-3.1.1.tar.bz2
+RUN tar xvjf rubberband-3.1.1.tar.bz2
+WORKDIR rubberband-3.1.1
+RUN meson build -Ddefault_library=static && ninja -C build && ninja -C build install
+WORKDIR /root
+
COPY id_rsa_build .ssh/id_rsa_build
COPY known_hosts .ssh/known_hosts
RUN chmod 600 .ssh/id_rsa_build .ssh/known_hosts
diff --git a/deploy/linux/docker/Dockerfile_deb.in b/deploy/linux/docker/Dockerfile_deb.in
index 9971862..df0bd59 100644
--- a/deploy/linux/docker/Dockerfile_deb.in
+++ b/deploy/linux/docker/Dockerfile_deb.in
@@ -23,7 +23,6 @@ RUN apt-get update && \
libxml2-utils \
libgl1-mesa-dev \
raptor2-utils \
- librubberband-dev \
portaudio19-dev \
qt5-default libqt5svg5-dev \
git \
@@ -50,6 +49,12 @@ RUN wget https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-li
RUN unzip ninja-linux.zip
RUN ln -s $(pwd)/ninja /usr/bin/ninja
+RUN wget https://breakfastquay.com/files/releases/rubberband-3.1.1.tar.bz2
+RUN tar xvjf rubberband-3.1.1.tar.bz2
+WORKDIR rubberband-3.1.1
+RUN meson build -Ddefault_library=static && ninja -C build && ninja -C build install
+WORKDIR /root
+
COPY id_rsa_build .ssh/id_rsa_build
COPY known_hosts .ssh/known_hosts
RUN chmod 600 .ssh/id_rsa_build .ssh/known_hosts
diff --git a/deploy/macos/notarize.sh b/deploy/macos/notarize.sh
index 0bcf035..2354f14 100755
--- a/deploy/macos/notarize.sh
+++ b/deploy/macos/notarize.sh
@@ -31,6 +31,13 @@ uuidfile=.notarization-uuid
statfile=.notarization-status
rm -f "$uuidfile" "$statfile"
+# At some point we need to switch to...
+#xcrun notarytool submit \
+# "$dmg" \
+# --apple-id "$user" \
+# --keychain-profile altool \
+# --wait --progress
+
xcrun altool --notarize-app \
-f "$dmg" \
--primary-bundle-id "$bundleid" \
@@ -57,7 +64,6 @@ while true ; do
"$uuid" \
-u "$user" \
-p @keychain:altool 2>&1 | tee "$statfile"
-
if grep -q 'Package Approved' "$statfile"; then
echo
echo "Approved! Status output is:"
diff --git a/main/MainWindow.cpp b/main/MainWindow.cpp
index 269465b..f51649b 100644
--- a/main/MainWindow.cpp
+++ b/main/MainWindow.cpp
@@ -186,6 +186,7 @@ MainWindow::MainWindow(AudioMode audioMode, MIDIMode midiMode, bool withOSCSuppo
setWindowTitle(QApplication::applicationName());
UnitDatabase *udb = UnitDatabase::getInstance();
+ udb->registerUnit("");
udb->registerUnit("Hz");
udb->registerUnit("dB");
udb->registerUnit("s");
diff --git a/main/PreferencesDialog.cpp b/main/PreferencesDialog.cpp
index b95212b..bdb20f6 100644
--- a/main/PreferencesDialog.cpp
+++ b/main/PreferencesDialog.cpp
@@ -267,6 +267,13 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) :
connect(resampleOnLoad, SIGNAL(stateChanged(int)),
this, SLOT(resampleOnLoadChanged(int)));
+ QCheckBox *finerTimeStretch = new QCheckBox;
+ m_finerTimeStretch = prefs->getFinerTimeStretch();
+ finerTimeStretch->setCheckState(m_finerTimeStretch ? Qt::Checked :
+ Qt::Unchecked);
+ connect(finerTimeStretch, SIGNAL(stateChanged(int)),
+ this, SLOT(finerTimeStretchChanged(int)));
+
QCheckBox *gaplessMode = new QCheckBox;
m_gapless = prefs->getUseGaplessMode();
gaplessMode->setCheckState(m_gapless ? Qt::Checked : Qt::Unchecked);
@@ -579,6 +586,11 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) :
row, 0);
subgrid->addWidget(resampleOnLoad, row++, 1, 1, 1);
+ subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
+ ("Use Finer Time Stretch"))),
+ row, 0);
+ subgrid->addWidget(finerTimeStretch, row++, 1, 1, 1);
+
subgrid->setRowStretch(row, 10);
m_tabOrdering[AudioIOTab] = m_tabs->count();
@@ -815,6 +827,13 @@ PreferencesDialog::resampleOnLoadChanged(int state)
}
void
+PreferencesDialog::finerTimeStretchChanged(int state)
+{
+ m_finerTimeStretch = (state == Qt::Checked);
+ m_applyButton->setEnabled(true);
+}
+
+void
PreferencesDialog::gaplessModeChanged(int state)
{
m_gapless = (state == Qt::Checked);
@@ -972,6 +991,7 @@ PreferencesDialog::applyClicked()
(m_propertyLayout));
prefs->setTuningFrequency(m_tuningFrequency);
prefs->setResampleOnLoad(m_resampleOnLoad);
+ prefs->setFinerTimeStretch(m_finerTimeStretch);
prefs->setUseGaplessMode(m_gapless);
prefs->setRunPluginsInProcess(m_runPluginsInProcess);
prefs->setShowSplash(m_showSplash);
diff --git a/main/PreferencesDialog.h b/main/PreferencesDialog.h
index c89a179..ec0b68f 100644
--- a/main/PreferencesDialog.h
+++ b/main/PreferencesDialog.h
@@ -69,6 +69,7 @@ protected slots:
void audioPlaybackDeviceChanged(int device);
void audioRecordDeviceChanged(int device);
void resampleOnLoadChanged(int state);
+ void finerTimeStretchChanged(int state);
void gaplessModeChanged(int state);
void vampProcessSeparationChanged(int state);
void tempDirRootChanged(QString root);
@@ -126,6 +127,7 @@ protected:
int m_audioPlaybackDevice;
int m_audioRecordDevice;
bool m_resampleOnLoad;
+ bool m_finerTimeStretch;
bool m_gapless;
bool m_runPluginsInProcess;
bool m_networkPermission;
diff --git a/main/main.cpp b/main/main.cpp
index 8d922b6..20c5f2f 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -213,8 +213,17 @@ public:
}
~SVApplication() override { }
- void setMainWindow(MainWindow *mw) { m_mainWindow = mw; }
- void releaseMainWindow() { m_mainWindow = nullptr; }
+ void setMainWindow(MainWindow *mw) {
+ m_mainWindow = mw;
+ for (auto f: m_pendingFilepaths) {
+ handleFilepathArgument(f, nullptr);
+ }
+ m_pendingFilepaths.clear();
+ }
+
+ void releaseMainWindow() {
+ m_mainWindow = nullptr;
+ }
virtual void commitData(QSessionManager &manager) {
if (!m_mainWindow) return;
@@ -228,6 +237,7 @@ public:
protected:
MainWindow *m_mainWindow;
+ std::vector<QString> m_pendingFilepaths;
bool event(QEvent *) override;
};
@@ -617,6 +627,12 @@ void SVApplication::handleFilepathArgument(QString path, SVSplash *splash){
static bool haveSession = false;
static bool haveMainModel = false;
static bool havePriorCommandLineModel = false;
+
+ if (!m_mainWindow) {
+ // Not attached yet
+ m_pendingFilepaths.push_back(path);
+ return;
+ }
MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed;
diff --git a/meson.build b/meson.build
index d384510..c81cebb 100644
--- a/meson.build
+++ b/meson.build
@@ -2,7 +2,7 @@
project(
'Sonic Visualiser',
'c', 'cpp',
- version: '4.5',
+ version: '4.5.1',
license: 'GPL-2.0-or-later',
default_options: [
'cpp_std=c++17',
@@ -96,7 +96,7 @@ if system == 'linux'
fftw3_dep = dependency('fftw3', version: '>= 3.0.0')
sndfile_dep = dependency('sndfile', version: '>= 1.0.16')
samplerate_dep = dependency('samplerate', version: '>= 0.1.2')
- rubberband_dep = dependency('rubberband')
+ rubberband_dep = dependency('rubberband', version: '>= 3.0.0')
sord_dep = dependency('sord-0', version: '>= 0.5')
serd_dep = dependency('serd-0', version: '>= 0.5')
capnp_dep = dependency('capnp', version: '>= 0.6')
@@ -138,6 +138,7 @@ if system == 'linux'
server_dependencies = [
capnp_dep,
sord_dep,
+ serd_dep,
dl_dep,
]
diff --git a/svapp/audio/AudioCallbackPlaySource.cpp b/svapp/audio/AudioCallbackPlaySource.cpp
index 8be906c..e6c0f0a 100644
--- a/svapp/audio/AudioCallbackPlaySource.cpp
+++ b/svapp/audio/AudioCallbackPlaySource.cpp
@@ -163,6 +163,10 @@ AudioCallbackPlaySource::checkWrappers()
}
if (!m_timeStretchWrapper) {
m_timeStretchWrapper = new TimeStretchWrapper(m_auditioningEffectWrapper);
+ m_timeStretchWrapper->setQuality
+ (Preferences::getInstance()->getFinerTimeStretch() ?
+ TimeStretchWrapper::Quality::Finer :
+ TimeStretchWrapper::Quality::Faster);
}
}
@@ -594,8 +598,16 @@ AudioCallbackPlaySource::playParametersChanged(int)
}
void
-AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName)
+AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName name)
{
+ if (name == "Use Finer Time Stretch") {
+ if (m_timeStretchWrapper) {
+ m_timeStretchWrapper->setQuality
+ (Preferences::getInstance()->getFinerTimeStretch() ?
+ TimeStretchWrapper::Quality::Finer :
+ TimeStretchWrapper::Quality::Faster);
+ }
+ }
}
void
diff --git a/svapp/audio/TimeStretchWrapper.cpp b/svapp/audio/TimeStretchWrapper.cpp
index 141cecb..3cd797f 100644
--- a/svapp/audio/TimeStretchWrapper.cpp
+++ b/svapp/audio/TimeStretchWrapper.cpp
@@ -25,6 +25,8 @@ TimeStretchWrapper::TimeStretchWrapper(ApplicationPlaybackSource *source) :
m_source(source),
m_stretcher(nullptr),
m_timeRatio(1.0),
+ m_quality(Quality::Finer),
+ m_qualityChangePending(false),
m_stretcherInputSize(16384),
m_channelCount(0),
m_lastReportedSystemLatency(0),
@@ -48,7 +50,7 @@ TimeStretchWrapper::setTimeStretchRatio(double ratio)
m_timeRatio = ratio;
// Stretcher will be updated by checkStretcher() from next call to
- // getSourceSamples()
+ // reset() or getSourceSamples()
}
double
@@ -58,13 +60,45 @@ TimeStretchWrapper::getTimeStretchRatio() const
}
void
-TimeStretchWrapper::reset()
+TimeStretchWrapper::setQuality(Quality quality)
{
lock_guard<mutex> guard(m_mutex);
- if (m_stretcher) {
+ SVDEBUG << "TimeStretchWrapper::setTimeStretchRatio: setting quality to "
+ << int(quality) << " (was " << int(m_quality) << ")" << endl;
+
+ if (m_quality != quality) {
+ m_qualityChangePending = true;
+ }
+
+ m_quality = quality;
+}
+
+TimeStretchWrapper::Quality
+TimeStretchWrapper::getQuality() const
+{
+ return m_quality;
+}
+
+void
+TimeStretchWrapper::reset()
+{
+ m_mutex.lock();
+
+ if (m_qualityChangePending) {
+
+ delete m_stretcher;
+ m_stretcher = nullptr;
+
+ m_mutex.unlock();
+ checkStretcher();
+ m_mutex.lock();
+
+ } else if (m_stretcher) {
m_stretcher->reset();
}
+
+ m_mutex.unlock();
}
int
@@ -144,19 +178,33 @@ TimeStretchWrapper::checkStretcher()
}
if (m_stretcher) {
- SVDEBUG << "TimeStretchWrapper::checkStretcher: setting stretcher ratio to " << m_timeRatio << endl;
- m_stretcher->setTimeRatio(m_timeRatio);
+ if (m_timeRatio != m_stretcher->getTimeRatio()) {
+ SVDEBUG << "TimeStretchWrapper::checkStretcher: setting stretcher ratio to " << m_timeRatio << endl;
+ m_stretcher->setTimeRatio(m_timeRatio);
+ }
return;
}
SVDEBUG << "TimeStretchWrapper::checkStretcher: creating stretcher with ratio " << m_timeRatio << endl;
+
+ RubberBandStretcher::Options options = 0;
+ if (m_quality == Quality::Finer) {
+ SVDEBUG << "TimeStretchWrapper::checkStretcher: using finer-quality stretcher" << endl;
+ options = RubberBandStretcher::OptionEngineFiner;
+ }
m_stretcher = new RubberBandStretcher
(size_t(round(m_sampleRate)),
m_channelCount,
- RubberBandStretcher::OptionProcessRealTime,
+ RubberBandStretcher::OptionProcessRealTime | options,
m_timeRatio);
+ if (m_quality == Quality::Finer) {
+ if (m_stretcher->getEngineVersion() != 3) {
+ SVDEBUG << "TimeStretchWrapper::checkStretcher: WARNING: Unexpected engine version " << m_stretcher->getEngineVersion() << " (expected 3)" << endl;
+ }
+ }
+
m_inputs.resize(m_channelCount);
for (auto &v: m_inputs) {
v.resize(m_stretcherInputSize);
diff --git a/svapp/audio/TimeStretchWrapper.h b/svapp/audio/TimeStretchWrapper.h
index b867cbf..ce5cc7c 100644
--- a/svapp/audio/TimeStretchWrapper.h
+++ b/svapp/audio/TimeStretchWrapper.h
@@ -52,7 +52,7 @@ public:
*/
TimeStretchWrapper(ApplicationPlaybackSource *source);
~TimeStretchWrapper();
-
+
/**
* Set a time stretch factor, i.e. playback speed, where 1.0 is
* normal speed
@@ -64,6 +64,22 @@ public:
*/
double getTimeStretchRatio() const;
+ enum class Quality {
+ Faster,
+ Finer
+ };
+
+ /**
+ * Set a quality preference. The default is Finer. Any change will
+ * take effect at the next call to reset().
+ */
+ void setQuality(Quality quality);
+
+ /**
+ * Obtain the current quality preference.
+ */
+ Quality getQuality() const;
+
/**
* Clear stretcher buffers.
*/
@@ -96,6 +112,8 @@ private:
ApplicationPlaybackSource *m_source;
RubberBand::RubberBandStretcher *m_stretcher;
double m_timeRatio;
+ Quality m_quality;
+ bool m_qualityChangePending;
std::vector<std::vector<float>> m_inputs;
std::mutex m_mutex;
int m_stretcherInputSize;
diff --git a/svcore/base/Pitch.cpp b/svcore/base/Pitch.cpp
index 9de3686..77b341b 100644
--- a/svcore/base/Pitch.cpp
+++ b/svcore/base/Pitch.cpp
@@ -39,7 +39,10 @@ Pitch::getPitchForFrequency(double frequency,
if (concertA <= 0.0) {
concertA = Preferences::getInstance()->getTuningFrequency();
}
- double p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0;
+ double p = 0.0;
+ if (frequency > 0.0) {
+ p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0;
+ }
int midiPitch = int(round(p));
double centsOffset = (p - midiPitch) * 100.0;
diff --git a/svcore/base/Preferences.cpp b/svcore/base/Preferences.cpp
index f158ea0..d60586c 100644
--- a/svcore/base/Preferences.cpp
+++ b/svcore/base/Preferences.cpp
@@ -48,6 +48,7 @@ Preferences::Preferences() :
m_resampleOnLoad(false),
m_gapless(true),
m_normaliseAudio(false),
+ m_finerTimeStretch(true),
m_viewFontSize(10),
m_backgroundMode(BackgroundFromTheme),
m_timeToTextMode(TimeToTextMs),
@@ -72,6 +73,7 @@ Preferences::Preferences() :
m_resampleOnLoad = settings.value("resample-on-load", false).toBool();
m_gapless = settings.value("gapless", true).toBool();
m_normaliseAudio = settings.value("normalise-audio", false).toBool();
+ m_finerTimeStretch = settings.value("finer-timestretch", true).toBool();
m_backgroundMode = BackgroundMode
(settings.value("background-mode", int(BackgroundFromTheme)).toInt());
m_timeToTextMode = TimeToTextMode
@@ -106,6 +108,7 @@ Preferences::getProperties() const
props.push_back("Resample On Load");
props.push_back("Use Gapless Mode");
props.push_back("Normalise Audio");
+ props.push_back("Use Finer Time Stretch");
props.push_back("Fixed Sample Rate");
props.push_back("Temporary Directory Root");
props.push_back("Background Mode");
@@ -141,6 +144,9 @@ Preferences::getPropertyLabel(const PropertyName &name) const
if (name == "Normalise Audio") {
return tr("Normalise audio signal when reading from audio file");
}
+ if (name == "Use Finer Time Stretch") {
+ return tr("Use fine-quality time stretcher");
+ }
if (name == "Omit Temporaries from Recent Files") {
return tr("Omit temporaries from Recent Files menu");
}
@@ -204,6 +210,9 @@ Preferences::getPropertyType(const PropertyName &name) const
if (name == "Normalise Audio") {
return ToggleProperty;
}
+ if (name == "Use Finer Time Stretch") {
+ return ToggleProperty;
+ }
if (name == "Omit Temporaries from Recent Files") {
return ToggleProperty;
}
@@ -626,6 +635,19 @@ Preferences::setNormaliseAudio(bool norm)
}
void
+Preferences::setFinerTimeStretch(bool finer)
+{
+ if (m_finerTimeStretch != finer) {
+ m_finerTimeStretch = finer;
+ QSettings settings;
+ settings.beginGroup("Preferences");
+ settings.setValue("finer-timestretch", finer);
+ settings.endGroup();
+ emit propertyChanged("Use Finer Time Stretch");
+ }
+}
+
+void
Preferences::setBackgroundMode(BackgroundMode mode)
{
if (m_backgroundMode != mode) {
diff --git a/svcore/base/Preferences.h b/svcore/base/Preferences.h
index 1dcf74a..8df2b3e 100644
--- a/svcore/base/Preferences.h
+++ b/svcore/base/Preferences.h
@@ -81,6 +81,9 @@ public:
/// True if audio files should be loaded with normalisation (max == 1)
bool getNormaliseAudio() const { return m_normaliseAudio; }
+ /// True if we should use higher-quality time stretcher where available
+ bool getFinerTimeStretch() const { return m_finerTimeStretch; }
+
enum BackgroundMode {
BackgroundFromTheme,
DarkBackground,
@@ -127,6 +130,7 @@ public slots:
void setResampleOnLoad(bool);
void setUseGaplessMode(bool);
void setNormaliseAudio(bool);
+ void setFinerTimeStretch(bool);
void setBackgroundMode(BackgroundMode mode);
void setTimeToTextMode(TimeToTextMode mode);
void setShowHMS(bool show);
@@ -166,6 +170,7 @@ private:
bool m_resampleOnLoad;
bool m_gapless;
bool m_normaliseAudio;
+ bool m_finerTimeStretch;
int m_viewFontSize;
BackgroundMode m_backgroundMode;
TimeToTextMode m_timeToTextMode;
diff --git a/svcore/base/UnitDatabase.cpp b/svcore/base/UnitDatabase.cpp
index 3e708f2..eddffea 100644
--- a/svcore/base/UnitDatabase.cpp
+++ b/svcore/base/UnitDatabase.cpp
@@ -42,7 +42,6 @@ UnitDatabase::getKnownUnits() const
void
UnitDatabase::registerUnit(QString unit)
{
- if (unit == "") return;
if (m_units.find(unit) == m_units.end()) {
m_units[unit] = m_nextId++;
emit unitDatabaseChanged();
@@ -56,7 +55,7 @@ UnitDatabase::getUnitId(QString unit, bool registerNew)
if (registerNew) registerUnit(unit);
else return -1;
}
- return m_units[unit];
+ return m_units.at(unit);
}
QString
diff --git a/svgui/layer/LayerGeometryProvider.h b/svgui/layer/LayerGeometryProvider.h
index 88b3e9b..719c3e3 100644
--- a/svgui/layer/LayerGeometryProvider.h
+++ b/svgui/layer/LayerGeometryProvider.h
@@ -71,7 +71,7 @@ public:
* Retrieve the id of this object.
*/
virtual int getId() const = 0;
-
+
/**
* Retrieve the first visible sample frame on the widget.
* This is a calculated value based on the centre-frame, widget
@@ -206,6 +206,14 @@ public:
virtual int scalePixelSize(int size) const = 0;
virtual double scalePenWidth(double width) const = 0;
virtual QPen scalePen(QPen pen) const = 0;
+
+ /**
+ * Retrieve the pixel scale factor for this object. Mostly we
+ * don't want to use this - call the geometry accessors instead
+ * which will do the right calculations. This is sometimes useful
+ * if we want to check whether something has changed.
+ */
+ virtual int getScaleFactor() const = 0;
virtual View *getView() = 0;
virtual const View *getView() const = 0;
diff --git a/svgui/layer/SliceLayer.cpp b/svgui/layer/SliceLayer.cpp
index a3073d6..67c4708 100644
--- a/svgui/layer/SliceLayer.cpp
+++ b/svgui/layer/SliceLayer.cpp
@@ -45,6 +45,7 @@ SliceLayer::SliceLayer() :
m_gain(1.0),
m_minbin(0),
m_maxbin(0),
+ m_cachedScaleFactor(1),
m_currentf0(0),
m_currentf1(0)
{
@@ -200,6 +201,11 @@ SliceLayer::getXForScalePoint(const LayerGeometryProvider *v,
int pw = v->getPaintWidth();
int origin = m_xorigins[v->getId()];
+
+ if (v->getScaleFactor() != m_cachedScaleFactor) {
+ origin = (origin * v->getScaleFactor()) / m_cachedScaleFactor;
+ }
+
int w = pw - origin;
if (w < 1) w = 1;
@@ -276,6 +282,10 @@ SliceLayer::getScalePointForX(const LayerGeometryProvider *v,
int pw = v->getPaintWidth();
int origin = m_xorigins[v->getId()];
+ if (v->getScaleFactor() != m_cachedScaleFactor) {
+ origin = (origin * v->getScaleFactor()) / m_cachedScaleFactor;
+ }
+
int w = pw - origin;
if (w < 1) w = 1;
@@ -355,6 +365,12 @@ SliceLayer::getYForValue(const LayerGeometryProvider *v, double value, double &n
int yorigin = m_yorigins[v->getId()];
int h = m_heights[v->getId()];
+
+ if (v->getScaleFactor() != m_cachedScaleFactor) {
+ yorigin = (yorigin * v->getScaleFactor()) / m_cachedScaleFactor;
+ h = (h * v->getScaleFactor()) / m_cachedScaleFactor;
+ }
+
double thresh = getThresholdDb();
double y = 0.0;
@@ -413,6 +429,12 @@ SliceLayer::getValueForY(const LayerGeometryProvider *v, double y) const
int yorigin = m_yorigins[v->getId()];
int h = m_heights[v->getId()];
+
+ if (v->getScaleFactor() != m_cachedScaleFactor) {
+ yorigin = (yorigin * v->getScaleFactor()) / m_cachedScaleFactor;
+ h = (h * v->getScaleFactor()) / m_cachedScaleFactor;
+ }
+
double thresh = getThresholdDb();
if (h <= 0) return value;
@@ -497,14 +519,21 @@ SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
int xorigin = getVerticalScaleWidth(v, true, paint) + 1;
m_xorigins[v->getId()] = xorigin; // for use in getFeatureDescription
-
+
+/* comment out while testing simpler variant below, to try to
+ * understand why the crosshair coords are wrong
+
int yorigin = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) -
paint.fontMetrics().height();
int h = yorigin - paint.fontMetrics().height() - 8;
-
+*/
+ int yorigin = v->getPaintHeight() - v->scalePixelSize(35);
+ int h = yorigin - v->scalePixelSize(20);
+
m_yorigins[v->getId()] = yorigin; // for getYForValue etc
m_heights[v->getId()] = h;
-
+ m_cachedScaleFactor = v->getScaleFactor();
+
if (h <= 0) return;
QPainterPath path;
diff --git a/svgui/layer/SliceLayer.h b/svgui/layer/SliceLayer.h
index 3fc7ef6..0ad213f 100644
--- a/svgui/layer/SliceLayer.h
+++ b/svgui/layer/SliceLayer.h
@@ -195,6 +195,7 @@ protected:
mutable std::map<int, int> m_xorigins; // LayerGeometryProvider id -> x
mutable std::map<int, int> m_yorigins; // LayerGeometryProvider id -> y
mutable std::map<int, int> m_heights; // LayerGeometryProvider id -> h
+ mutable int m_cachedScaleFactor;
mutable sv_frame_t m_currentf0;
mutable sv_frame_t m_currentf1;
mutable std::vector<float> m_values;
diff --git a/svgui/layer/SpectrumLayer.cpp b/svgui/layer/SpectrumLayer.cpp
index 83404d6..807ecce 100644
--- a/svgui/layer/SpectrumLayer.cpp
+++ b/svgui/layer/SpectrumLayer.cpp
@@ -570,6 +570,11 @@ SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
paint.setPen(mapper.getContrastingColour());
int xorigin = m_xorigins[v->getId()];
+
+ if (v->getScaleFactor() != m_cachedScaleFactor) {
+ xorigin = (xorigin * v->getScaleFactor()) / m_cachedScaleFactor;
+ }
+
paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
@@ -597,10 +602,10 @@ SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
double value = getValueForY(v, cursorPos.y());
PaintAssistant::drawVisibleText(v, paint,
- xorigin + 2,
- cursorPos.y() - 2,
- QString("%1 V").arg(value),
- PaintAssistant::OutlinedText);
+ xorigin + 2,
+ cursorPos.y() - 2,
+ QString("%1 V").arg(value),
+ PaintAssistant::OutlinedText);
if (value > m_threshold) {
AudioLevel::Quantity sort = getValueALQuantity();
diff --git a/svgui/layer/WaveformLayer.cpp b/svgui/layer/WaveformLayer.cpp
index 22e1f3a..0a29efa 100644
--- a/svgui/layer/WaveformLayer.cpp
+++ b/svgui/layer/WaveformLayer.cpp
@@ -20,6 +20,7 @@
#include "base/Profiler.h"
#include "base/RangeMapper.h"
#include "base/Strings.h"
+#include "base/ScaleTickIntervals.h"
#include "ColourDatabase.h"
#include "PaintAssistant.h"
@@ -1526,7 +1527,19 @@ WaveformLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &pa
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
if (m_scale == LinearScale) {
- return paint.fontMetrics().width("0.0") + 13;
+ QString sampleText = "0.0";
+ if (m_gain != 1.f) {
+ int n = int(ceil(log10(m_gain)));
+ if (n > 2) {
+ sampleText = "0.0e+00";
+ } else {
+ while (n > 0) {
+ sampleText += "0";
+ --n;
+ }
+ }
+ }
+ return paint.fontMetrics().width(sampleText) + 13;
} else {
return std::max(paint.fontMetrics().width(tr("0dB")),
paint.fontMetrics().width(Strings::minus_infinity)) + 13;
@@ -1554,14 +1567,19 @@ WaveformLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &pain
double gain = m_gain;
+ int n = 10;
+
+ std::vector<ScaleTickIntervals::Tick> linearTicks;
+ if (m_scale == LinearScale) {
+ linearTicks = ScaleTickIntervals::linear({ 0.0, 1.0 / gain, n });
+ }
+
for (int ch = minChannel; ch <= maxChannel; ++ch) {
int lastLabelledY = -1;
if (ch < (int)m_effectiveGains.size()) gain = m_effectiveGains[ch];
-
- int n = 10;
-
+
for (int i = 0; i <= n; ++i) {
double val = 0.0, nval = 0.0;
@@ -1570,13 +1588,27 @@ WaveformLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &pain
switch (m_scale) {
case LinearScale:
- val = (i * gain) / n;
- text = QString("%1").arg(double(i) / n);
- if (i == 0) text = "0.0";
- else {
+ if (in_range_for(linearTicks, i)) {
+ val = linearTicks[i].value * gain;
+ text = QString::fromUtf8(linearTicks[i].label.c_str());
+ if (i != 0) {
+ nval = -val;
+ }
+ } else {
+ val = linearTicks[0].value * gain;
+ text = QString::fromUtf8(linearTicks[0].label.c_str());
+ }
+
+ /*
+ val = double(i) / n;
+ text = QString("%1").arg(double(i) / (gain * n), 0, 'g', 2);
+// if (i == 0) text = "0.0";
+// else {
+ if (i != 0) {
nval = -val;
- if (i == n) text = "1.0";
+// if (i == n) text = "1.0";
}
+ */
break;
case MeterScale:
@@ -1627,7 +1659,9 @@ WaveformLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &pain
} else {
ty += toff;
}
- paint.drawText(tx, ty, text);
+ if (!(i == n && ch > 0)) {
+ paint.drawText(tx, ty, text);
+ }
lastLabelledY = ty - toff;
diff --git a/svgui/view/View.h b/svgui/view/View.h
index 9202a19..edea49b 100644
--- a/svgui/view/View.h
+++ b/svgui/view/View.h
@@ -70,6 +70,8 @@ public:
* ids, but ViewProxy objects share the id of their View.
*/
int getId() const override { return m_id; }
+
+
/**
* Retrieve the first visible sample frame on the widget.
@@ -413,6 +415,8 @@ public:
sv_frame_t getAlignedPlaybackFrame() const;
void updatePaintRect(QRect r) override { update(r); }
+
+ int getScaleFactor() const override { return 1; } // See ViewProxy
View *getView() override { return this; }
const View *getView() const override { return this; }
diff --git a/svgui/view/ViewProxy.h b/svgui/view/ViewProxy.h
index faf296e..b326487 100644
--- a/svgui/view/ViewProxy.h
+++ b/svgui/view/ViewProxy.h
@@ -52,6 +52,9 @@ public:
int getId() const override {
return m_view->getId();
}
+ int getScaleFactor() const override {
+ return m_scaleFactor;
+ }
sv_frame_t getStartFrame() const override {
return alignToReference(m_view->getStartFrame());
}