diff --git a/.gitignore b/.gitignore index 63a41df..b30a492 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ bin *.DS_Store -.classpath -.project -.settings/ +build/ *.o diff --git a/.gradle/7.2/dependencies-accessors/dependencies-accessors.lock b/.gradle/7.2/dependencies-accessors/dependencies-accessors.lock new file mode 100644 index 0000000..b2ab4da Binary files /dev/null and b/.gradle/7.2/dependencies-accessors/dependencies-accessors.lock differ diff --git a/.gradle/7.2/dependencies-accessors/gc.properties b/.gradle/7.2/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/7.2/executionHistory/executionHistory.bin b/.gradle/7.2/executionHistory/executionHistory.bin new file mode 100644 index 0000000..a003daa Binary files /dev/null and b/.gradle/7.2/executionHistory/executionHistory.bin differ diff --git a/.gradle/7.2/executionHistory/executionHistory.lock b/.gradle/7.2/executionHistory/executionHistory.lock new file mode 100644 index 0000000..7112633 Binary files /dev/null and b/.gradle/7.2/executionHistory/executionHistory.lock differ diff --git a/.gradle/7.2/fileChanges/last-build.bin b/.gradle/7.2/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/7.2/fileChanges/last-build.bin differ diff --git a/.gradle/7.2/fileHashes/fileHashes.bin b/.gradle/7.2/fileHashes/fileHashes.bin new file mode 100644 index 0000000..3701320 Binary files /dev/null and b/.gradle/7.2/fileHashes/fileHashes.bin differ diff --git a/.gradle/7.2/fileHashes/fileHashes.lock b/.gradle/7.2/fileHashes/fileHashes.lock new file mode 100644 index 0000000..446a089 Binary files /dev/null and b/.gradle/7.2/fileHashes/fileHashes.lock differ diff --git a/.gradle/7.2/fileHashes/resourceHashesCache.bin b/.gradle/7.2/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..c7b298a Binary files /dev/null and b/.gradle/7.2/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/7.2/gc.properties b/.gradle/7.2/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/7.6/checksums/checksums.lock b/.gradle/7.6/checksums/checksums.lock new file mode 100644 index 0000000..2c5b330 Binary files /dev/null and b/.gradle/7.6/checksums/checksums.lock differ diff --git a/.gradle/7.6/dependencies-accessors/dependencies-accessors.lock b/.gradle/7.6/dependencies-accessors/dependencies-accessors.lock new file mode 100644 index 0000000..c40c5ed Binary files /dev/null and b/.gradle/7.6/dependencies-accessors/dependencies-accessors.lock differ diff --git a/.gradle/7.6/dependencies-accessors/gc.properties b/.gradle/7.6/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/7.6/executionHistory/executionHistory.bin b/.gradle/7.6/executionHistory/executionHistory.bin new file mode 100644 index 0000000..ad51f8e Binary files /dev/null and b/.gradle/7.6/executionHistory/executionHistory.bin differ diff --git a/.gradle/7.6/executionHistory/executionHistory.lock b/.gradle/7.6/executionHistory/executionHistory.lock new file mode 100644 index 0000000..8da333b Binary files /dev/null and b/.gradle/7.6/executionHistory/executionHistory.lock differ diff --git a/.gradle/7.6/fileChanges/last-build.bin b/.gradle/7.6/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/7.6/fileChanges/last-build.bin differ diff --git a/.gradle/7.6/fileHashes/fileHashes.bin b/.gradle/7.6/fileHashes/fileHashes.bin new file mode 100644 index 0000000..3f0b12e Binary files /dev/null and b/.gradle/7.6/fileHashes/fileHashes.bin differ diff --git a/.gradle/7.6/fileHashes/fileHashes.lock b/.gradle/7.6/fileHashes/fileHashes.lock new file mode 100644 index 0000000..6d9184f Binary files /dev/null and b/.gradle/7.6/fileHashes/fileHashes.lock differ diff --git a/.gradle/7.6/fileHashes/resourceHashesCache.bin b/.gradle/7.6/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..3426e0a Binary files /dev/null and b/.gradle/7.6/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/7.6/gc.properties b/.gradle/7.6/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..d2152b0 Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..f2f7d9b --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Fri Jan 13 21:06:59 CET 2023 +gradle.version=7.6 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..a2e425d Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/checksums/checksums.lock b/.gradle/checksums/checksums.lock new file mode 100644 index 0000000..9d48e1d Binary files /dev/null and b/.gradle/checksums/checksums.lock differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000..18bbd03 Binary files /dev/null and b/.gradle/file-system.probe differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..3adee5a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +JGaborator \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..611e7c8 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..8e51d7a --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..87a20fc --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 9f58921..548a9e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # JGaborator - High resolution spectral transforms from Java -This library calculates fine grained constant-Q spectral representations of audio signals quickly from Java. The spectral transform can be visualized or further processed in a (Music Information Retrieval) processing chain. +This library calculates fine-grained constant-Q spectral representations of audio signals quickly from Java. The spectral transform can be visualized or further processed in a (Music Information Retrieval) processing chain. The calculation of a [Gabor transform](https://en.wikipedia.org/wiki/Gabor_transform) is done by a C++ library named [Gaborator](http://gaborator.com). A Java native interface (JNI) bridge to the C++ Gaborator is provided here. A combination of Gaborator and a fast FFT library (such as [pfft](https://bitbucket.org/jpommier/pffft)) allows fine grained constant-Q transforms at a rate of about 200 times real-time on moderate hardware. @@ -8,9 +8,9 @@ For more information on the Gaborator C++ library by Andreas Gustafsson, please While the gaborator allows reversible transforms, only a forward transform (from time domain to the spectral domain) is currently supported by JGaborator. -A spectral visualization tool for sprectral information is part of this package. See below for a screenshot: +A spectral visualization tool for spectral information is part of this package. See below for a screenshot: -![JGaborator](build/jgaborator.png "A screenshot of JGaborator in action.") +![JGaborator](media/jgaborator.png "A screenshot of JGaborator in action.") ## Using JGaborator @@ -42,10 +42,9 @@ List coefficients = zsazsa.getCoefficents() * `src` contains the java source files * `gaborator` contains the C++ JNI bridge and a makefile - * `gaborator\gaborator-1.2` The gaborator C++ library, licensed under AGPL - * `gaborator\pfft` the pfft C++ library, licensed under a BSD type license -* `build` ant build files -* `lib` a Java dependency: the TarsosDSP audio processing library. + * `gaborator\gaborator-1.x` The gaborator C++ library, licensed under AGPL + * `gaborator\pffft` the pffft c-library, licensed under a BSD type license +* `media` ## Compilation and development for JGaborator @@ -78,7 +77,7 @@ The makefile contains similar instructions. On macOS the Apple's vDSP library can be used by defining GABORATOR_USE_VDSP and linking with the Accelerate framework. The following should suffice: ~~~~~~~~ -c++ -std=c++11 -I"gaborator-1.2" -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin" -O3 -ffast-math -DGABORATOR_USE_VDSP -o libjgaborator.so jgaborator.cc -framework Accelerate +c++ -std=c++11 -I"gaborator-1.7" -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin" -O3 -ffast-math -DGABORATOR_USE_VDSP -o libjgaborator.so jgaborator.cc -framework Accelerate ~~~~~~~~ The makefile contains similar instructions. To compile the mac version call `make mac` If the JAVA_HOME environment variable is not set, run the following before calling `c++`: diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1434215 --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '7.1.2' +} + +group 'be.ugent.jgaborator' +version '0.7' + +repositories { + maven { + name = "TarsosDSP repository" + url = "https://mvn.0110.be/releases" + } +} + +dependencies { + implementation 'be.tarsos.dsp:core:2.5' + implementation 'be.tarsos.dsp:jvm:2.5' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/build/precompiled/aarch64_libjgaborator.dylib b/build/precompiled/aarch64_libjgaborator.dylib deleted file mode 100755 index 6b15d27..0000000 Binary files a/build/precompiled/aarch64_libjgaborator.dylib and /dev/null differ diff --git a/build/precompiled/libjgaborator.so b/build/precompiled/libjgaborator.so deleted file mode 100755 index 569550e..0000000 Binary files a/build/precompiled/libjgaborator.so and /dev/null differ diff --git a/gaborator/Makefile b/gaborator/Makefile index 46b0a86..4bd56f0 100644 --- a/gaborator/Makefile +++ b/gaborator/Makefile @@ -1,18 +1,35 @@ + +JNI_INCLUDES=-I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin" -I"$(JAVA_HOME)/include/linux" -I"$(JAVA_HOME)/include/win32" -I"$(JAVA_HOME)/include/win64" + + all: # Performance can be significantly improved by using a faster FFT library. # On macOS, you can use the FFT from Apple's vDSP library by defining GABORATOR_USE_VDSP and linking with the Accelerate framework # On Linux and NetBSD, you can use the PFFFT (Pretty Fast FFT) library which is what is done below: cc -c -O3 -ffast-math -fPIC pffft/pffft.c -o pffft/pffft.o cc -c -O3 -ffast-math -fPIC -DFFTPACK_DOUBLE_PRECISION pffft/fftpack.c -o pffft/fftpack.o - c++ -std=c++11 -I"gaborator-1.2" -I"pffft" -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/linux" -fPIC -shared -O3 -ffast-math -DGABORATOR_USE_PFFFT -o ../build/precompiled/libjgaborator.so jgaborator.cc pffft/pffft.o pffft/fftpack.o + c++ -std=c++11 -I"gaborator-1.7" -I"pffft" $(JNI_INCLUDES) -fPIC -shared -O3 -ffast-math -DGABORATOR_USE_PFFFT -o ../build/precompiled/libjgaborator.so jgaborator.cc pffft/pffft.o pffft/fftpack.o mac: - c++ -std=c++11 -I"gaborator-1.2" -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin" -O3 -ffast-math -DGABORATOR_USE_VDSP -o ../build/precompiled/libjgaborator.dylib jgaborator.cc -framework Accelerate + c++ -std=c++11 -I"gaborator-1.7" $(JNI_INCLUDES) -O3 -ffast-math -DGABORATOR_USE_VDSP -o ../build/precompiled/libjgaborator.dylib jgaborator.cc -framework Accelerate mac_dummy: - c++ -std=c++11 -I"gaborator-1.2" -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin" -O3 -ffast-math -DGABORATOR_USE_VDSP -o ../build/precompiled/libjgaborator.dylib jgaborator_dummy.cc -framework Accelerate + c++ -std=c++11 -I"gaborator-1.7" $(JNI_INCLUDES) -O3 -ffast-math -DGABORATOR_USE_VDSP -o ../build/precompiled/libjgaborator.dylib jgaborator_dummy.cc -framework Accelerate + +clean: + rm *.so *.class pfft/*o + +zig_windows_example: + echo $(JNI_INCLUDES) + zig cc -target x86_64-windows-gnu -c -O3 -ffast-math -fPIC pffft/pffft.c -o pffft/pffft.o + zig cc -target x86_64-windows-gnu -c -O3 -ffast-math -fPIC -DFFTPACK_DOUBLE_PRECISION pffft/fftpack.c -o pffft/fftpack.o + zig c++ -target x86_64-windows-gnu -I"pffft" -I"gaborator-1.7" $(JNI_INCLUDES) -O3 -ffast-math -DGABORATOR_USE_PFFFT -o libjgaborator.dll jgaborator.cc pffft/pffft.o pffft/fftpack.o + +zig: + zig cc -target $(ZIG_TARGET_PLATFORM) -c -O3 -ffast-math -fPIC pffft/pffft.c -o pffft/pffft.o + zig cc -target $(ZIG_TARGET_PLATFORM) -c -O3 -ffast-math -fPIC -DFFTPACK_DOUBLE_PRECISION pffft/fftpack.c -o pffft/fftpack.o + zig c++ -target $(ZIG_TARGET_PLATFORM) -I"pffft" -I"gaborator-1.7" $(JNI_INCLUDES) -O3 -ffast-math -DGABORATOR_USE_PFFFT -o $(ZIG_OUTPUT_FILE) jgaborator.cc pffft/pffft.o pffft/fftpack.o + -clean: - rm *.so *.class diff --git a/gaborator/gaborator-1.2/CHANGES b/gaborator/gaborator-1.2/CHANGES deleted file mode 100644 index 9173edf..0000000 --- a/gaborator/gaborator-1.2/CHANGES +++ /dev/null @@ -1,34 +0,0 @@ -1.2 - -Add overview documentation. - -Add real-time FAQ. - -Actually include version.h in the release. - -Fix off-by-one error in defintion of analyzer constructor ff_min -argument. - -Fix incorrect return value of band_ff() for DC band. - -Add streaming example code. - -Add analyzer::analysis_support() and analyzer::synthesis_support(). - -Document analyzer::band_ff(). - -Improve signal to noise ratio at low numbers of bands per octave. - -Note the need for -mfpu=neon on ARM in render.html. - -1.1 - -Added CHANGES file. - -Added reference documentation. - -New include file gaborator/version.h. - -1.0 - -Initial release diff --git a/gaborator/gaborator-1.2/doc/allkernels_bpo12_ffmin0.03_ffref0.5_anl_wob.png b/gaborator/gaborator-1.2/doc/allkernels_bpo12_ffmin0.03_ffref0.5_anl_wob.png deleted file mode 100644 index 91e8305..0000000 Binary files a/gaborator/gaborator-1.2/doc/allkernels_bpo12_ffmin0.03_ffref0.5_anl_wob.png and /dev/null differ diff --git a/gaborator/gaborator-1.2/doc/allkernels_bpo12_ffmin0.03_ffref0.5_syn_wob.png b/gaborator/gaborator-1.2/doc/allkernels_bpo12_ffmin0.03_ffref0.5_syn_wob.png deleted file mode 100644 index e995c42..0000000 Binary files a/gaborator/gaborator-1.2/doc/allkernels_bpo12_ffmin0.03_ffref0.5_syn_wob.png and /dev/null differ diff --git a/gaborator/gaborator-1.2/doc/grid_bpo12_ffmin0.03_ffref0.5_wob.png b/gaborator/gaborator-1.2/doc/grid_bpo12_ffmin0.03_ffref0.5_wob.png deleted file mode 100644 index 9984b43..0000000 Binary files a/gaborator/gaborator-1.2/doc/grid_bpo12_ffmin0.03_ffref0.5_wob.png and /dev/null differ diff --git a/gaborator/gaborator-1.2/doc/ref/gaborator_h.html b/gaborator/gaborator-1.2/doc/ref/gaborator_h.html deleted file mode 100644 index b5545e7..0000000 --- a/gaborator/gaborator-1.2/doc/ref/gaborator_h.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - -Gaborator reference: gaborator.h - - -

Gaborator reference: gaborator.h

- -

class gaborator::parameters

- -

A parameters object holds a set -of spectrum analysis parameters.

- -

Constructor

-
-gaborator::parameters::parameters(unsigned int bands_per_octave,
-                                  double ff_min,
-                                  double ff_ref = 1.0)
-
-
-
bands_per_octave
-
The number of frequency bands per octave. - Values from 6 to 192 (inclusive) are supported. Values outside - this range may not work, or may cause degraded performance.
-
ff_min
-
The lower limit of the analysis frequency range, in units of the - sample rate. The analysis filter bank will extend low enough in - frequency that ff_min falls between the two lowest - frequency bandpass filters. - Values from 0.001 to 0.13 are supported.
-
ff_ref
-
The reference frequency, in units of the sample rate. - This allows fine-tuning of the analysis and synthesis filter - banks such that the center frequency of one of the filters - is aligned with ff_ref. If ff_ref - falls outside the frequency range of the filter bank, this - works as if the range were extended to include - ff_ref. Must be positive. A typical value - when analyzing music is 440.0 / fs, where - fs is the sample rate in Hz. -
-
-

Comparison

-

-Comparison operators are provided for compatibility with -standard container classes. The ordering is arbitrary but consistent. -

-
-bool gaborator::parameters::operator<(const gaborator::parameters &rhs) const
-bool gaborator::parameters::operator==(const gaborator::parameters &rhs) const
-
- -

template<class T> class gaborator::coefs

-

-A coefs object stores a set of spectrogram coefficients. -It is a dynamic data structure and will be automatically grown to -accommodate new time ranges as they are analyzed by calling -analyzer::analyze(). The template argument T -must match that of the analyzer (usually float). -

-

Constructor

-
-gaborator::coefs::coefs(gaborator::analyzer &a)
-
-

-Construct an empty set of coefficients for use with the spectrum -analyzer a. This represents a signal that is zero -at all points in time. -

- -

template<class T> class gaborator::analyzer

-

-The analyzer object performs spectrum analysis and/or resynthesis -according to the given parameters. The template argument T is -the floating-point type to use for the calculations. This is typically float; -alternatively, double can be used for increased accuracy at the -expense of speed and memory consumption.

- -

Constructor

- -
-gaborator::analyzer::analyzer(const gaborator::parameters &params)
-
-
-
params
-
The spectrum analysis parameters. -
- -

Analysis and synthesis

- -
-void
-gaborator::analyzer::analyze(const T *signal,
-                     int64_t t0,
-                     int64_t t1,
-                     gaborator::coefs<T> &coefs) const
-
-

Spectrum analyze the samples at *signal and add the -resulting coefficients to coefs. -

-
signal
-
The signal samples to analyze, beginning with the sample from time t0, - and numbering t1 - t0 in all. -
t0
-
The point in time when the sample at signal[0] was taken, - in samples. By convention, this is 0 for the first sample in - the audio track, but this reference point is arbitrary, and - negative times are valid. Accuracy begins to successively decrease - outside the range of about ±108 samples, so using - large time values should be avoided when they are not necessary because - of the length of the track. -
-
t1
-
The point in time of the sample one past the - end of the array of samples at signal, - in samples. -
-
coefs
The coefficient object that the results of the - spectrum analysis are added to. -
-

If the coefs object already contains some -coefficients, the new coefficients are summed to those already -present. Because the analysis is a linear operation, this allows a -signal to be analyzed in parts, by making multiple calls -to analyze() with non-overlapping ranges that together -cover the entire signal. For efficiency, the ranges should preferably -be large and aligned on multiples of a large powers of two, as in -analyze(first_131072_samples, 0, 131072, coefs), -analyze(next_131072_samples, 131072, 262144, coefs), -etc. -

- -
-void
-gaborator::analyzer::synthesize(const gaborator::coefs<T> &coefs,
-                                uint64_t t0,
-                                uint64_t t1,
-                                T *signal) const
-
-

Synthesize signal samples from the coefficients coef and store -them at *signal. -

-
-
coefs
The coefficients to synthesize the signal from.
-
t0
-
The point in time of the first sample to synthesize, - in samples, using the same time scale as in analyze().
-
t0
-
The point in time of the sample one past the last one to synthesize.
-
signal
-
The synthesized signal samples will be written here, - beginning with the sample from time t0, - and numbering t1 - t0 in all.
-
-

The time range t0...t1 -may extend outside the range analyzed using analyze(), -in which case the signal is assumed to be zero in the un-analyzed range.

- -

Frequency Band Numbering

- -

The frequency bands of the analysis filter bank are numbered by -nonnegative integers that increase towards lower (sic) frequencies. -There is a number of bandpass bands corresponding to the -logarithmically spaced bandpass analysis filters, from near 0.5 -(half the sample rate) to -near fmin, and a single lowpass band containing the -residual signal from frequencies below fmin. -The numbering can be examined using the following methods: -

- -
-int gaborator::analyzer::bandpass_bands_begin() const
-
-

-Return the smallest valid bandpass band number, corresponding to the highest-frequency -bandpass filter.

-
-int gaborator::analyzer::bandpass_bands_end() const
-
-

-Return the bandpass band number one past the highest valid bandpass band number, -corresponding to one past the lowest-frequency bandpass filter.

-
-int gaborator::analyzer::band_lowpass() const
-
-

-Return the band number of the lowpass band.

-
-int gaborator::analyzer::band_ff(int band) const
-
-

-Return the center frequency of band number band, in units of the -sampling frequency. -

- -

Support

-
-double gaborator::analyzer::analysis_support() const
-
-

Returns the one-sided worst-case support of any of the analysis filters. -When calling analyze() with a sample at time t, -only spectrogram coefficients within the time range t ± support -will be significantly changed. Coefficients outside the range may change, -but the changes will sufficiently small that they may be ignored without -significantly reducing accuracy.

- -
-double gaborator::analyzer::synthesis_support() const
-
-

Returns the one-side worst-cased support of any of the -reconstruction filters. When calling synthesize() to -synthesize a sample at time t, the sample will only be -significantly affected by spectrogram coefficients in the time -range t ± support. Coefficients outside the range may -be used in the synthesis, but substituting zeroes for the actual -coefficient values will not significantly reduce accuracy.

- -

Functions

- -

Iterating Over the Coefficients

- -
-template <class T, class F>
-void gaborator::apply(const gaborator::analyzer<T> &a,
-                      const gaborator::coefs<T> &c,
-                      F f)
-
-

-Apply the function f to each coefficient in the coefficient set c. -

-
-
a
-
The spectrum analyzer that produced the coefficients c
-
c
-
A set of spectrogram coefficients
-
f
-
A function to apply to each coefficient in c, - with the call signature -
void f(std::complex<float> &coef, int band, int64_t t)
-
-
coef
-
A reference to a single complex coefficient. This may be read and - optionally modified in-place.
-
band
-
The band number of the frequency band the coefficient coef pertains to. - This may be either a bandpass band or the lowpass band.
-
t
-
The point in time the coefficient coef pertains to, in samples
-
-
-
-
-template <class T, class F>
-void gaborator::apply(const gaborator::analyzer<T> &a,
-                      const gaborator::coefs<T> &c,
-                      F f,
-                      int64_t t0,
-                      int64_t t1)
-
-

-As above, but only apply the function f to the coefficients -for points in time t that satisfy t0 <= t < t1. -

- -

Forgetting Coefficients

-
-template 
-void gaborator::forget_before(const gaborator::analyzer<T> &a,
-                              gaborator::coefs<T> &c,
-                              int64_t limit)
-
-

Allow the coefficients for points in time before limit -(a time in units of samples) to be forgotten. -Streaming applications can use this to free memory used by coefficients -that are no longer needed. Coefficients that have been forgotten will -read as zero. This does not guarantee that all coefficients before -limit are forgotten, only that ones for -limit or later are not, and that the amount of memory -consumed by any remaining coefficients before limit is -bounded.

- - - diff --git a/gaborator/gaborator-1.2/gaborator/gaborator.h b/gaborator/gaborator-1.2/gaborator/gaborator.h deleted file mode 100644 index 4fcd0be..0000000 --- a/gaborator/gaborator-1.2/gaborator/gaborator.h +++ /dev/null @@ -1,2083 +0,0 @@ -// -// Constant Q spectrum analysis and resynthesis -// -// Copyright (C) 2015-2018 Andreas Gustafsson. This file is part of -// the Gaborator library source distribution. See the file LICENSE at -// the top level of the distribution for license information. -// - -#ifndef _GABORATOR_GABORATOR_H -#define _GABORATOR_GABORATOR_H - -#define __STDC_LIMIT_MACROS - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "gaborator/fft.h" -#include "gaborator/gaussian.h" -#include "gaborator/pod_vector.h" -#include "gaborator/pool.h" -#include "gaborator/ref.h" -#include "gaborator/vector_math.h" - - -namespace gaborator { - -using std::complex; - -// An integer identifying an audio sample -typedef int64_t sample_index_t; - -// An integer identifying a coefficient -typedef int64_t coef_index_t; - -// An integer identifying a slice -typedef int64_t slice_index_t; - -// See https://tauday.com/tau-manifesto -static const double tau = 2.0 * M_PI; - -// Round up to next higher or equal power of 2 - -inline int -next_power_of_two(int x) { - --x; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - return x + 1; -} - -// Determine if x is a power of two. -// Note that this considers 0 to be a power of two. - -static inline bool -is_power_of_two(unsigned int x) { - return (x & (x - 1)) == 0; -} - -// Given a power of two v, determine log2(v) -// https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 - -static inline unsigned int whichp2(unsigned int v) { - assert(is_power_of_two(v)); - unsigned int r = (v & 0xAAAAAAAA) != 0; - r |= ((v & 0xCCCCCCCC) != 0) << 1; - r |= ((v & 0xF0F0F0F0) != 0) << 2; - r |= ((v & 0xFF00FF00) != 0) << 3; - r |= ((v & 0xFFFF0000) != 0) << 4; - return r; -} - -// Floor division: return the integer part of a / b -// rounded down (not towards zero). For positive b only. - -inline int64_t floor_div(int64_t a, int64_t b) { - assert(b > 0); - if (a >= 0) - return a / b; - else - return (a - b + 1) / b; -} - -// Floating point modulus, the remainder r of a / b -// satisfying 0 <= r < b even for negative a. -// For positive b only. - -static inline double -sane_fmod(double a, double b) { - assert(b > 0); - double m = fmod(a, b); - if (m < 0) - m += b; - return m; -} - -// Do an arithmetic left shift of a 64-bit signed integer. This is -// what a << b ought to do, but according to the C++11 draft (n3337), -// section 5.8, that invokes undefined behavior when a is negative. -// GCC is actually smart enough to optimize this into a single shlq -// instruction. -// -// No corresponding kludge is needed for right shifts, because a right -// shift of a negative signed integer is implementation-defined, not -// undefined, and we trust implementations to define it sanely. - -static inline int64_t -shift_left(int64_t a, unsigned int b) { - if (a < 0) - return -(((uint64_t) -a) << b); - else - return (((uint64_t) a) << b); -} - -// Convert between complex types - -template -complex c2c(complex c) { return complex(c.real(), c.imag()); } - -// Convert a sequence of complex values to real - -template -O complex2real(I b, I e, O o) { - while (b != e) { - *o++ = (*b++).real(); - } - return o; -} - -// Test a sequence for being all zero - -template -bool is_zero(I b, I e) { - while (b != e) { - if (*b) - return false; - ++b; - } - return true; -} - -// A vector-like object that allows arbitrary integer indices -// (positive or negative, but excluding the largest possible integer) -// and automatically resizes the storage. Uses storage proportional -// to the difference between the smallest and largest index value (for -// example, if indices range from -102 to -100 (inclusive), memory use -// is on the order of 3 elements). -// -// T is the element type -// I is the integer index type - -template -struct range_vector { - range_vector(): - lower(std::numeric_limits::max()), - upper(std::numeric_limits::min()) - { } -private: - T *unchecked_get(I i) { - return &v[i & ((I)v.size() - 1)]; - } - const T *unchecked_get(I i) const { - return &v[i & ((I)v.size() - 1)]; - } - -public: - // Note: Pointer returned becomes invalid when range_vector - // is changed - T * - get(I i, bool create) { - if (! has_index(i)) { - if (create) - extend(i); - else - return 0; - } - return unchecked_get(i); - } - - // Get a pointer to an existing element, or null if out of range - const T * - get(I i) const { - if (! has_index(i)) - return 0; - return unchecked_get(i); - } - - // Note: Reference returned becomes invalid when range_vector - // is changed - T & - get_or_create(I i) { - return *get(i, true); - } - - // Get a reference to the element at index i, which must be valid - T & - get_existing(I i) { - assert(has_index(i)); - return *unchecked_get(i); - } - - // Const version of the above - const T & - get_existing(I i) const { - assert(has_index(i)); - return *unchecked_get(i); - } - -private: - void extend(I i) { - I new_lower = lower; - I new_upper = upper; - if (i < lower) - new_lower = i; - if (i + 1 > upper) - new_upper = i + 1; - I old_size = v.size(); - I new_need = new_upper - new_lower; - if (new_need > old_size) { - if (old_size == 0) { - v.resize(1); - } else { - I new_size = old_size; - while (new_size < new_need) - new_size *= 2; - v.resize(new_size); - if (old_size) { - for (I j = lower; j < upper; j++) { - I jo = j & (old_size - 1); - I jn = j & (new_size - 1); - if (jo != jn) - std::swap(v[jo], v[jn]); - } - } - } - } - lower = new_lower; - upper = new_upper; - } - -public: - // Erase the elements whose index is less than "limit" - void erase_before(I limit) { - I i = lower; - for (;i < upper && i < limit; i++) - *unchecked_get(i) = T(); - lower = i; - } - - I begin_index() const { return lower; } - I end_index() const { return upper; } - bool empty() const { return lower >= upper; } - bool has_index(ssize_t i) const { return i >= lower && i < upper; } - -private: - std::vector v; - I lower, upper; -}; - -// Calculate the size of the alias-free part (the "filet") -// of a signal slice of size "fftsize" - -static inline unsigned int filet_part(unsigned int fftsize) { - return fftsize >> 1; -} - -// Calculate the size of the padding (the "fat") at each -// end of a signal slice of size "fftsize" - -static inline unsigned fat_part(unsigned int fftsize) { - return fftsize >> 2; -} - -// Frequency band parameters shared between octaves - -template -struct band_params: public refcounted { - typedef complex C; - bool dc; // True iff this is the DC band - unsigned int sftsize; // Size of "short FFT" spanning the band - unsigned int sftsize_log2; // log2(sftsize) - fft *sft; // Fourier transform for windows, of size sftsize - std::vector kernel; // Frequency-domain filter kernel - std::vector dual_kernel; // Dual of the above - pod_vector shift_kernel; // Complex exponential for fractional frequency compensation - pod_vector shift_kernel_conj; // Conjugate of the above - int fq_offset_int; // Frequency offset in bins (big-fft bin of left window edge) - double ff; // Center (bp) or corner (lp) frequency in units of the sampling frequency - double center; // Center frequency in units of FFT bins - int icenter; // Center frequency rounded to nearest integer FFT bin - double ffsd; // Standard deviation of the bandpass Gaussian, as fractional frequency - float anl_support; - float syn_support; -}; - -// Downsampling parameters. These have some similarity to band -// parameters, but only some. For example, these may use a real -// rather than complex FFT for the "short FFT". - -template -struct downsampling_params { - typedef complex C; - unsigned int sftsize; - std::vector kernel; // Frequency-domain filter kernel - std::vector dual_kernel; -#if GABORATOR_USE_REAL_FFT - rfft *rsft; -#else - fft *sft; -#endif - double time_support; // Filter time domain support, each side -}; - -// Forward declarations -template struct analyzer; -template struct zone; -template > struct coefs; -template struct sliced_coefs; -template > struct rw_row_view; - -// Abstract class for tracking changes to a coefficient set. -// This may be used for updating a resolution pyramid of -// magnitude data. - -template -struct shadow { - virtual ~shadow() { } - virtual void update(const coefs > &msc, sample_index_t i0, sample_index_t i1) = 0; - // Deprecated - virtual void update(int oct, int ze, const sliced_coefs > &sc, slice_index_t sli0, slice_index_t sli1) = 0; -}; - -// Coefficient metadata. This contains information describing a -// "coefs" structure that is common to many instances and should not -// be duplicated in each one. In practice, there will be two -// coefs_meta structures per zone per analyzer, one for unpadded coefs -// and one for padded coefs. - -struct coefs_meta { - typedef std::vector shape_vector; - void init(const shape_vector &shape) { - band_offsets.resize(shape.size()); - unsigned int offset = 0; - for (unsigned int i = 0; i < shape.size(); i++) { - band_offsets[i] = offset; - offset += shape[i]; - } - total_size = offset; - } - // Offset of the beginning of each band in the data array - std::vector band_offsets; - // Total size of data array (in elements, not bytes) - unsigned int total_size; -}; - -// Coefficients of a single octave for a single input signal slice. -// These are used both as part of the final sliced coefficients -// and for temporary padded coefficients during analysis/synthesis. -// C is the coefficient type, typically complex but can also -// be e.g. unsigned int to store cluster numbers, or float to store -// magnitudes. - -template -struct oct_coefs: public refcounted { - oct_coefs(const coefs_meta &meta_, bool clear_ = true): - meta(meta_), - data(meta.total_size), - bands(*this) - { - if (clear_) - clear(); - } - uint64_t estimate_memory_usage() const { - return meta.total_size * sizeof(C) + sizeof(*this); - } - void clear() { - memset(data.data(), 0, data.size() * sizeof(C)); - } - // Deep copy - oct_coefs &operator=(const oct_coefs &rhs) { - assert(data.size() == rhs.data.size()); - memcpy(data.data(), rhs.data.data(), data.size() * sizeof(C)); - return *this; - } - - const coefs_meta &meta; - - // The data for all the bands are allocated together - // as a single vector to reduce the number of allocations - pod_vector data; - // Vector-like collection of pointers into "data", one for each band - struct band_array { - band_array(oct_coefs &outer_): outer(outer_) { } - C *operator[](size_t i) const { - return outer.data.data() + outer.meta.band_offsets[i]; - } - size_t size() const { return outer.meta.band_offsets.size(); } - oct_coefs &outer; - } bands; -private: - oct_coefs(const oct_coefs &); -}; - - -// Add the oct_coefs "b" to the oct_coefs "a" - -template -void add(zone &z, oct_coefs &a, const oct_coefs &b) { - unsigned int n_bands = a.bands.size(); - assert(n_bands == b.bands.size()); - for (unsigned int obno = 0; obno < n_bands; obno++) { - unsigned int len = filet_part(z.bandparams[obno]->sftsize); - complex *band_a = a.bands[obno]; - complex *band_b = b.bands[obno]; - for (unsigned int j = 0; j < len; j++) { - band_a[j] += band_b[j]; - } - } -} - -// Temporary coefficients used during analysis/synthesis. -// These include padding (aka fat). - -template -struct padded_coefs: public oct_coefs { - padded_coefs(const coefs_meta &meta_, bool clear_ = true): - oct_coefs(meta_, clear_) { } -}; - -// Sliced coefficients. These cover an arbitrary time range, but only -// a single octave. Template argument is as for struct oct_coefs. - -template -struct sliced_coefs { - typedef range_vector >, slice_index_t> slices_t; - slices_t slices; - uint64_t estimate_memory_usage() const { - unsigned int n = 0; - size_t size_each = 0; - for (slice_index_t sl = slices.begin_index(); sl < slices.end_index(); sl++) { - const ref > &t = slices.get_existing(sl); - if (t) { - if (! size_each) - size_each = t->estimate_memory_usage(); - n++; - } - } - return n * size_each; - } -}; - -// Multirate sliced coefficients. These cover an arbitrary time -// range and the full frequency space (all octaves). -// Template arguments are as for struct coef. -// Note default for template argument C defined in forward declaration. - -template -struct coefs { - coefs(const analyzer &anl_, shadow *shadow_ = 0): - octaves(anl_.n_octaves), shadow0(shadow_) - { } - uint64_t estimate_memory_usage() const { - uint64_t s = 0; - for (unsigned int oct = 0; oct < octaves.size(); oct++) - s += octaves[oct].estimate_memory_usage(); - return s; - } - std::vector > octaves; - shadow *shadow0; -}; - -// Perform an fftshift of the range between iterators a and b. -// Not optimized - not for use in inner loops. - -template -void fftshift(I b, I e) { - int len = e - b; - assert(len % 2 == 0); - for (int i = 0; i < len / 2; i++) - std::swap(*(b + i), *(b + len / 2 + i)); -} - -// Given an unsigned index i into an FFT of a power-of-two size -// "size", return the corresponding signed index, ranging from -size/2 -// to size/2-1. This is equivalent to sign extension of an integer of -// log2(size) bits; see Hacker's Delight, page 18. This can be used -// to convert an FFT index into a frequency (positive or negative; -// note that Nyquist is considered negatitive = -fs/2). - -static inline int signed_index(unsigned int i, unsigned int size) { - unsigned int t = size >> 1; - return (i ^ t) - t; -} - -// Construct a frequency-domain lowpass filter whose response is the -// convolution of a rectangle and a gaussian. The cutoff freqency is -// ff_cutoff (a fractional frequency), and the standard deviation of -// the gaussian is ff_sd. The returned filter covers the full -// frequency range from 0 to fs (with negative frequencies at the end, -// the usual convention for FFT spectra). -// -// When center=true, construct a time-domain window instead, -// passing the center of the time-domain signal. -// -// The result is stored between iterators b and e, which must have a -// real value_type. - -template -inline void gaussian_windowed_lowpass(double ff_cutoff, double ff_sd, - I b, I e, bool center = false) -{ - size_t len = e - b; - double inv_len = 1.0 / len; - for (I it = b; it != e; ++it) { - size_t i = it - b; - double thisff; - if (center) - // Symmetric around center - thisff = std::abs(i - (len * 0.5)) * inv_len; - else - // Symmetric around zero - thisff = (i > len / 2 ? len - i : i) * inv_len; - double x = thisff - ff_cutoff; - double v = gaussian_edge(ff_sd, -x); - *it = v; - } -} - -// A set of octaves having identical parameters form a "zone", -// and their shared parameters are stored in a "struct zone". - -template -struct zone: public refcounted { - zone(): n_bands(0) { } - ~zone() { } - unsigned int n_bands; // Total number of bands, including DC band if lowest octave - // Band parameters by increasing frequency; DC band is index 0 if present - std::vector > > bandparams; - // Pseudo-bands mimicing the response of bands in the - // neighboring octaves, used for calculating the duals only - std::vector > > mock_bandparams; - pod_vector power; - pod_vector power_nodc; - coefs_meta cmeta[2]; // Unpadded and padded -}; - -template -struct octave { - zone *z; - unsigned int n_bands_above; // Total number of bands in higher octaves -}; - - -// Helper function for pushing parameters onto the vectors in struct zone - -template -void push(std::vector > > &v, band_params *p) { - v.push_back(ref >(p)); -} - -// A set of spectum analysis parameters - -struct parameters { - parameters(unsigned int bands_per_octave_, double ff_min_, - double ff_ref_ = 1.0, - double overlap_ = 0.7, - double max_error_ = 1e-5): - bands_per_octave(bands_per_octave_), - ff_min(ff_min_), - ff_ref(ff_ref_), - overlap(overlap_), - max_error(max_error_) - { } - // Provide an operator< so that we can create a set or map of parameters - bool operator<(const parameters &b) const { -#define GABORATOR_COMPARE_LESS(member) do { \ - if (member < b.member) \ - return true; \ - if (member > b.member) \ - return false; \ - } while(0) - GABORATOR_COMPARE_LESS(bands_per_octave); - GABORATOR_COMPARE_LESS(ff_min); - GABORATOR_COMPARE_LESS(ff_ref); - GABORATOR_COMPARE_LESS(overlap); - GABORATOR_COMPARE_LESS(max_error); -#undef GABORATOR_COMPARE_LESS - // Equal - return false; - } - bool operator==(const parameters &b) const { - return !((*this < b) || (b < *this)); - } - template friend class analyzer; - unsigned int bands_per_octave; - double ff_min; - double ff_ref; - double overlap; - double max_error; -}; - -// Index of first slice affected by sample at t0 - -// fft number i covers the sample range -// t = (i * filetsize .. i * filetsize + (fftsize - 1)) -// t >= i * filetsize and t < i * filetsize + fftsize -// A sample at t affects ffts i where -// i <= t / filetsize and -// i > (t - fftsize) / filetsize -// the filet of fft number i covers the sample range -// (fat + (i * filetsize) .. fat + (i * filetsize) + (filetsize - 1)) - -static inline slice_index_t affected_slice_0(sample_index_t t0, unsigned int fftsize) { - return floor_div(t0 - fftsize, filet_part(fftsize)) + 1; -} - -// Index of first slice not affected by samples before t1 - -static inline slice_index_t affected_slice_1(sample_index_t t1, unsigned int fftsize) { - return floor_div(t1 - 1, filet_part(fftsize)) + 1; -} - - -// Multiply a vector by a scalar, in-place. -// Used only at the setup stage, so performance is not critical. - -template -void scale_vector(V &v, S s) { - for (size_t i = 0; i < v.size(); i++) - v[i] *= s; -} - -// Fill the buffer at dst, of length dstlen, with data from src where -// available, otherwise with zeroes. The data in src covers dst indices -// from i0 (inclusive) to i1 (exclusive). - -template -void copy_overlapping_zerofill(T *dst, unsigned int dstlen, const T *src, - int64_t src_i0, int64_t src_i1) -{ - int64_t overlap_begin = std::max((int64_t) 0, src_i0); - int64_t overlap_end = std::min((int64_t)dstlen, src_i1); - if (overlap_end <= overlap_begin) { - // No overlap - std::fill(dst, dst + dstlen, 0); - } else { - // Overlap - if (overlap_begin != 0) - std::fill(dst, dst + overlap_begin, 0); - std::copy(src + overlap_begin - src_i0, src + overlap_end - src_i0, dst + overlap_begin); - if (overlap_end != dstlen) - std::fill(dst + overlap_end, dst + dstlen, 0); - } -} - -// Given a set of FFT coefficients "coefs" of a real -// sequence, where only positive-frequency coefficients -// (including DC and Nyquist) are valid, return the -// coefficient for an arbitrary frequency index "i" -// which may correspond to a negative frequency, or -// even an alias outside the range (0..fftsize-1). - -template -complex get_real_spectrum_coef(complex *coefs, int i, unsigned int fftsize) { - i &= fftsize - 1; - // Note that this is >, not >=, becase fs/2 is considered nonnegative - bool neg_fq = (i > (int)(fftsize >> 1)); - if (neg_fq) { - i = fftsize - i; - } - complex c = coefs[i]; - if (neg_fq) { - c = conj(c); - } - return c; -} - -template -struct analyzer: public refcounted { - typedef complex C; - analyzer(const parameters ¶ms_): - params(params_) - { - // Sanity check - assert(params.ff_min < 0.5); - - // The frequency increases by this factor from one band - // to the next - band_spacing_log2 = 1.0 / params.bands_per_octave; - band_spacing = exp2(band_spacing_log2); - - // The tuning adjustment, as a log2ff. This is a number between - // 0 and band_spacing_log2, corresponding to a frequency at - // or slightly above the sampling frequency where a band - // center would fall if they actually went that high. - // Tuning is done by increasing the center frequencies of - // all bands by this amount relative to the untuned case - // where one band would fall on fs exactly. - tuning_log2ff = sane_fmod(log2(params.ff_ref), band_spacing_log2); - - // Calculate the frequency of band0, the "fs/8 band", the - // lowest band in each octave except possibly the lowest octave. - // Its frequency will fs/8, or a fraction of the band spacing higher - // due to tuning. The magic -3 derives from fs/8 as log2(1/8). - band0_log2ff = -3 + tuning_log2ff; - - // Calculate the total number of bands needed so that - // the lowest band has a frequency <= params.ff_min. - // end_log2ff = the log2ff of the band after the last (just past fs/2) - double end_log2ff = tuning_log2ff - 1; - n_bandpass_bands_total = - (unsigned int)ceil((end_log2ff - log2(params.ff_min)) / band_spacing_log2); - - // Calculate the kernel support needed for band0, - // and size the FFT accordingly. This duplicates some - // code at the beginning of make_band(). - double band0_ff = exp2(band0_log2ff); - double band0_time_sd = time_sd(band0_ff); - - double band0_time_support = gaussian_support(band0_time_sd, params.max_error); - double band0_time_synthesis_support = band0_time_support * synthesis_support_multiplier(); - - fftsize_log2 = 3; - fftsize = 1 << fftsize_log2; - while (band0_time_synthesis_support > fat_part(fftsize)) { - fftsize_log2++; - fftsize <<= 1; - } - init_with_fftsize(); - } - - void init_with_fftsize() { - // Clear everything, including things that may have been set - // on a previous try. - sftsize_max = 0; - octaves.clear(); - zones.clear(); - - inv_fftsize_double = 1.0 / fftsize; - inv_fftsize_t = (T) inv_fftsize_double; - -#if GABORATOR_USE_REAL_FFT - rft = pool, int>::shared.get(fftsize); -#else - ft = pool, int>::shared.get(fftsize); -#endif - - // Band number starting at 0 close to fs/2 and increasing - // with decreasing frequency - int tbno = 0; - // Band number of the fs/8 band in the current octave - int base = 0; - int zno = 0; - // Loop over the octaves, from high to low frequencies, - // creating new zones where needed - for (;;) { - int max_bands_this_octave = (zno == 0) ? - params.bands_per_octave * 2 : params.bands_per_octave; - base += max_bands_this_octave; - int bands_remaining = n_bandpass_bands_total - tbno; - int bands_this_octave = std::min(max_bands_this_octave, bands_remaining); - int bands_below = bands_remaining - bands_this_octave; - bool dc_zone = (bands_below == 0); - bool dc_adjacent_zone = (bands_below < (int)params.bands_per_octave); - if (zno < 2 || dc_zone || dc_adjacent_zone) { - make_zone(zno, base - (tbno + bands_this_octave), base - tbno, dc_zone, bands_below); - zno++; - } - octaves.push_back(octave()); - octaves.back().z = zones[zno - 1].get(); - octaves.back().n_bands_above = tbno; - tbno += bands_this_octave; - if (dc_zone) - break; - } - n_octaves = octaves.size(); - - // Verify the total number of bands - n_bands_total = 0; - for (unsigned int i = 0; i < n_octaves; i++) { - n_bands_total += octaves[i].z->n_bands; - } - assert(n_bandpass_bands_total + 1 == n_bands_total); - - // Set up the downsampling parameters in dsparams. - - // Downsampling is always by a factor of two. - // dsparams.sftsize is the size of the FFT used to go back to - // the time domain after discarding the top half of the - // spectrum. - dsparams.sftsize = fftsize >> 1; - dsparams.kernel.resize(dsparams.sftsize); - dsparams.dual_kernel.resize(dsparams.sftsize); - - // In terms of the post-downsampling sampling frequency, - // the downsampling lowpass filter transition band - // starts at the top edge of the highest band, - // and ranges to fs/2. We use the worst-case center - // frequency for the highest band, fs/4, and add - // the support. - double f0 = 0.25 + ff_sd(0.25); - double f1 = 0.5; - - // The cutoff frequency is in the center of the transition band - double ff = (f0 + f1) * 0.5; - double support = (f1 - f0) * 0.5; - double ff_sd = gaussian_support_inv(support, params.max_error); - // Fudge: make the lowpass a bit steeper (and correspondingly - // wider in the time dimension). This seems to improve the S/N - // a bit. - ff_sd *= 0.7813026596806952; - - // Calculate and save the time-domain support of the - // downsampling lowpass filter for use in analyze_sliced(). - double time_sd = sd_f2t(ff_sd); - dsparams.time_support = gaussian_support(time_sd, params.max_error); - - // Use the convolution of a rectagle and a gaussian. - // A piecewise function composed from two half-gaussians - // joined by a horizontal y=1 segment is not quite smooth - // enough. - gaussian_windowed_lowpass(ff, ff_sd, dsparams.kernel.begin(), dsparams.kernel.end()); - // Put the passband in the middle - fftshift(dsparams.kernel.begin(), dsparams.kernel.end()); - // The dual_kernel field of the downsampling pseudo-band holds - // the upsampling filter, identical to the downsampling filter - // except for amplitude scaling. - std::copy(dsparams.kernel.begin(), dsparams.kernel.end(), - dsparams.dual_kernel.begin()); - // Prescale the downsampling filter - scale_vector(dsparams.kernel, inv_fftsize_double); - // Prescale the upsampling filter - scale_vector(dsparams.dual_kernel, 1.0 / dsparams.sftsize); -#if GABORATOR_USE_REAL_FFT - dsparams.rsft = pool, int>::shared.get(dsparams.sftsize); -#else - dsparams.sft = pool, int>::shared.get(dsparams.sftsize); -#endif - top_band_log2ff = band0_log2ff + band_spacing_log2 * (2 * params.bands_per_octave - 1); - - ffref_gbno = (int)rint((top_band_log2ff - log2(params.ff_ref)) / band_spacing_log2); - } - - void make_zone(unsigned int zno, int bb, int be, bool dc_zone, int bands_below) { - assert(zones.size() == zno); - zone *z = new zone(); - zones.push_back(ref >(z)); - - // The maximum number of mock bands to insert between the DC - // band and the real bands. - int n_mock_bands = 6; - - if (dc_zone) { - // This zone goes all the way to DC - band_params *dc_band = make_band(bb - 1, true); - push(z->bandparams, dc_band); - } else { - // There are other zones below this; add mock bands - // to simulate them for purposes of calculating the dual - if (n_mock_bands > bands_below) - n_mock_bands = bands_below; - // Mock DC band - push(z->mock_bandparams, - make_band(bb - 1 - n_mock_bands, true)); - // Mock bandpass bands - for (int i = bb - 1; i > bb - 1 - n_mock_bands; i--) - push(z->mock_bandparams, make_band(i, false)); - } - - // The actual bandpass bands of this zone - for (int i = bb; i < be; i++) - push(z->bandparams, make_band(i, false)); - - // If there are other zones above this, add mock bands - // to simulate them for purposes of calculating the dual - for (int i = be; i < (int)params.bands_per_octave * 2; i++) - push(z->mock_bandparams, make_band(i, false)); - - z->n_bands = z->bandparams.size(); - - // Accumulate window power for calculating dual - - z->power.resize(fftsize); - std::fill(z->power.data(), z->power.data() + fftsize, 0); - for (unsigned int obno = 0; obno < z->bandparams.size(); obno++) - accumulate_power(z->bandparams[obno].get(), z->power.data()); - for (unsigned int obno = 0; obno < z->mock_bandparams.size(); obno++) - accumulate_power(z->mock_bandparams[obno].get(), z->power.data()); - - // Calculate complex exponentials for non-integer center frequency adjustment - for (unsigned int obno = 0; obno < z->bandparams.size(); obno++) { - band_params *bp = z->bandparams[obno].get(); - for (unsigned int i = 0; i < bp->sftsize; i++) { - unsigned int ii = i; - double arg = tau * ((double)ii / bp->sftsize) * -(bp->center - bp->icenter); - C t(cos(arg), sin(arg)); - bp->shift_kernel[i] = t; - bp->shift_kernel_conj[i] = conj(t); - } - } - - // Calculate duals - for (unsigned int obno = 0; obno < z->bandparams.size(); obno++) { - band_params *bp = z->bandparams[obno].get(); - for (unsigned int i = 0; i < bp->sftsize; i++) { - // ii = large-FFT bin number - int ii = i + bp->fq_offset_int; - bp->dual_kernel[i] = bp->kernel[i] / z->power[ii & (fftsize - 1)]; - } - } - - // Initialize coefficient metadata for unpadded and padded coefficients - for (int padded = 0; padded < 2; padded++) { - typename coefs_meta::shape_vector shape(z->bandparams.size()); - for (unsigned int i = 0; i < z->bandparams.size(); i++) { - unsigned int len = z->bandparams[i]->sftsize; - if (! padded) - len >>= 1; - shape[i] = len; - } - z->cmeta[padded].init(shape); - } - - // Free the mock band parameters - z->mock_bandparams.clear(); - z->power.clear(); - } - - // Add the power of the kernel in "*bp" to "power" - void - accumulate_power(band_params *bp, T *power) { - for (unsigned int i = 0; i < bp->sftsize; i++) { - // ii = large-FFT bin number - unsigned int ii = (i + bp->fq_offset_int) & (fftsize - 1); - assert(ii >= 0 && ii < fftsize); - T y = bp->kernel[i]; - T p = y * y; - power[ii] += p; - if (! bp->dc) { - unsigned int ni = fftsize - ii; - ni &= fftsize - 1; // XXX is this correct? - assert(ni < fftsize); - power[ni] += p; - } - } - } - - // Given a fractional frequency, return the standard deviation - // of the frequency-domain window as a fractional frequency - double ff_sd(double ff) const { return params.overlap * (band_spacing - 1) * ff; } - - // Given a fractional frequency, return the standard deviation - // of the time-domain window in samples. - // - // ff_sd = 1.0 / (tau * t_sd) - // per http://users.ece.gatech.edu/mrichard/Gaussian%20FT%20and%20random%20process.pdf - // and python test program gaussian-overlap.py - // => (tau * t_sd) * ff_sd = 1.0 - // => t_sd = 1.0 / (tau * f_sd) - double time_sd(double ff) const { return 1.0 / (tau * ff_sd(ff)); } - - // Defining Q as the frequency divided by the half-power bandwidth, - // we get - // - // norm_gaussian(sd, hbw) = sqrt(2) - // - // (%i1) e1: exp(-(hbw * hbw) / (2 * sd * sd)) = 1 / sqrt(2); - // (%i2) solve(e1, hbw); - // (%o2) [hbw = - sqrt(log(2)) sd, hbw = sqrt(log(2)) sd] - // - // Q = ff / (2 * sqrt(log(2)) * ff_sd(ff)) - // = 1.0 / ((2 * sqrt(log(2)) * (params.overlap * band_spacing - 1))) - double q() const { - return 1.0 / ((2 * sqrt(log(2)) * params.overlap * (band_spacing - 1))); - } - - // Find the worst-case time support of the analysis filters, i.e., - // the largest distance in time between a signal sample and a - // coefficient affected by that sample. - double analysis_support() const { - int gbno = n_bands_total - 2; // Last before DC - int oct; - unsigned int obno; - bool valid = bno_split(gbno, oct, obno, false); - assert(valid); - double ff = band_ff(oct, obno); - double timesd = time_sd(ff); - double time_support = gaussian_support(timesd, params.max_error); - return time_support; - } - - // Ditto for the resynthesis filters. - double synthesis_support() const { - return analysis_support() * synthesis_support_multiplier(); - } - - // Empirical formula for synthesis support multiplier - double synthesis_support_multiplier() const { - return 1.8 / params.overlap; - } - - // Return the fractional frequency of relative band number - // "rbno" (relative to the sampling frequency of its octave). - double rbno_ff(double rbno) const { - double log2ff = band0_log2ff + rbno * band_spacing_log2; - return exp2(log2ff); - } - - // Return the fractional frequency of band number "obno" - // in octave "oct", scaling according to the octave - double band_ff(int oct, int obno) const { - int gbno = bno_merge(oct, obno); - return exp2(tuning_log2ff - 1 - (gbno + 1) * band_spacing_log2); - } - - // Return the coefficient index (the time in terms of coefficient - // subsamples) of the first cofficient of slice "sli" of band - // "obno" in octave "oct" - coef_index_t coef_time(slice_index_t sli, int oct, int obno) const { - int sftsize = octaves[oct].z->bandparams[obno]->sftsize; - return fat_part(sftsize) + sli * filet_part(sftsize); - } - - // Return the sample index (the time in terms of samples) time of - // coefficient "i" in slice "sli" of band "obno" in octave "oct" - sample_index_t sample_time(slice_index_t sli, int i, int oct, int obno) const { - coef_index_t sst = coef_time(sli, oct, obno) + i; - return shift_left(sst, band_scale_exp(oct, obno)); - } - - // Calculate band parameters for a single band. - // - // rbno is a "relative band number" indicating a frequency - // within its octave: it is 0 for the fs/8 band and increases - // with frequency. It is not always the same as the index - // into its octaves' bandparams[] or the coefficient bands - // (those would be called obno, not rbno). - // - // If dc is true, this is the DC band, and rbno indicates - // the cutoff frequency; it is one less than the rbno of - // the lowest-frequency bandpass band. - - band_params * - make_band(double rbno, bool dc) { - if (dc) - // Make the actual DC band cutoff frequency a bit higher, - // by an empirically chosen fraction of a band, to reduce - // power fluctuations. - rbno += 0.8750526596806952; - - // For bandpass bands, the center frequency, or - // for the DC band, the lowpass cutoff frequency, - // as a fractional frequency. - double ff = rbno_ff(rbno); - - // Standard deviation of the bandpass Gaussian, - // as a fractional frequency - double ffsd = ff_sd(ff); - // The support of the Gaussian, i.e., the smallest standard - // deviation at which it can be truncated on each side - // without the error exceeding our part of the error budget, - // which is some fraction of params.max. Note - // that this is one-sided; the full width of the support - // is 2 * ff_support. - double ff_support = gaussian_support(ffsd, params.max_error * 0.5); - // Additional support for the flat portion of the DC band lowpass - double dc_support = dc ? ff : 0; - // The support as the number of FFT frequency bands needed, - // allowing for both sides of the Gaussian. - int fq_2support = int(ceil((ff_support + dc_support) * 2 * fftsize)); - - band_params *bp = new band_params; - bp->dc = dc; - bp->sftsize = next_power_of_two(fq_2support); - bp->sftsize_log2 = whichp2(bp->sftsize); - sftsize_max = std::max(sftsize_max, bp->sftsize); - bp->sft = pool, int>::shared.get(bp->sftsize); - - bp->kernel.resize(bp->sftsize); - bp->dual_kernel.resize(bp->sftsize); - bp->shift_kernel.resize(bp->sftsize); - bp->shift_kernel_conj.resize(bp->sftsize); - - if (dc) - bp->center = 0; - else - bp->center = ff * fftsize; - bp->icenter = (int)rint(bp->center); - bp->ff = ff; - bp->fq_offset_int = bp->icenter - (bp->sftsize >> 1); - bp->ffsd = ffsd; - - // Calculate frequency-domain window kernel - - if (dc) { - // The cutoff frequency is a fraction of the - // fftsize, but gaussian_windowed_lowpass() - // designs the filter in terms of the the - // sftsize, so we need to scale the frequencies - // accordingly. - double scale = fftsize / bp->sftsize; - gaussian_windowed_lowpass( - ff * scale, - ffsd * scale, - bp->kernel.begin(), bp->kernel.end()); - fftshift(bp->kernel.begin(), bp->kernel.end()); - } else { - for (unsigned int i = 0; i < bp->sftsize; i++) { - // ii = large-FFT band number - unsigned int ii = i + bp->fq_offset_int; - // this_ff = fractional frequency of this kernel sample - double this_ff = ii * inv_fftsize_double; - T y = norm_gaussian(ffsd, this_ff - ff); - bp->kernel[i] = y; - } - } - - return bp; - } - - // Index of first slice affected by sample at t0 - slice_index_t affected_slice_b(sample_index_t t0) const { - return affected_slice_0(t0, fftsize); - } - - // Index of first slice not affected by sample at t1 - slice_index_t affected_slice_e(sample_index_t t1) const { - return affected_slice_1(t1, fftsize); - } - - // Sample index of the first sample in the filet of slice si - sample_index_t slice_filet_begin(slice_index_t si) const { - // XXX optimize using shift - return si * (sample_index_t)filet_part(fftsize) + fat_part(fftsize); - } - - // Analyze a single slice of signal. - // The signal in "real_signal", which is fftsize samples long. - // The sample time of the first sample is "t0". - // Returns a set of spectrogram coefficients through "c1", - // and the decimated-by-2 signal through *downsampled_dst. - // Uses temporary buffers passed through buf0...buf3. - - void - analyze_one_slice(int oct, T *real_signal, sample_index_t t0, - oct_coefs &c1, - T *downsampled_dst, - pod_vector &buf0, // fftsize - pod_vector &buf1, // fftsize - pod_vector &buf2, // largest sftsize - pod_vector &buf3 // largest sftsize - ) const - { - zone &z = *octaves[oct].z; - assert(c1.bands.size() == z.n_bands); - - pod_vector &spectrum(buf1); -#if GABORATOR_USE_REAL_FFT - rft->transform(real_signal, spectrum.data()); -#else - // Real to complex - pod_vector &signal(buf0); - std::copy(real_signal, real_signal + fftsize, signal.begin()); - ft->transform(signal.data(), spectrum.data()); -#endif - pod_vector &tmp(buf2); - - T scale_factor = inv_fftsize_t; - - for (unsigned int obno = 0; obno < z.bandparams.size(); obno++) { - band_params *bp = z.bandparams[obno].get(); - C *sdata = tmp.data(); - - // Multiply a slice of the spectrum by the frequency- - // domain window and store in sdata. The band center - // frequency is at the center of the spectrum slice and - // the center of the window, but in sdata, it needs to be - // at the beginning to be ready for the inverse FFT. - // Therefore, we need to perform an ifftshift. Also, - // we need to take care not to overrun the beginning or - // end of the spectrum - for the dc band, we always - // need to wrap around to negative frequencies, and - // potentially it could happen with other bands, too - // if they are really wide. To avoid the overhead of - // checking in the inner loop, use a separate slow path - // for the rare cases where wrapping happens. - - size_t half_size = bp->sftsize >> 1; - int start_index = bp->fq_offset_int; - int end_index = bp->fq_offset_int + bp->sftsize; - if (start_index >= 0 && end_index < (int)((fftsize >> 1) + 1)) { - // Fast path: the slice lies entirely within the - // positive-frequency half of the spectrum (including - // DC and Nyquist). We still need to handle the - // positive and negative frequency halves of the slice - // (as opposed to the whole spectrum) separately. - // Positive frequencies - elementwise_product(sdata, spectrum.data() + start_index + half_size, - bp->kernel.data() + half_size, half_size); - // Negative frequencies - elementwise_product(sdata + half_size, spectrum.data() + start_index, - bp->kernel.data(), half_size); - } else { - // Slow path - // Positive frequencies - for (size_t i = 0; i < half_size; i++) - sdata[i] = get_real_spectrum_coef(spectrum.data(), - start_index + half_size + i, fftsize) * bp->kernel[half_size + i]; - // Negative frequencies - for (size_t i = 0; i < half_size; i++) - sdata[half_size + i] = get_real_spectrum_coef(spectrum.data(), - start_index + i, fftsize) * bp->kernel[i]; - } - - // Switch to time domain - C *band = buf3.data(); - bp->sft->itransform(sdata, band); - - // Extract filet, adjust for non-integer center frequency, - // correct phase, scale amplitude, and write to the output - // coefficients. - double ff = bp->center * inv_fftsize_double; - double arg = -tau * t0 * ff; - C phase_times_scale = C(cos(arg), sin(arg)) * scale_factor; - elementwise_product_times_scalar(c1.bands[obno], band + fat_part(bp->sftsize), - bp->shift_kernel.data() + fat_part(bp->sftsize), - phase_times_scale, filet_part(bp->sftsize)); - } - - // Downsample - if (oct + 1 < (int) n_octaves) { - pod_vector &sdata(buf2); - // This is using a larger buffer than we actually need - pod_vector &ddata(buf0); - assert(ddata.size() >= dsparams.sftsize); - // Extract the low-frequency part of "spectrum" into "sdata" - // and multiply it by the lowpass filter frequency response. - // This means both positive and negative low frequencies. - size_t half_size = dsparams.sftsize >> 1; - assert(fftsize - half_size == 3 * half_size); -#if GABORATOR_USE_REAL_FFT - // Positive frequencies - elementwise_product(sdata.data(), spectrum.data(), - dsparams.kernel.data() + half_size, half_size); - // Nyquist - sdata[half_size] = 0; - // Use the same buffer as the complex FFT, but as floats - T *real_ddata = reinterpret_cast(ddata.data()); - dsparams.rsft->itransform(sdata.data(), real_ddata); - // Beginning and end of filet part - T *b = real_ddata + fat_part(dsparams.sftsize); - T *e = b + filet_part(dsparams.sftsize); - std::copy(b, e, downsampled_dst); -#else - // Positive frequencies - elementwise_product(sdata.data(), spectrum.data(), - dsparams.kernel.data() + half_size, half_size); - // Negative requencies - elementwise_product(sdata.data() + half_size, spectrum.data() + fftsize - half_size, - dsparams.kernel.data(), half_size); - dsparams.sft->itransform(sdata.data(), ddata.data()); - // Beginning and end of filet part - C *b = ddata.data() + fat_part(dsparams.sftsize); - C *e = b + filet_part(dsparams.sftsize); - complex2real(b, e, downsampled_dst); -#endif - } - } - - void - synthesize_one_slice(int oct, const padded_coefs &c, - const pod_vector &downsampled, - sample_index_t t0, - T *signal_out, - pod_vector &buf0, // fftsize - pod_vector &buf2 // largest sftsize - ) const - { - zone &z = *octaves[oct].z; - pod_vector &signal(buf0); - std::fill(signal.begin(), signal.end(), 0); - - for (unsigned int obno = 0; obno < z.bandparams.size(); obno++) { - band_params *bp = z.bandparams[obno].get(); - - C *indata = c.bands[obno]; - pod_vector &sdata(buf2); - - T scale_factor = (T)1 / bp->sftsize; - - // Apply phase correction, adjust for non-integer center frequency, - // and apply scale factor. Note that phase must be calculated in double - // precision. - double ff = bp->center * inv_fftsize_double; - double arg = tau * t0 * ff; - C phase_times_scale = C(cos(arg), sin(arg)) * scale_factor; - elementwise_product_times_scalar(sdata.data(), indata, bp->shift_kernel_conj.data(), - phase_times_scale, bp->sftsize); - - // Switch to frequency domain - bp->sft->transform(sdata.data()); - - // Multiply signal spectrum by frequency-domain dual window, - // accumulating result in signal. - - for (unsigned int i = 0; i < bp->sftsize; i++) { - int iii = (bp->fq_offset_int + i) & (fftsize - 1); - // Note the ifftshift of the input index, as f=0 appears in the middle - // of the window - C v = sdata[i ^ (bp->sftsize >> 1)] * bp->dual_kernel[i]; - // Frequency symmetry - signal[iii] += v; - if (! bp->dc) - signal[(fftsize - iii) & (fftsize - 1)] += conj(v); - } - } - - if (oct + 1 < (int) n_octaves) { - // Upsample the downsampled data from the lower octaves - pod_vector &sdata(buf2); - assert(downsampled.size() == dsparams.sftsize); - assert(sdata.size() >= dsparams.sftsize); -#if GABORATOR_USE_REAL_FFT - dsparams.rsft->transform(downsampled.data(), sdata.begin()); -#else - // Real to complex - std::copy(downsampled.begin(), downsampled.end(), sdata.begin()); - dsparams.sft->transform(sdata.data()); -#endif - for (unsigned int i = 0; i < dsparams.sftsize; i++) { - sdata[i] *= dsparams.dual_kernel[i ^ (dsparams.sftsize >> 1)]; - } - - // This implicitly zero pads the spectrum, by - // not adding anything to the middle part - // The splitting of the nyquist band is per - // http://dsp.stackexchange.com/questions/14919/upsample-data-using-ffts-how-is-this-exactly-done - // but should not really matter because - // there should be no energy there to speak of - // thanks to the windowing above. - assert(dsparams.sftsize == fftsize / 2); - unsigned int i; - for (i = 0; i < dsparams.sftsize / 2; i++) - signal[i] += sdata[i]; - //C nyquist = sdata[i] * (T)0.5; - C nyquist = sdata[i] * (T)0.5; - signal[i] += nyquist; - signal[i + fftsize / 2] += nyquist; - i++; - for (;i < dsparams.sftsize; i++) - signal[i + fftsize / 2] += sdata[i]; - } - - // Switch to time domain -#if GABORATOR_USE_REAL_FFT - rft->itransform(signal.data(), signal_out); -#else - ft->itransform(signal.data()); - // Copy real part to output - complex2real(signal.begin(), signal.end(), signal_out); -#endif - } - -private: - - // Analyze a signal segment consisting of any number of samples. - // oct is the octave; this is 0 except in recursive calls - // real_signal points to the first sample - // t0 is the sample time of the first sample - // t1 is the sample time of the sample after the last sample - // coefficients are added to msc - - void - analyze_sliced(int oct, const T *real_signal, sample_index_t t0, sample_index_t t1, coefs &msc) const { - sliced_coefs &sc = msc.octaves[oct]; - - // Length of alias-free section; also the overlap period - unsigned int filetsize = filet_part(fftsize); - unsigned int fatsize = fat_part(fftsize); - - // Find the range of slices affected by the sample range - slice_index_t si0 = affected_slice_b(t0); - slice_index_t si1 = affected_slice_e(t1); - - // Length of each downsampled slice (including padding) - unsigned int dslen = fftsize >> 1; - // Ditto without padding - unsigned int dsfiletlen = filet_part(dslen); - // Total length of downsampled data - unsigned int dstotlen = (si1 - si0) * dsfiletlen; - - // The range of sample times covered by the "downsampled" array - sample_index_t tmp = (int64_t)si0 * (int)filetsize + (int)fatsize; - assert((tmp & 1) == 0); - sample_index_t dst0 = tmp >> 1; - sample_index_t dst1 = dst0 + (int)dstotlen; - - // Not all of the "downsampled" array actually contains - // nonzero data. Calculate adjusted bounds to use in the - // recursive analysis so that we don't needlessly analyze - // zeroes. - int ds_support = dsparams.time_support; - sample_index_t dst0a = std::max(dst0, (t0 >> 1) - ds_support); - sample_index_t dst1a = std::min(dst1, (t1 >> 1) + 1 + ds_support); - - pod_vector downsampled(dstotlen); - pod_vector slice(fftsize); - - // Allocate buffers for analyze_one_slice(), to be shared between - // successive calls to avoid repeated allocation - pod_vector buf0(fftsize); - pod_vector buf1(fftsize); - pod_vector buf2(sftsize_max); - pod_vector buf3(sftsize_max); - - zone &z = *octaves[oct].z; - - // Temporary coefficients used in slow path below; allocated only - // if needed, and only once per call - ref > temp_coefs; - - // For each slice - for (slice_index_t si = si0; si < si1; si++) { - sample_index_t slice_t0 = si * (sample_index_t)filetsize; - sample_index_t slice_t1 = slice_t0 + (sample_index_t)fftsize; - - // Intersection of signal and slice - sample_index_t ss_t0 = std::max(t0, slice_t0); - sample_index_t ss_t1 = std::min(t1, slice_t1); - // If the intersection is empty, the affected_* - // calculations above must be wrong - assert(ss_t1 >= ss_t0); - - // If this part of the signal is all zero, we don't need - // the coefficients, and we know the downsampling will - // produce zero, too. - if (is_zero(real_signal + (ss_t0 - t0), - real_signal + (ss_t1 - t0))) - { - // Gather zeroes in lieu of downsampled data - if (oct + 1 < (int)n_octaves) { - unsigned int dsi0 = (si - si0) * dslen / 2; - unsigned int dsi1 = dsi0 + dslen / 2; - std::fill(downsampled.data() + dsi0, downsampled.data() + dsi1, 0); - } - } else { - // For each sample in the slice - // XXX optimize away copy when no zero fill is needed? - - copy_overlapping_zerofill(slice.data(), fftsize, real_signal, t0 - slice_t0, t1 - slice_t0); - - T *downsampled_dst = downsampled.data() + (si - si0) * filet_part(dslen); - - bool created; - oct_coefs &ssc(get_or_create_coefs_uninit(sc, si, oct, created)); - - if (created) { - // Fast path: just store new coefficients directly - analyze_one_slice(oct, slice.data(), slice_t0, ssc, - downsampled_dst, buf0, buf1, buf2, buf3); - } else { - // Slow path: add to existing coefficients - if (! temp_coefs) - temp_coefs.reset(new oct_coefs(coef_meta(oct), false)); - analyze_one_slice(oct, slice.data(), slice_t0, *temp_coefs.get(), - downsampled_dst, buf0, buf1, buf2, buf3); - add(z, ssc, *temp_coefs.get()); - } - } - } - - if (msc.shadow0) { - // Note the "0" argument to band_scale_exp; we assume - // band 0 in the octave has the lowest exponent and - // therefore corresponds to the deepest level of resolution - // pyramid needed. If there are bands with a higher exponent - // in the octave, their resolution pyramid will go deeper - // than strictly necessary, but that's harmless. - msc.shadow0->update(oct, band_scale_exp(oct, 0), sc, si0, si1); - } - - // Recurse - if (oct + 1 < (int)n_octaves) - analyze_sliced(oct + 1, downsampled.data() + (dst0a - dst0), dst0a, dst1a, msc); - } - - // Resynthesize audio from the coefficients in "msc". The audio will - // cover samples from t0 (inclusive) to t1 (exclusive), and is stored - // starting at *real_signal, which must have room for (t1 - t0) - // samples. The octave "oct" is 0 except in recursive calls. - - void - synthesize_sliced(int oct, const coefs &msc, sample_index_t t0, sample_index_t t1, T *real_signal) const { - const sliced_coefs &sc = msc.octaves[oct]; - - int filetsize = filet_part(fftsize); - int fatsize = fat_part(fftsize); - - slice_index_t si0 = affected_slice_b(t0); - slice_index_t si1 = affected_slice_e(t1); - - // sub_signal holds the reconstructed subsampled signal from the lower octaves, - // for the entire time interval covered by the slices - int sub_signal_len = ((si1 - si0) * filetsize + 2 * fatsize) / 2; - pod_vector sub_signal(sub_signal_len); - memset(sub_signal.data(), 0, sub_signal_len * sizeof(T)); - if (oct + 1 < (int)n_octaves) { - ssize_t sub_t0 = si0 * (filetsize / 2); - ssize_t sub_t1 = sub_t0 + sub_signal_len; - // Recurse - assert(sub_t1 - sub_t0 == (ssize_t)sub_signal.size()); - synthesize_sliced(oct + 1, msc, sub_t0, sub_t1, sub_signal.data()); - } - - // Allocate buffers for synthesize_one_slice(), to be shared - // between successive calls to avoid repeated allocation - pod_vector buf0(fftsize); - //pod_vector buf1(fftsize); - pod_vector buf2(sftsize_max); - pod_vector downsampled(dsparams.sftsize); - - // For each slice - for (slice_index_t si = si0; si < si1; si++) { - sample_index_t slice_t0 = si * filetsize; - if (! sc.slices.has_index(si)) { - // Zero fill. Some code duplication with the copying - // at the end of the function. - sample_index_t b = std::max(slice_t0 + fatsize, t0); - sample_index_t e = std::min(slice_t0 + fftsize - fatsize, t1); - for (sample_index_t i = b; i < e; i++) - real_signal[i - t0] = 0; - continue; - } - padded_coefs c(coef_meta(oct, true), false); - - for (unsigned int obno = 0; obno < c.bands.size(); obno++) { - unsigned int padded_len = octaves[oct].z->bandparams[obno]->sftsize; - unsigned int len = filet_part(padded_len); - for (int neighbor = -1; neighbor <= 1; neighbor++) { - slice_index_t ni = si + neighbor; - // Location in destination where sample 0 of source is copied, - // or would be if it was inside the buffer range - int copy_dest_offset = (len >> 1) + len * neighbor; - int copy_source_offset = 0; - int copy_len = len; - if (copy_dest_offset < 0) { - int adj = -copy_dest_offset; - copy_dest_offset += adj; - copy_source_offset += adj; - copy_len -= adj; - } else if (copy_dest_offset + copy_len >= (int)padded_len) { - int adj = copy_dest_offset + copy_len - padded_len; - copy_len -= adj; - } - - C *destp = &c.bands[obno][copy_dest_offset]; - if (ni < sc.slices.begin_index() || ni >= sc.slices.end_index()) { - // Zero pad - for (int j = 0; j < copy_len; j++) - destp[j] = 0; - } else { - const ref > &t = sc.slices.get_existing(ni); - if (t) { - const C *srcp = &t->bands[obno][copy_source_offset]; - for (int j = 0; j < copy_len; j++) - destp[j] = srcp[j]; - } - } - } - } - - // Copy downsampled signal to "downsampled" for upsampling - if (oct + 1 < (int) n_octaves) { - int bi = (si - si0) * filet_part(dsparams.sftsize); - int ei = bi + dsparams.sftsize; - assert(bi >= 0); - assert(ei <= (int)sub_signal.size()); - std::copy(sub_signal.begin() + bi, - sub_signal.begin() + ei, - downsampled.begin()); - } - - T signal_slice[fftsize]; - synthesize_one_slice(oct, c, downsampled, slice_t0, signal_slice, buf0, buf2); - - // Copy overlapping part - sample_index_t b = std::max(slice_t0 + fatsize, t0); - sample_index_t e = std::min(slice_t0 + fftsize - fatsize, t1); - for (sample_index_t i = b; i < e; i++) - real_signal[i - t0] = signal_slice[i - slice_t0]; - } - } - -public: - // The main analysis entry point. - // The resulting coefficients are added to any existing coefficients in "msc". - - void analyze(const T *real_signal, sample_index_t t0, sample_index_t t1, - coefs &msc, int n_threads = 1) const - { - analyze1(real_signal, t0, t1, msc, n_threads, 1); - } - - void analyze1(const T *real_signal, sample_index_t t0, sample_index_t t1, - coefs &msc, int n_threads, int level) const - { - assert(msc.octaves.size() == n_octaves); - (void)n_threads; - analyze_sliced(0, real_signal, t0, t1, msc); - } - - // The main synthesis entry point - - void - synthesize(const coefs &msc, sample_index_t t0, sample_index_t t1, - T *real_signal, int n_threads = 1) const { - (void)n_threads; - synthesize_sliced(0, msc, t0, t1, real_signal); - } - - // Get an existing coefficient slice, or create a new one. - // Note that this hides the distinction between two types - // of nonexistence: that of slices outside the range - // of the range_vector, and that of missing slices within - // the range (having a null ref). CT is the coefficient - // type, which is typically C aka complex, but can - // be different, for example float to represent magnitudes. - template - oct_coefs &get_or_create_coefs(sliced_coefs &sc, - slice_index_t i, unsigned int oct) const - { - ref > &p(sc.slices.get_or_create(i)); - if (! p) - p.reset(new oct_coefs(coef_meta(oct))); - return *p; - } - - // As above, but return uninitialized coefficients and set created - // to true if the coefficients did not already exist. This is - // just an optimization, turning a memset and add-to-memory - // into a store. - template - oct_coefs &get_or_create_coefs_uninit(sliced_coefs &sc, - slice_index_t i, unsigned int oct, bool &created) const - { - ref > &p(sc.slices.get_or_create(i)); - if (! p) { - p.reset(new oct_coefs(coef_meta(oct), false)); - created = true; - } else { - created = false; - } - return *p; - } - - // Get a pointer to an existing existing coefficient slice, - // or null if one does not exist. Like get_or_create_coefs(), - // this hides the distinction between the two types of nonexistence. - template - oct_coefs *get_existing_coefs(const sliced_coefs &sc, - slice_index_t i) const - { - // XXX optimize to not lookup twice - if (! sc.slices.has_index(i)) - return 0; - const ref > &p(sc.slices.get_existing(i)); - return p.get(); - } - - - // Split a "global band number" gbno into an octave and band - // number within octave ("obno"). - // - // Global band numbers start at 0 for the band at or close to - // fs/2, and increase towards lower frequencies. - // - // Include the DC band if "dc" is true. - // Returns true iff valid. - - bool bno_split(int gbno, int &oct, unsigned int &obno, bool dc) const { - if (gbno < 0) { - // Above top octave - return false; - } else if (gbno < 2 * (int)params.bands_per_octave) { - // Within top octave - oct = 0; - obno = 2 * params.bands_per_octave - 1 - gbno; - return true; - } else if (gbno < (int)n_bands_total - 1) { - // Within a middle octave, or within non-DC part of bottom octave - int t = gbno - 2 * params.bands_per_octave; - assert(t >= 0); - oct = 1 + t / params.bands_per_octave; - obno = params.bands_per_octave - 1 - (t % params.bands_per_octave); - if (oct == (int)n_octaves - 1) { - // This octave may be shorter and has DC band at the - // beginning, count from the end instead - obno -= params.bands_per_octave; - obno += octaves[oct].z->n_bands; - //obno++; // Skip the DC band - } - assert(obno >= 0 && obno < octaves[oct].z->n_bands); - return true; - } else if (gbno == (int)n_bands_total - 1 && dc) { - // DC - oct = n_octaves - 1; - obno = 0; - return true; - } else { - return false; - } - } - - // The inverse of the above. Returns a gbno. The arguments must - // be valid. - - int bno_merge(int oct, unsigned int obno) const { - unsigned int n_bands = octaves[oct].z->n_bands; - assert(obno < n_bands); - int bno_from_end = n_bands - 1 - obno; - return bno_from_end + octaves[oct].n_bands_above; - } - - - // Get the range of sample indices that a set of coefficients - // pertain to. - void get_coef_bounds(const coefs &msc, sample_index_t &si0, sample_index_t &si1) const { - // Look at the lowest-frequency band, since it has the greatest support - // XXX what about DC? - int gbno = n_bands_total - 2; - - int oct; - unsigned int obno; // Band number within octave - bool r = bno_split(gbno, oct, obno, false); - assert(r); - - const typename sliced_coefs::slices_t &slices = msc.octaves[oct].slices; - // signal samples per band sample - int exp = band_scale_exp(oct, obno); - // times number of samples in band - exp += octaves[oct].z->bandparams[obno]->sftsize_log2 - 1; - si0 = shift_left((sample_index_t)slices.begin_index(), exp); - si1 = shift_left((sample_index_t)slices.end_index(), exp); - } - - // Return the time step (aka downsampling factor) of band "gbno". - // If gbno is out of range, zero is returned. - unsigned int band_step_log2(int gbno) const { - int oct; - unsigned int obno; - bool valid = bno_split(gbno, oct, obno, true); - if (! valid) - return 0; - return band_scale_exp(oct, obno); - } - - int bandpass_bands_begin() const { return 0; } - int bandpass_bands_end() const { return n_bands_total - 1; } - - int bands_begin() const { return 0; } - int bands_end() const { return n_bands_total; } - - // Get the band number of the lowpass band - int band_lowpass() const { return n_bands_total - 1; } - - // XXX simplify! - double band_ff(int gbno) { - if (gbno == band_lowpass()) - return 0; - int oct; - unsigned int obno; - bool valid = bno_split(gbno, oct, obno, true); - assert(valid); - return band_ff(oct, obno); - } - - - // Convenience function to get the coefficient metadata for a given octave, - // with or without padding - coefs_meta &coef_meta(unsigned int oct, bool padded = false) const { - return octaves[oct].z->cmeta[padded]; - } - - ~analyzer() { - } - - // Get the base 2 logarithm of the downsampling factor of - // band "obno" in octave "oct" - int band_scale_exp(int oct, unsigned int obno) const { - return fftsize_log2 - octaves[oct].z->bandparams[obno]->sftsize_log2 + oct; - } - // Members initialized in the constructor, and listed in - // order of initialization - parameters params; - double band_spacing_log2; - double band_spacing; - double tuning_log2ff; - double band0_log2ff; - unsigned int n_bandpass_bands_total; - unsigned int fftsize_log2; // log2(fftsize) - unsigned int fftsize; // The size of the main FFT, a power of two. - - // The following members may get assigned more than once, if we - // need to try more than one FFT size. - - double inv_fftsize_double; // 1.0 / fftsize - T inv_fftsize_t; // 1.0f / fftsize (if using floats) - unsigned int sftsize_max; // The size of the largest band FFT, a power of two - unsigned int n_octaves; - - std::vector > > zones; - downsampling_params dsparams; - - // Fourier transform object for transforming a full slice -#if GABORATOR_USE_REAL_FFT - rfft *rft; -#else - fft *ft; -#endif - std::vector > octaves; // Per-octave parameters - unsigned int n_bands_total; // Total number of frequency bands, including DC - double top_band_log2ff; // log2 of fractional frequency of the highest-frequency band - int ffref_gbno; // Band number of the reference frequency -}; - - - -// Iterate over the slices holding coefficients for a row (band) -// in the spectrogram with indices ranging from i0 to i1, and call -// the "process_existing_slice" method of the given "dest" object -// for each full or partial slice of coefficients, and/or the -// "process_missing_slice" method for each nonexistent slice. -// -// Template parameters: -// T is the spectrogram value type -// D is the dest object type -// C is the coefficient type - -template > -struct row_foreach_slice { - typedef C value_type; - // With sc arg - row_foreach_slice(const analyzer &anl_, - const sliced_coefs &sc_, - int oct_, unsigned int obno_): - anl(anl_), oct(oct_), obno(obno_), sc(sc_) - { - init(); - } - // With msc arg - row_foreach_slice(const analyzer &anl_, - const coefs &msc, - int oct_, unsigned int obno_): - anl(anl_), oct(oct_), obno(obno_), sc(msc.octaves[oct]) - { - init(); - assert(oct < (int)msc.octaves.size()); - } -private: - void init() { - // This works for power-of-two-sized filets only. To extend - // it to other lengths, we will need a divmod function that - // works correctly for negative arguments. - unsigned int slice_len = - filet_part(anl.octaves[oct].z->bandparams[obno]->sftsize); - sh = whichp2(slice_len); - } -public: - void operator()(coef_index_t i0, coef_index_t i1, D &dest) const { - assert(i0 <= i1); - // Band size (power of two) - int bsize = 1 << sh; - // Adjust for t=0 being outside the filet - int fatsize = bsize >> 1; - i0 -= fatsize; - i1 -= fatsize; - coef_index_t i = i0; - while (i < i1) { - // Slice index - slice_index_t sli = i >> sh; - // Band vector index - int bvi = i & (bsize - 1); - int len = bsize - bvi; - coef_index_t remain = i1 - i; - if (remain < len) - len = remain; - oct_coefs *c = anl.get_existing_coefs(sc, sli); - if (c) { - dest.process_existing_slice(c->bands[obno] + bvi, len); - } else { - dest.process_missing_slice(len); - } - i += len; - } - } - const analyzer &anl; - int oct; - unsigned int obno; - unsigned int sh; - const sliced_coefs ≻ -}; - -// Helper class for row_source - -template -struct writer_dest { - writer_dest(OI output_): output(output_) { } - void process_existing_slice(C *bv, size_t len) { - // Can't use std::copy here because it takes the output - // iterator by value, and using the return value does not - // work, either. - for (size_t i = 0; i < len; i++) - *output++ = bv[i]; - } - void process_missing_slice(size_t len) { - for (size_t i = 0; i < len; i++) - *output++ = 0; - } - OI output; -}; - -// Retrieve a sequence of coefficients from a row (band) in the -// spectrogram, with indices ranging from i0 to i1. The indices can -// be negative, and can extend outside the available data, in which -// case zero is returned. The coefficients are written through the -// output iterator "output". -// Template arguments: -// T is the spectrogram value type -// OI is the output iterator type -// C is the coefficient value type - -template > -struct row_source { - // With sc arg - row_source(const analyzer &anl_, - const sliced_coefs &sc_, - int oct_, unsigned int obno_): - slicer(anl_, sc_, oct_, obno_) - { } - // With msc arg - row_source(const analyzer &anl_, - const coefs &msc_, - int oct_, unsigned int obno_): - slicer(anl_, msc_, oct_, obno_) - { } - OI operator()(coef_index_t i0, coef_index_t i1, OI output) const { - writer_dest dest(output); - slicer(i0, i1, dest); - return dest.output; - } - row_foreach_slice, C> slicer; -}; - - -// T -> f() -> OI::value_type - -template -struct transform_output_iterator: public std::iterator { - typedef T value_type; - transform_output_iterator(F f_, OI output_): f(f_), output(output_) { } - transform_output_iterator& operator=(T v) { - *output++ = f(v); - return *this; - } - transform_output_iterator& operator*() { return *this; } - transform_output_iterator& operator++() { return *this; } - transform_output_iterator& operator++(int) { return *this; } - F f; - OI output; -}; - -// Apply the function f to each existing coefficient in the -// coefficient set msc. - -template -void apply(const analyzer &anl, const coefs &msc, F f) { - typedef complex C; - unsigned int n_oct = msc.octaves.size(); - for (unsigned int oct = 0; oct < n_oct; oct++) { - const sliced_coefs &sc = msc.octaves[oct]; - slice_index_t bi = sc.slices.begin_index(); - slice_index_t ei = sc.slices.end_index(); - for (slice_index_t s = bi; s < ei; s++) { - const ref > &t = sc.slices.get_existing(s); - if (! t) - continue; - const oct_coefs &c = *t; - unsigned int n_bands = c.bands.size(); - for (unsigned int obno = 0; obno < n_bands; obno++) { - C *band = c.bands[obno]; - unsigned int len = filet_part(anl.octaves[oct].z->bandparams[obno]->sftsize); - int bno = anl.bno_merge(oct, obno); - sample_index_t st = anl.sample_time(s, 0, oct, obno); - int time_step = 1 << anl.band_scale_exp(oct, obno); - for (unsigned int i = 0; i < len; i++) { - f(band[i], bno, st); - st += time_step; - } - } - } - } -} - -// Apply the function f to each existing coefficient in the -// coefficient set msc within the time range st0 to st1. - -template -void apply(const analyzer &anl, const coefs &msc, F f, - sample_index_t st0, - sample_index_t st1) -{ - typedef complex C; - unsigned int n_oct = msc.octaves.size(); - for (unsigned int oct = 0; oct < n_oct; oct++) { - const sliced_coefs &sc = msc.octaves[oct]; - unsigned int n_bands = anl.octaves[oct].z->n_bands; - for (unsigned int obno = 0; obno < n_bands; obno++) { - int exp = anl.band_scale_exp(oct, obno); - int time_step = 1 << exp; - // Find the range of valid coefficient indices within the - // given range of sample times. - coef_index_t ci0 = (st0 + time_step - 1) >> exp; - coef_index_t ci1 = (st1 + time_step - 1) >> exp; - int bno = anl.bno_merge(oct, obno); - // Helper class for row_foreach_slice() - struct apply_dest { - // st is the sample time of the first coefficient - // sample in the range - apply_dest(int bno_, sample_index_t st_, int step_, F f_): - bno(bno_), st(st_), step(step_), f(f_) - { } - void process_existing_slice(C *bv, size_t len) { - for (size_t i = 0; i < len; i++) { - f(bv[i], bno, st); - st += step; - } - } - void process_missing_slice(size_t len) { - st += len * step; - } - int bno; - sample_index_t st; - int step; - F f; - } dest(bno, shift_left(ci0, exp), 1 << exp, f); - row_foreach_slice(anl, sc, oct, obno)(ci0, ci1, dest); - } - } -} - -template -void forget_before(const analyzer &anl, coefs &msc, - sample_index_t limit) -{ - typedef complex C; - unsigned int n_oct = msc.octaves.size(); - for (unsigned int oct = 0; oct < n_oct; oct++) { - sliced_coefs &sc = msc.octaves[oct]; - // Convert limit from samples to slices, rounding down. - // The "- 1" is because we only use the filet part. - sample_index_t fat = fat_part(anl.fftsize) << oct; - slice_index_t sli = (limit - fat) >> (oct + anl.fftsize_log2 - 1); - sc.slices.erase_before(sli); - } -} - - -} // namespace - -#endif diff --git a/gaborator/gaborator-1.2/gaborator/render.h b/gaborator/gaborator-1.2/gaborator/render.h deleted file mode 100644 index 184e70b..0000000 --- a/gaborator/gaborator-1.2/gaborator/render.h +++ /dev/null @@ -1,315 +0,0 @@ -// -// Rendering of spectrogram images -// -// Copyright (C) 2015-2018 Andreas Gustafsson. This file is part of -// the Gaborator library source distribution. See the file LICENSE at -// the top level of the distribution for license information. -// - -#ifndef _GABORATOR_RENDER_H -#define _GABORATOR_RENDER_H - -#include "gaborator/gaborator.h" -#include "gaborator/resample2.h" - -namespace gaborator { - - -// Convert a floating-point linear brightness value in the range 0..1 -// into an 8-bit pixel value, with clamping and (rough) gamma -// correction. This nominally uses the sRGB gamma curve, but the -// current implementation cheats and uses a gamma of 2 because it can -// be calculated quickly using a square root. - -template -unsigned int float2pixel_8bit(T val) { - // Clamp before gamma correction so we don't take the square root - // of a negative number; those can arise from bicubic - // interpolation. While we're at it, let's also skip the gamma - // correction for small numbers that will round to zero anyway, - // and especially denormals which could rigger GCC bug target/83240. - static const T almost_zero = 1.0 / 65536; - if (val < almost_zero) - val = 0; - if (val > 1) - val = 1; - return (unsigned int)(sqrtf(val) * 255.0f); -} - - -// Magnitude - -template -struct complex_abs_fob { - T operator()(const complex &c) { - return complex_abs(c); - } -}; - - -// A source object for resample2() that provides the absolute -// values of a row of spectrogram coordinates. - -template -struct abs_row_source { - typedef complex C; - - typedef transform_output_iterator abs_writer_t; - abs_row_source(const analyzer &frs_, - const sliced_coefs &sc_, - int oct_, unsigned int obno_, - NORMF normf_): - rs(frs_, sc_, oct_, obno_), - normf(normf_) - { } - OI operator()(sample_index_t i0, sample_index_t i1, OI output) const { - abs_writer_t abswriter(normf, output); - abs_writer_t abswriter_end = rs(i0, i1, abswriter); - return abswriter_end.output; - } - row_source rs; - NORMF normf; -}; - -// Helper class for abs_row_source specialization below - -template -struct abs_writer_dest { - abs_writer_dest(OI output_): output(output_) { } - void process_existing_slice(C *bv, size_t len) { - complex_magnitude(bv, output, len); - output += len; - } - void process_missing_slice(size_t len) { - for (size_t i = 0; i < len; i++) - *output++ = 0; - } - OI output; -}; - -// Partial specialization of class abs_row_source for NORMF = complex_abs_fob, -// for vectorization. - -template -struct abs_row_source > { - typedef complex C; - // Note unused last arg - abs_row_source(const analyzer &frs_, - const sliced_coefs &sc_, - int oct_, unsigned int obno_, - complex_abs_fob): - slicer(frs_, sc_, oct_, obno_) - { } - OI operator()(coef_index_t i0, coef_index_t i1, OI output) const { - abs_writer_dest dest(output); - slicer(i0, i1, dest); - return dest.output; - } - row_foreach_slice, C> slicer; -}; - -// Render a single line (single frequency band), with scaling by -// powers of two in the horizontal (time) dimension, and filtering to -// avoid aliasing when minifying. - -template -OI render_p2scale_line(const analyzer &frs, - const coefs &msc, - int gbno, - int64_t xorigin, - sample_index_t i0, sample_index_t i1, int e, - bool interpolate, - OI output, - NORMF normf) -{ - int oct; - unsigned int obno; // Band number within octave - bool clip = ! frs.bno_split(gbno, oct, obno, false); - if (clip) { - for (sample_index_t i = i0; i < i1; i++) - *output++ = (T)0; - return output; - } - abs_row_source - abs_rowsource(frs, msc.octaves[oct], oct, obno, normf); - - // Scale by the downsampling factor of the band - int scale_exp = frs.band_scale_exp(oct, obno); - output = resample2(abs_rowsource, xorigin, - i0, i1, e - scale_exp, - interpolate, output); - return output; -} - -// Render a two-dimensional image with scaling by powers of two in the -// horizontal direction only. In the vertical direction, there is -// always a one-to-one correspondence between bands and pixels. -// yi0 and yi1 already have the yorigin applied, so there is no -// yorigin argument. - -template -OI render_p2scale_noyscale(const analyzer &frs, - const coefs &msc, - int64_t xorigin, - int64_t xi0, int64_t xi1, int xe, - int64_t yi0, int64_t yi1, - bool interpolate, - OI output, - NORMF normf) -{ - assert(xi1 >= xi0); - int w = xi1 - xi0; - int gbno0 = yi0; - int gbno1 = yi1; - for (int gbno = gbno0; gbno < gbno1; gbno++) { - int oct; - unsigned int obno; // Band number within octave - bool clip = ! frs.bno_split(gbno, oct, obno, false); - if (clip) { - for (int x = 0; x < w; x++) - *output++ = (T)0; - } else { - output = render_p2scale_line(frs, msc, gbno, xorigin, - xi0, xi1, xe, - interpolate, output, normf); - } - } - return output; -} - -// Source data from a column of a row-major two-dimensional array. -// data points to the beginning of a row-major array with an x -// range of x0..x1 and an y range from y0..y1, and operator() -// returns data from column x (where x is within the range x0..x1). - -template -struct transverse_source { - transverse_source(float *data_, - int64_t x0_, int64_t x1_, int64_t y0_, int64_t y1_, - int64_t x_): - data(data_), - x0(x0_), x1(x1_), y0(y0_), y1(y1_), - x(x_), - stride(x1 - x0) - { } - OI operator()(int64_t i0, int64_t i1, OI out) const { - assert(x >= x0); - assert(x <= x1); - assert(i1 >= i0); - assert(i0 >= y0); - assert(i1 <= y1); - float *p = data + (x - x0) + (i0 - y0) * stride; - while (i0 != i1) { - *out++ = *p; - p += stride; - ++i0; - } - return out; - } - float *data; - int64_t x0, x1, y0, y1, x; - size_t stride; -}; - -template -struct stride_iterator: public std::iterator { - stride_iterator(I it_, size_t stride_): it(it_), stride(stride_) { } - T& operator*() { return *it; } - stride_iterator& operator++() { - it += stride; - return *this; - } - stride_iterator operator++(int) { - stride_iterator old = *this; - it += stride; - return old; - } - I it; - size_t stride; -}; - -// Render a two-dimensional image with scaling by powers of two in -// both the horizontal (time) and vertical (frequency) directions. -// The output may be written through "output" out of order, so -// "output" must be a random access iterator. - -// Note the default template argument for NORMF. This is needed -// because the compiler won't deduce the type of NORMF from the -// default function argument "NORMF normf = complex_abs_fob()" -// when the normf argument is omitted; it is considered a "non-deduced -// context", being "a template parameter used in the parameter type of -// a function parameter that has a default argument that is being used -// in the call for which argument deduction is being done". -// Unfortuantely, this work-around of providing a default template -// argument requires C++11. - -template > -void render_p2scale(const analyzer &frs, - const coefs &msc, - int64_t xorigin, int64_t yorigin, - int64_t xi0, int64_t xi1, int xe, - int64_t yi0, int64_t yi1, int ye, - OI output, - bool interpolate = true, - NORMF normf = complex_abs_fob()) -{ - // Construct a temporary float image of the right width, - // but still needing scaling of the height. Include - // extra scanlines at the top and bottom for interpolation. - - // Find the image bounds in the spectrogram coordinate system, - // including the interpolation margin. The Y bounds are in - // bands and are used both to determine what to render into the - // temporary image and for short-circuiting; the X bounds are in - // samples, and are only used for short-circuiting. - int64_t ysi0, ysi1; - resample2_support(yorigin, yi0, yi1, ye, ysi0, ysi1); - int64_t xsi0, xsi1; - resample2_support(xorigin, xi0, xi1, xe, xsi0, xsi1); - - // Short-circuiting: if the image to be rendered falls entirely - // outside the data, just set it to zero instead of resampling down - // (potentially) high-resolution zeros to the display resolution. - // This makes a difference when zooming out by a large factor, for - // example such that the entire spectrogram falls within a single - // tile; that tile will necessarily be expensive to calculate, but - // the other tiles need not be, and mustn't be if we are going to - // keep the total amount of work bounded by O(L) with respect - // to the signal length L regardless of zoom. - int64_t cxi0, cxi1; - frs.get_coef_bounds(msc, cxi0, cxi1); - if (ysi1 < 0 || // Entirely above - ysi0 >= frs.n_bands_total - 1 || // Entirely below - xsi1 < cxi0 || // Entirely to the left - xsi0 >= cxi1) // Entirely to the right - { - size_t n = (yi1 - yi0) * (xi1 - xi0); - for (size_t i = 0; i < n; i++) - output[i] = (T)0; - return; - } - - // Allocate buffer for temporary image resampled in the X - // direction but not yet in the Y direction - size_t n_pixels = (ysi1 - ysi0) * (xi1 - xi0); - pod_vector render_data(n_pixels); - - // Render data resampled in the X direction - float *p = render_data.data(); - render_p2scale_noyscale(frs, msc, xorigin, xi0, xi1, xe, - ysi0, ysi1, interpolate, p, normf); - - // Resample in the Y direction - for (int64_t xi = xi0; xi < xi1; xi++) { - transverse_source src(render_data.data(), - xi0, xi1, ysi0, ysi1, - xi); - stride_iterator dest(output + (xi - xi0), (xi1 - xi0)); - resample2(src, yorigin, yi0, yi1, ye, interpolate, dest); - } -} - - -} // namespace - -#endif diff --git a/gaborator/gaborator-1.7/CHANGES b/gaborator/gaborator-1.7/CHANGES new file mode 100644 index 0000000..3294dc5 --- /dev/null +++ b/gaborator/gaborator-1.7/CHANGES @@ -0,0 +1,99 @@ + +1.7 + +Miscellaneous bug fixes. + +Support lower numbers of bands per octave, down to 4. + +Further improve the performance of analyzing short signal blocks. + +The "Frequency-Domain Filtering" and "Streaming" examples now use +a white noise and impulse signal, respectively. + +1.6 + +Add "API Introduction" documentation section that was missing +from version 1.5, causing broken links. + +Improve analysis and resynthesis performance when using PFFFT or vDSP +by automatically enabling the use of real rather than complex FFTs +where applicable. + +1.5 + +Add navigation links to the HTML documentation. + +Add a code example demonstrating synthesis of musical notes. + +Add a function process() for iterating over coefficients sets with +greater flexibility than apply(). Also add a function fill() for +algorithmically creating new coefficients. + +Make the C++ declarations in the API reference documents more closely +resemble actual C++ code. + +Add a method gaborator::analyzer::band_ref() returning the band number +corresponding to the reference frequency. + +1.4 + +Support building the library as C++17, while retaining compatibility +with C++11. + +Further improve the performance of analyzing short signal blocks, and +of signal blocks not aligned to large powers of two. + +Add a code example mesasuring the resynthesis signal-to-noise +ratio (SNR). + +1.3 + +Eliminate some compiler warnings. + +Declare gaborator::analyzer::band_ff() const, making the code match +the documentation. + +Fix incorrect return type of gaborator::analyzer::band_ff() in the +documentation. + +Improve performance of analyzing short signal blocks. + +Remove special-case optimization of analyzing signal slices of all +zeros, as it caused incorrect results. + +Support up to 384 bands per octave. + +1.2 + +Add overview documentation. + +Add real-time FAQ. + +Actually include version.h in the release. + +Fix off-by-one error in defintion of analyzer constructor ff_min +argument. + +Fix incorrect return value of band_ff() for DC band. + +Add streaming example code. + +Add analyzer::analysis_support() and analyzer::synthesis_support(). + +Document analyzer::band_ff(). + +Improve signal to noise ratio at low numbers of bands per octave. + +Note the need for -mfpu=neon on ARM in render.html. + +1.1 + +Added CHANGES file. + +Added reference documentation. + +New include file gaborator/version.h. + +1.0 + +Initial release diff --git a/gaborator/gaborator-1.2/LICENSE b/gaborator/gaborator-1.7/LICENSE similarity index 87% rename from gaborator/gaborator-1.2/LICENSE rename to gaborator/gaborator-1.7/LICENSE index aa53284..abcfc9f 100644 --- a/gaborator/gaborator-1.2/LICENSE +++ b/gaborator/gaborator-1.7/LICENSE @@ -1,5 +1,5 @@ -The Gaborator library is Copyright (C) 1992-2018 Andreas Gustafsson. +The Gaborator library is Copyright (C) 1992-2019 Andreas Gustafsson. License to distribute and modify the code is hereby granted under the terms of the GNU Affero General Public License, version 3 (henceforth, diff --git a/gaborator/gaborator-1.2/README b/gaborator/gaborator-1.7/README similarity index 100% rename from gaborator/gaborator-1.2/README rename to gaborator/gaborator-1.7/README diff --git a/gaborator/gaborator-1.2/doc/agpl-3.0.txt b/gaborator/gaborator-1.7/doc/agpl-3.0.txt similarity index 100% rename from gaborator/gaborator-1.2/doc/agpl-3.0.txt rename to gaborator/gaborator-1.7/doc/agpl-3.0.txt diff --git a/gaborator/gaborator-1.2/doc/doc.css b/gaborator/gaborator-1.7/doc/doc.css similarity index 69% rename from gaborator/gaborator-1.2/doc/doc.css rename to gaborator/gaborator-1.7/doc/doc.css index e6edd08..86aab82 100644 --- a/gaborator/gaborator-1.2/doc/doc.css +++ b/gaborator/gaborator-1.7/doc/doc.css @@ -24,6 +24,9 @@ img { background: #000; } h2 { + margin-top: 2em; +} +h3 { margin-top: 1.5em; } /* http://code.stephenmorley.org/html-and-css/fixing-browsers-broken-monospace-font-handling/ */ @@ -31,3 +34,20 @@ pre, code, kbd, samp, tt { font-family:monospace,monospace; font-size:1em; } +pre.forward_decl { +/* Needed for syntax checking, but avoid clutter for human readers */ + display: none; +} +div.class_def { + margin-left: 2em; +} +div.nav { + margin-top: 30px; + font-style: oblique; +} +div.nav span.prev { + float: left; +} +div.nav span.next { + float: right; +} \ No newline at end of file diff --git a/gaborator/gaborator-1.2/doc/filter-response.png b/gaborator/gaborator-1.7/doc/filter-response.png similarity index 100% rename from gaborator/gaborator-1.2/doc/filter-response.png rename to gaborator/gaborator-1.7/doc/filter-response.png diff --git a/gaborator/gaborator-1.2/doc/filter.html b/gaborator/gaborator-1.7/doc/filter.html similarity index 81% rename from gaborator/gaborator-1.2/doc/filter.html rename to gaborator/gaborator-1.7/doc/filter.html index 7170b02..25aa939 100644 --- a/gaborator/gaborator-1.2/doc/filter.html +++ b/gaborator/gaborator-1.7/doc/filter.html @@ -1,6 +1,6 @@ @@ -59,7 +59,8 @@

Reading the Audio

memset(&sfinfo, 0, sizeof(sfinfo)); SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo); if (! sf_in) { - std::cerr << "could not open input audio file\n"; + std::cerr << "could not open input audio file: " + << sf_strerror(sf_in) << "\n"; exit(1); } double fs = sfinfo.samplerate; @@ -110,7 +111,7 @@

Precalculating Gains

     for (int band = analyzer.bandpass_bands_begin(); band < analyzer.bandpass_bands_end(); band++) {
-        float f_hz = analyzer.band_ff(band) * fs;
+        double f_hz = analyzer.band_ff(band) * fs;
         band_gains[band] = 1.0 / sqrt(f_hz / 20.0);
     }
 
@@ -147,17 +148,28 @@

Spectrum Analysis

Filtering

The filtering is done using the function -gaborator::apply(), which applies a user-defined function to -each spectrogram coefficient. Here, that user-defined function is a +process(), which applies a user-defined function +to each spectrogram coefficient. Here, that user-defined function is a lambda expression that multiplies the coefficient by the appropriate precalculated frequency-dependent gain, modifying the coefficient in place. The unused int64_t argument is the time in units -of samples; this could be use to implement a time-varying filter if desired.

+of samples; this could be use to implement a time-varying filter if +desired.

+

+The second and third argument to process() specify a +range of frequency bands to process; here we pass INT_MIN, +INT_MAX to process all of them. Similarly, the fourth and +fifth argument specify a time range to process, and we pass +INT64_MIN, INT64_MAX to process all the coefficients +in coefs regardless of time. +

-        apply(analyzer, coefs,
-            [&](std::complex<float> &coef, int band, int64_t) {
+        process([&](int band, int64_t, std::complex<float> &coef) {
                 coef *= band_gains[band];
-            });
+            },
+            INT_MIN, INT_MAX,
+            INT64_MIN, INT64_MAX,
+            coefs);
 

Resynthesis

@@ -189,10 +201,12 @@

Writing the Audio

to make sure that any samples too loud for the file format will saturate; by default, libsndfile makes them wrap around, which sounds really bad.

+
     SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo);
     if (! sf_out) {
-        std::cerr << "could not open output audio file\n";
+        std::cerr << "could not open output audio file: "
+            << sf_strerror(sf_out) << "\n";
         exit(1);
     }
     sf_command(sf_out, SFC_SET_CLIPPING, NULL, SF_TRUE);
@@ -203,6 +217,7 @@ 

Writing the Audio

} sf_close(sf_out);
+

Postamble

@@ -215,30 +230,29 @@

Postamble

Compiling

-

Like Example 1, this example +

Like Example 1, this example can be built using a one-line build command:

-
+
 c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` filter.cc `pkg-config --libs sndfile` -o filter
 

Or using the vDSP FFT on macOS:

 c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` filter.cc `pkg-config --libs sndfile` -framework Accelerate -o filter
 
-

Or using PFFFT (see Example 1 for how to download and build PFFFT):

-
+

Or using PFFFT (see Example 1 for how to download and build PFFFT):

+
 c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` filter.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o filter
 

Running

-

To filter the file guitar.wav that was downloaded in -Example 1, simply run

+

Running the following shell commands will download an example +audio file containing five seconds of white noise and filter it, +producing pink noise.

-./filter guitar.wav guitar_filtered.wav
+wget http://download.gaborator.com/audio/white_noise.wav
+./filter white_noise.wav pink_noise.wav
 
-

The resulting lowpass filtered audio in guitar_filtered.wav will -sound muffled compared to the original, but less so than it would with a -6 dB/octave filter.

Frequency response

The following plot shows the actual measured frequency response of the @@ -246,5 +260,7 @@

Frequency response

ripple:

Frequency response plot + + diff --git a/gaborator/gaborator-1.7/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_anl_wob.png b/gaborator/gaborator-1.7/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_anl_wob.png new file mode 100644 index 0000000..b75fa6c Binary files /dev/null and b/gaborator/gaborator-1.7/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_anl_wob.png differ diff --git a/gaborator/gaborator-1.7/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_syn_wob.png b/gaborator/gaborator-1.7/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_syn_wob.png new file mode 100644 index 0000000..03ff1a1 Binary files /dev/null and b/gaborator/gaborator-1.7/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_syn_wob.png differ diff --git a/gaborator/gaborator-1.7/doc/gen/grid_v1_bpo12_ffmin0.03_ffref0.5_wob.png b/gaborator/gaborator-1.7/doc/gen/grid_v1_bpo12_ffmin0.03_ffref0.5_wob.png new file mode 100644 index 0000000..91fdf7b Binary files /dev/null and b/gaborator/gaborator-1.7/doc/gen/grid_v1_bpo12_ffmin0.03_ffref0.5_wob.png differ diff --git a/gaborator/gaborator-1.2/doc/index.html b/gaborator/gaborator-1.7/doc/index.html similarity index 60% rename from gaborator/gaborator-1.2/doc/index.html rename to gaborator/gaborator-1.7/doc/index.html index 46d4582..d7d3231 100644 --- a/gaborator/gaborator-1.2/doc/index.html +++ b/gaborator/gaborator-1.7/doc/index.html @@ -1,20 +1,21 @@ + The Gaborator

The Gaborator

The Gaborator is a library that generates constant-Q spectrograms -for visualization and analysis of audio signals. It also supports an -accurate inverse transformation of the spectrogram coefficients back into -audio for spectral effects and editing.

+for visualization and analysis of audio signals. It also supports a +fast and accurate inverse transformation of the spectrogram coefficients +back into audio for spectral effects and editing.

The Gaborator implements the invertible constant-Q transform of Velasco, Holighaus, Dörfler, and Grill, described in the papers @@ -25,34 +26,18 @@

The Gaborator

using Gaussian bandpass filters and an efficient multi-rate architecture.

-

The Gaborator is written in C++11 and runs on POSIX systems such as -macOS, Linux, and NetBSD. It has been tested on Intel x86_64 and ARM -processors.

+

The Gaborator is written in C++11 and compatible with C++14 and C++17. +It has been tested on macOS, Linux, NetBSD, FreeBSD, and iOS, on Intel +x86_64 and ARM processors.

The Gaborator is open source under the GNU Affero General Public License, version 3, and is also available for commercial licensing. See the file LICENSE for details.

-

Release Notes

- -

This is still an early release, and API changes are expected.

- -

The distribution includes the core spectrum analysis, resynthesis, and -spectrogram rendering code, along with some example code and documentation. -Some features that have been implemented but are not yet ready for -release have been omitted, for example the support for parallel -analysis and synthesis using multiple CPU cores. Also, the current API -still lacks functions for convenient row- or column-wise access to -the coefficients at specific frequencies or times; the only way to -access the coefficients is the apply() method, which iterates over -the entire coefficient set or a time range in an indeterminate -order. -

-

Example Code

-

The following examples demonstrate the use of the library for -various applications. They are presented in a "literate +

The following examples demonstrate the use of the library in +various scenarios. They are presented in a "literate programming" style, with the code embedded in the commentary rather than the other way around. Concatenating the code fragments in each example yields a complete C++ @@ -62,24 +47,25 @@

Example Code

  • Example 1: Rendering a Spectrogram Image
  • Example 2: Frequency-Domain Filtering
  • Example 3: Streaming
  • - - -

    How it Works

    -

    The following document outlines the operation of the library.

    -

    API Reference

    -

    The following documents define the public library API. -Any classes and functions not listed here -should be considered private and are subject to change or -deletion without notice.

    +

    The following documents define the library API. +

    +

    How it Works

    +

    The following document outlines the operation of the library.

    + +

    FAQ

    • Is it real-time?
    • diff --git a/gaborator/gaborator-1.2/doc/overview.html b/gaborator/gaborator-1.7/doc/overview.html similarity index 89% rename from gaborator/gaborator-1.2/doc/overview.html rename to gaborator/gaborator-1.7/doc/overview.html index bc28618..cfb3857 100644 --- a/gaborator/gaborator-1.2/doc/overview.html +++ b/gaborator/gaborator-1.7/doc/overview.html @@ -1,16 +1,16 @@ -Overview +Overview of Operation -

      Overview

      +

      Overview of Operation

      The Gaborator performs three main functions:

        @@ -67,7 +67,7 @@

        Analysis

        that would make the plot hard to read because both the lowpass filter and the lowest-frequency bandpass filters would be extremely narrow.

        -Analysis filters +Analysis filters

        The output of each bandpass filter is shifted down in frequency to a complex quadrature baseband. The baseband signal is then resampled @@ -95,7 +95,7 @@

        Analysis

        conceptually, the grid extends arbitrarily far in time, in both the positive and the negative direction.

        -Sampling grid +Sampling grid

        Resynthesis

        @@ -107,8 +107,7 @@

        Resynthesis

        plot shows the frequency responses of the reconstruction filters corresponding to the analysis filters shown earlier.

        - -Reconstruction filters +Reconstruction filters

        Although the bandpass filters may look similar to the Gaussian filters of the analysis filter bank, their shapes are actually subtly @@ -119,7 +118,7 @@

        Spectrogram Rendering

        Rendering a spectrogram image from the coefficients involves taking the magnitude of each complex coefficient, and then resampling the resulting multi-resolution grid of magnitudes -into to an evenly spaced pixel grid.

        +into an evenly spaced pixel grid.

        Because the coefficient sample rate varies by frequency band, the resampling required in the horizontal (time) direction also varies. @@ -129,5 +128,8 @@

        Spectrogram Rendering

        between coefficients and pixels, and the low-frequency bands have more than one pixel per coefficient and require upsampling (interpolation).

        + + + diff --git a/gaborator/gaborator-1.2/doc/realtime.html b/gaborator/gaborator-1.7/doc/realtime.html similarity index 76% rename from gaborator/gaborator-1.2/doc/realtime.html rename to gaborator/gaborator-1.7/doc/realtime.html index 5db05ca..5ea5bec 100644 --- a/gaborator/gaborator-1.2/doc/realtime.html +++ b/gaborator/gaborator-1.7/doc/realtime.html @@ -1,6 +1,6 @@ @@ -71,32 +71,43 @@

        Does it have low latency?

        fraction of a second.

        In analysis and visualization applications that don't need to -perform resynthesis, it may be possible to partly hide the latency by +perform resynthesis, it is possible to partly hide the latency by taking advantage of the fact that the coefficients for the higher frequencies exhibit lower latency than those for low frequencies. For example, a live spectrogram display could update the high-frequency parts of the display before the corresponding low-frequency parts. Alternatively, low-frequency parts of the -spectrogram could be drawn multiple times, effectively animating +spectrogram may be drawn multiple times, effectively animating the display of the low-frequency coefficients as they converge to -their final values.

        +their final values. This approach can be seen in action in +the Spectrolite +iOS app.

        Does it support small blocks sizes?

        -

        Yes, but there is a severe performance penalty. +

        Yes, but there is a significant performance penalty. The Gaborator works most efficiently when the signal is processed in large blocks, preferably 217 samples or more, corresponding to several seconds of signal at typical audio sample -rates. A real-time application aiming for low latency will want to +rates.

        + +

        A real-time application aiming for low latency will want to use smaller blocks, for examples 25 to 210 -samples. The CPU time it takes to process such a small block will be almost -constant regardless of its size, so the total CPU time consumed will -rise proportionally to the inverse of the block size. For sufficiently -small blocks, it will exceed the duration of the signal, at which point -the system can no longer be considered real-time. For example, -analyzing a 44.1 kHz audio stream on a 2.5 GHz Intel Core i5 CPU, this -happens at block sizes below about 27 = 128 samples. -

        +samples, and processing these will be significantly slower. +For example, as of version 1.4, analyzing a signal in blocks of +210 samples takes roughly five times as much CPU as +analyzing it in blocks of 220 samples.

        + +

        For sufficiently small blocks, the processing time will exceed the +duration of the signal, at which point the system can no longer be +considered real-time. For example, analyzing a 48 kHz audio +stream on a 2.5 GHz Intel Core i5 CPU, this happens at block sizes +below about 24 = 16 samples.

        + +

        The resynthesis code is currently less optimized for small block +sizes than the analysis code, so the performance penalty for +resynthesizing small blocks is even greater than for analyzing small +blocks.

        Can it process a signal stream of any length?

        @@ -109,5 +120,7 @@

        Does it avoid dynamic memory allocation in the audio processing path?

        Currently, no — it dynamically allocates both the coefficient data structures and various temporary buffers.

        + + diff --git a/gaborator/gaborator-1.7/doc/ref/gaborator_h.html b/gaborator/gaborator-1.7/doc/ref/gaborator_h.html new file mode 100644 index 0000000..206901e --- /dev/null +++ b/gaborator/gaborator-1.7/doc/ref/gaborator_h.html @@ -0,0 +1,462 @@ + + + + + +Gaborator reference: gaborator.h + + +

        Gaborator reference: gaborator.h

        + +

        Spectrum Analysis Parameters

        + +

        A parameters object holds a set of parameters that +determine the frequency range and resolution of the spectrum +analysis.

        + +
        +class parameters {
        +
        + +
        +

        Constructor

        +
        +parameters(unsigned int bands_per_octave,
        +           double ff_min,
        +           double ff_ref = 1.0);
        +
        +
        +
        bands_per_octave
        +
        The number of frequency bands per octave. + Values from 4 to 384 (inclusive) are supported. +
        +
        ff_min
        +
        The lower limit of the analysis frequency range, in units of the + sample rate. The analysis filter bank will extend low enough in + frequency that ff_min falls between the two lowest + frequency bandpass filters. + Values from 0.001 to 0.13 are supported.
        +
        ff_ref
        +
        The reference frequency, in units of the sample rate. + This allows fine-tuning of the analysis and synthesis filter + banks such that the center frequency of one of the filters + is aligned with ff_ref. If ff_ref + falls outside the frequency range of the bandpass filter bank, this + works as if the range were extended to include + ff_ref. Must be positive. A typical value + when analyzing music is 440.0 / fs, where + fs is the sample rate in Hz. +
        +
        +

        Comparison

        +

        +Comparison operators are provided for compatibility with +standard container classes. The ordering is arbitrary but consistent. +

        +
        +bool operator<(const parameters &rhs) const;
        +bool operator==(const parameters &rhs) const;
        +
        + +
        +
        +};
        +
        + +

        Spectrogram Coefficients

        + +
        +template<class T> class analyzer;
        +
        + +

        +A coefs object stores a set of spectrogram coefficients. +It is a dynamic data structure and will be automatically grown to +accommodate new time ranges, for example as newly recorded audio is analyzed. +The template argument T +must match that of the analyzer (usually float). +The template argument C is the data type used to store each +coefficient value; there is usually no need to specify it explicitly as +it will default to std::complex<T>. +

        + +
        +template<class T, class C = std::complex<T>>
        +class coefs {
        +
        +
        +

        Constructor

        +
        +coefs(analyzer<T> &a);
        +
        +

        +Construct an empty set of coefficients for use with the spectrum +analyzer a. This represents a signal that is zero +at all points in time. +

        + +
        +
        +};
        +
        + +

        Spectrum Analyzer

        + +

        +The analyzer object performs spectrum analysis and/or resynthesis +according to the given parameters. The template argument T is +the floating-point type to use for the calculations. This is typically float; +alternatively, double can be used for increased accuracy at the +expense of speed and memory consumption.

        + +
        template<class T>
        +class analyzer {
        +
        + +

        Constructor

        + +
        +analyzer(const parameters &params);
        +
        +
        +
        params
        +
        The spectrum analysis parameters. +
        + +

        Analysis and synthesis

        + +
        +void
        +analyze(const T *signal,
        +        int64_t t0,
        +        int64_t t1,
        +        coefs<T> &coefs) const;
        +
        +

        Spectrum analyze the samples at *signal and add the +resulting coefficients to coefs. +

        +
        signal
        +
        The signal samples to analyze, beginning with the sample from time t0 + and ending with the last sample before time t1, for a total of + t1 - t0 samples. +
        t0
        +
        The point in time when the sample at signal[0] was taken, + in samples. For example, when analyzing an audio recording, this is typically + 0 for the first sample in the recording, but this reference point is arbitrary, + and negative times are valid. Accuracy begins to successively decrease + outside the range of about ±108 samples, so using + large time values should be avoided when they are not necessary because + of the length of the track. +
        +
        t1
        +
        The point in time of the sample one past the + end of the array of samples at signal, + in samples. +
        +
        coefs
        The coefficient object that the results of the + spectrum analysis are added to. +
        +

        If the coefs object already contains some +coefficients, the new coefficients are summed to those already +present. Because the analysis is a linear operation, this allows a +signal to be analyzed in blocks, by making multiple calls +to analyze() with non-overlapping ranges that together +cover the entire signal. For efficiency, the blocks should +be large, as in +analyze(first_131072_samples, 0, 131072, coefs), +analyze(next_131072_samples, 131072, 262144, coefs), +etc. +

        + +
        +void
        +synthesize(const coefs<T> &coefs,
        +           uint64_t t0,
        +           uint64_t t1,
        +           T *signal) const;
        +
        +

        Synthesize signal samples from the coefficients coef and store +them at *signal. +

        +
        +
        coefs
        The coefficients to synthesize the signal from.
        +
        t0
        +
        The point in time of the first sample to synthesize, + in samples, using the same time scale as in analyze().
        +
        t1
        +
        The point in time of the sample one past the last one to synthesize.
        +
        signal
        +
        The synthesized signal samples will be written here, + beginning with the sample from time t0 and + and ending with the last sample before time t1, + for a total of t1 - t0 samples.
        +
        +

        The time range t0...t1 may extend outside +the range analyzed using analyze(), in which case the +signal is assumed to be zero in the un-analyzed range.

        + +

        A signal may be synthesized in blocks by making multiple calls to +analyze() with different sample ranges. For efficiency, +the blocks should be large, and each t0 should +be multiple of a large power of two.

        + +

        Frequency Band Numbering

        + +

        The frequency bands of the analysis filter bank are numbered by +nonnegative integers that increase towards lower (sic) frequencies. +There is a number of bandpass bands corresponding to the +logarithmically spaced bandpass analysis filters, from near 0.5 +(half the sample rate) to +near fmin, and a single lowpass band containing the +residual signal from frequencies below fmin. +The numbering can be examined using the following methods: +

        + +
        +int bandpass_bands_begin() const;
        +
        +

        +Return the smallest valid bandpass band number, corresponding to the +highest-frequency bandpass filter.

        +
        +int bandpass_bands_end() const;
        +
        +

        +Return the bandpass band number one past the highest valid bandpass +band number, corresponding to one past the lowest-frequency bandpass +filter. +

        +
        +int band_lowpass() const;
        +
        +

        +Return the band number of the lowpass band. +

        +
        +int band_ref() const;
        +
        +

        +Return the band number corresponding to the reference frequency +ff_ref. If ff_ref falls within +the frequency range of the bandpass filter bank, this will +be a valid bandpass band number, otherwise it will not. +

        +
        +double band_ff(int band) const;
        +
        +

        +Return the center frequency of band number band, in units of the +sampling frequency. +

        + +

        Support

        +
        +double analysis_support() const;
        +
        +

        Returns the one-sided worst-case time domain support of any of the +analysis filters. When calling analyze() with a sample at time t, +only spectrogram coefficients within the time range t ± support +will be significantly changed. Coefficients outside the range may change, +but the changes will sufficiently small that they may be ignored without +significantly reducing accuracy.

        + +
        +double synthesis_support() const;
        +
        +

        Returns the one-sided worst-case time domain support of any of the +reconstruction filters. When calling synthesize() to +synthesize a sample at time t, the sample will only be +significantly affected by spectrogram coefficients in the time +range t ± support. Coefficients outside the range may +be used in the synthesis, but substituting zeroes for the actual +coefficient values will not significantly reduce accuracy.

        + +
        +
        +};
        +
        + +

        Functions

        + +

        Iterating Over Existing Coefficients

        + +
        +template <class T, class F, class C0, class... CI>
        +void process(F f,
        +             int b0,
        +             int b1,
        +             int64_t t0,
        +             int64_t t1,
        +             coefs<T, C0> &coefs0,
        +             coefs<T, CI>&... coefsi);
        +
        + +

        +Process one or more coefficient sets coefs0... by applying +the function f to each coefficient present in coefs0, +in an indeterminate order.

        +

        +

        This can be optionally limited to coefficients whose +band number b and sample time t satisfy +b0b < b1 and +t0t < t1. +To process every coefficient present +in coefs0, pass INT_MIN, INT_MAX, INT64_MIN, INT64_MAX +for the arguments b0, b1, t0, +and t1, respectively. +

        +

        The function f should have the call signature

        +
        +
        +template<class T>
        +void f(int b, int64_t t, std::complex<T> &c0, std::complex<T> &ci...);
        +
        +

        where

        +
        +
        b
        +
        The band number of the frequency band the coefficients + c0 and ci... pertain to. + This may be either a bandpass band or the lowpass band.
        +
        t
        +
        The point in time the coefficients c0 and + ci... pertain to, in samples
        +
        c0
        +
        A reference to a complex coefficient from coefs0
        +
        ci...
        +
        Optional references to complex coefficients from the additional + coefficient sets coefsi....
        +
        +
        + + +

        The function f may read and/or modify each of the +coefficients passed through c0 and each +ci....

        + +

        The first coefficient set c0 is a special case when +it comes to the treatment of missing values. Coefficients missing +from c0 will not be iterated over at all, but when a +coefficient is iterated over and is missing from one of the additional +coefficient sets ci..., it will be automatically created +and initialized to zero in that additional coefficient set.

        + +

        Note: The template parameters C0 +and CI... exist to support the processing of coefficient +sets containing data of types other +than std::complex<T>, which is not currently part of the +documented API. In typical use, there is no need to specify them when +calling apply() because the template parameter list +can be deduced, but if they are expicitly specified, they should all +be std::complex<T>. +

        + +

        Creating New Coefficients

        + +
        +template <class T, class F, class C0, class... CI>
        +void fill(F f,
        +          int b0,
        +          int b1,
        +          int64_t t0,
        +          int64_t t1,
        +          coefs<T, C0> &coefs0,
        +          coefs<T, CI>&... coefsi);
        +
        +

        +Fill a region of the time-frequency plane with coefficients +and apply the function f to each. +

        +

        This works like process() except that it is not limited +to processing coefficients that already exist in coefs0; +instead, any missing coefficients in coefs0 as well as +any of the coefsi... are created and initialized to zero +before f is called.

        + +

        The t0 and t1 arguments must specify an +explicit, bounded time range — they must not be given as +INT64_MIN and/or INT64_MAX as that would mean creating coefficients +for an an astronomically large time range, requiring a correspondingly +astronomical amount of memory.

        + +

        Forgetting Coefficients

        +
        +template <class T>
        +void forget_before(const analyzer<T> &a,
        +                   coefs<T> &c,
        +                   int64_t limit);
        +
        +

        Allow the coefficients for points in time before limit +(a time in units of samples) to be forgotten. +Streaming applications can use this to free memory used by coefficients +that are no longer needed. Coefficients that have been forgotten will +read as zero. This does not guarantee that all coefficients before +limit are forgotten, only that ones for +limit or later are not, and that the amount of memory +consumed by any remaining coefficients before limit is +bounded.

        + +

        Legacy API For Iterating Over Existing Coefficients

        + +

        Prior to version 1.5, the only way to iterate over +coefficients was the apply() function. +It is similar to process(), except that it +

        +
          +
        • requires an additional analyzer argument, +
        • takes arguments in a different order, +
        • applies a function f taking arguments in a different order, +
        • does not support restricting the processing to a range of band numbers, +
        • only supports iterating over a single coefficient set, and +
        • provides default values for t0 and t1. +
        +

        In new code, process() is preferred.

        + +
        +template <class T, class F>
        +void apply(const analyzer<T> &a,
        +           coefs<T> &c,
        +           F f,
        +           int64_t t0 = INT64_MIN,
        +           int64_t t1 = INT64_MAX);
        +
        +

        +Apply the function f to each coefficient in the coefficient +set c for points in time t that satisfy +t0t < t1. +If the t0 and t1 arguments are omitted, f +is applied to every coefficient. +

        +
        +
        a
        +
        The spectrum analyzer that produced the coefficients c
        +
        c
        +
        A set of spectrogram coefficients
        +
        f
        +
        A function to apply to each coefficient in c, + with the call signature +
        +template<class T>
        +void f(std::complex<T> &coef, int band, int64_t t);
        +
        +
        +
        coef
        +
        A reference to a single complex coefficient. This may be read and/or modified.
        +
        band
        +
        The band number of the frequency band the coefficient coef0 pertains to. + This may be either a bandpass band or the lowpass band.
        +
        t
        +
        The point in time the coefficient c0 pertains to, in samples
        +
        t0
        When not INT64_MIN, only apply f to the coefficients for time ≥ t0
        +
        t1
        When not INT64_MAX, only apply f to the coefficients for time < t1
        +
        +
        +
        + + + + + diff --git a/gaborator/gaborator-1.7/doc/ref/intro.html b/gaborator/gaborator-1.7/doc/ref/intro.html new file mode 100644 index 0000000..d45d775 --- /dev/null +++ b/gaborator/gaborator-1.7/doc/ref/intro.html @@ -0,0 +1,41 @@ + + + + + +Gaborator reference: API Introdution + + +

        Gaborator reference: API Introduction

        + +

        The public API of the Gaborator library is defined in the HTML +documentation in the form of annotated C++ declarations. These are +similar to the actual declarations in the respective header files, but +simplified for clarity and omitting implementation details.

        + +

        The actual implementation in the header file may be different in a +number of ways but nonetheless compatible with the documented API. +For example, classes may be declared using the keyword struct +rather than class, function parameter names may be +different, types may be declared using different but equivalent +typedefs, and functions or templates in the header file may have +additional arguments with default values. Any classes, functions, and +other definitions not mentioned in the documentation should be +considered private and are subject to change or deletion without +notice. +

        + +

        All definitions are in the namespace gaborator. +Applications need to either prefix class names +with gaborator::, or use using namespace +gaborator;. +

        + + + + + diff --git a/gaborator/gaborator-1.2/doc/ref/render_h.html b/gaborator/gaborator-1.7/doc/ref/render_h.html similarity index 81% rename from gaborator/gaborator-1.2/doc/ref/render_h.html rename to gaborator/gaborator-1.7/doc/ref/render_h.html index ae00b47..2ea3351 100644 --- a/gaborator/gaborator-1.2/doc/ref/render_h.html +++ b/gaborator/gaborator-1.7/doc/ref/render_h.html @@ -1,6 +1,6 @@ @@ -20,10 +20,10 @@

        Spectrogram Rendering with Power-of-Two Scaling

        int64_t xorigin, int64_t yorigin, int64_t xi0, int64_t xi1, int xe, int64_t yi0, int64_t yi1, int ye, - OI output) + OI output);

    Render a rectangular array of pixel values representing signal -signal amplitudes in time-frequency space, optionally scaling up or +amplitudes in time-frequency space, optionally scaling up or down by powers of two.

    @@ -58,15 +58,17 @@

    Spectrogram Rendering with Power-of-Two Scaling

    Utility Functions

     template <class T>
    -unsigned int float2pixel_8bit(T amp)
    +unsigned int float2pixel_8bit(T amp);
     

    Convert a normalized amplitude value to a 8-bit greyscale pixel value.

    amp
    A floating point value representing a signal amplitude, nominally ranging from 0 to 1
    -

    Returns an pixel value ranging from 0 to 255 (inclusive), nominally -using the sRGB gamma.

    +

    Returns an pixel value ranging from 0 to 255 (inclusive), using an +approximation of the sRGB gamma.

    + + diff --git a/gaborator/gaborator-1.2/doc/render.html b/gaborator/gaborator-1.7/doc/render.html similarity index 95% rename from gaborator/gaborator-1.2/doc/render.html rename to gaborator/gaborator-1.7/doc/render.html index 34b47d0..981f954 100644 --- a/gaborator/gaborator-1.2/doc/render.html +++ b/gaborator/gaborator-1.7/doc/render.html @@ -1,6 +1,6 @@ @@ -57,14 +57,15 @@

    Reading the Audio

    The audio file is read using the libsndfile library and stored in a std::vector<float>. Note that although libsndfile is used in this example, -the Gaborator library itself does not depend or +the Gaborator library itself does not depend on or use libsndfile.

         SF_INFO sfinfo;
         memset(&sfinfo, 0, sizeof(sfinfo));
         SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo);
         if (! sf_in) {
    -        std::cerr << "could not open input audio file\n";
    +        std::cerr << "could not open input audio file: "
    +            << sf_strerror(sf_in) << "\n";
             exit(1);
         }
         double fs = sfinfo.samplerate;
    @@ -207,7 +208,7 @@ 

    Rendering an Image

    Although a horizontal scale of one pixel per signal sample is a mathematically pleasing reference point, this reference scale is not -used in practice because it would result in spectrogram that is much +used in practice because it would result in a spectrogram that is much too stretched out horizontally. A more typical scale factor might be 210 = 1024, yielding one pixel for every 1024 signal samples, which is about one pixel per 23 milliseconds of signal at a @@ -322,7 +323,7 @@

    Postamble

    }
    -

    Compiling

    +

    Compiling

    If you are using macOS, Linux, NetBSD, or a similar system, you can build the example by running the following command in the examples @@ -330,7 +331,7 @@

    Compiling

    You need to have libsndfile is installed and supported by pkg-config.

    -
    +
     c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` render.cc `pkg-config --libs sndfile` -o render
     
    @@ -352,24 +353,24 @@

    Compiling for Speed

    or the exact version that was used for testing from gaborator.com:

    -
    +
     wget http://download.gaborator.com/mirror/pffft/29e4f76ac53b.zip
     unzip 29e4f76ac53b.zip
     mv jpommier-pffft-29e4f76ac53b pffft
     

    Then, compile it:

    -
    +
     cc -c -O3 -ffast-math pffft/pffft.c -o pffft/pffft.o
     

    (If you are building for ARM, you will need to add -mfpu=neon to both the above compilation command and the ones below.)

    PFFFT is single precision only, but it comes with a copy of FFTPACK which can be used for double-precision FFTs. Let's compile that, too:

    -
    +
     cc -c -O3 -ffast-math -DFFTPACK_DOUBLE_PRECISION pffft/fftpack.c -o pffft/fftpack.o
     

    Then build the example and link it with both PFFFT and FFTPACK:

    -
    +
     c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` render.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o render
     
    @@ -388,5 +389,7 @@

    Example Output

    The JPEG file produced by the above will look like this:

    Spectrogram + + diff --git a/gaborator/gaborator-1.7/doc/snr.html b/gaborator/gaborator-1.7/doc/snr.html new file mode 100644 index 0000000..632c320 --- /dev/null +++ b/gaborator/gaborator-1.7/doc/snr.html @@ -0,0 +1,121 @@ + + + + + +Gaborator Example 4: Measuring the Signal-to-Noise Ratio + + +

    Example 4: Measuring the Signal-to-Noise Ratio

    + +

    Introduction

    + +

    This example measures the signal-to-noise ratio (SNR) of the +resynthesis by analyzing and resynthesizing a test signal +and comparing the resynthesis result to the original. +

    + +

    Since it does not involve any audio file I/O, this example +does not require the sndfile library, making it the shortest +and simplest one by far.

    + +

    Preamble

    + +
    +#include <iostream>
    +#include <iomanip>
    +#include <random>
    +#include <gaborator/gaborator.h>
    +
    + +

    Amplitude Measurement

    +

    To calculate the signal-to-noise ratio, we need to measure the +amplitude of the orignal signal and the error residue. We will use +the root-mean-square amplitude, which is calculcated by the +function rms(). +

    +
    +double rms(const std::vector<float> &v) {
    +    double sqsum = 0;
    +    for (size_t i = 0; i < v.size(); i++) {
    +        sqsum += v[i] * v[i];
    +    }
    +    return sqrt(sqsum);
    +}
    +
    + +

    Main Program

    + +

    For the test signal, we use a million samples of white noise with a +uniform amplitude distribution between -1 and +1.

    +
    +int main(int argc, char **argv) {
    +    size_t len = 1000000;
    +    std::vector<float> signal_in(len);
    +    std::minstd_rand rand;
    +    std::uniform_real_distribution<> uniform(-1.0, 1.0);
    +    for (size_t i = 0; i < len; i++)
    +        signal_in[i] = uniform(rand);
    +
    +

    Then we create a spectrum analyzer with 48 bands per octave +and a frequency range of 3 decades (0.0005 to 0.5 times the sample rate):

    +
    +    gaborator::parameters params(48, 5e-4);
    +    gaborator::analyzer<float> analyzer(params);
    +
    +

    ...and run the spectrum analyzis:

    +
    +    gaborator::coefs<float> coefs(analyzer);
    +    analyzer.analyze(signal_in.data(), 0, len, coefs);
    +
    +

    ...resynthesize the signal into signal_out: +

    +    std::vector<float> signal_out(len);
    +    analyzer.synthesize(coefs, 0, len, signal_out.data());
    +
    +

    ...measure the resynthesis error:

    +
    +    std::vector<float> error(len);
    +    for (size_t i = 0; i < len; i++)
    +         error[i] = signal_out[i] - signal_in[i];
    +
    +

    ...calculate the signal-to-noise ratio:

    +
    +    double snr = rms(signal_in) / rms(error);
    +
    +

    ...and print it in decibels:

    +
    +    std::cout << std::fixed << std::setprecision(1) << 20 * log10(snr) << " dB\n";
    +}
    +
    +

    Compiling

    +

    Like Example 1, this example +can be built using a one-line build command: +

    +
    +c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` snr.cc `pkg-config --libs sndfile` -o snr
    +
    +

    Or using the vDSP FFT on macOS:

    +
    +c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` snr.cc `pkg-config --libs sndfile` -framework Accelerate -o snr
    +
    +

    Or using PFFFT (see Example 1 for how to download and build PFFFT):

    +
    +c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` snr.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o snr
    +
    + +

    Running

    +

    The program is run with no arguments:

    +
    +./snr
    +
    +

    This will print the SNR which should be more than 100 dB if the library is working correctly.

    + + + + + diff --git a/gaborator/gaborator-1.2/doc/spectrogram.jpg b/gaborator/gaborator-1.7/doc/spectrogram.jpg similarity index 100% rename from gaborator/gaborator-1.2/doc/spectrogram.jpg rename to gaborator/gaborator-1.7/doc/spectrogram.jpg diff --git a/gaborator/gaborator-1.2/doc/stream.html b/gaborator/gaborator-1.7/doc/stream.html similarity index 84% rename from gaborator/gaborator-1.2/doc/stream.html rename to gaborator/gaborator-1.7/doc/stream.html index 6663cc8..f270ecc 100644 --- a/gaborator/gaborator-1.2/doc/stream.html +++ b/gaborator/gaborator-1.7/doc/stream.html @@ -1,6 +1,6 @@ @@ -49,7 +49,8 @@

    Opening the Streams

    memset(&sfinfo, 0, sizeof(sfinfo)); SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo); if (! sf_in) { - std::cerr << "could not open input audio file\n"; + std::cerr << "could not open input audio file: " + << sf_strerror(sf_in) << "\n"; exit(1); } if (sfinfo.channels != 1) { @@ -60,7 +61,8 @@

    Opening the Streams

    SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo); if (! sf_out) { - std::cerr << "could not open output audio file\n"; + std::cerr << "could not open output audio file: " + << sf_strerror(sf_out) << "\n"; exit(1); }
    @@ -109,8 +111,8 @@

    Calculating Latency

    size_t analysis_support = ceil(analyzer.analysis_support());

    Similarly, when resynthesizing audio from coefficients, calculating -a sample at time t involves applying a symmetric FIR -reconstruction filter, calculating a weighted average of both past and +a sample at time t involves applying symmetric FIR +reconstruction filters, calculating a weighted average of both past and future spectrogram coefficients. The support of the widest reconstruction filter can be calculated by calling gaborator::analyzer::synthesis_support(): @@ -183,18 +185,19 @@

    Streaming

    Therefore, it is now safe to examine and/or modify these coefficients as required by your application. Here, by way of example, we simply change their signs to invert the phase of the signal. -Note that unlike the earlier filter example where apply() +Note that unlike the earlier filter example where prorcess() applied a function to all the coefficients, here it is applied only to -the coefficients within the time range specified by the last two arguments. +the coefficients within a limited time range.

    -        apply(
    -            analyzer, coefs,
    -            [&](std::complex<float> &coef, int, int64_t) {
    +        process(
    +            [&](int, int64_t, std::complex<float> &coef) {
                      coef = -coef;
                 },
    -            t_in - analysis_support,
    -            t_in - analysis_support + blocksize);
    +            INT_MIN, INT_MAX,
    +            t_in - (int)analysis_support,
    +            t_in - (int)analysis_support + (int)blocksize,
    +            coefs);
     

    Next, we will generate a block of output samples. To get correct results, @@ -204,7 +207,7 @@

    Streaming

    time range t - synthesis_support...t + synthesis_support. To ensure that the resynthesis uses only coefficients that have already been processed by -the apply() call above, the most recent block of samples +the process() call above, the most recent block of samples that can safely be resynthesized ranges from t_out = t_in - analysis_support - synthesis_support to t_out + blocksize.

    @@ -244,26 +247,33 @@

    Postamble

    Compiling

    Like the previous ones, this example can also be built using a one-line build command:

    -
    +
     c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` stream.cc `pkg-config --libs sndfile` -o stream
     

    Or using the vDSP FFT on macOS:

     c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` stream.cc `pkg-config --libs sndfile` -framework Accelerate -o stream
     
    -

    Or using PFFFT (see Example 1 for how to download and build PFFFT):

    -
    +

    Or using PFFFT (see Example 1 for how to download and build PFFFT):

    +
     c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` stream.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o stream
     

    Running

    -

    To process the file guitar.wav that was downloaded in -Example 1, run

    +

    Running the following shell commands will download an example +audio file containing an impulse (a single sample of maximum amplitude) +padded with silence to a total of 65536 samples, and process it.

    -./stream guitar.wav guitar_streamed.wav
    +wget http://download.gaborator.com/audio/impulse.wav
    +./stream impulse.wav impulse_streamed.wav
     
    -

    The file guitar_streamed.wav will be identical to guitar.wav -except that the signal will be of opposite polarity, and delayed by the latency of + +

    The file impulse_streamed.wav will be identical to +impulse.wav except that the impulse will be of +opposite polarity, and delayed by the latency of analysis_support + synthesis_support samples.

    + + + diff --git a/gaborator/gaborator-1.7/doc/synth.html b/gaborator/gaborator-1.7/doc/synth.html new file mode 100644 index 0000000..edfa6f2 --- /dev/null +++ b/gaborator/gaborator-1.7/doc/synth.html @@ -0,0 +1,213 @@ + + + + + +Gaborator Example 5: Synthesis from Scratch + + +

    Example 5: Synthesis from Scratch

    + +

    Introduction

    + +

    This example demonstrates how to synthesize a signal by creating +spectrogram coefficients from scratch rather than by analyzing an +existing signal. It creates a random pentatonic melody of decaying +sine waves as spectrogram coefficients and then synthesizes audio +from them. +

    + +

    Preamble

    + +

    This example program takes a single command line argument, the name +of the output file.

    +
    +#include <memory.h>
    +#include <iostream>
    +#include <sndfile.h>
    +#include <gaborator/gaborator.h>
    +
    +int main(int argc, char **argv) {
    +    if (argc < 2) {
    +        std::cerr << "usage: synth output.wav\n";
    +        exit(1);
    +    }
    +
    + +

    Synthesis Parameters

    + +

    Although this example does not perform any analysis, we nonetheless +need to create an analyzer object, as it is used for both +analysis and synthesis purposes. To generate the frequencies of the +12-note equal-tempered scale, we need 12 bands per octave; a multiple +of 12 would also work, but here we don't need the added frequency +resolution that would bring, and the time resolution would be +worse.

    + +

    To simplify converting MIDI note numbers to band numbers, we choose +the frequency of MIDI note 0 as the reference frequency; this is +8.18 Hz, which happens to be outside the frequency range of the +bandpass filter bank, but that doesn't matter.

    + +
    +    double fs = 44100;
    +    gaborator::parameters params(12, 20.0 / fs, 8.18 / fs);
    +    gaborator::analyzer<float> analyzer(params);
    +
    + +

    Melody Parameters

    + +

    +We will use the A minor pentatonic scale, which contains the +following notes (using the MIDI note numbering):

    +
    +    static int pentatonic[] = { 57, 60, 62, 64, 67 };
    +
    + +

    +The melody will consist of 64 notes, at a tempo of 120 beats per +minute: +

    +
    +    int n_notes = 64;
    +    double tempo = 120.0;
    +    double beat_duration = 60.0 / tempo;
    +
    + +

    +The variable volume determines the amplitude of +each note, and has been chosen such that there will be no clipping +of the final output. +

    +
    +    float volume = 0.2;
    +
    + +

    Composition

    + +

    We start with an empty coefficient set:

    +
    +    gaborator::coefs<float> coefs(analyzer);
    +
    + +

    Each note is chosen randomly from the pentatonic scale and added +to the coefficient set by calling the function fill(). +The fill() function is similar to the process() +function used in previous examples, except that it can be used to +create new coefficients rather than just modifying existing ones.

    + +

    Each note is created by calling fill() on a region of +the time-frequency plane that covers a single band in the frequency +dimension and the duration of the note in the time dimension. Each +coefficient within this region is set to a complex number whose +magnitude decays exponentially over time, like the amplitude of a +plucked string. The phase is arbitrarily set to zero by using an +imaginary part of zero. Since notes can overlap, the new coefficients +are added to any existing ones using the += operator +rather than overwriting them.

    + +

    Note that band numbers increase towards lower frequencies but MIDI +note numbers increase towards higher frequencies, hence the minus sign +in front of midi_note. +

    + +
    +    for (int i = 0; i < n_notes; i++) {
    +        int midi_note = pentatonic[rand() % 5];
    +        double note_start_time = beat_duration * i;
    +        double note_end_time = note_start_time + 3.0;
    +        int band = analyzer.band_ref() - midi_note;
    +        fill([&](int, int64_t t, std::complex<float> &coef) {
    +                float amplitude =
    +                    volume * expf(-2.0f * (float)(t / fs - note_start_time));
    +                coef += std::complex<float>(amplitude, 0.0f);
    +            },
    +            band, band + 1,
    +            note_start_time * fs, note_end_time * fs,
    +            coefs);
    +    }
    +
    + +

    Synthesis

    + +

    We can now synthesize audio from the coefficients by +calling synthesize(). Audio will be generated +starting half a second before the first note to allow for the pre-ringing +of the synthesis filter, and ending a few seconds after the +last note to allow for its decay. +

    +
    +    double audio_start_time = -0.5;
    +    double audio_end_time = beat_duration * n_notes + 5.0;
    +    int64_t start_frame = audio_start_time * fs;
    +    int64_t end_frame = audio_end_time * fs;
    +    size_t n_frames = end_frame - start_frame;
    +    std::vector<float> audio(n_frames);
    +    analyzer.synthesize(coefs, start_frame, end_frame, audio.data());
    +
    + +

    Writing the Audio

    + +

    Since there is no input audio file to inherit a file format from, +we need to choose a file format for the output file by filling in the +sfinfo structure:

    +
    +    SF_INFO sfinfo;
    +    memset(&sfinfo, 0, sizeof(sfinfo));
    +    sfinfo.samplerate = fs;
    +    sfinfo.channels = 1;
    +    sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
    +
    + +

    The rest is identical to +Example 2: +

    +
    +    SNDFILE *sf_out = sf_open(argv[1], SFM_WRITE, &sfinfo);
    +    if (! sf_out) {
    +        std::cerr << "could not open output audio file: "
    +            << sf_strerror(sf_out) << "\n";
    +        exit(1);
    +    }
    +    sf_command(sf_out, SFC_SET_CLIPPING, NULL, SF_TRUE);
    +    sf_count_t n_written = sf_writef_float(sf_out, audio.data(), n_frames);
    +    if (n_written != n_frames) {
    +        std::cerr << "write error\n";
    +        exit(1);
    +    }
    +    sf_close(sf_out);
    +    return 0;
    +}
    +
    + +

    Compiling

    +

    Like Example 1, this example +can be built using a one-line build command: +

    +
    +c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` synth.cc `pkg-config --libs sndfile` -o synth
    +
    +

    Or using the vDSP FFT on macOS:

    +
    +c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` synth.cc `pkg-config --libs sndfile` -framework Accelerate -o synth
    +
    +

    Or using PFFFT (see Example 1 for how to download and build PFFFT):

    +
    +c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` synth.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o synth
    +
    + +

    Running

    +

    The example program can be run using the command

    +
    +./synth melody.wav
    +
    +

    The resulting audio will be in melody.wav.

    + + + + + diff --git a/gaborator/gaborator-1.2/examples/filter.cc b/gaborator/gaborator-1.7/examples/filter.cc similarity index 83% rename from gaborator/gaborator-1.2/examples/filter.cc rename to gaborator/gaborator-1.7/examples/filter.cc index abced1b..ad3891d 100644 --- a/gaborator/gaborator-1.2/examples/filter.cc +++ b/gaborator/gaborator-1.7/examples/filter.cc @@ -14,7 +14,8 @@ int main(int argc, char **argv) { memset(&sfinfo, 0, sizeof(sfinfo)); SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo); if (! sf_in) { - std::cerr << "could not open input audio file\n"; + std::cerr << "could not open input audio file: " + << sf_strerror(sf_in) << "\n"; exit(1); } double fs = sfinfo.samplerate; @@ -31,7 +32,7 @@ int main(int argc, char **argv) { gaborator::analyzer analyzer(params); std::vector band_gains(analyzer.bands_end()); for (int band = analyzer.bandpass_bands_begin(); band < analyzer.bandpass_bands_end(); band++) { - float f_hz = analyzer.band_ff(band) * fs; + double f_hz = analyzer.band_ff(band) * fs; band_gains[band] = 1.0 / sqrt(f_hz / 20.0); } band_gains[analyzer.band_lowpass()] = band_gains[analyzer.bandpass_bands_end() - 1]; @@ -41,17 +42,20 @@ int main(int argc, char **argv) { channel[i] = audio[i * sfinfo.channels + ch]; gaborator::coefs coefs(analyzer); analyzer.analyze(channel.data(), 0, channel.size(), coefs); - apply(analyzer, coefs, - [&](std::complex &coef, int band, int64_t) { + process([&](int band, int64_t, std::complex &coef) { coef *= band_gains[band]; - }); + }, + INT_MIN, INT_MAX, + INT64_MIN, INT64_MAX, + coefs); analyzer.synthesize(coefs, 0, channel.size(), channel.data()); for (sf_count_t i = 0; i < n_frames; i++) audio[i * sfinfo.channels + ch] = channel[i]; } SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo); if (! sf_out) { - std::cerr << "could not open output audio file\n"; + std::cerr << "could not open output audio file: " + << sf_strerror(sf_out) << "\n"; exit(1); } sf_command(sf_out, SFC_SET_CLIPPING, NULL, SF_TRUE); diff --git a/gaborator/gaborator-1.2/examples/render.cc b/gaborator/gaborator-1.7/examples/render.cc similarity index 95% rename from gaborator/gaborator-1.2/examples/render.cc rename to gaborator/gaborator-1.7/examples/render.cc index 2798476..72cd7d3 100644 --- a/gaborator/gaborator-1.2/examples/render.cc +++ b/gaborator/gaborator-1.7/examples/render.cc @@ -15,7 +15,8 @@ int main(int argc, char **argv) { memset(&sfinfo, 0, sizeof(sfinfo)); SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo); if (! sf_in) { - std::cerr << "could not open input audio file\n"; + std::cerr << "could not open input audio file: " + << sf_strerror(sf_in) << "\n"; exit(1); } double fs = sfinfo.samplerate; diff --git a/gaborator/gaborator-1.7/examples/snr.cc b/gaborator/gaborator-1.7/examples/snr.cc new file mode 100644 index 0000000..b4d65d8 --- /dev/null +++ b/gaborator/gaborator-1.7/examples/snr.cc @@ -0,0 +1,32 @@ +// See ../doc/snr.html for commentary + +#include +#include +#include +#include +double rms(const std::vector &v) { + double sqsum = 0; + for (size_t i = 0; i < v.size(); i++) { + sqsum += v[i] * v[i]; + } + return sqrt(sqsum); +} +int main(int argc, char **argv) { + size_t len = 1000000; + std::vector signal_in(len); + std::minstd_rand rand; + std::uniform_real_distribution<> uniform(-1.0, 1.0); + for (size_t i = 0; i < len; i++) + signal_in[i] = uniform(rand); + gaborator::parameters params(48, 5e-4); + gaborator::analyzer analyzer(params); + gaborator::coefs coefs(analyzer); + analyzer.analyze(signal_in.data(), 0, len, coefs); + std::vector signal_out(len); + analyzer.synthesize(coefs, 0, len, signal_out.data()); + std::vector error(len); + for (size_t i = 0; i < len; i++) + error[i] = signal_out[i] - signal_in[i]; + double snr = rms(signal_in) / rms(error); + std::cout << std::fixed << std::setprecision(1) << 20 * log10(snr) << " dB\n"; +} diff --git a/gaborator/gaborator-1.2/examples/stream.cc b/gaborator/gaborator-1.7/examples/stream.cc similarity index 83% rename from gaborator/gaborator-1.2/examples/stream.cc rename to gaborator/gaborator-1.7/examples/stream.cc index 0c41857..fb2da1b 100644 --- a/gaborator/gaborator-1.2/examples/stream.cc +++ b/gaborator/gaborator-1.7/examples/stream.cc @@ -14,7 +14,8 @@ int main(int argc, char **argv) { memset(&sfinfo, 0, sizeof(sfinfo)); SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo); if (! sf_in) { - std::cerr << "could not open input audio file\n"; + std::cerr << "could not open input audio file: " + << sf_strerror(sf_in) << "\n"; exit(1); } if (sfinfo.channels != 1) { @@ -25,13 +26,12 @@ int main(int argc, char **argv) { SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo); if (! sf_out) { - std::cerr << "could not open output audio file\n"; + std::cerr << "could not open output audio file: " + << sf_strerror(sf_out) << "\n"; exit(1); } sf_command(sf_in, SFC_SET_NORM_FLOAT, NULL, SF_FALSE); sf_command(sf_out, SFC_SET_NORM_FLOAT, NULL, SF_FALSE); - - gaborator::parameters params(12, 200.0 / fs, 440.0 / fs); gaborator::analyzer analyzer(params); size_t analysis_support = ceil(analyzer.analysis_support()); @@ -48,13 +48,14 @@ int main(int argc, char **argv) { if (n_read < blocksize) std::fill(buf.data() + n_read, buf.data() + blocksize, 0); analyzer.analyze(buf.data(), t_in, t_in + blocksize, coefs); - apply( - analyzer, coefs, - [&](std::complex &coef, int, int64_t) { + process( + [&](int, int64_t, std::complex &coef) { coef = -coef; }, - t_in - analysis_support, - t_in - analysis_support + blocksize); + INT_MIN, INT_MAX, + t_in - (int)analysis_support, + t_in - (int)analysis_support + (int)blocksize, + coefs); int64_t t_out = t_in - analysis_support - synthesis_support; analyzer.synthesize(coefs, t_out, t_out + blocksize, buf.data()); sf_count_t n_written = sf_writef_float(sf_out, buf.data(), blocksize); diff --git a/gaborator/gaborator-1.7/examples/synth.cc b/gaborator/gaborator-1.7/examples/synth.cc new file mode 100644 index 0000000..b5aff70 --- /dev/null +++ b/gaborator/gaborator-1.7/examples/synth.cc @@ -0,0 +1,62 @@ +// See ../doc/synth.html for commentary + +#include +#include +#include +#include + +int main(int argc, char **argv) { + if (argc < 2) { + std::cerr << "usage: synth output.wav\n"; + exit(1); + } + double fs = 44100; + gaborator::parameters params(12, 20.0 / fs, 8.18 / fs); + gaborator::analyzer analyzer(params); + static int pentatonic[] = { 57, 60, 62, 64, 67 }; + int n_notes = 64; + double tempo = 120.0; + double beat_duration = 60.0 / tempo; + float volume = 0.2; + gaborator::coefs coefs(analyzer); + for (int i = 0; i < n_notes; i++) { + int midi_note = pentatonic[rand() % 5]; + double note_start_time = beat_duration * i; + double note_end_time = note_start_time + 3.0; + int band = analyzer.band_ref() - midi_note; + fill([&](int, int64_t t, std::complex &coef) { + float amplitude = + volume * expf(-2.0f * (float)(t / fs - note_start_time)); + coef += std::complex(amplitude, 0.0f); + }, + band, band + 1, + note_start_time * fs, note_end_time * fs, + coefs); + } + double audio_start_time = -0.5; + double audio_end_time = beat_duration * n_notes + 5.0; + int64_t start_frame = audio_start_time * fs; + int64_t end_frame = audio_end_time * fs; + size_t n_frames = end_frame - start_frame; + std::vector audio(n_frames); + analyzer.synthesize(coefs, start_frame, end_frame, audio.data()); + SF_INFO sfinfo; + memset(&sfinfo, 0, sizeof(sfinfo)); + sfinfo.samplerate = fs; + sfinfo.channels = 1; + sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + SNDFILE *sf_out = sf_open(argv[1], SFM_WRITE, &sfinfo); + if (! sf_out) { + std::cerr << "could not open output audio file: " + << sf_strerror(sf_out) << "\n"; + exit(1); + } + sf_command(sf_out, SFC_SET_CLIPPING, NULL, SF_TRUE); + sf_count_t n_written = sf_writef_float(sf_out, audio.data(), n_frames); + if (n_written != n_frames) { + std::cerr << "write error\n"; + exit(1); + } + sf_close(sf_out); + return 0; +} diff --git a/gaborator/gaborator-1.7/gaborator/affine_transform.h b/gaborator/gaborator-1.7/gaborator/affine_transform.h new file mode 100644 index 0000000..c2ea997 --- /dev/null +++ b/gaborator/gaborator-1.7/gaborator/affine_transform.h @@ -0,0 +1,42 @@ +// +// A class for affine transforms (ax + b) of scalar values +// +// Copyright (C) 2020-2021 Andreas Gustafsson. This file is part of +// the Gaborator library source distribution. See the file LICENSE at +// the top level of the distribution for license information. +// + +#ifndef _GABORATOR_AFFINE_TRANSFORM_H +#define _GABORATOR_AFFINE_TRANSFORM_H + +namespace gaborator { + +struct affine_transform { + affine_transform(): a(0), b(0) { } + affine_transform(double a_, double b_): a(a_), b(b_) { } + affine_transform(const affine_transform &rhs): a(rhs.a), b(rhs.b) { } + double operator()(double x) const { return a * x + b; } + affine_transform inverse() const { + return affine_transform(1.0 / a, -b / a); + } + static affine_transform identity() { return affine_transform(1, 0); } + double a, b; +}; + +// Composition + +static inline affine_transform +operator *(const affine_transform &a, const affine_transform &b) { + return affine_transform(a.a * b.a, a.a * b.b + a.b); +} + +// Equality + +static inline bool +operator ==(const affine_transform &a, const affine_transform &b) { + return a.a == b.a && a.b == b.b; +} + +} // namespace + +#endif diff --git a/gaborator/gaborator-1.2/gaborator/fft.h b/gaborator/gaborator-1.7/gaborator/fft.h similarity index 52% rename from gaborator/gaborator-1.2/gaborator/fft.h rename to gaborator/gaborator-1.7/gaborator/fft.h index e0a0e37..66921e9 100644 --- a/gaborator/gaborator-1.2/gaborator/fft.h +++ b/gaborator/gaborator-1.7/gaborator/fft.h @@ -1,7 +1,7 @@ // // Fast Fourier transform // -// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 2016-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -13,8 +13,17 @@ #if GABORATOR_USE_VDSP #include "gaborator/fft_vdsp.h" +#define GABORATOR_USE_REAL_FFT 1 +#define GABORATOR_MIN_FFT_SIZE 1 #elif GABORATOR_USE_PFFFT #include "gaborator/fft_pffft.h" +#define GABORATOR_USE_REAL_FFT 1 +#define GABORATOR_MIN_FFT_SIZE 32 +#else +// Use the naive FFT +// Do not define GABORATOR_USE_REAL_FFT as it is slower than +// using the complex code. +#define GABORATOR_MIN_FFT_SIZE 1 #endif #endif diff --git a/gaborator/gaborator-1.2/gaborator/fft_naive.h b/gaborator/gaborator-1.7/gaborator/fft_naive.h similarity index 96% rename from gaborator/gaborator-1.2/gaborator/fft_naive.h rename to gaborator/gaborator-1.7/gaborator/fft_naive.h index 73d8ef7..875d99e 100644 --- a/gaborator/gaborator-1.2/gaborator/fft_naive.h +++ b/gaborator/gaborator-1.7/gaborator/fft_naive.h @@ -1,7 +1,7 @@ // // Fast Fourier transform, naive reference implementations // -// Copyright (C) 1992-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 1992-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -102,8 +102,8 @@ struct fft { private: // Initialize twiddle factor array void init_wtab() { - unsigned int wt_size = wtab.size(); - for (unsigned int i = 0; i < wt_size; ++i) { + size_t wt_size = wtab.size(); + for (size_t i = 0; i < wt_size; ++i) { double arg = (-2.0 * M_PI / n_) * i; wtab[i] = C(cos(arg), sin(arg)); } @@ -128,7 +128,6 @@ struct fft { twiddle_vector wtab; }; -#if GABORATOR_USE_REAL_FFT // Real FFT // // This is a trivial implementation offering no performance advantage @@ -186,7 +185,6 @@ struct rfft { fft cf; }; -#endif } // Namespace diff --git a/gaborator/gaborator-1.2/gaborator/fft_pffft.h b/gaborator/gaborator-1.7/gaborator/fft_pffft.h similarity index 94% rename from gaborator/gaborator-1.2/gaborator/fft_pffft.h rename to gaborator/gaborator-1.7/gaborator/fft_pffft.h index 4dea7ea..b3e64b9 100644 --- a/gaborator/gaborator-1.2/gaborator/fft_pffft.h +++ b/gaborator/gaborator-1.7/gaborator/fft_pffft.h @@ -1,7 +1,7 @@ // // Fast Fourier transform using PFFFT // -// Copyright (C) 2017-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 2017-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -76,14 +76,14 @@ struct fft *> { PFFFT_Setup *setup; }; -// Support transforming std::vector >::iterator +// Support transforming std::vector>::iterator template <> -struct fft >::iterator>: +struct fft>::iterator>: public fft *> { typedef fft *> base; - typedef std::vector >::iterator I; + typedef std::vector>::iterator I; fft(unsigned int n_): fft *>(n_) { } void transform(I a) { @@ -154,7 +154,6 @@ struct fft *> { std::vector wsave; }; -#if GABORATOR_USE_REAL_FFT // Real FFT template <> @@ -207,7 +206,6 @@ struct rfft *> { unsigned int n; PFFFT_Setup *setup; }; -#endif #undef GABORATOR_PFFFT_CHECK_ALIGN diff --git a/gaborator/gaborator-1.2/gaborator/fft_vdsp.h b/gaborator/gaborator-1.7/gaborator/fft_vdsp.h similarity index 57% rename from gaborator/gaborator-1.2/gaborator/fft_vdsp.h rename to gaborator/gaborator-1.7/gaborator/fft_vdsp.h index cd7e8b7..f0fe232 100644 --- a/gaborator/gaborator-1.2/gaborator/fft_vdsp.h +++ b/gaborator/gaborator-1.7/gaborator/fft_vdsp.h @@ -1,7 +1,7 @@ // // Fast Fourier transform using the Apple vDSP framework // -// Copyright (C) 2013-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 2013-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -108,14 +108,83 @@ struct fft *> { FFTSetup setup; }; -// Support transforming std::vector >::iterator +// Real FFT template <> -struct fft >::iterator>: +struct rfft *> { + typedef std::complex *CI; // Complex iterator + typedef const std::complex *CONST_CI; + typedef typename std::iterator_traits::value_type C; // complex + typedef typename C::value_type T; // float/double + typedef T *RI; // Real iterator + typedef const T *CONST_RI; + + rfft(unsigned int n_): n(n_), log2n(log2_int_exact(n)) { + setup = vDSP_create_fftsetup(log2n, kFFTRadix2); + } + ~rfft() { + vDSP_destroy_fftsetup(setup); + } + + unsigned int size() { return n; } + + // out-of-place only + void + transform(CONST_RI in, CI out) { + DSPSplitComplex si; + si.realp = (float *) in; + si.imagp = (float *) in + 1; + DSPSplitComplex so; + so.realp = (float *) out; + so.imagp = (float *) out + 1; + vDSP_fft_zrop(setup, + &si, 2, + &so, 2, + log2n, kFFTDirection_Forward); + // Undo vDSP scaling + for (unsigned int i = 0; i < (n >> 1); i++) + out[i] *= (T)0.5; + C tmp = out[0]; +#if GABORATOR_REAL_FFT_NEGATIVE_FQS + for (unsigned int i = 1; i < (n >> 1); i++) + out[n - i] = conj(out[i]); +#endif + out[0] = C(tmp.real(), 0); + out[n >> 1] = C(tmp.imag(), 0); + } + + void + itransform(CONST_CI in, RI out) { + C tmp = in[0]; + const_cast(in)[0] = C(tmp.real(), in[n >> 1].real()); + DSPSplitComplex si; + si.realp = (float *) in; + si.imagp = (float *) in + 1; + DSPSplitComplex so; + so.realp = (float *) out; + so.imagp = (float *) out + 1; + vDSP_fft_zrop(setup, + &si, 2, + &so, 2, + log2n, kFFTDirection_Inverse); + const_cast(in)[0] = tmp; + } + +private: + // Size of the transform + unsigned int n; + unsigned int log2n; + FFTSetup setup; +}; + +// Support transforming std::vector>::iterator + +template <> +struct fft>::iterator>: public fft *> { typedef fft *> base; - typedef typename std::vector >::iterator I; + typedef typename std::vector>::iterator I; fft(unsigned int n_): fft *>(n_) { } void transform(I a) { diff --git a/gaborator/gaborator-1.7/gaborator/gaborator.h b/gaborator/gaborator-1.7/gaborator/gaborator.h new file mode 100644 index 0000000..358f2e2 --- /dev/null +++ b/gaborator/gaborator-1.7/gaborator/gaborator.h @@ -0,0 +1,2966 @@ +// +// Constant Q spectrum analysis and resynthesis +// +// Copyright (C) 2015-2021 Andreas Gustafsson. This file is part of +// the Gaborator library source distribution. See the file LICENSE at +// the top level of the distribution for license information. +// + +#ifndef _GABORATOR_GABORATOR_H +#define _GABORATOR_GABORATOR_H + +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gaborator/fft.h" +#include "gaborator/gaussian.h" +#include "gaborator/affine_transform.h" +#include "gaborator/pod_vector.h" +#include "gaborator/pool.h" +#include "gaborator/ref.h" +#include "gaborator/vector_math.h" + + +namespace gaborator { + +using std::complex; + +// An integer identifying an audio sample +typedef int64_t sample_index_t; + +// An integer identifying a coefficient +typedef int64_t coef_index_t; + +// An integer identifying a slice +typedef int64_t slice_index_t; + +// See https://tauday.com/tau-manifesto +static const double tau = 2.0 * M_PI; + +// Round up to next higher or equal power of 2 + +inline int +next_power_of_two(int x) { + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; +} + +// Determine if x is a power of two. +// Note that this considers 0 to be a power of two. + +static inline bool +is_power_of_two(unsigned int x) { + return (x & (x - 1)) == 0; +} + +// Given a power of two v, determine log2(v) +// https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 + +static inline unsigned int whichp2(unsigned int v) { + assert(is_power_of_two(v)); + unsigned int r = (v & 0xAAAAAAAA) != 0; + r |= ((v & 0xCCCCCCCC) != 0) << 1; + r |= ((v & 0xF0F0F0F0) != 0) << 2; + r |= ((v & 0xFF00FF00) != 0) << 3; + r |= ((v & 0xFFFF0000) != 0) << 4; + return r; +} + +// Floor division: return the integer part of a / b +// rounded down (not towards zero). For positive b only. + +inline int64_t floor_div(int64_t a, int64_t b) { + assert(b > 0); + if (a >= 0) + return a / b; + else + return (a - b + 1) / b; +} + +// Floating point modulus, the remainder r of a / b +// satisfying 0 <= r < b even for negative a. +// For positive b only. + +static inline double +sane_fmod(double a, double b) { + assert(b > 0); + double m = fmod(a, b); + if (m < 0) + m += b; + return m; +} + +// Do an arithmetic left shift of a 64-bit signed integer. This is +// what a << b ought to do, but according to the C++11 draft (n3337), +// section 5.8, that invokes undefined behavior when a is negative. +// GCC is actually smart enough to optimize this into a single shlq +// instruction. +// +// No corresponding kludge is needed for right shifts, because a right +// shift of a negative signed integer is implementation-defined, not +// undefined, and we trust implementations to define it sanely. + +static inline int64_t +shift_left(int64_t a, unsigned int b) { + if (a < 0) + return -(((uint64_t) -a) << b); + else + return (((uint64_t) a) << b); +} + +// Convert between complex types + +template +complex c2c(complex c) { return complex(c.real(), c.imag()); } + +// Convert a sequence of complex values to real + +template +O complex2real(I b, I e, O o) { + while (b != e) { + *o++ = (*b++).real(); + } + return o; +} + +// A vector-like object that allows arbitrary integer indices +// (positive or negative, but excluding the largest possible integer) +// and automatically resizes the storage. Uses storage proportional +// to the difference between the smallest and largest index value (for +// example, if indices range from -102 to -100 (inclusive), memory use +// is on the order of 3 elements). +// +// T is the element type +// I is the integer index type + +template +struct range_vector { + range_vector() { + init_bounds(); + } + range_vector(const range_vector &) = default; + range_vector &operator=(const range_vector &rhs) = default; + range_vector(range_vector &&rhs): + v(std::move(rhs.v)), + lower(rhs.lower), + upper(rhs.upper) + { + rhs.init_bounds(); + } + range_vector &operator=(range_vector &&rhs) { + if (this == &rhs) + return *this; + v = std::move(rhs.v); + lower = rhs.lower; + upper = rhs.upper; + rhs.init_bounds(); + return *this; + } +private: + void init_bounds() { + lower = std::numeric_limits::max(); + upper = std::numeric_limits::min(); + } + T *unchecked_get(I i) { + return &v[(size_t)(i & ((I)v.size() - 1))]; + } + const T *unchecked_get(I i) const { + return &v[i & ((I)v.size() - 1)]; + } + +public: + // Get a pointer to an existing element, or null if out of range + const T * + get(I i) const { + if (! has_index(i)) + return 0; + return unchecked_get(i); + } + + // Note: Reference returned becomes invalid when range_vector + // is changed + T & + get_or_create(I i) { + if (! has_index(i)) + extend(i); + return *unchecked_get(i); + + } + + // Get a reference to the element at index i, which must be valid + T & + get_existing(I i) { + assert(has_index(i)); + return *unchecked_get(i); + } + + // Const version of the above + const T & + get_existing(I i) const { + assert(has_index(i)); + return *unchecked_get(i); + } + +private: + void extend(I i) { + I new_lower = lower; + I new_upper = upper; + if (i < lower) + new_lower = i; + if (i + 1 > upper) + new_upper = i + 1; + I old_size = v.size(); + I new_need = new_upper - new_lower; + if (new_need > old_size) { + if (old_size == 0) { + v.resize(1); + } else { + I new_size = old_size; + while (new_size < new_need) + new_size *= 2; + v.resize(new_size); + if (old_size) { + for (I j = lower; j < upper; j++) { + I jo = j & (old_size - 1); + I jn = j & (new_size - 1); + if (jo != jn) + std::swap(v[jo], v[jn]); + } + } + } + } + lower = new_lower; + upper = new_upper; + } + +public: + // Erase the elements whose index is less than "limit" + void erase_before(I limit) { + I i = lower; + for (; i < upper && i < limit; i++) + *unchecked_get(i) = T(); + lower = i; + } + + void clear() { + v.clear(); + init_bounds(); + } + + I begin_index() const { return lower; } + I end_index() const { return upper; } + bool empty() const { return lower >= upper; } + bool has_index(I i) const { return i >= lower && i < upper; } + +private: + std::vector v; + I lower, upper; +}; + +// Calculate the size of the alias-free part (the "filet") +// of a signal slice of size "fftsize" + +static inline unsigned int filet_part(unsigned int fftsize) { + return fftsize >> 1; +} + +// Calculate the size of the padding (the "fat") at each +// end of a signal slice of size "fftsize" + +static inline unsigned fat_part(unsigned int fftsize) { + return fftsize >> 2; +} + +// Per-band, per-plan data + +template +struct band_plan { + typedef complex C; + unsigned int sftsize; // Size of "short FFT" spanning the band + unsigned int sftsize_log2; // log2(sftsize) + fft *sft; // Fourier transform for windows, of size sftsize + std::vector kernel; // Frequency-domain filter kernel + std::vector dual_kernel; // Dual of the above + pod_vector shift_kernel; // Complex exponential for fractional frequency compensation + pod_vector shift_kernel_conj; // Conjugate of the above + int fq_offset_int; // Frequency offset in bins (big-FFT bin of left window edge) + double center; // Center frequency in units of FFT bins + int icenter; // Center frequency rounded to nearest integer FFT bin +}; + +// Frequency band parameters shared between octaves + +template +struct band_params: public refcounted { + typedef complex C; + bool dc; // True iff this is the lowpass (DC) band + double ff; // Center (bp) or corner (lp) frequency in units of the sampling frequency + double ffsd; // Standard deviation of the bandpass Gaussian, as fractional frequency + unsigned int step; // Signal samples per coefficient sample + unsigned int step_log2; // log2(step) + double ff_support; // Filter support in frequency domain + double time_support; // Filter support in time domain, in octave subsamples + std::vector> anl_plans; + std::vector> syn_plans; +}; + +// Downsampling parameters. These have some similarity to band +// parameters, but only some. For example, these may use a real +// rather than complex FFT for the "short FFT". + +template +struct downsampling_params { + typedef complex C; + unsigned int sftsize; + std::vector kernel; // Frequency-domain filter kernel + std::vector dual_kernel; +#if GABORATOR_USE_REAL_FFT + rfft *rsft; +#else + fft *sft; +#endif +}; + +// Forward declarations +template struct analyzer; +template struct zone; +template> struct coefs; +template struct sliced_coefs; +template *, class C = complex> + struct row_source; +template *, class C = complex> + struct row_dest; +template *, class C = complex> + struct row_add_dest; + +// Abstract class for tracking changes to a coefficient set. +// This may be used for updating a resolution pyramid of +// magnitude data. + +template +struct shadow { + virtual ~shadow() { } + virtual void update(const coefs> &msc, + sample_index_t i0, sample_index_t i1) = 0; + // Deprecated + virtual void update(int oct, int ze, const sliced_coefs> &sc, + slice_index_t sli0, slice_index_t sli1) = 0; +}; + +// Per-band coefficient metadata, shared between octaves. + +struct band_coefs_meta { + unsigned int slice_len; // Number of coefficients per slice + unsigned short slice_len_log2; // Log2 of the above + // Log2 of the downsampling factor of the coefficients in + // this band relative to the signal samples. The value + // applies as such in the top octave; in other octaves, + // the octave number needs to be added. + unsigned short step_log2; + // Offset of the beginning of this band in the data array + unsigned int band_offset; +}; + +// Per-zone coefficient metadata. This contains information +// describing an "oct_coefs" structure that is common to many +// instances and should not be duplicated in each one. + +struct zone_coefs_meta { + typedef std::vector band_vector; + void init(const band_vector &bands_) { + bands = bands_; + unsigned int offset = 0; + for (band_coefs_meta &b: bands) { + b.band_offset = offset; + offset += b.slice_len; + } + total_size = offset; + } + band_vector bands; + // Total size of data array (in elements, not bytes) + unsigned int total_size; +}; + +// Per-octave coefficient metadata. +// Cf. struct octave + +struct oct_coefs_meta { + zone_coefs_meta *z; + unsigned int n_bands_above; // Total number of bands in higher octaves +}; + +// Coefficient metadata for multirate coefficients. + +struct coefs_meta: public refcounted { + coefs_meta() = default; + coefs_meta(const coefs_meta &) = delete; + unsigned int n_octaves; + unsigned int n_bands_total; + unsigned int bands_per_octave; + unsigned int slice_len; // octave subsamples per slice + std::vector zones; + std::vector octaves; +}; + +// Split a "global band number" gbno into an octave and band +// number within octave ("obno"). +// +// Global band numbers start at 0 for the band at or close to +// fs/2, and increase towards lower frequencies. +// +// Include the DC band if "dc" is true. +// Returns true iff gbno is valid. + +static inline bool +bno_split(const coefs_meta &meta, int gbno, int &oct, unsigned int &obno, bool dc) { + if (gbno < 0) { + // Above top octave + return false; + } else if (gbno >= (int)meta.n_bands_total - 1) { + // At or below DC + if (gbno == (int)meta.n_bands_total - 1) { + // At DC + if (dc) { + oct = meta.n_octaves - 1; + obno = 0; + return true; + } else { + return false; + } + } else { + // Below DC + return false; + } + } else { + // Within bandpass region + // Start by determining the octave + int n_bands_top_octave = (int)meta.octaves[0].z->bands.size(); + if (gbno < n_bands_top_octave) { + // Top octave + oct = 0; + obno = n_bands_top_octave - 1 - gbno; + return true; + } + gbno -= n_bands_top_octave; + int oct_tmp = 1 + gbno / meta.bands_per_octave; + int obno_tmp = gbno % meta.bands_per_octave; + oct = oct_tmp; + // Now determine the band within the octave. + // obno_tmp counts down, but obno counts up. + obno = (unsigned int)meta.octaves[oct_tmp].z->bands.size() - 1 - obno_tmp; + return true; + } +} + +// The inverse of bno_split(). Returns a gbno. The arguments must +// be valid. + +static inline +int bno_merge(const coefs_meta &meta, int oct, unsigned int obno) { + unsigned int n_bands = (unsigned int)meta.octaves[oct].z->bands.size(); + assert(obno < n_bands); + int bno_from_end = n_bands - 1 - obno; + return bno_from_end + meta.octaves[oct].n_bands_above; +} + +// Coefficients of a single octave for a single input signal slice. +// C is the coefficient type, typically complex but can also +// be e.g. unsigned int to store cluster numbers, or float to store +// magnitudes. + +template +struct oct_coefs: public refcounted { + oct_coefs(const zone_coefs_meta &zmeta_, bool clear_ = true): + zmeta(zmeta_), + data(zmeta.total_size), + bands(*this) + { + if (clear_) + clear(); + } + oct_coefs(const oct_coefs &) = delete; + uint64_t estimate_memory_usage() const { + return zmeta.total_size * sizeof(C) + sizeof(*this); + } + void clear() { + std::fill(data.begin(), data.end(), C()); + } + // Deep copy + oct_coefs &operator=(const oct_coefs &rhs) { + assert(data.size() == rhs.data.size()); + memcpy(data.data(), rhs.data.data(), data.size() * sizeof(C)); + return *this; + } + + const zone_coefs_meta &zmeta; + + // The data for all the bands are allocated together + // as a single vector to reduce the number of allocations + pod_vector data; + // Vector-like collection of pointers into "data", one for each band + struct band_array { + band_array(oct_coefs &outer_): outer(outer_) { } + C *operator[](size_t i) const { + return outer.data.data() + outer.zmeta.bands[i].band_offset; + } + size_t size() const { return outer.zmeta.bands.size(); } + oct_coefs &outer; + } bands; +}; + +// Add the oct_coefs "b" to the oct_coefs "a" + +template +void add(oct_coefs &a, const oct_coefs &b) { + size_t n_bands = a.bands.size(); + assert(n_bands == b.bands.size()); + for (size_t obno = 0; obno < n_bands; obno++) { + unsigned int len = a.zmeta.bands[obno].slice_len; + C *band_a = a.bands[obno]; + C *band_b = b.bands[obno]; + for (unsigned int j = 0; j < len; j++) { + band_a[j] += band_b[j]; + } + } +} + +// Sliced coefficients. These cover an arbitrary time range, but only +// a single octave. Template argument is as for struct oct_coefs. +// This is default constructible so that we can create an array of +// them, but not usable until "meta" has been set up. + +template +struct sliced_coefs { + typedef range_vector>, slice_index_t> slices_t; + uint64_t estimate_memory_usage() const { + unsigned int n = 0; + size_t size_each = 0; + for (slice_index_t sl = slices.begin_index(); sl < slices.end_index(); sl++) { + const ref> &t = slices.get_existing(sl); + if (t) { + if (! size_each) + size_each = (size_t)t->estimate_memory_usage(); + n++; + } + } + return n * size_each; + } + void clear() { + slices.clear(); + } + zone_coefs_meta *meta; + slices_t slices; +}; + +// Get a pointer to an existing existing coefficient slice, +// or null if one does not exist. Like get_or_create_coefs(), +// this hides the distinction between the two types of nonexistence. + +template +oct_coefs *get_existing_coefs(const sliced_coefs &sc, + slice_index_t i) +{ + const ref> *p = sc.slices.get(i); + if (! p) + return 0; + return p->get(); +} + +// Get an existing coefficient slice, or create a new one. Note that +// this hides the distinction between two types of nonexistence: that +// of slices outside the range of the range_vector, and that of +// missing slices within the range (having a null ref). CT is the +// coefficient type, which is typically C aka complex, but can be +// different, for example float to represent magnitudes. + +template +oct_coefs &get_or_create_coefs(sliced_coefs &sc, slice_index_t i) { + ref> &p(sc.slices.get_or_create(i)); + if (! p) + p.reset(new oct_coefs(*sc.meta)); + return *p; +} + +// Return the signal sample time corresponding to coefficient sample 0 +// of coefficient slice 0, for slices of length len. It would be nice +// if this were zero, but for historical reasons, it's offset by half +// a slice (corresponding to the analysis fat). + +static inline int coef_offset(int len) { + return len >> 1; +} + +// Get the base 2 logarithm of the downsampling factor of +// band "obno" in octave "oct" + +static inline int +band_scale_exp(const zone_coefs_meta &meta, int oct, unsigned int obno) { + return meta.bands[obno].step_log2 + oct; +} + +// Return the coefficient index (the time in terms of coefficient +// subsamples) of the first cofficient of slice "sli" of band +// "obno" in octave "oct" + +static inline coef_index_t +coef_time(const zone_coefs_meta &meta, slice_index_t sli, int oct, int obno) { + int len = meta.bands[obno].slice_len; + return coef_offset(len) + sli * len; +} + +// Return the sample index (the time in terms of samples) time of +// coefficient "i" in slice "sli" of band "obno" in octave "oct" + +static inline sample_index_t +sample_time(const zone_coefs_meta &meta, slice_index_t sli, int i, int oct, int obno) { + coef_index_t sst = coef_time(meta, sli, oct, obno) + i; + return shift_left(sst, band_scale_exp(meta, oct, obno)); +} + +// Multirate sliced coefficients. These cover an arbitrary time +// range and the full frequency range (all octaves). +// Template arguments: +// T analyzer sample data type +// C coefficient data type +// Note default for template argument C defined in forward declaration. + +template +struct coefs { + typedef C value_type; + coefs(const analyzer &anl_, shadow *shadow_ = 0): + octaves(anl_.n_octaves), shadow0(shadow_) + { + meta = anl_.cmeta_any.get(); + // Set up shortcut pointer to zone metadata in each octave + for (unsigned int oct = 0; oct < octaves.size(); oct++) + octaves[oct].meta = meta->octaves[oct].z; + } + uint64_t estimate_memory_usage() const { + uint64_t s = 0; + for (unsigned int oct = 0; oct < octaves.size(); oct++) + s += octaves[oct].estimate_memory_usage(); + return s; + } + void clear() { + for (unsigned int oct = 0; oct < octaves.size(); oct++) + octaves[oct].clear(); + } + coefs_meta *meta; + std::vector> octaves; + shadow *shadow0; +}; + +// Read coefficients i0..i1 of band gbno in msc into buf. + +template +void read(const coefs &msc, int gbno, + coef_index_t i0, coef_index_t i1, C *buf) +{ + int oct; + unsigned int obno; // Band number within octave + bool valid = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); + assert(valid); + row_source(msc, oct, obno)(i0, i1, buf); +} + +template +void write(coefs &msc, int gbno, + coef_index_t i0, coef_index_t i1, C *buf) +{ + int oct; + unsigned int obno; // Band number within octave + bool valid = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); + assert(valid); + row_dest(msc, oct, obno)(i0, i1, buf); +} + +template +void add(coefs &msc, int gbno, + coef_index_t i0, coef_index_t i1, C *buf) +{ + int oct; + unsigned int obno; // Band number within octave + bool valid = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); + assert(valid); + row_add_dest(msc, oct, obno)(i0, i1, buf); +} + +// Return the base 2 logarithm of the time step (aka downsampling +// factor) of band "gbno". + +static inline +unsigned int band_step_log2(const coefs_meta &meta, int gbno) { + int oct; + unsigned int obno; + bool valid = bno_split(meta, gbno, oct, obno, true); + assert(valid); + return band_scale_exp(*meta.octaves[oct].z, oct, obno); +} + + +// Convert a signal time t into a coefficient sample +// index. t must coincide with a coefficient sample time. + +static inline +coef_index_t t2i_exact(const coefs_meta &meta, int gbno, sample_index_t t) { + int shift = band_step_log2(meta, gbno); + int64_t mask = ((sample_index_t)1 << shift) - 1; + assert((t & mask) == 0); + return t >> shift; +} + +// Read a single coefficient sample at signal time t, +// which must coincide with a coefficient sample time + +template +C read1t(const coefs &msc, int gbno, sample_index_t t) { + coef_index_t i = t2i_exact(*msc.meta, gbno, t); + C c; + read(msc, gbno, i, i + 1, &c); + return c; +} + +// Read a single coefficient sample at signal time t, +// which must coincide with a coefficient sample time + +template +void write1t(coefs &msc, int gbno, sample_index_t t, C c) { + coef_index_t i = t2i_exact(*msc.meta, gbno, t); + write(msc, gbno, i, i + 1, &c); +} + +// Perform an fftshift of the range between iterators a and b. +// Not optimized - not for use in inner loops. + +template +void fftshift(I b, I e) { + size_t len = e - b; + assert(len % 2 == 0); + for (size_t i = 0; i < len / 2; i++) + std::swap(*(b + i), *(b + len / 2 + i)); +} + +// Given an unsigned index i into an FFT of a power-of-two size +// "size", return the corresponding signed index, ranging from -size/2 +// to size/2-1. This is equivalent to sign extension of an integer of +// log2(size) bits; see Hacker's Delight, page 18. This can be used +// to convert an FFT index into a frequency (positive or negative; +// note that Nyquist is considered negatitive = -fs/2). + +static inline int signed_index(unsigned int i, unsigned int size) { + unsigned int t = size >> 1; + return (i ^ t) - t; +} + +// Evaluate a Gaussian windowed lowpass filter frequency response. +// This is the convolution of a rectangle centered at f=0 and a Gaussian, +// and corresponds to a Gaussian windowed sinc in the time domain. +// The -6 dB cutoff freqency is ff_cutoff (a fractional frequency), +// the standard deviation of the Gaussian is ff_sd, and the frequency +// response is evaluated at ff. The frequency response is smooth at +// f=0 even if the transition bands overlap. + +inline double +gaussian_windowed_lowpass_1(double ff_cutoff, double ff_sd, double ff) { + return + // A rectangle is the sum of a rising step and a later falling + // step, or the difference between a rising step and a later + // rising step. By linearity, a Gaussian filtered rectangle + // is the difference between two Gaussian filtered rising + // steps. + gaussian_edge(ff_sd, -ff + ff_cutoff) - + gaussian_edge(ff_sd, -ff - ff_cutoff); +} + +// Fill a sequence with a frequency-ddomain lowpass filter as above. +// The returned filter covers the full frequency range from 0 to fs +// (with negative frequencies at the end, the usual convention for FFT +// spectra). +// +// When center=true, construct a time-domain window instead, +// passing the center of the time-domain signal. +// +// The result is stored between iterators b and e, which must have a +// real value_type. + +template +inline void gaussian_windowed_lowpass(double ff_cutoff, double ff_sd, + I b, I e, bool center = false) +{ + size_t len = e - b; + double inv_len = 1.0 / len; + for (I it = b; it != e; ++it) { + size_t i = it - b; + double thisff; + if (center) + // Symmetric around center + thisff = std::abs(i - (len * 0.5)) * inv_len; + else + // Symmetric around zero + thisff = (i > len / 2 ? len - i : i) * inv_len; + *it = gaussian_windowed_lowpass_1(ff_cutoff, ff_sd, thisff); + } +} + +// A set of octaves having identical parameters form a "zone", +// and their shared parameters are stored in a "struct zone". + +template +struct zone: public refcounted { + zone(): n_bands(0) { } + ~zone() { } + // Zone number, 0..3 + unsigned int zno; + // Total number of bands, including DC band if lowest octave + unsigned int n_bands; + unsigned int max_step_log2; + // Band parameters by increasing frequency; DC band is index 0 if + // present + std::vector>> bandparams; + std::vector>> mock_bandparams; +}; + +template +struct octave { + zone *z; +}; + +// Helper function for pushing parameters onto the vectors in struct zone + +template +void push(std::vector>> &v, band_params *p) { + v.push_back(ref>(p)); +} + +// Phase conventions: coef_phase::absolute means the phase of a +// coefficient at time tc is relative to e^(i tau f t), and +// coef_phase::local means it is relative to +// e^(i tau f (t - tc)) + +enum class coef_phase { global, local }; + +// A set of spectrum analysis parameters + +struct parameters { + parameters(unsigned int bands_per_octave_, + double ff_min_, + double ff_ref_ = 1.0, + double overlap_ = 0.7, + double max_error_ = 1e-5): + bands_per_octave(bands_per_octave_), + ff_min(ff_min_), + ff_ref(ff_ref_), + overlap(overlap_), + max_error(max_error_), + coef_scale(1.0), + synthesis(true), + multirate(true) + { + init_v1(); + } + // Pseudo-constructor with version 1 defaults + static parameters v1(unsigned int bands_per_octave_, + double ff_min_, + double ff_ref_ = 1.0, + double overlap_ = 0.7, + double max_error_ = 1e-5) + { + parameters p(bands_per_octave_, ff_min_, ff_ref_, overlap_, max_error_); + p.init_v1(); + return p; + } + // Pseudo-constructor with version 2 defaults + static parameters v2(unsigned int bands_per_octave_, + double ff_min_, + double ff_ref_ = 1.0, + double overlap_ = 0.7, + double max_error_ = 1e-5) + { + parameters p(bands_per_octave_, ff_min_, ff_ref_, overlap_, max_error_); + p.init_v2(); + return p; + } + void init_v1() { + phase = coef_phase::global; + bandwidth_version = 1; + lowpass_version = 1; + } + void init_v2() { + phase = coef_phase::local; + bandwidth_version = 2; + lowpass_version = 2; + } + // Provide an operator< so that we can create a set or map of parameters + bool operator<(const parameters &b) const { +#define GABORATOR_COMPARE_LESS(member) do { \ + if (member < b.member) \ + return true; \ + if (member > b.member) \ + return false; \ + } while(0) + GABORATOR_COMPARE_LESS(bands_per_octave); + GABORATOR_COMPARE_LESS(ff_min); + GABORATOR_COMPARE_LESS(ff_ref); + GABORATOR_COMPARE_LESS(overlap); + GABORATOR_COMPARE_LESS(max_error); + GABORATOR_COMPARE_LESS(phase); + GABORATOR_COMPARE_LESS(bandwidth_version); + GABORATOR_COMPARE_LESS(lowpass_version); + GABORATOR_COMPARE_LESS(coef_scale); + GABORATOR_COMPARE_LESS(synthesis); + GABORATOR_COMPARE_LESS(multirate); +#undef GABORATOR_COMPARE_LESS + // Equal + return false; + } + bool operator==(const parameters &b) const { + return !((*this < b) || (b < *this)); + } + // The frequency increases by a factor of band_spacing from + // one bandpass band to the next. + double band_spacing_log2() const { + return 1.0 / bands_per_octave; + } + double band_spacing() const { + return exp2(band_spacing_log2()); + } + // The standard deviation of the Gaussian in units of the mean + double sd() const { + return overlap * + (bandwidth_version == 1 ? + band_spacing() - 1 : + log(2) / bands_per_octave); + } + + // Defining Q as the frequency divided by the half-power bandwidth, + // we get + // + // norm_gaussian(sd, hbw) = sqrt(2) + // + // (%i1) e1: exp(-(hbw * hbw) / (2 * sd * sd)) = 1 / sqrt(2); + // (%i2) solve(e1, hbw); + // (%o2) [hbw = - sqrt(log(2)) sd, hbw = sqrt(log(2)) sd] + double q() const { + return 1.0 / (2 * sqrt(log(2)) * sd()); + } + + template friend class analyzer; + unsigned int bands_per_octave; + double ff_min; + double ff_ref; + double overlap; + double max_error; + coef_phase phase; + int bandwidth_version; + int lowpass_version; + double coef_scale; + bool synthesis; // Synthesis is supported + bool multirate; +}; + +// Like std::fill, but returns the end iterator + +template +I fill(I b, I e, T v) { + std::fill(b, e, v); + return e; +} + +// Multiply a vector by a scalar, in-place. +// Used only at the setup stage, so performance is not critical. + +template +void scale_vector(V &v, S s) { + for (auto &e: v) + e *= s; +} + +// Zero-padding source wrapper. This returns data from the underlying +// source within the interval src_i0 to src_i1, and zero elsewhere. + +template +struct zeropad_source { + typedef typename std::iterator_traits::value_type T; + zeropad_source(const S &source_, int64_t src_i0_, int64_t src_i1_): + source(source_), src_i0(src_i0_), src_i1(src_i1_) + { } + OI operator()(int64_t i0, int64_t i1, OI output) const { + int64_t overlap_begin = std::max(i0, src_i0); + int64_t overlap_end = std::min(i1, src_i1); + if (overlap_end <= overlap_begin) { + // No overlap + output = gaborator::fill(output, output + (i1 - i0), (T)0); + } else { + // Some overlap + if (overlap_begin != i0) { + output = gaborator::fill(output, output + (overlap_begin - i0), (T)0); + } + output = source(overlap_begin, overlap_end, output); + if (overlap_end != i1) { + output = gaborator::fill(output, output + (i1 - overlap_end), (T)0); + } + } + return output; + } + const S &source; + int64_t src_i0, src_i1; +}; + +template +struct pointer_source { + pointer_source(const T *p_, int64_t buf_i0_, int64_t buf_i1_): + p(p_), buf_i0(buf_i0_), buf_i1(buf_i1_) { } + T *operator()(int64_t i0, int64_t i1, T *output) const { + assert(i1 >= i0); + assert(i0 >= buf_i0); + assert(i1 <= buf_i1); + return std::copy(p + (i0 - buf_i0), p + (i1 - buf_i0), output); + } + const T *p; + int64_t buf_i0, buf_i1; +}; + +// Fill the buffer at dst, of length dstlen, with data from src where +// available, otherwise with zeroes. The data in src covers dst indices +// from i0 (inclusive) to i1 (exclusive). + +template +void copy_overlapping_zerofill(T *dst, size_t dstlen, const T *src, + int64_t i0, int64_t i1) +{ + pointer_source ps(src, i0, i1); + zeropad_source, T *> zs(ps, i0, i1); + zs(0, dstlen, dst); +} + +// Given a set of FFT coefficients "coefs" of a real sequence, where +// only positive-frequency coefficients (including DC and Nyquist) are +// valid, return the coefficient for an arbitrary frequency index "i" +// which may correspond to a negative frequency, or even an alias +// outside the range (0..fftsize-1). + +template +complex get_real_spectrum_coef(complex *coefs, int i, unsigned int fftsize) { + i &= fftsize - 1; + // Note that this is >, not >=, becase fs/2 is considered nonnegative + bool neg_fq = (i > (int)(fftsize >> 1)); + if (neg_fq) { + i = fftsize - i; + } + complex c = coefs[i]; + if (neg_fq) { + c = conj(c); + } + return c; +} + +// A set of buffers of various sizes used for temporary vectors during +// analysis. These are allocated as a single block to reduce the +// number of dynamic memory allocations. + +template +struct buffers { + static const size_t maxbufs = 10; + typedef complex C; + buffers(unsigned int fftsize_max, + unsigned int sftsize_max): + n(0) + { + offset[0] = 0; + // Define the size of each buffer + def(fftsize_max * sizeof(C)); // 0 + def(fftsize_max * sizeof(C)); // 1 + def(sftsize_max * sizeof(C)); // 2 + def(sftsize_max * sizeof(C)); // 3 + def(sftsize_max * sizeof(C)); // 4 + def(fftsize_max * sizeof(T)); // 5 + assert(n <= maxbufs); + data = ::operator new(offset[n]); + } + ~buffers() { + ::operator delete(data); + } + void def(size_t size) { + size_t o = offset[n++]; + offset[n] = o + size; + } + // A single buffer of element type E + template + struct buffer { + typedef E *iterator; + buffer(void *b_, void *e_): + b((E *)b_), e((E *)e_) + { } + iterator begin() const { return b; } + iterator end() const { return e; } + E *data() { return b; } + const E *data() const { return b; } + E &operator[](size_t i) { return b[i]; } + const E &operator[](size_t i) const { return b[i]; } + size_t size() const { return e - b; } + private: + E *b; + E *e; + }; + // Get buffer number "i" as a vector-like object with element type "E" + // and a length of "len" elements. + template + buffer get(size_t i, size_t len) { + len *= sizeof(E); + size_t o = offset[i]; + assert(len <= offset[i + 1] - o); + return buffer((char *)data + o, (char *)data + o + len); + } +private: + void *data; + size_t n; + size_t offset[maxbufs + 1]; +}; + +// Get the bounds of the range of existing coefficients for a +// given band, in units of coefficient samples. + +template +void get_band_coef_bounds(const coefs &msc, int oct, unsigned int obno, + coef_index_t &ci0_ret, coef_index_t &ci1_ret) +{ + const sliced_coefs &sc = msc.octaves[oct]; + const typename sliced_coefs::slices_t &slices = sc.slices; + if (slices.empty()) { + // Don't try to convert int64t_min/max slices to coef time + ci0_ret = 0; + ci1_ret = 0; + return; + } + // Convert from slices to coefficient samples + ci0_ret = coef_time(*sc.meta, slices.begin_index(), oct, obno); + ci1_ret = coef_time(*sc.meta, slices.end_index(), oct, obno); +} + +template +void get_band_coef_bounds(const coefs &msc, int gbno, + coef_index_t &ci0_ret, coef_index_t &ci1_ret) +{ + int oct; + unsigned int obno; // Band number within octave + bool r = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); + assert(r); + get_band_coef_bounds(msc, oct, obno, ci0_ret, ci1_ret); +} + +// Evaluate the frequency-domain analysis filter kernel of band "bp" +// at frequency "ff" + +template +double eval_kernel(parameters *, band_params *bp, double ff) { + if (bp->dc) { + return gaussian_windowed_lowpass_1(bp->ff, bp->ffsd, ff); + } else { + return norm_gaussian(bp->ffsd, ff - bp->ff); + } +} + +// Evaluate the frequency-domain synthesis filter kernel of band "bp" +// at frequency "ff" + +template +double eval_dual_kernel(parameters *params, band_params *bp, double ff) { + double gain = 1.0; + if (params->lowpass_version == 2) { + if (bp->dc) { + // Adjust the gain of the reconstruction lowpass + // filter to make the overall gain similar to to + // the bandpass region. + double adjusted_overlap = params->sd() / + (log(2) / params->bands_per_octave); + double avg_bandpass_gain = adjusted_overlap * sqrt(M_PI); + gain = avg_bandpass_gain * 0.5; + } + } + return eval_kernel(params, bp, ff) * gain; +} + +template +struct analyzer: public refcounted { + typedef complex C; + struct plan; + + analyzer(const parameters ¶ms_): + params(params_), + max_step_log2(0), + fftsize_max(0), + sftsize_max(0) + { + // Sanity check + assert(params.ff_min < 0.5); + + band_spacing_log2 = params.band_spacing_log2(); + band_spacing = params.band_spacing(); + + // The tuning adjustment, as a log2ff. This is a number between + // 0 and band_spacing_log2, corresponding to a frequency at + // or slightly above the sampling frequency where a band + // center would fall if they actually went that high. + // Tuning is done by increasing the center frequencies of + // all bands by this amount relative to the untuned case + // where one band would fall on fs exactly. + tuning_log2ff = sane_fmod(log2(params.ff_ref), band_spacing_log2); + + // Calculate the total number of bands needed so that + // the lowest band has a frequency <= params.ff_min. + // end_log2ff = the log2ff of the band after the last (just past fs/2); + // the -1 below is the log of the /2 above + double end_log2ff = tuning_log2ff - 1; + n_bandpass_bands_total = + (unsigned int)ceil((end_log2ff - log2(params.ff_min)) / + band_spacing_log2); + n_bands_total = n_bandpass_bands_total + 1; + + top_band_log2ff = end_log2ff - band_spacing_log2; + + ffref_gbno = (int)rint((top_band_log2ff - log2(params.ff_ref)) / + band_spacing_log2); + + // Establish affine transforms for converting between + // log-frequencies (log2(ff)) and bandpass band numbers. + // Derivation: + //ff = exp2(tuning_log2ff - 1 - (gbno + 1) * band_spacing_log2) + //log2(ff) = tuning_log2ff - 1 - (gbno + 1) * band_spacing_log2 + //tuning_log2ff - 1 - (gbno + 1) * band_spacing_log2 = log2(ff) + //-(gbno + 1) * band_spacing_log2 = log2(ff) - tuning_log2ff + 1 + //-(gbno + 1) * band_spacing_log2 = log2(ff) - tuning_log2ff + 1 + //-(gbno + 1) = (log2(ff) - tuning_log2ff + 1) / band_spacing_log2 + //-gbno - 1 = (log2(ff) - tuning_log2ff + 1) / band_spacing_log2 + //-gbno = ((log2(ff) - tuning_log2ff + 1) / band_spacing_log2) + 1 + //gbno = -(((log2(ff) - tuning_log2ff + 1) / band_spacing_log2) + 1) + //gbno = a log2(ff) + b, + // where a = -1 / band_spacing_log2 = -params.bands_per_octave + // and b = -a * tuning_log2ff + a - 1 + // The cast to double is necessary because we can't take the + // negative of an unsigned int. + double a = -(double)params.bands_per_octave; + double b = -a * tuning_log2ff + a - 1; + log2ff_bandpass_band = affine_transform(a, b); + bandpass_band_log2ff = log2ff_bandpass_band.inverse(); + + { + // Precalculate the parameters of the downsampling filter. + // These are the same for all plans, and need to be + // calculated before creating the plans; in particular, we + // need to know the support before we can create the + // plans, because in low-bpo cases, it can determine the + // minimum amount of fat needed. The filter kernel is + // specific to the plan as it depends on the FFT size, + // and will be calculated later. + + // When operating at a high Q, the we will need to use + // large FFTs in any case, and it makes sense to use a + // narrow transition band because we can get that + // essentially for free, and the passband will be + // correspondingly wider, which will allow processing more + // bands at the lower sample rate. Conversely, at low + // Q, we should use a wide transition band so that the + // FFTs can be kept short. + + // The filter is defined in terms of the lower + // (downsampled) sample rate. + + // Make the transition band the same width as the width + // (two-sided support) of a band at ff=0.25, but don't let + // the low edge go below 0.25 to make sure we have a + // reasonable amount of passband left. + double f1 = 0.5; + double f0 = + std::max(f1 - 2 * gaussian_support(ff_sd(0.25), params.max_error), + 0.25); + assert(f0 < f1); + + // The cutoff frequency is in the center of the transition band + double ff = (f0 + f1) * 0.5; + double support = (f1 - f0) * 0.5; + double ff_sd = gaussian_support_inv(support, params.max_error); + + // Calculate and save the time-domain support of the + // downsampling lowpass filter for use in analyze_sliced(). + double time_sd = sd_f2t(ff_sd); + + // Set members + ds_passband = f0; + ds_ff = ff; + ds_ff_sd = ff_sd; + // Since the filter is designed at the lower sample rate, + // ds_time_support is in the unit of lower octave samples + ds_time_support = gaussian_support(time_sd, params.max_error * 0.5); + } + + // Determine the octave structure, packing each band into the + // lowest octave possible. For now, while bpo is restricted + // to integer values, this just means determining how many + // bands need to go in the top octave, and the remaining ones + // will be divided into groups of bpo bands (except possibly + // the last). + int gbno; + for (gbno = bandpass_bands_begin(); gbno < bandpass_bands_end(); gbno++) { + double ff = bandpass_band_ff(gbno); + double ffsd = ff_sd(ff); + double ff_support = gaussian_support(ffsd, params.max_error * 0.5); + // If the bandpass support falls within the downsampling filter + // passband of the next octave, we can switch octaves. + if (params.multirate && ff + ff_support <= ds_passband / 2) + break; + } + n_bands_top_octave = gbno; + + // Figure out the number of octaves, keeping in mind that + // the top octave is of variable size, and DC band is added + // to the bottom octave even if that makes it larger than + // the others. + n_octaves = 1 // The top octave + + (n_bandpass_bands_total - n_bands_top_octave + + (params.bands_per_octave - 1)) / params.bands_per_octave; + + // Calculate the kernel support needed for the lowest-frequency + // bandpass band to use as a basis for an initial estimate of + // the FFT size needed. This duplicates some code at the + // beginning of make_band(). + assert(n_bands_top_octave >= 1); + int low_bp_band = n_bands_top_octave - 1; + double low_bp_band_time_sd = time_sd(band_ff(low_bp_band)); + + double low_bp_band_time_analysis_support = + gaussian_support(low_bp_band_time_sd, params.max_error); + double low_bp_band_time_synthesis_support = + low_bp_band_time_analysis_support * synthesis_support_multiplier(); + + make_zones(); + + // Make analysis plans + // Since ds_time_support is in the unit of lower octave samples, + // we need to multiply it by two to get upper octave samples. + unsigned int max_support = + std::max(ceil(low_bp_band_time_analysis_support), + ds_time_support * 2); + unsigned int size = next_power_of_two(max_support * 2); + ref p; + for (;;) { + p = new plan(this, false, size, max_support); + if (p->ok) + break; + size *= 2; + } + anl_plans.push_back(p); // Smallest possible plan + p = new plan(this, false, size * 2, max_support); + assert(p->ok); + anl_plans.push_back(p); // Next larger plan + + if (params.synthesis) { + // Make synthesis plan (only one for now) + max_support = std::max(ceil(low_bp_band_time_synthesis_support), + ds_time_support * 2); + // Room for at at least the two fats + as much filet + size = next_power_of_two(max_support * 2) * 2; + p = new plan(this, true, size, max_support); + assert(p->ok); + syn_plans.push_back(p); + } + + for (int i = 0; i < (int)anl_plans.size(); i++) + make_band_plans(i, false); + for (int i = 0; i < (int)syn_plans.size(); i++) + make_band_plans(i, true); + + // Find the largest fftsize and sftsize of any plan + for (size_t i = 0; i < anl_plans.size(); i++) { + fftsize_max = std::max(fftsize_max, anl_plans[i]->fftsize); + sftsize_max = std::max(sftsize_max, anl_plans[i]->sftsize_max); + } + for (size_t i = 0; i < syn_plans.size(); i++) { + fftsize_max = std::max(fftsize_max, syn_plans[i]->fftsize); + sftsize_max = std::max(sftsize_max, syn_plans[i]->sftsize_max); + } + + // Lay out the coefficient structures according to the + // synthesis plan if we have one, or the largest analysis + // plan if not. + std::vector> *cmeta_source = + params.synthesis ? &syn_plans : &anl_plans; + ref &largest_plan(((*cmeta_source)[cmeta_source->size() - 1])); + cmeta_any = make_meta(filet_part(largest_plan->fftsize)); + } + + void make_zones() { + // Band number starting at 0 close to fs/2 and increasing + // with decreasing frequency + int tbno = 0; + int oct = 0; + int zno = 0; + // Loop over the octaves, from high to low frequencies, + // creating new zones where needed + for (;;) { + int max_bands_this_octave = (zno == 0) ? + n_bands_top_octave : params.bands_per_octave; + int bands_remaining = n_bandpass_bands_total - tbno; + int bands_this_octave = std::min(max_bands_this_octave, bands_remaining); + int bands_below = bands_remaining - bands_this_octave; + bool dc_zone = (bands_below == 0); + bool dc_adjacent_zone = (bands_below < (int)params.bands_per_octave); + if (zno < 2 || dc_zone || dc_adjacent_zone || + params.bands_per_octave < 6) + { + make_zone(oct, zno, tbno, tbno + bands_this_octave, + dc_zone, bands_below); + zno++; + } + octaves.push_back(octave()); + octaves.back().z = zones[zno - 1].get(); + oct++; + tbno += bands_this_octave; + if (dc_zone) + break; + } + assert(octaves.size() == n_octaves); + } + + // Create a zone consisting of the bandpass bands band0 + // (inclusive) to band1 (exclusive), using the usual gbno + // numbering going from high to low frequencies, and + // possible a lowpass band band1. + + void make_zone(int oct, unsigned int zno, + int band0, int band1, + bool dc_zone, int bands_below) + { + assert(zones.size() == zno); + zone *z = new zone(); + z->zno = zno; + zones.push_back(ref>(z)); + + pod_vector power; + + // Create the real (non-mock) bands, from low to high + // frequency. + if (dc_zone) + // This zone has a lowpass band + push(z->bandparams, make_band(oct, band1, true, false)); + // The actual (non-mock) bandpass bands of this zone + for (int i = band1 - 1; i >= band0; i--) + push(z->bandparams, make_band(oct, i, false, false)); + + if (! dc_zone) { + // There are other zones below this; add mock bands + // to simulate them for purposes of calculating the dual. + + // Identify the lowest frequency of interest in the zone + assert(z->bandparams.size() >= 1); + band_params *low_band = z->bandparams[0].get(); + double zone_bottom_ff = low_band->ff - low_band->ff_support; + + int i = band1; + for (; i < band1 + bands_below; i++) { + band_params *mock_band = make_band(oct, i, false, true); + push(z->mock_bandparams, mock_band); + // There's no point in creating further mock bands + // once they no longer overlap with the current zone. + // The condition used here may cause the creation of + // one more mock band than is actually needed, as it + // is easier to create the band first and check for + // overlap later than the other way round. + if (mock_band->ff + mock_band->ff_support < zone_bottom_ff) { + i++; + break; + } + } + // Create a mock lowpass band. This may correspond to the + // actual lowpass band, or if the loop above exited early, + // it may merely be be a placeholder that serves no real + // purpose other than making the power vector look better. + push(z->mock_bandparams, make_band(oct, i, true, true)); + } + + // If there are other zones above this, add mock bands + // to simulate them for purposes of calculating the dual, + // but only up to the Nyquist frequency of the current + // octave. + int nyquist_band = oct * (int)params.bands_per_octave; + for (int i = band0 - 1; i >= nyquist_band; i--) + push(z->mock_bandparams, make_band(oct, i, false, true)); + + z->n_bands = (unsigned int)z->bandparams.size(); + + // Find the largest coefficient step in the zone, as this will + // determine the necessary alignment of signal slices in time, + // but make it at least two (corresponding max_step_log2 = 1) + // because the downsampling code requires alignement to even + // indices. + unsigned int m = 1; + for (unsigned int obno = 0; obno < z->bandparams.size(); obno++) { + m = std::max(m, z->bandparams[obno]->step_log2); + } + z->max_step_log2 = m; + + max_step_log2 = std::max(max_step_log2, m); + } + + // Calculate band parameters for a single band. + // + // If dc is true, this is the DC band, and gbno indicates + // the cutoff frequency; it is one more than the gbno of + // the lowest-frequency bandpass band. + + band_params * + make_band(int oct, double gbno, bool dc, bool mock) { + band_params *bp = new band_params; + if (dc) + // Make the actual DC band cutoff frequency a bit higher, + // by an empirically chosen fraction of a band, to reduce + // power fluctuations. + gbno -= 0.8750526596806952; + + // For bandpass bands, the center frequency, or for the + // lowpass band, the lowpass cutoff frequency, as a + // fractional frequency, in terms of the octave's sample + // rate. + double ff = ldexp(bandpass_band_ff(gbno), oct); + + // Standard deviation of the bandpass Gaussian, as a + // fractional frequency + double ffsd = ff_sd(ff); + double time_sd = sd_f2t(ffsd); + double time_support = + gaussian_support(time_sd, params.max_error); + + // The support of the Gaussian, i.e., the smallest standard + // deviation at which it can be truncated on each side + // without the error exceeding our part of the error budget, + // which is some fraction of params.max_error. Note + // that this is one-sided; the full width of the support + // is 2 * ff_support. + double bp_ff_support = gaussian_support(ffsd, params.max_error * 0.5); + // Additional support for the flat portion of the DC band lowpass + double dc_support = dc ? ff : 0; + // Total frequency-domain support for this band, one-sided + double band_support = bp_ff_support + dc_support; + // Total support needed for this band, two-sided + double band_2support = band_support * 2; + + // Determine the downsampling factor for this band. + int exp = 0; + while (band_2support <= 0.5) { + band_2support *= 2; + exp++; + } + bp->step_log2 = exp; + bp->step = 1U << bp->step_log2; + + bp->dc = dc; + bp->ff = ff; + bp->ff_support = band_support; + bp->time_support = time_support; + bp->ffsd = ffsd; + + return bp; + } + + // Given a fractional frequency, return the standard deviation + // of the frequency-domain window as a fractional frequency + double ff_sd(double ff) const { return params.sd() * ff; } + + // Given a fractional frequency, return the standard deviation + // of the time-domain window in samples. + // + // ff_sd = 1.0 / (tau * t_sd) + // per http://users.ece.gatech.edu/mrichard/ + // Gaussian%20FT%20and%20random%20process.pdf + // and python test program gaussian-overlap.py + // => (tau * t_sd) * ff_sd = 1.0 + // => t_sd = 1.0 / (tau * f_sd) + double time_sd(double ff) const { return 1.0 / (tau * ff_sd(ff)); } + + double q() const { return params.q(); } + + // Find the worst-case time support of the analysis filters, i.e., + // the largest distance in time between a signal sample and a + // coefficient affected by that sample. + double analysis_support() const { + // The lowpass filter is the steepest one + return analysis_support(band_lowpass()); + } + + // Find the time support of the analysis filter for bandpass band + // number gbno + double analysis_support(double gbno) const { + return gaussian_support(time_sd(bandpass_band_ff(gbno)), + params.max_error); + } + + // Ditto for the resynthesis filters. + double synthesis_support() const { + return analysis_support() * synthesis_support_multiplier(); + } + + double synthesis_support(double gbno) const { + return analysis_support(gbno) * synthesis_support_multiplier(); + } + + // The synthesis support multiplier, the factor by which the + // synthesis filters are wider than the analysis filters in + // the time domain. + double synthesis_support_multiplier() const { + if (! params.synthesis) + return 1.0; + // At high ff_min, the top band may be the one with the widest support. + if (params.ff_min > 0.25) + return 4; + if (params.bands_per_octave <= 4) + return 2.5; + return 2.3; + } + + // Get the center frequency of bandpass band "gbno", which + // need not be a valid bandpass band number; out-of-range + // arguments will return extrapolated frequencies based on + // the logarithmic band spacing. + double bandpass_band_ff(double gbno) const { + return exp2(bandpass_band_log2ff(gbno)); + } + + // Get the band number of the bandpass band corresponding + // to the fractional frequency "ff", as a floating point + // number. This is the inverse of bandpass_band_ff(). + double ff_bandpass_band(double ff) const { + return log2ff_bandpass_band(log2(ff)); + } + + int choose_plan(const std::vector> &plans, int64_t size) const + { + unsigned int i = 0; + while (i < plans.size() - 1 && plans[i]->filet_size < size) + i++; + return i; + } + + void + synthesize_one_slice(int oct, int pno, const coefs &msc, + const pod_vector &downsampled, + sample_index_t t0, + T *signal_out, + pod_vector &buf0, // fftsize + pod_vector &buf2, // largest sftsize + pod_vector &buf3 // largest sftsize + ) const + { + const plan &plan(*syn_plans[pno]); + zone &z = *octaves[oct].z; + pod_vector &signal(buf0); + std::fill(signal.begin(), signal.end(), (T)0); + + pod_vector &coefbuf(buf3); + + for (unsigned int obno = 0; obno < z.bandparams.size(); obno++) { + band_params *bp = z.bandparams[obno].get(); + band_plan *bpl = &bp->syn_plans[pno]; + + // log2 of the coefficient downsampling factor + int coef_shift = bp->step_log2; + coef_index_t ii = t0 >> coef_shift; + + read(msc, bno_merge(oct, obno), ii, ii + bpl->sftsize, coefbuf.data()); + C *indata = coefbuf.data(); + + pod_vector &sdata(buf2); + + T scale_factor = (T)1 / ((T)params.coef_scale * bpl->sftsize); + + // Apply phase correction, adjust for non-integer center + // frequency, and apply scale factor. Note that phase + // must be calculated in double precision. + + // We can't use bp->ff here because in the case of the + // lowpass band, it's the cutoff rather than the center. + double ff = bpl->center * plan.inv_fftsize_double; + double arg = (params.phase == coef_phase::global) ? + tau * t0 * ff : 0; + C phase_times_scale = C(cos(arg), sin(arg)) * scale_factor; + elementwise_product_times_scalar(sdata.data(), indata, + bpl->shift_kernel_conj.data(), + phase_times_scale, bpl->sftsize); + + // Switch to frequency domain + bpl->sft->transform(sdata.data()); + + // Multiply signal spectrum by frequency-domain dual window, + // accumulating result in signal. + + for (unsigned int i = 0; i < bpl->sftsize; i++) { + int iii = (bpl->fq_offset_int + i) & (plan.fftsize - 1); + // Note the ifftshift of the input index, as f=0 + // appears in the middle of the window + C v = sdata[i ^ (bpl->sftsize >> 1)] * bpl->dual_kernel[i]; + // Frequency symmetry + signal[iii] += v; + if (params.lowpass_version == 2 || ! bp->dc) + signal[-iii & (plan.fftsize - 1)] += conj(v); + } + } + + if (oct + 1 < (int) n_octaves) { + // Upsample the downsampled data from the lower octaves + pod_vector &sdata(buf2); + assert(downsampled.size() == plan.dsparams.sftsize); + assert(sdata.size() >= plan.dsparams.sftsize); +#if GABORATOR_USE_REAL_FFT + plan.dsparams.rsft->transform(downsampled.data(), sdata.begin()); +#else + // Real to complex + std::copy(downsampled.begin(), downsampled.end(), sdata.begin()); + plan.dsparams.sft->transform(sdata.data()); +#endif + for (unsigned int i = 0; i < plan.dsparams.sftsize; i++) { + sdata[i] *= plan.dsparams.dual_kernel + [i ^ (plan.dsparams.sftsize >> 1)]; + } + + // This implicitly zero pads the spectrum, by not adding + // anything to the middle part. The splitting of the + // Nyquist band is per http://dsp.stackexchange.com/ + // questions/14919/upsample-data-using-ffts-how-is-this- + // exactly-done but should not really matter because there + // should be no energy there to speak of thanks to the + // windowing above. + + assert(plan.dsparams.sftsize == plan.fftsize / 2); + unsigned int i; + for (i = 0; i < plan.dsparams.sftsize / 2; i++) + signal[i] += sdata[i]; + //C nyquist = sdata[i] * (T)0.5; + C nyquist = sdata[i] * (T)0.5; + signal[i] += nyquist; + signal[i + plan.fftsize / 2] += nyquist; + i++; + for (;i < plan.dsparams.sftsize; i++) + signal[i + plan.fftsize / 2] += sdata[i]; + } + + // Switch to time domain +#if GABORATOR_USE_REAL_FFT + plan.rft->itransform(signal.data(), signal_out); +#else + plan.ft->itransform(signal.data()); + // Copy real part to output + complex2real(signal.begin(), signal.end(), signal_out); +#endif + } + +private: + + // Analyze a signal segment consisting of any number of samples. + // oct is the octave; this is 0 except in recursive calls + // real_signal points to the first sample + // t0 is the sample time of the first sample + // t1 is the sample time of the sample after the last sample + // coefficients are added to msc + + void + analyze_sliced(buffers &buf, int oct, const T *real_signal, + sample_index_t t0, sample_index_t t1, + double included_ds_support, + coefs &msc) const + { + assert(t1 >= t0); + int pno = choose_plan(anl_plans, t1 - t0); + const plan &plan(*anl_plans[pno]); + + // Even though we don't align the FFTs to full filet-size + // slices in this code path, we still need to align them to + // coefficient samples so that we don't have to do expensive + // sub-sample time shifts. Specifically, we need to align + // them to the largest coefficient time step of the octave. + // slice_t0 is the sample time of the first sample in the + // filet (not the FFT as a whole). + zone &z = *octaves[oct].z; + sample_index_t slice_t0 = t0 & ~((1 << z.max_step_log2) - 1); + //printf("slice t0 = %d\n", (int)slice_t0); + + // Find the number of slices we need to divide the signal into. + // We need it ahead of time so that we can size the "downsampled" + // array accordingly. + uint64_t n_slices = ((t1 - slice_t0) + (plan.filet_size - 1)) / plan.filet_size; + + // The "downsampled" array needs to fit one filet per slice + + // the fat on each side, all of half size thanks to downsampling. + // Length of each downsampled slice (including padding) + uint64_t dstotlen = ((n_slices * plan.filet_size) + (2 * plan.fat_size)) >> 1; + + // The range of sample times covered by the "downsampled" array + sample_index_t tmp = slice_t0 - (int)plan.fat_size; + assert((tmp & 1) == 0); + sample_index_t dst0 = tmp >> 1; + sample_index_t dst1 = dst0 + (int64_t)dstotlen; + + // Not all of the "downsampled" array actually contains + // nonzero data. Calculate adjusted bounds to use in the + // recursive analysis so that we don't needlessly analyze + // zeroes. + int ds_support = (int)ceil(ds_time_support - included_ds_support); + sample_index_t dst0a = std::max(dst0, (t0 >> 1) - ds_support); + sample_index_t dst1a = std::min(dst1, (t1 >> 1) + 1 + ds_support); + + // Buffer for the downsampled signal. Since the size depends + // on the total amount of signal analyzed in this call (being + // about half of it), it can't be preallocated, but has to be + // dynamically allocated in each call. + pod_vector downsampled(dstotlen); + // "downsampled" will be added to, not assigned to, so we need + // to set it to zero initially. + std::fill(downsampled.begin(), downsampled.end(), 0); + + auto slice(buf.template get(5, plan.fftsize)); + // Clear the fat on both ends (once) + std::fill(slice.data(), slice.data() + plan.fat_size, 0); + std::fill(slice.data() + slice.size() - plan.fat_size, + slice.data() + slice.size(), 0); + + // For each slice. Note that slice_i counts from 0, not from + // the slice index of the first slice. + for (uint64_t slice_i = 0; slice_i < n_slices; slice_i++) { + if (slice_t0 >= t1) + break; + sample_index_t slice_t1 = std::min(slice_t0 + plan.filet_size, t1); + // Copy into filet part of aligned buffer, possibly zero padding + // if the remaining signal is shorter than a full slice. + copy_overlapping_zerofill(slice.data() + (int)plan.fat_size, + plan.filet_size, + real_signal, + t0 - slice_t0, + t1 - slice_t0); + // Analyze the slice + auto spectrum(buf.template get(1, plan.fftsize)); +#if GABORATOR_USE_REAL_FFT + plan.rft->transform(slice.data(), spectrum.data()); +#else + // Real to complex + auto signal(buf.template get(0, plan.fftsize)); + std::copy(slice.data(), slice.data() + plan.fftsize, signal.begin()); + plan.ft->transform(signal.data(), spectrum.data()); +#endif + auto tmp(buf.template get(2, plan.sftsize_max)); + auto coefbuf(buf.template get(4, plan.sftsize_max)); + + T scale_factor = (T)params.coef_scale * plan.inv_fftsize_t; + + for (unsigned int obno = 0; obno < z.bandparams.size(); obno++) { + band_params *bp = z.bandparams[obno].get(); + band_plan *bpl = &bp->anl_plans[pno]; + C *sdata = tmp.data(); + + // Multiply a slice of the spectrum by the frequency- + // domain window and store in sdata. + // + // We need to take care not to overrun the beginning or + // end of the spectrum - for the dc band, we always + // need to wrap around to negative frequencies, and + // potentially it could happen with other bands, too, + // if they are really wide. To avoid the overhead of + // checking in the inner loop, use a separate slow path + // for the rare cases where wrapping happens. + + int start_index = bpl->fq_offset_int; + int end_index = bpl->fq_offset_int + bpl->sftsize; + if (start_index >= 0 && end_index < (int)((plan.fftsize >> 1) + 1)) { + // Fast path: the slice lies entirely within the + // positive-frequency half of the spectrum (including + // DC and Nyquist). + elementwise_product(sdata, + spectrum.data() + start_index, + bpl->kernel.data(), + bpl->sftsize); + } else { + // Slow path + for (size_t i = 0; i < bpl->sftsize; i++) + sdata[i] = get_real_spectrum_coef(spectrum.data(), + (int)(start_index + i), plan.fftsize) * bpl->kernel[i]; + } + // The band center frequency is at the center of the + // spectrum slice and at the center of the window, so + // it also ends up at the center of sdata. The center + // frequency of the band is considered f=0, so for the + // ifft, it should be at index 0, not the center. + // Therefore, in principle we should perform an + // ifftshift of sdata here before the ifft, but since + // the time-domain data are going to be multiplied by + // the shift kernel anyway, the ifftshift is baked + // into the shift kernel by flipping the sign of every + // other element so that it is effectively free. + + // Switch to time domain + auto band(buf.template get(3, plan.sftsize_max)); + bpl->sft->itransform(sdata, band.data()); + + // Apply ifftshift, adjust for non-integer center + // frequency, correct phase, scale amplitude, and add + // to the output coefficients. + double ff = bpl->center * plan.inv_fftsize_double; + double arg; + if (params.phase == coef_phase::global) + arg = -tau * (slice_t0 - plan.fat_size) * ff; + else + arg = 0; + C phase_times_scale = C(cos(arg), sin(arg)) * scale_factor; + elementwise_product_times_scalar(coefbuf.data(), band.data(), + bpl->shift_kernel.data(), + phase_times_scale, + bpl->sftsize); + // log2 of the coefficient downsampling factor + int coef_shift = bp->step_log2; + assert(((slice_t0 - (int)plan.fat_size) & ((1 << coef_shift) - 1)) == 0); + coef_index_t ii = (slice_t0 - (int)plan.fat_size) >> coef_shift; + + // Only part of coefbuf contains substantially nonzero + // data: that corresponding to the signal interval + // t0..t1 + the actual support of the filter for this band. + // There's no point adding the zeros to the coefficients, + // so trim. + int support = (int)ceil(bp->time_support); + coef_index_t ii0 = std::max(ii, (t0 - support) >> coef_shift); + coef_index_t ii1 = std::min(ii + bpl->sftsize, + ((t1 + support) >> coef_shift) + 1); + add(msc, bno_merge(oct, obno), ii0, ii1, coefbuf.data() + (ii0 - ii)); + } + + // Downsample + if (oct + 1 < (int) n_octaves) { + T *downsampled_dst = downsampled.data() + + slice_i * (plan.filet_size >> 1); + + auto sdata(buf.template get(2, plan.sftsize_max)); + // This is using a larger buffer than we actually need + auto ddata(buf.template get(0, plan.sftsize_max)); + assert(ddata.size() >= plan.dsparams.sftsize); + // Extract the low-frequency part of "spectrum" into "sdata" + // and multiply it by the lowpass filter frequency response. + // This means both positive and negative low frequencies. + size_t half_size = plan.dsparams.sftsize >> 1; + assert(plan.fftsize - half_size == 3 * half_size); +#if GABORATOR_USE_REAL_FFT + // Positive frequencies + elementwise_product(sdata.data(), spectrum.data(), + plan.dsparams.kernel.data() + half_size, + half_size); + // Nyquist + sdata[half_size] = 0; + // Use the same buffer as the complex FFT, but as floats + T *real_ddata = reinterpret_cast(ddata.data()); + plan.dsparams.rsft->itransform(sdata.data(), real_ddata); + // Only accumulate nonzero part + size_t n = ((slice_t1 - slice_t0) + 2 * (int)plan.fat_size + 1) >> 1; + for (size_t i = 0; i < n; i++) + downsampled_dst[i] += real_ddata[i]; +#else + // Positive frequencies + elementwise_product(sdata.data(), spectrum.data(), + plan.dsparams.kernel.data() + half_size, + half_size); + // Negative requencies + elementwise_product(sdata.data() + half_size, + spectrum.data() + plan.fftsize - half_size, + plan.dsparams.kernel.data(), half_size); + // Convert to time domain + plan.dsparams.sft->itransform(sdata.data(), ddata.data()); + for (unsigned int i = 0; i < plan.dsparams.sftsize; i++) + downsampled_dst[i] += ddata[i].real(); +#endif + } + + // Next slice + slice_t0 = slice_t1; + } + + // Recurse + if (oct + 1 < (int)n_octaves) + analyze_sliced(buf, oct + 1, downsampled.data() + (dst0a - dst0), + dst0a, dst1a, ds_time_support / 2, msc); + } + + // Resynthesize audio from the coefficients in "msc". The audio will + // cover samples from t0 (inclusive) to t1 (exclusive), and is stored + // starting at *real_signal, which must have room for (t1 - t0) + // samples. The octave "oct" is 0 except in recursive calls. + + void + synthesize_sliced(int oct, const coefs &msc, + sample_index_t t0, sample_index_t t1, + T *real_signal) const + { + int pno = choose_plan(syn_plans, t1 - t0); + const plan &plan(*syn_plans[pno]); + + // XXX clean up - no need to pass support arg + slice_index_t si0 = plan.affected_slice_b(t0, plan.oct_support); + slice_index_t si1 = plan.affected_slice_e(t1, plan.oct_support); + + // sub_signal holds the reconstructed subsampled signal from + // the lower octaves, for the entire time interval covered by + // the slices + int sub_signal_len = ((si1 - si0) * plan.filet_size + 2 * plan.fat_size) / 2; + pod_vector sub_signal(sub_signal_len); + std::fill(sub_signal.begin(), sub_signal.end(), 0); + if (oct + 1 < (int)n_octaves) { + int64_t sub_t0 = si0 * (plan.filet_size / 2); + int64_t sub_t1 = sub_t0 + sub_signal_len; + // Recurse + assert(sub_t1 - sub_t0 == (int64_t)sub_signal.size()); + synthesize_sliced(oct + 1, msc, sub_t0, sub_t1, sub_signal.data()); + } + + // Allocate buffers for synthesize_one_slice(), to be shared + // between successive calls to avoid repeated allocation + pod_vector buf0(plan.fftsize); + //pod_vector buf1(fftsize); + pod_vector buf2(plan.sftsize_max); + pod_vector buf3(plan.sftsize_max); + + pod_vector downsampled(plan.dsparams.sftsize); + pod_vector signal_slice(plan.fftsize); + + // For each slice + for (slice_index_t si = si0; si < si1; si++) { + sample_index_t slice_t0 = si * plan.filet_size; + + // Copy downsampled signal to "downsampled" for upsampling + if (oct + 1 < (int) n_octaves) { + int bi = (si - si0) * filet_part(plan.dsparams.sftsize); + int ei = bi + plan.dsparams.sftsize; + assert(bi >= 0); + assert(ei <= (int)sub_signal.size()); + std::copy(sub_signal.begin() + bi, + sub_signal.begin() + ei, + downsampled.begin()); + } + + synthesize_one_slice(oct, pno, msc, downsampled, slice_t0, + signal_slice.data(), buf0, buf2, buf3); + + // Copy overlapping part + sample_index_t b = std::max(slice_t0 + plan.fat_size, t0); + sample_index_t e = std::min(slice_t0 + plan.fftsize - plan.fat_size, t1); + for (sample_index_t i = b; i < e; i++) + real_signal[i - t0] = signal_slice[i - slice_t0]; + } + } + +public: + // The main analysis entry point. + // The resulting coefficients are added to any existing + // coefficients in "msc". + + void analyze(const T *real_signal, sample_index_t t0, sample_index_t t1, + coefs &msc, int n_threads = 1) const + { + analyze1(real_signal, t0, t1, msc, n_threads, 1); + } + + void analyze1(const T *real_signal, sample_index_t t0, sample_index_t t1, + coefs &msc, int n_threads, int level) const + { + assert(msc.octaves.size() == n_octaves); + (void)n_threads; + buffers buf(fftsize_max, sftsize_max); + analyze_sliced(buf, 0, real_signal, t0, t1, 0, msc); + } + + // The main synthesis entry point + + void + synthesize(const coefs &msc, sample_index_t t0, sample_index_t t1, + T *real_signal, int n_threads = 1) const + { + assert(params.synthesis); + (void)n_threads; + synthesize_sliced(0, msc, t0, t1, real_signal); + } + + bool bno_split(int gbno, int &oct, unsigned int &obno, bool dc) const { + return gaborator::bno_split(*cmeta_any, gbno, oct, obno, dc); + } + + int bno_merge(int oct, unsigned int obno) const { + return gaborator::bno_merge(*cmeta_any, oct, obno); + } + + // Get the bounds of the range of existing coefficients for all bands, + // in units of signal samples. + void get_coef_bounds(const coefs &msc, + sample_index_t &si0_ret, sample_index_t &si1_ret) + const + { + // The greatest coefficient range typically occurs in the + // lowest bandpass band, but this is not always the case, + // so to be certain, check them all. + sample_index_t min_si0 = INT64_MAX; + sample_index_t max_si1 = INT64_MIN; + for (int band = bands_begin(); band != bands_end(); band++) { + coef_index_t ci0, ci1; + get_band_coef_bounds(msc, band, ci0, ci1); + // Convert from coefficient samples to signal samples + int exp = band_scale_exp(band); + sample_index_t si0 = shift_left(ci0, exp); + sample_index_t si1 = shift_left(ci1 - 1, exp) + 1; + min_si0 = std::min(min_si0, si0); + max_si1 = std::max(max_si1, si1); + } + si0_ret = min_si0; + si1_ret = max_si1; + } + + unsigned int band_step_log2(int gbno) const { + return gaborator::band_step_log2(*cmeta_any, gbno); + } + + int bandpass_bands_begin() const { return 0; } + int bandpass_bands_end() const { return n_bands_total - 1; } + + int bands_begin() const { return 0; } + int bands_end() const { return n_bands_total; } + + int band_lowpass() const { return n_bands_total - 1; } + int band_ref() const { return ffref_gbno; } + + // Get the center frequency of band number gbno as a fractional + // frequency. gbno must be a valid band number. For the lowpass + // band, this returns zero. + double band_ff(int gbno) const { + if (gbno == band_lowpass()) + return 0; + return bandpass_band_ff(gbno); + } + + ~analyzer() { + } + + // Get the base 2 logarithm of the downsampling factor of + // band "obno" in octave "oct" + int band_scale_exp(int oct, unsigned int obno) const { + return gaborator::band_scale_exp(*cmeta_any->octaves[oct].z, oct, obno); + } + + // Get the base 2 logarithm of the downsampling factor of + // band "gbno" + int band_scale_exp(int gbno) const { + int oct; + unsigned int obno; // Band number within octave + bool r = bno_split(gbno, oct, obno, true); + assert(r); + return band_scale_exp(oct, obno); + } + + // Get the base 2 logarithm of the highest downsampling factor of + // any band + int band_scale_exp_max() const { + return band_scale_exp(bandpass_bands_end() - 1); + } + + + // Find the sample time of the band "gbno" coefficient closest to + // time "t". "gbno" must be a valid band number. + sample_index_t nearest_coef_sample(int gbno, double t) const { + int shift = band_step_log2(gbno); + return shift_left((sample_index_t) round(ldexp(t, -shift)), shift); + } + // Find the highest coefficient sample time less than or equal to + // "t" for band "gbno". "gbno" must be a valid band number. + sample_index_t floor_coef_sample(int gbno, double t) const { + int shift = band_step_log2(gbno); + return shift_left((sample_index_t) floor(ldexp(t, -shift)), shift); + } + // Find the lowestt coefficient sample time greater than or equal + // to "t" for band "gbno". "gbno" must be a valid band number. + sample_index_t ceil_coef_sample(int gbno, double t) const { + int shift = band_step_log2(gbno); + return shift_left((sample_index_t) ceil(ldexp(t, -shift)), shift); + } + + // Members initialized in the constructor, and listed in + // order of initialization + parameters params; + double band_spacing_log2; + double band_spacing; + double tuning_log2ff; + affine_transform log2ff_bandpass_band; + affine_transform bandpass_band_log2ff; + unsigned int n_bandpass_bands_total; + unsigned int n_bands_top_octave; + + struct plan: public refcounted { + plan(const plan &) = delete; + plan(analyzer *anl, bool synthesis_, unsigned int fftsize_, double support_): + ok(false), + synthesis(synthesis_), + fftsize(fftsize_), + oct_support(support_), + sftsize_max(0) + { + fftsize_log2 = whichp2(fftsize); + + inv_fftsize_double = 1.0 / fftsize; + inv_fftsize_t = (T) inv_fftsize_double; + +#if GABORATOR_USE_REAL_FFT + rft = pool, int>::shared.get(fftsize); +#else + ft = pool, int>::shared.get(fftsize); +#endif + // Set up the downsampling parameters in dsparams. + + // Downsampling is always by a factor of two. + // dsparams.sftsize is the size of the FFT used to go back to + // the time domain after discarding the top half of the + // spectrum. + dsparams.sftsize = fftsize >> 1; + dsparams.kernel.resize(dsparams.sftsize); + if (synthesis) + dsparams.dual_kernel.resize(dsparams.sftsize); + + // Use the convolution of a rectangle and a Gaussian. + // A piecewise function composed from two half-gaussians + // joined by a horizontal y=1 segment is not quite smooth + // enough. Put the passband in the middle. + for (int i = 0; i < (int)dsparams.sftsize; i++) + dsparams.kernel[i] = + gaussian_windowed_lowpass_1(anl->ds_ff, anl->ds_ff_sd, + ((double)i / dsparams.sftsize) - 0.5); + + if (synthesis) { + // The dual_kernel field of the downsampling pseudo-band holds + // the upsampling filter, identical to the downsampling filter + // except for amplitude scaling. + std::copy(dsparams.kernel.begin(), dsparams.kernel.end(), + dsparams.dual_kernel.begin()); + } + // Prescale the downsampling filter + scale_vector(dsparams.kernel, inv_fftsize_double); + if (synthesis) { + // Prescale the upsampling filter + scale_vector(dsparams.dual_kernel, 1.0 / dsparams.sftsize); + } +#if GABORATOR_USE_REAL_FFT + dsparams.rsft = pool, int>::shared.get(dsparams.sftsize); +#else + dsparams.sft = pool, int>::shared.get(dsparams.sftsize); +#endif + // It may be possible to reduce the size of the fat from 1/4 + // of the fftsize, but we need to keep things aligned with the + // coefficients, and it needs to be even for downsampling. + if (! synthesis) { + unsigned int align = 1 << std::max(anl->max_step_log2, 2U); + fat_size = (oct_support + (align - 1)) & ~(align - 1); + // There must be room for at least one signal sample in each + // half of the FFT; it can't be all fat + if (!(fat_size < (fftsize >> 1))) + return; // fail + } else { + fat_size = fftsize >> 2; + } + filet_size = fftsize - 2 * fat_size; + + // Constructor was successful + ok = true; + } + + // Index of first slice affected by sample at t0 + + // fft number i covers the sample range + // t = (i * filetsize .. i * filetsize + (fftsize - 1)) + // t >= i * filetsize and t < i * filetsize + fftsize + // A sample at t affects ffts i where + // i <= t / filetsize and + // i > (t - fftsize) / filetsize + // the filet of fft number i covers the sample range + // (fat + (i * filetsize) .. fat + (i * filetsize) + (filetsize - 1)) + // + // However, due to the FFT size being rounded up to a power of two, + // the outermost parts have near-zero weights and can be ignored; + // this is done by adjusting the time values by the width of that + // outermost part, which is (fatsize - support) + + slice_index_t affected_slice_b(sample_index_t t0, unsigned int support) const { + return floor_div(t0 - fftsize + (fat_part(fftsize) - support), filet_part(fftsize)) + 1; + } + + // Index of first slice not affected by sample at t1 + slice_index_t affected_slice_e(sample_index_t t1, unsigned int support) const { + return floor_div(t1 - 1 - (fat_part(fftsize) - support), filet_part(fftsize)) + 1; + } + + bool ok; + bool synthesis; + + unsigned int fftsize_log2; // log2(fftsize) + unsigned int fftsize; // The size of the main FFT, a power of two. + unsigned int fat_size; + unsigned int filet_size; + + // The width of the widest filter in the time domain, in + // octave subsamples + unsigned int oct_support; + + double inv_fftsize_double; // 1.0 / fftsize + T inv_fftsize_t; // 1.0f / fftsize (if using floats) + + unsigned int sftsize_max; // The size of the largest band FFT, a power of two + downsampling_params dsparams; + + // Fourier transform object for transforming a full slice +#if GABORATOR_USE_REAL_FFT + rfft *rft; +#else + fft *ft; +#endif + }; + + // Calculate per-plan, per-band coefficients for plan "pno", + // a synthesis plan if "syn" is true, otherwise an analysis plan. + + void make_band_plans(int pno, bool syn) { + std::vector::plan>> &plans + (syn ? syn_plans : anl_plans); + plan &plan(*plans[pno].get()); + + for (int zno = 0; zno < (int)zones.size(); zno++) { + zone *z = zones[zno].get(); + + make_band_plans_2(z->bandparams, pno, syn, false); + make_band_plans_2(z->mock_bandparams, pno, syn, true); + + if (plan.synthesis) { + // Accumulate window power for calculating dual + std::vector power(plan.fftsize); + // Real bands + for (unsigned int i = 0; i < z->bandparams.size(); i++) { + band_params *bp = z->bandparams[i].get(); + band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; + accumulate_power(plan, bp, bpl, power.data()); + } + // Mock bands + for (unsigned int i = 0; i < z->mock_bandparams.size(); i++) { + band_params *bp = z->mock_bandparams[i].get(); + band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; + accumulate_power(plan, bp, bpl, power.data()); + } + + // Calculate duals + for (unsigned int obno = 0; obno < z->bandparams.size(); obno++) { + band_params *bp = z->bandparams[obno].get(); + band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; + for (unsigned int i = 0; i < bpl->sftsize; i++) { + // ii = large-FFT bin number + int ii = i + bpl->fq_offset_int; + bpl->dual_kernel[i] /= power[ii & (plan.fftsize - 1)]; + } + // The analysis kernels are no longer needed + bpl->kernel = std::vector(); + bpl->shift_kernel = pod_vector(); + } + z->mock_bandparams.clear(); + } + } + } + + void make_band_plans_2(std::vector>> &bv, int pno, + bool syn, bool mock) + { + std::vector::plan>> &plans + (syn ? syn_plans : anl_plans); + plan &plan(*plans[pno].get()); + + for (unsigned int obno = 0; obno < bv.size(); obno++) { + band_params *bp = bv[obno].get(); + std::vector> *bplv = syn ? &bp->syn_plans : &bp->anl_plans; + // XXX redundant resizes + bplv->resize(plans.size()); + band_plan *bpl = &(*bplv)[pno]; + + // Note that bp->step_log2 cannot be negative, meaning + // that the bands can only be subsampled, not oversampled. + unsigned int sftsize = plan.fftsize >> bp->step_log2; + + // PFFFT has a minimum size + sftsize = std::max(sftsize, (unsigned int)GABORATOR_MIN_FFT_SIZE); + + bpl->sftsize = sftsize; + bpl->sftsize_log2 = whichp2(bpl->sftsize); + + if (! mock) { + plan.sftsize_max = std::max(plan.sftsize_max, bpl->sftsize); + bpl->sft = pool, int>::shared.get(bpl->sftsize); + } + + bpl->kernel.resize(bpl->sftsize); + bpl->shift_kernel.resize(bpl->sftsize); + if (plan.synthesis) { + bpl->dual_kernel.resize(bpl->sftsize); + bpl->shift_kernel_conj.resize(bpl->sftsize); + } + + if (bp->dc) + bpl->center = 0; + else + bpl->center = bp->ff * plan.fftsize; + bpl->icenter = (int)rint(bpl->center); + bpl->fq_offset_int = bpl->icenter - (bpl->sftsize >> 1); + + // Calculate frequency-domain window kernel, possibly with + // wrap-around + for (unsigned int i = 0; i < bpl->sftsize; i++) + bpl->kernel[i] = 0; + // i loops over the kernel, with i=0 at the center. + // The range is twice the support on each side so that + // any excess space in the kernel due to rounding up + // the size to a power of two is filled in with actual + // Gaussian values rather than zeros. + int fq_support = (int)ceil(bp->ff_support * plan.fftsize); + for (int i = - 2 * fq_support; i < 2 * fq_support; i++) { + // ii = large-FFT band number of this kernel sample + int ii = i + bpl->fq_offset_int + (int)bpl->sftsize / 2; + // this_ff = fractional frequency of this kernel sample + double this_ff = ii * plan.inv_fftsize_double; + // ki = kernel index + int ki = ii - bpl->fq_offset_int; + // When sftsize == fftsize, the support of the kernel can + // exceed sftsize, and in this case, it should be allowed + // to wrap so that it remains smooth. When sftsize < fftsize, + // sftsize is large enough for the support and no wrapping + // is needed or wanted. + if (bpl->kernel.size() == plan.fftsize && !mock) { + bpl->kernel[ki & (plan.fftsize - 1)] += + eval_kernel(¶ms, bp, this_ff); + if (plan.synthesis) + bpl->dual_kernel[ki & (plan.fftsize - 1)] += + eval_dual_kernel(¶ms, bp, this_ff); + } else { + if (ki >= 0 && ki < (int)bpl->kernel.size()) { + bpl->kernel[ki] += eval_kernel(¶ms, bp, this_ff); + if (plan.synthesis) + bpl->dual_kernel[ki] = eval_dual_kernel(¶ms, bp, this_ff); + } + } + } + } + + // Calculate complex exponentials for non-integer center + // frequency adjustment and phase convention adjustment + for (unsigned int obno = 0; obno < bv.size(); obno++) { + band_params *bp = bv[obno].get(); + band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; + for (unsigned int i = 0; i < bpl->sftsize; i++) { + double center = + (params.phase == coef_phase::global) ? bpl->center : 0; + double arg = tau * ((double)i / bpl->sftsize) * -(center - bpl->icenter); + C t(cos(arg), sin(arg)); + // Apply ifftshift of spectrum in time domain + bpl->shift_kernel[i] = (i & 1) ? -t : t; + if (plan.synthesis) + // Conjugate kernel does not have ifftshift + bpl->shift_kernel_conj[i] = conj(t); + } + } + } + + // Add the power of the kernel in "*bp" to "power" + void + accumulate_power(plan &plan, band_params *bp, band_plan *bpl, T *power) { + for (unsigned int i = 0; i < bpl->sftsize; i++) { + // ii = large-FFT bin number + unsigned int ii = i + bpl->fq_offset_int; + ii &= plan.fftsize - 1; + assert(ii >= 0 && ii < plan.fftsize); + T p = bpl->kernel[i] * bpl->dual_kernel[i]; + power[ii] += p; + if (params.lowpass_version == 2 || ! bp->dc) { + unsigned int ni = -ii; + ni &= plan.fftsize - 1; + power[ni] += p; + } + } + } + + // Create coefficient metadata based on a slice length + coefs_meta *make_meta(int slice_len) const { + coefs_meta *cmeta = new coefs_meta; + cmeta->n_octaves = n_octaves; + cmeta->n_bands_total = n_bands_total; + cmeta->bands_per_octave = params.bands_per_octave; + cmeta->slice_len = slice_len; + cmeta->zones.resize(zones.size()); + for (unsigned int zi = 0; zi < zones.size(); zi++) { + zone *z = zones[zi].get(); + typename zone_coefs_meta::band_vector bv(z->bandparams.size()); + for (unsigned int i = 0; i < z->bandparams.size(); i++) { + unsigned int step_log2 = z->bandparams[i]->step_log2; + bv[i].step_log2 = step_log2; + bv[i].slice_len_log2 = whichp2(slice_len) - step_log2; + bv[i].slice_len = 1 << bv[i].slice_len_log2; + } + cmeta->zones[zi].init(bv); + } + cmeta->octaves.resize(octaves.size()); + int tbno = 0; + for (unsigned int i = 0; i < n_octaves; i++) { + cmeta->octaves[i].z = &cmeta->zones[this->octaves[i].z->zno]; + cmeta->octaves[i].n_bands_above = tbno; + tbno += cmeta->octaves[i].z->bands.size(); + } + return cmeta; + } + + std::vector> octaves; // Per-octave parameters + std::vector>> zones; + unsigned int max_step_log2; + + std::vector> anl_plans; + std::vector> syn_plans; + + unsigned int n_octaves; + unsigned int n_bands_total; // Total number of frequency bands, including DC + + double top_band_log2ff; // log2 of fractional frequency of the highest-frequency band + int ffref_gbno; // Band number of the reference frequency + + // Width of the downsampling filter passband in terms of the + // downsampled sample rate (between 0.25 and 0.5) + double ds_passband; + double ds_ff; // Downsampling filter -6 dB transition frequency + double ds_ff_sd; // Downsampling filter standard deviation + double ds_time_support; // Downsampling filter time-domain kernel support, each side + + unsigned int fftsize_max; // Largest FFT size of any plan + unsigned int sftsize_max; // Largest SFT size of any plan + + ref cmeta_any; +}; + + +// Iterate over the slices of a row (band) having slice length +// 2^sh that contain coefficients with indices ranging from i0 +// (inclusive) to i1 (exclusive), and call the function f for +// each such slice (full or partial), with the arguments +// +// sli - slice index +// bvi - index of first coefficient to process within the slice +// len - number of coefficients to process within the slice + +template +void foreach_slice(unsigned int sh, coef_index_t i0, coef_index_t i1, F f) { + // Note that this can be called with i0 > i1 and needs to handle + // that case gracefully. + // Band size (power of two) + int bsize = 1 << sh; + // Adjust for t=0 being outside the filet + int fatsize = bsize >> 1; + i0 -= fatsize; + i1 -= fatsize; + coef_index_t i = i0; + while (i < i1) { + // Slice index + slice_index_t sli = i >> sh; + // Band vector index + unsigned int bvi = i & (bsize - 1); + unsigned int len = bsize - bvi; + unsigned int remain = (unsigned int)(i1 - i); + if (remain < len) + len = remain; + f(sli, bvi, len); + i += len; + } +} + +// As foreach_slice, but call the "process_existing_slice" method of +// the given "dest" object for each full or partial slice of +// coefficients, and/or the "process_missing_slice" method for each +// nonexistent slice. +// +// Template parameters: +// T is the spectrogram value type +// D is the dest object type +// C is the coefficient type + +template > +struct row_foreach_slice { + typedef C value_type; + row_foreach_slice(const coefs &msc, + int oct_, unsigned int obno_): + oct(oct_), obno(obno_), sc(msc.octaves[oct]), + sh(sc.meta->bands[obno].slice_len_log2) + { + assert(oct < (int)msc.octaves.size()); + } +public: + void operator()(coef_index_t i0, coef_index_t i1, D &dest) const { + foreach_slice(sh, i0, i1, + [this, &dest](slice_index_t sli, unsigned int bvi, unsigned int len) { + oct_coefs *c = get_existing_coefs(sc, sli); + if (c) { + dest.process_existing_slice(c->bands[obno] + bvi, len); + } else { + dest.process_missing_slice(len); + } + }); + } + int oct; + unsigned int obno; + const sliced_coefs ≻ + unsigned int sh; +}; + +// Helper class for row_source + +template +struct writer_dest { + writer_dest(OI output_): output(output_) { } + void process_existing_slice(C *bv, size_t len) { + // Can't use std::copy here because it takes the output + // iterator by value, and using the return value does not + // work, either. + for (size_t i = 0; i < len; i++) + *output++ = bv[i]; + } + void process_missing_slice(size_t len) { + for (size_t i = 0; i < len; i++) + *output++ = C(); + } + OI output; +}; + +// Retrieve a sequence of coefficients from a row (band) in the +// spectrogram, with indices ranging from i0 to i1. The indices can +// be negative, and can extend outside the available data, in which +// case zero is returned. The coefficients are written through the +// output iterator "output". +// Template arguments: +// T is the spectrogram value type +// OI is the output iterator type +// C is the coefficient value type +// Note defaults for template arguments defined in forward declaration. + +template +struct row_source { + row_source(const coefs &msc_, + int oct_, unsigned int obno_): + slicer(msc_, oct_, obno_) + { } + OI operator()(coef_index_t i0, coef_index_t i1, OI output) const { + writer_dest dest(output); + slicer(i0, i1, dest); + return dest.output; + } + row_foreach_slice, C> slicer; +}; + +// The opposite of row_source: store a sequence of coefficients into +// a row (band) in the spectrogram. This duplicates quite a lot of +// the row_source code above (without comments); the main part that's +// different is marked by the comments "Begin payload" and "End +// payload". Other differences: iterator is called II rather than OI, +// and the coefs are not const. + +template +struct row_dest { + typedef C value_type; + row_dest(coefs &msc, + int oct_, unsigned int obno_): + oct(oct_), obno(obno_), sc(msc.octaves[oct]), + sh(sc.meta->bands[obno].slice_len_log2) + { + assert(oct < (int)msc.octaves.size()); + } +public: + II operator()(coef_index_t i0, coef_index_t i1, II input) const { + assert(i0 <= i1); + int bsize = 1 << sh; + int fatsize = bsize >> 1; + i0 -= fatsize; + i1 -= fatsize; + coef_index_t i = i0; + while (i < i1) { + slice_index_t sli = i >> sh; + unsigned int bvi = i & (bsize - 1); + unsigned int len = bsize - bvi; + unsigned int remain = (unsigned int)(i1 - i); + if (remain < len) + len = remain; + int bvie = bvi + len; + // Begin payload + oct_coefs *c = &get_or_create_coefs(sc, sli); + C *bv = c->bands[obno]; + for (int j = bvi; j < bvie; j++) + bv[j] = *input++; + i += len; + // End payload + } + return input; + } + int oct; + unsigned int obno; + sliced_coefs ≻ + unsigned int sh; +}; + +// One more set of duplicated code, now for adding to coefficients + +template +struct row_add_dest { + typedef C value_type; + row_add_dest(coefs &msc, + int oct_, unsigned int obno_): + oct(oct_), obno(obno_), sc(msc.octaves[oct]), + sh(sc.meta->bands[obno].slice_len_log2) + { + assert(oct < (int)msc.octaves.size()); + } +public: + II operator()(coef_index_t i0, coef_index_t i1, II input) const { + assert(i0 <= i1); + int bsize = 1 << sh; + int fatsize = bsize >> 1; + i0 -= fatsize; + i1 -= fatsize; + coef_index_t i = i0; + while (i < i1) { + slice_index_t sli = i >> sh; + unsigned int bvi = i & (bsize - 1); + unsigned int len = bsize - bvi; + unsigned int remain = (unsigned int)(i1 - i); + if (remain < len) + len = remain; + int bvie = bvi + len; + // Begin payload + oct_coefs *c = &get_or_create_coefs(sc, sli); + C *bv = c->bands[obno]; + for (int j = bvi; j < bvie; j++) + bv[j] += *input++; + i += len; + // End payload + } + return input; + } + int oct; + unsigned int obno; + sliced_coefs ≻ + unsigned int sh; +}; + +// Helper for process() below. Here, the function f() operates on an +// array of consecutive coefficient samples rather than a single +// sample. + +template +void apply_to_slice(bool create, + F f, + int b0, // = INT_MIN + int b1, // = INT_MAX + sample_index_t st0, // = INT64_MIN + sample_index_t st1, // = INT64_MAX + coefs& coefs0, + coefs&... coefsi) +{ + b0 = std::max(b0, 0); + b1 = std::min(b1, (int)coefs0.meta->n_bands_total); + for (int band = b0; band < b1; band++) { + int oct; + unsigned int obno; + bool valid = gaborator::bno_split(*coefs0.meta, band, oct, obno, true); + assert(valid); + + int exp = coefs0.meta->octaves[oct].z->bands[obno].step_log2 + oct; + int time_step = 1 << exp; + + coef_index_t ci0 = (st0 + time_step - 1) >> exp; + coef_index_t ci1 = ((st1 - 1) >> exp) + 1; + if (! create) { + // Restrict to existing coefficient index range + coef_index_t cib0, cib1; + get_band_coef_bounds(coefs0, oct, obno, cib0, cib1); + ci0 = std::max(ci0, cib0); + ci1 = std::min(ci1, cib1); + } + unsigned int sh = coefs0.meta->octaves[oct].z->bands[obno].slice_len_log2; + sample_index_t st = shift_left(ci0, exp); + foreach_slice(sh, ci0, ci1, + [&](slice_index_t sli, unsigned int bvi, + unsigned int len) + { + oct_coefs *c = create ? + &get_or_create_coefs(coefs0.octaves[oct], sli) : + get_existing_coefs(coefs0.octaves[oct], sli); + if (c) { + // p0 points to coefficient from the first set + C0 *p0 = c->bands[obno] + bvi; + f(band, st, time_step, len, p0, + get_or_create_coefs(coefsi.octaves[oct], sli).bands[obno] + bvi...); + } + st += len * time_step; + }); + } +} + +// Common implementation of process() and fill() + +template +void apply_common(bool create, + F f, + int b0, // = INT_MIN + int b1, // = INT_MAX + sample_index_t st0, // = INT64_MIN + sample_index_t st1, // = INT64_MAX + coefs &coefs0, + coefs&... coefsi) +{ + apply_to_slice(create, + [&](int bno, int64_t st, int time_step, + unsigned len, C0 *p0, CI *...pi) + { + for (unsigned int i = 0; i < len; i++) { + f(bno, st, *p0++, *pi++...); + st += time_step; + } + }, b0, b1, st0, st1, coefs0, coefsi...); +} + +// Iterate over one or more coefficient sets in parallel and apply the +// function f, passing it a coefficient from each set as an argument. +// +// The application can be optionally limited to coefficients within +// the band range b0 to b1 and/or the sample time range st0 to st1. +// +// The first coefficient set ("channel 0") is treated specially; it +// determines which coefficients are iterated over (optionally further +// restricted by b0/b1/st0/st1). That is, the iteration is over the +// coefficients that already exist in channel 0, and no new +// coefficients will be allocated in channel 0. In the other +// channels, new coefficients will be created on demand when they are +// missing from that channel but present in channel 0. +// +// The coefficients may be of a different data type in each set. +// +// The arguments to f() are: +// +// int bno Band number +// int64_t st Sample time +// C &p0 Coefficient from first set +// C... &pi Coefficient from subsequent set + +template +void process(F f, + int b0, // = INT_MIN + int b1, // = INT_MAX + sample_index_t t0, // = INT64_MIN + sample_index_t t1, // = INT64_MAX + coefs &coefs0, + coefs&... coefsi) +{ + apply_common(false, f, b0, b1, t0, t1, coefs0, coefsi...); +} + +template +void fill(F f, + int b0, // = INT_MIN + int b1, // = INT_MAX + sample_index_t t0, // = INT64_MIN + sample_index_t t1, // = INT64_MAX + coefs &coefs0, + coefs&... coefsi) +{ + apply_common(true, f, b0, b1, t0, t1, coefs0, coefsi...); +} + +// Apply the function f to each existing coefficient in the +// coefficient set msc within the time range st0 to st1. The +// initial analyzer argument is ignored. +// +// The arguments to f() are: +// +// C &c Coefficient +// int b Band number +// int64_t t Time in samples +// +// This is for backwards compatibility; process() is now preferred. + +template +void apply(const analyzer &, coefs &msc, F f, + sample_index_t st0 = INT64_MIN, + sample_index_t st1 = INT64_MAX) +{ + process([&](int b, int64_t t, complex& c) { + f(c, b, t); + }, INT_MIN, INT_MAX, st0, st1, msc); +} + +template +void forget_before(const analyzer &, coefs &msc, + sample_index_t limit, bool clean_cut = false) +{ + typedef complex C; + unsigned int n_oct = (unsigned int) msc.octaves.size(); + for (unsigned int oct = 0; oct < n_oct; oct++) { + sliced_coefs &sc = msc.octaves[oct]; + // Convert limit from samples to slices, rounding down. + // This assumes all bands in the octave have the same + // time range, as they must. + // First convert samples to coefficients, rounding down + int obno = 0; // Any band would do, and band 0 always exists + zone_coefs_meta *zmeta = msc.meta->octaves[oct].z; + coef_index_t ci = limit >> band_scale_exp(*zmeta, oct, obno); + // Then convert coefficients to slices, accounting for + // fat and rounding down + unsigned int slice_len = zmeta->bands[obno].slice_len; + unsigned int slice_len_log2 = zmeta->bands[obno].slice_len_log2; + int fat = slice_len >> 1; + slice_index_t sli = (ci - fat) >> slice_len_log2; + sc.slices.erase_before(sli); + if (clean_cut) { + // Partially erase slice at boundary, if any + const ref> *t = sc.slices.get(sli); + if (! t) + continue; + if (! *t) + continue; + const oct_coefs &c = **t; + unsigned int n_bands = (unsigned int)c.bands.size(); + for (unsigned int obno = 0; obno < n_bands; obno++) { + C *band = c.bands[obno]; + unsigned int len = sc.meta->bands[obno].slice_len; + sample_index_t st = sample_time(*sc.meta, sli, 0, oct, obno); + int time_step = 1 << band_scale_exp(*sc.meta, oct, obno); + for (unsigned int i = 0; i < len; i++) { + if (st < limit) + band[i] = 0; + else + break; + st += time_step; + } + } + } + } +} + + +} // namespace + +#endif diff --git a/gaborator/gaborator-1.2/gaborator/gaussian.h b/gaborator/gaborator-1.7/gaborator/gaussian.h similarity index 57% rename from gaborator/gaborator-1.2/gaborator/gaussian.h rename to gaborator/gaborator-1.7/gaborator/gaussian.h index 01c113a..1ee76b3 100644 --- a/gaborator/gaborator-1.2/gaborator/gaussian.h +++ b/gaborator/gaborator-1.7/gaborator/gaussian.h @@ -1,7 +1,7 @@ // // The Gaussian and related functions // -// Copyright (C) 2015-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 2015-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -48,7 +48,7 @@ double gaussian_edge(double sd, double x) { return (erf(erf_arg) + 1) * 0.5; } -// Translate the time-domain standard deviation of a gaussian +// Translate the time-domain standard deviation of a Gaussian // (in samples) into the corresponding frequency-domain standard // deviation (as a fractional frequency), or vice versa. @@ -60,21 +60,64 @@ static inline double sd_f2t(double ff_sd) { return sd_t2f(ff_sd); } -// Given a gaussian with standard deviation "sd" and a maximum error +// Given a Gaussian with standard deviation "sd" and a maximum error // "max_error", calculate the support needed on each side to keep the // area below the curve within max_error of the exact value. -static inline double gaussian_support(double sd, double max_error) { +static inline +double gaussian_area_support(double sd, double max_error) { return sd * M_SQRT2 * erfc_inv(max_error); } // Inverse of the above: given a support and maximum error, calculate // the standard deviation. -static inline double gaussian_support_inv(double support, double max_error) { +static inline +double gaussian_area_support_inv(double support, double max_error) { return support / (M_SQRT2 * erfc_inv(max_error)); } +// Given a gaussian with standard deviation "sd" and a maximum error +// "max_error", calculate the support needed on each side for the +// value to fall to a factor of "max_error" of the peak. + +static inline +double gaussian_value_support(double sd, double max_error) { + return sd * M_SQRT2 * sqrt(-log(max_error)); +} + +// Inverse of the above: given a support and maximum error, calculate +// the standard deviation. + +static inline +double gaussian_value_support_inv(double support, double max_error) { + return support / (M_SQRT2 * sqrt(-log(max_error))); +} + +// Choose which criterion to use + +#if 1 +static inline +double gaussian_support(double support, double max_error) { + return gaussian_area_support(support, max_error); +}; + +static inline +double gaussian_support_inv(double support, double max_error) { + return gaussian_area_support_inv(support, max_error); +}; +#else +static inline +double gaussian_support(double support, double max_error) { + return gaussian_value_support(support, max_error); +}; + +static inline +double gaussian_support_inv(double support, double max_error) { + return gaussian_value_support_inv(support, max_error); +}; +#endif + } // namespace #endif diff --git a/gaborator/gaborator-1.2/gaborator/pod_vector.h b/gaborator/gaborator-1.7/gaborator/pod_vector.h similarity index 81% rename from gaborator/gaborator-1.2/gaborator/pod_vector.h rename to gaborator/gaborator-1.7/gaborator/pod_vector.h index f3ebd27..e578864 100644 --- a/gaborator/gaborator-1.2/gaborator/pod_vector.h +++ b/gaborator/gaborator-1.7/gaborator/pod_vector.h @@ -1,7 +1,7 @@ // // A vector class without default-initialization, for "plain old data" // -// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 2016-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -15,13 +15,20 @@ namespace gaborator { +// A vector for storing Plain Old Data. This is similar to a +// std::vector, except that it does not zero-initialize elements, +// and that it guarantees that data() returns a non-NULL pointer +// even when the vector contains zero elements. + template struct pod_vector { + typedef T value_type; typedef T *iterator; pod_vector() { b = e = 0; } - pod_vector(size_t size_) { + explicit pod_vector(size_t size_) { + // Allocate raw uninitialized memory b = static_cast(::operator new(size_ * sizeof(T))); e = b + size_; } @@ -77,7 +84,7 @@ struct pod_vector { if (&a == this) return *this; _free(); - b = new T[a.size()]; + b = static_cast(::operator new(a.size() * sizeof(T))); e = b + a.size(); std::copy(a.b, a.e, b); //if (size()) fprintf(stderr, "pod_vector = %d\n", (int)size()); @@ -87,6 +94,7 @@ struct pod_vector { pod_vector &operator=(pod_vector &&x) noexcept { if (&x == this) return *this; + _free(); b = x.b; e = x.e; x.b = x.e = 0; @@ -95,8 +103,10 @@ struct pod_vector { #endif private: void _free() { + // Free as raw uninitialized memory ::operator delete(b); } +private: T *b; T *e; }; diff --git a/gaborator/gaborator-1.2/gaborator/pool.h b/gaborator/gaborator-1.7/gaborator/pool.h similarity index 100% rename from gaborator/gaborator-1.2/gaborator/pool.h rename to gaborator/gaborator-1.7/gaborator/pool.h diff --git a/gaborator/gaborator-1.2/gaborator/ref.h b/gaborator/gaborator-1.7/gaborator/ref.h similarity index 63% rename from gaborator/gaborator-1.2/gaborator/ref.h rename to gaborator/gaborator-1.7/gaborator/ref.h index 3254352..fb4562f 100644 --- a/gaborator/gaborator-1.2/gaborator/ref.h +++ b/gaborator/gaborator-1.7/gaborator/ref.h @@ -18,44 +18,59 @@ struct refcounted { unsigned int refcount; }; +// Template functions for manual reference counting, without using the +// ref<> class. It would be tempting to make these methods of struct +// refcounted, but that won't work because it would lose the full +// object type and invoke operator delete on the base class. + +template +void incref(T &r) { + r.refcount++; +} + +template +void decref(T &r) { + r.refcount--; + if (r.refcount == 0) + delete &r; +} + template struct ref { ref(): p(0) { } ref(T *p_): p(p_) { - incref(); + _incref(); } ref(const ref &o): p(o.p) { - incref(); + _incref(); } ref &operator=(const ref &o) { reset(o.p); return *this; } ~ref() { reset(); } void reset() { - decref(); + _decref(); p = 0; } void reset(T *n) { if (n == p) return; - decref(); + _decref(); p = n; - incref(); + _incref(); } T *get() const { return p; } T *operator->() const { return p; } T &operator*() const { return *p; } operator bool() const { return p; } private: - void incref() { + void _incref() { if (! p) return; - p->refcount++; + incref(*p); } - void decref() { + void _decref() { if (! p) return; - p->refcount--; - if (p->refcount == 0) - delete p; + decref(*p); } T *p; }; diff --git a/gaborator/gaborator-1.7/gaborator/render.h b/gaborator/gaborator-1.7/gaborator/render.h new file mode 100644 index 0000000..2b8df98 --- /dev/null +++ b/gaborator/gaborator-1.7/gaborator/render.h @@ -0,0 +1,506 @@ +// +// Rendering of spectrogram images +// +// Copyright (C) 2015-2021 Andreas Gustafsson. This file is part of +// the Gaborator library source distribution. See the file LICENSE at +// the top level of the distribution for license information. +// + +#ifndef _GABORATOR_RENDER_H +#define _GABORATOR_RENDER_H + +#include "gaborator/gaborator.h" +#include "gaborator/resample2.h" + +namespace gaborator { + + +// Convert a floating-point linear brightness value in the range 0..1 +// into an 8-bit pixel value, with clamping and (rough) gamma +// correction. This nominally uses the sRGB gamma curve, but the +// current implementation cheats and uses a gamma of 2 because it can +// be calculated quickly using a square root. + +template +unsigned int float2pixel_8bit(T val) { + // Clamp before gamma correction so we don't take the square root + // of a negative number; those can arise from bicubic + // interpolation. While we're at it, let's also skip the gamma + // correction for small numbers that will round to zero anyway, + // and especially denormals which could rigger GCC bug target/83240. + static const T almost_zero = 1.0 / 65536; + if (val < almost_zero) + val = 0; + if (val > 1) + val = 1; + return (unsigned int)(sqrtf(val) * 255.0f); +} + +////////////////////////////////////////////////////////////////////////////// + +// Power-of-two rendering + + +// Magnitude + +template +struct complex_abs_fob { + T operator()(const complex &c) { + return complex_abs(c); + } + typedef T return_type; +}; + + +// T -> f() -> OI::value_type + +template +struct transform_output_iterator: public std::iterator { + typedef T value_type; + transform_output_iterator(F f_, OI output_): f(f_), output(output_) { } + transform_output_iterator& operator=(T v) { + *output++ = f(v); + return *this; + } + transform_output_iterator& operator*() { return *this; } + transform_output_iterator& operator++() { return *this; } + transform_output_iterator& operator++(int) { return *this; } + F f; + OI output; +}; + +// A source object for resample2() that provides the +// values of a row of spectrogram coordinates transformed +// by function normf (typically an absolute value function). +// +// T is the analyzer signal type +// C is the coefficient type +// OI is the output iterator type + +template +struct abs_row_source { + typedef transform_output_iterator abs_writer_t; + abs_row_source(const coefs &coefs_, + int oct_, unsigned int obno_, + NORMF normf_): + rs(coefs_, oct_, obno_), + normf(normf_) + { } + OI operator()(sample_index_t i0, sample_index_t i1, OI output) const { + abs_writer_t abswriter(normf, output); + abs_writer_t abswriter_end = rs(i0, i1, abswriter); + return abswriter_end.output; + } + row_source rs; + NORMF normf; +}; + +// Helper class for abs_row_source specialization below + +template +struct abs_writer_dest { + abs_writer_dest(OI output_): output(output_) { } + void process_existing_slice(C *bv, size_t len) { + complex_magnitude(bv, output, len); + output += len; + } + void process_missing_slice(size_t len) { + for (size_t i = 0; i < len; i++) + *output++ = 0; + } + OI output; +}; + +// Partial specialization of class abs_row_source for NORMF = complex_abs_fob, +// for vectorization. + +template +struct abs_row_source> { + // Note unused last arg + abs_row_source(const coefs &coefs_, + int oct_, unsigned int obno_, + complex_abs_fob): + slicer(coefs_, oct_, obno_) + { } + OI operator()(coef_index_t i0, coef_index_t i1, OI output) const { + abs_writer_dest dest(output); + slicer(i0, i1, dest); + return dest.output; + } + row_foreach_slice, C> slicer; +}; + +// Render a single line (single frequency band), with scaling in the +// horizontal (time) dimension, and filtering to avoid aliasing when +// minifying. + +template +OI render_line(const analyzer &anl, + const coefs &msc, + int gbno, + affine_transform xf, + sample_index_t i0, sample_index_t i1, + OI output, + NORMF normf) +{ + typedef typename NORMF::return_type RST; + + int oct; + unsigned int obno; // Band number within octave + bool clip = ! bno_split(*msc.meta, gbno, oct, obno, false); + if (clip) { + for (sample_index_t i = i0; i < i1; i++) + *output++ = (T)0; + return output; + } + abs_row_source + abs_rowsource(msc, oct, obno, normf); + + // Scale by the downsampling factor of the band + int scale_exp = band_scale_exp(*msc.octaves[oct].meta, oct, obno); + RESAMPLER x_resampler(zoom_p2(xf, -scale_exp)); + output = x_resampler(abs_rowsource, i0, i1, output); + return output; +} + +// Render a two-dimensional image with scaling in the horizontal +// direction only. In the vertical direction, there is always a +// one-to-one correspondence between bands and pixels. yi0 and yi1 +// already have the yorigin applied, so there is no yorigin argument. + +template +OI render_noyscale(const analyzer &anl, + const coefs &msc, + affine_transform x_xf, + int64_t xi0, int64_t xi1, + int64_t yi0, int64_t yi1, + OI output, + int64_t output_stride, + NORMF normf) +{ + assert(xi1 >= xi0); + int w = (int)(xi1 - xi0); + assert(w >= 0); + int gbno0 = (int)yi0; + int gbno1 = (int)yi1; + for (int gbno = gbno0; gbno < gbno1; gbno++) { + render_line + (anl, msc, gbno, x_xf, + xi0, xi1, + output, normf); + output += output_stride; + } + return output; +} + +// Source data from a column of a row-major two-dimensional array. +// data points to the beginning of a row-major array with an x +// range of x0..x1 and an y range from y0..y1, and operator() +// returns data from column x (where x is within the range x0..x1). + +template +struct transverse_source { + transverse_source(T *data_, + int64_t x0_, int64_t x1_, int64_t y0_, int64_t y1_, + int64_t x_): + data(data_), + x0(x0_), x1(x1_), y0(y0_), y1(y1_), + x(x_), + stride(x1 - x0) + { } + OI operator()(int64_t i0, int64_t i1, OI out) const { + assert(x >= x0); + assert(x <= x1); + assert(i1 >= i0); + assert(i0 >= y0); + assert(i1 <= y1); + T *p = data + (x - x0) + (i0 - y0) * stride; + while (i0 != i1) { + *out++ = *p; + p += stride; + ++i0; + } + return out; + } + T *data; + int64_t x0, x1, y0, y1, x; + size_t stride; +}; + +template +struct stride_iterator: public std::iterator + ::value_type> +{ + typedef typename std::iterator_traits::value_type T; + stride_iterator(I it_, size_t stride_): it(it_), stride(stride_) { } + T& operator*() { return *it; } + stride_iterator& operator++() { + it += stride; + return *this; + } + stride_iterator operator++(int) { + stride_iterator old = *this; + it += stride; + return old; + } + I it; + size_t stride; +}; + +struct updated_nop { + void operator()(int64_t x0, int64_t x1, int64_t y0, int64_t y1) { } +}; + +// Render a two-dimensional image with scaling in both the horizontal +// (time) and vertical (frequency) directions. The output may be +// written through "output" out of order, so "output" must be a random +// access iterator. + +// Note the default template argument for NORMF. This is needed +// because the compiler won't deduce the type of NORMF from the +// default function argument "NORMF normf = complex_abs_fob()" +// when the normf argument is omitted; it is considered a "non-deduced +// context", being "a template parameter used in the parameter type of +// a function parameter that has a default argument that is being used +// in the call for which argument deduction is being done". +// Unfortuantely, this work-around of providing a default template +// argument requires C++11. + +// This supports incremental rendering where only the parts of the +// output image affected by analyzing a given time range of samples +// are updated, the time range being from inc_i0 (inclusive) to inc_i1 +// (exclusive). The updated parts of the image consist of zero or +// more non-overlapping rectangles; to find out what those are, pass a +// function "update" which will be called will each rectangle in turn +// after it has been updated. + +// For non-incremental rendering, pass inc_i0 = INT64_MIN and inc_i1 = +// INT64_MAX. + +// OI is the output iterator type +// T is the coefficient type + +template , + class RESAMPLER = lanczos2_pow2_resampler, + class UPDATEDF = updated_nop> +void render_incremental( + const analyzer &anl, + const coefs &msc, + affine_transform x_xf, affine_transform y_xf, + int64_t xi0, int64_t xi1, + int64_t yi0, int64_t yi1, + int64_t inc_i0, int64_t inc_i1, + OI output, + int64_t output_stride, + NORMF normf = complex_abs_fob(), + UPDATEDF updated = updated_nop()) +{ + assert(xi1 >= xi0); + assert(yi1 >= yi0); + assert(inc_i1 >= inc_i0); + + // The data type to reasmple + typedef typename NORMF::return_type RST; + + // Vertical resampler + RESAMPLER y_resampler(y_xf); + + // Find the image bounds in the spectrogram coordinate system, + // including the interpolation margin. The Y bounds are in + // bands and are used both to determine what to render into the + // temporary image and for short-circuiting. The X bounds are in + // coefficient samples, and are only used for short-circuiting. + // The X bounds will be calculated later if we need them. + int64_t ysi0, ysi1; + y_resampler.support(yi0, yi1, ysi0, ysi1); + + // Calculate adjusted image X bounds based on the updated signal + // range for incremental rendering, and return an estimate of the + // numbers of pixels we can avoid rendering at this Y coordinate + // by using the adjusted X bounds. + + auto savings = [&](int64_t y, int64_t &adj_x0, int64_t &adj_x1) { + // Find the highest-index / lowest-frequency band used + // as a resampler input for output pixel y; it will have + // the widest analysis support in the x direction. + // Note that we pass y twice, and ignore the ysi0 result. + int64_t ysi0, ysi1; + y_resampler.support(y, y, ysi0, ysi1); + int64_t band = ysi1; + // Clamp the band to the valid range + band = std::max(band, (int64_t)anl.bandpass_bands_begin()); + band = std::min(band, (int64_t)anl.bandpass_bands_end() - 1); + + // Find the analysis support in the time (x) dimension, + // in signal samples + double support = anl.analysis_support(band); + // Convert from signal samples to coefficient samples + int scale_exp = anl.band_scale_exp((int)band); + + // Extend the updated coefficient range by the analysis + // support, and map it back to pixel space to find the + // affected pixel range, taking the resampler support + // into account. + RESAMPLER x_resampler(zoom_p2(x_xf, -scale_exp)); + int64_t ceil_support = ceil(support); + + // inv_support() calculates both sides of the support at once, + // but in the one-sided case, passing INT64_MIN/MAX may cause + // overflow and undefined behavior. Therefore, we pass a + // dummy value of zero instead, and make sure not to use the + // corresponding output value. This may cause the two inputs + // to inv_support() to be out of order, so it needs to accept + // that. + x_resampler.inv_support( + inc_i0 == INT64_MIN ? (int64_t)0 : (inc_i0 - ceil_support) >> scale_exp, + inc_i1 == INT64_MAX ? (int64_t)0 : (inc_i1 + ceil_support) >> scale_exp, + adj_x0, + adj_x1); + + if (inc_i0 == INT64_MIN) { + adj_x0 = xi0; + } else { + adj_x0 = std::max(xi0, adj_x0); + // Don't let width go negative + adj_x0 = std::min(adj_x0, xi1); + } + + if (inc_i1 == INT64_MAX) { + adj_x1 = xi1; + } else { + adj_x1 = std::min(xi1, adj_x1); + // Don't let width go negative + adj_x1 = std::max(adj_x1, adj_x0); + } + + assert(adj_x0 <= adj_x1); + + return (adj_x0 - xi0) + (xi1 - adj_x1); + }; + + if (!(inc_i0 == INT64_MIN && inc_i1 == INT64_MAX)) { + // Incremental rendering has been requested + int64_t adj_x0_top, adj_x1_top, adj_x0_bot, adj_x1_bot; + // See how much rendering we can save per line at the bottom, + // and calcualate adjusted bounds + int64_t bot_savings = savings(ysi1, adj_x0_bot, adj_x1_bot); + // See how much rendering we can save per line at the top + int64_t top_savings = savings(ysi0, adj_x0_top, adj_x1_top); + // Adjust bounds and output pointer to realize the bottom + // savings + if (adj_x0_bot == adj_x1_bot) + return; + output += adj_x0_bot - xi0; + xi0 = adj_x0_bot; + xi1 = adj_x1_bot; + + // If the savings at the top are significantly greater than + // at the bottom, it pays to subdivde the area to render, + // so that the top part can benefit from the greater savings + // there. + if (((top_savings - bot_savings) * (yi1 - yi0)) > 1000) { + // Subdivide vertically + int64_t ysplit = (yi1 + yi0) >> 1; + size_t output_offset = (ysplit - yi0) * output_stride; + render_incremental + (anl, msc, x_xf, y_xf, + xi0, xi1, + yi0, ysplit, + inc_i0, inc_i1, + output, output_stride, normf, updated); + render_incremental + (anl, msc, x_xf, y_xf, + xi0, xi1, + ysplit, yi1, + inc_i0, inc_i1, + output + output_offset, output_stride, normf, updated); + return; + } + } + + // Horizontal resampler, used only to calculate the support for + // short-circuiting. Since the resampling factor varies by band, + // the support also varies; use the largest resampling factor of + // any band to get the worst-case support. + int worstcase_band = anl.bandpass_bands_end() - 1; + RESAMPLER x_resampler(zoom_p2(x_xf, -anl.band_scale_exp(worstcase_band))); + int64_t xsi0, xsi1; + x_resampler.support(xi0, xi1, xsi0, xsi1); + + // Short-circuiting: if the image to be rendered falls entirely + // outside the data, just set it to zero instead of resampling down + // (potentially) high-resolution zeros to the display resolution. + // This makes a difference when zooming out by a large factor, for + // example such that the entire spectrogram falls within a single + // tile; that tile will necessarily be expensive to calculate, but + // the other tiles need not be, and mustn't be if we are to keep + // the total amount of work bounded by O(L) with respect to the + // signal length L regardless of zoom. + coef_index_t cxi0, cxi1; + get_band_coef_bounds(msc, worstcase_band, cxi0, cxi1); + if (ysi1 < 0 || // Entirely above + ysi0 >= anl.n_bands_total - 1 || // Entirely below + xsi1 < cxi0 || // Entirely to the left + xsi0 >= cxi1) // Entirely to the right + { + size_t n = (size_t)((yi1 - yi0) * (xi1 - xi0)); + for (size_t i = 0; i < n; i++) + output[i] = (T)0; + return; + } + + if (y_xf.a == 1 && y_xf.b == 0) { + // No Y resampling needed, render data resampled in the X + // direction only. + render_noyscale + (anl, msc, x_xf, xi0, xi1, yi0, yi1, + output, output_stride, normf); + } else { + // Construct a temporary image resampled in the + // X direction, but not yet in the Y direction. Include + // extra scanlines at the top and bottom for interpolation. + size_t n_pixels = (size_t)((ysi1 - ysi0) * (xi1 - xi0)); + pod_vector render_data(n_pixels); + + // Render data resampled in the X direction + RST *p = render_data.data(); + render_noyscale + (anl, msc, x_xf, xi0, xi1, + ysi0, ysi1, p, xi1 - xi0, normf); + + // Resample in the Y direction + for (int64_t xi = xi0; xi < xi1; xi++) { + transverse_source src(render_data.data(), + xi0, xi1, ysi0, ysi1, + xi); + stride_iterator dest(output + (xi - xi0), output_stride); + y_resampler(src, yi0, yi1, dest); + } + } + updated(xi0, xi1, yi0, yi1); +} + +template , + class RESAMPLER = lanczos2_pow2_resampler> +void render_p2scale(const analyzer &anl, + const coefs &msc, + int64_t xorigin, int64_t yorigin, + int64_t xi0, int64_t xi1, int xe, + int64_t yi0, int64_t yi1, int ye, + OI output, + NORMF normf = complex_abs_fob()) +{ + // Provide default inc_i0, inc_i1, and output_stride + render_incremental + (anl, msc, + affine_transform(ldexp(1, xe), xorigin), + affine_transform(ldexp(1, ye), yorigin), + xi0, xi1, yi0, yi1, + INT64_MIN, INT64_MAX, + output, xi1 - xi0, normf); +} + + +} // namespace + +#endif diff --git a/gaborator/gaborator-1.2/gaborator/resample2.h b/gaborator/gaborator-1.7/gaborator/resample2.h similarity index 58% rename from gaborator/gaborator-1.2/gaborator/resample2.h rename to gaborator/gaborator-1.7/gaborator/resample2.h index 1e446a6..c8b6e08 100644 --- a/gaborator/gaborator-1.2/gaborator/resample2.h +++ b/gaborator/gaborator-1.7/gaborator/resample2.h @@ -1,7 +1,7 @@ // // Fast resampling by powers of two // -// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 2016-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -14,9 +14,10 @@ #include #include - +#include #include // std::copy +#include "gaborator/affine_transform.h" #include "gaborator/pod_vector.h" namespace gaborator { @@ -50,6 +51,35 @@ namespace gaborator { // This latter point of view is how the code actually works. // +// A power-of-two transform, as in y = 2^e x + origin + +struct p2_transform { + p2_transform(int e_, int64_t origin_): e(e_), origin(origin_) { } + // Convert a linear transform into a p2_transform + p2_transform(affine_transform xf) { + int exp; + double m = frexp(xf.a, &exp); + assert(m == 0.5); + e = exp - 1; + origin = xf.b; + assert(origin == xf.b); // No fraction + } + int e; + int64_t origin; +}; + +// Scale a transform by a power of two + +static inline p2_transform +zoom_p2(p2_transform xf, int e) { + return p2_transform(xf.e + e, xf.origin); +} + +static inline affine_transform +zoom_p2(affine_transform xf, int e) { + return affine_transform(ldexp(xf.a, e), xf.b); +} + // Resample data from "source", generating a view between indices // i0 and i1 of the scale determined by exponent e, and storing // i1 - i0 samples starting at *out. @@ -61,13 +91,14 @@ namespace gaborator { // S is the type of the data source // OI is the output iterator type -template -T *resample2_ptr(const S &source, int64_t origin, - int64_t i0, int64_t i1, int e, - bool interpolate, T *out) +template +OI resample2_p2xf(const S &source, p2_transform xf, + int64_t i0, int64_t i1, + bool interpolate, OI out) { + typedef typename std::iterator_traits::value_type T; assert(i1 >= i0); - if (e > 0) { + if (xf.e > 0) { // Downsample // Calculate super-octave coordinates // margin is the support of the resampling kernel (on each side, @@ -80,7 +111,8 @@ T *resample2_ptr(const S &source, int64_t origin, // Get super-octave data gaborator::pod_vector super_data(si1 - si0); T *p = super_data.data(); - p = resample2_ptr(source, origin, si0, si1, e - 1, interpolate, p); + p = resample2_p2xf(source, p2_transform(xf.e - 1, xf.origin), + si0, si1, interpolate, p); assert(p == super_data.data() + si1 - si0); for (int64_t i = i0; i < i1; i++) { int64_t si = 2 * i - si0; @@ -115,17 +147,17 @@ T *resample2_ptr(const S &source, int64_t origin, } *out++ = val; } - } else if (e < 0) { + } else if (xf.e < 0) { // Upsample if (! interpolate) { // Return nearest neighbor. If the pixel lies // exactly at the midpoint between the neighbors, // return their average. - int sh = -e; + int sh = -xf.e; int64_t si0 = i0 >> sh; int64_t si1 = ((i1 - 1) >> sh) + 1 + 1; gaborator::pod_vector source_data(si1 - si0); - source(origin + si0, origin + si1, source_data.data()); + source(xf.origin + si0, xf.origin + si1, source_data.data()); for (int64_t i = i0; i < i1; i++) { int64_t si = (i >> sh) - si0; T val; @@ -149,7 +181,8 @@ T *resample2_ptr(const S &source, int64_t origin, // Get sub-octave data gaborator::pod_vector sub_data(si1 - si0); T *p = sub_data.data(); - p = resample2_ptr(source, origin, si0, si1, e + 1, interpolate, p); + p = resample2_p2xf(source, p2_transform(xf.e + 1, xf.origin), + si0, si1, interpolate, p); assert(p == sub_data.data() + si1 - si0); for (int64_t i = i0; i < i1; i++) { int64_t si = (i >> 1) - si0; @@ -170,20 +203,23 @@ T *resample2_ptr(const S &source, int64_t origin, } } else { // e == 0 - out = source(origin + i0, origin + i1, out); + out = source(xf.origin + i0, xf.origin + i1, out); } return out; } +// As above, but taking an affine_transform. + template -OI resample2(const S &source, int64_t origin, - int64_t i0, int64_t i1, int e, +OI resample2(const S &source, affine_transform lxf, + int64_t i0, int64_t i1, bool interpolate, OI out) { + p2_transform xf(lxf); typedef typename std::iterator_traits::value_type T; gaborator::pod_vector data(i1 - i0); T *p = data.data(); - p = resample2_ptr(source, origin, i0, i1, e, interpolate, p); + p = resample2_p2xf(source, xf, i0, i1, interpolate, p); return std::copy(data.data(), data.data() + (i1 - i0), out); } @@ -195,27 +231,94 @@ OI resample2(const S &source, int64_t origin, // return an unnecessarily large support when interpolation is off inline void -resample2_support(int64_t origin, int64_t i0, int64_t i1, int e, +resample2_support(affine_transform lxf, int64_t i0, int64_t i1, int64_t &si0_ret, int64_t &si1_ret) { - // Conservative + p2_transform xf(lxf); int margin = 2; - if (e > 0) { - // Note code duplication wrt resample2_ptr(). + if (xf.e > 0) { + // Note code duplication wrt resample2_p2xf(). // Also note tail recursion. - int64_t si0 = i0 * 2 - margin * 2 + 1; - int64_t si1 = i1 * 2 + margin * 2; - resample2_support(origin, si0, si1, e - 1, si0_ret, si1_ret); - } else if (e < 0) { + int64_t si0 = i0 * 2 - margin + 1; + int64_t si1 = i1 * 2 + margin; + resample2_support(zoom_p2(lxf, -1), + si0, si1, si0_ret, si1_ret); + } else if (xf.e < 0) { int64_t si0 = (i0 >> 1) - margin; int64_t si1 = ((i1 - 1) >> 1) + margin + 1; - resample2_support(origin, si0, si1, e + 1, si0_ret, si1_ret); + resample2_support(zoom_p2(lxf, +1), + si0, si1, si0_ret, si1_ret); } else { - si0_ret = origin + i0; - si1_ret = origin + i1; + si0_ret = xf.origin + i0; + si1_ret = xf.origin + i1; } } +// Inverse of the above, more or less: calculate the range of +// destination indices that depend on a given range of source indices. + +inline void +resample2_inv_support(affine_transform lxf, int64_t si0, int64_t si1, + int64_t &i0_ret, int64_t &i1_ret) +{ + p2_transform xf(lxf); + // Conservative + int margin = 2; + if (xf.e > 0) { + int64_t di0, di1; + resample2_inv_support(zoom_p2(lxf, -1), + si0, si1, di0, di1); + i0_ret = di0 >> 1; + i1_ret = (di1 >> 1) + 1; + } else if (xf.e < 0) { + int64_t di0, di1; + resample2_inv_support(zoom_p2(lxf, +1), + si0, si1, di0, di1); + i0_ret = di0 * 2 - margin + 1; + i1_ret = di1 * 2 + margin; + } else { + i0_ret = si0 - xf.origin; + i1_ret = si1 - xf.origin; + } +} + +// Class wrappers for compatibility with other resamplers. + +// Lanczos2 power-of-two resampler + +struct lanczos2_pow2_resampler { + lanczos2_pow2_resampler(affine_transform xf_): xf(xf_) { } + template + OI operator()(const S &source, int64_t i0, int64_t i1, OI out) const { + return resample2(source, xf, i0, i1, true, out); + } + void support(int64_t i0, int64_t i1, int64_t &si0_ret, int64_t &si1_ret) const { + return resample2_support(xf, i0, i1, si0_ret, si1_ret); + } + void inv_support(int64_t si0, int64_t si1, int64_t &i0_ret, int64_t &i1_ret) { + return resample2_inv_support(xf, si0, si1, i0_ret, i1_ret); + } + affine_transform xf; +}; + +// Nearest-neighbor power-of-two resampler +// XXX simplify + +struct nn_pow2_resampler { + nn_pow2_resampler(affine_transform xf_): xf(xf_){ } + template + OI operator()(const S &source, int64_t i0, int64_t i1, OI out) const { + return resample2(source, xf, i0, i1, false, out); + } + void support(int64_t i0, int64_t i1, int64_t &si0_ret, int64_t &si1_ret) const { + return resample2_support(xf, i0, i1, si0_ret, si1_ret); + } + void inv_support(int64_t si0, int64_t si1, int64_t &i0_ret, int64_t &i1_ret) { + return resample2_inv_support(xf, si0, si1, i0_ret, i1_ret); + } + affine_transform xf; +}; + } // namespace #endif diff --git a/gaborator/gaborator-1.2/gaborator/vector_math.h b/gaborator/gaborator-1.7/gaborator/vector_math.h similarity index 93% rename from gaborator/gaborator-1.2/gaborator/vector_math.h rename to gaborator/gaborator-1.7/gaborator/vector_math.h index c71e52e..f1d9cca 100644 --- a/gaborator/gaborator-1.2/gaborator/vector_math.h +++ b/gaborator/gaborator-1.7/gaborator/vector_math.h @@ -69,9 +69,9 @@ static inline void elementwise_product_naive(T *r, U *a, V *b, - int n) + size_t n) { - for (int i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) r[i] = complex_mul(a[i], b[i]); } @@ -81,9 +81,9 @@ elementwise_product_times_scalar_naive(T *r, U *a, V *b, S s, - int n) + size_t n) { - for (int i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) r[i] = a[i] * b[i] * s; } @@ -92,9 +92,9 @@ template static inline void complex_magnitude_naive(I *inv, O *outv, - int n) + size_t n) { - for (int i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) outv[i] = std::sqrt(inv[i].real() * inv[i].real() + inv[i].imag() * inv[i].imag()); } @@ -119,7 +119,7 @@ static inline void elementwise_product(std::complex *cv, const std::complex *av, const std::complex *bv, - int n) + size_t n) { assert((n & 1) == 0); n >>= 1; @@ -142,7 +142,7 @@ static inline void elementwise_product(std::complex *cv, const std::complex *av, const float *bv, - int n) + size_t n) { assert((n & 3) == 0); n >>= 2; @@ -163,7 +163,7 @@ elementwise_product_times_scalar(std::complex *cv, const std::complex *av, const std::complex *bv, std::complex d, - int n) + size_t n) { assert((n & 1) == 0); n >>= 1; @@ -183,7 +183,7 @@ elementwise_product_times_scalar(std::complex *cv, static inline void complex_magnitude(std::complex *inv, float *outv, - int n) + size_t n) { // Processes four complex values (32 bytes) at a time , // outputting four scalar magnitudes (16 bytes) at a time. @@ -225,7 +225,7 @@ static inline void elementwise_product(std::complex *c, const std::complex *a, const std::complex *b, - int n) + size_t n) { elementwise_product_naive(c, a, b, n); } @@ -234,7 +234,7 @@ static inline void elementwise_product(std::complex *c, const std::complex *a, const double *b, - int n) + size_t n) { elementwise_product_naive(c, a, b, n); } @@ -245,7 +245,7 @@ elementwise_product_times_scalar(T *r, U *a, V *b, S s, - int n) + size_t n) { elementwise_product_times_scalar_naive(r, a, b, s, n); } @@ -254,7 +254,7 @@ template static inline void complex_magnitude(std::complex *inv, O *outv, - int n) + size_t n) { complex_magnitude_naive(inv, outv, n); } @@ -269,7 +269,7 @@ static inline void elementwise_product(T *r, U *a, V *b, - int n) + size_t n) { elementwise_product_naive(r, a, b, n); } @@ -280,7 +280,7 @@ elementwise_product_times_scalar(T *r, U *a, V *b, S s, - int n) + size_t n) { elementwise_product_times_scalar_naive(r, a, b, s, n); } @@ -289,7 +289,7 @@ template static inline void complex_magnitude(I *inv, O *outv, - int n) + size_t n) { complex_magnitude_naive(inv, outv, n); } diff --git a/gaborator/gaborator-1.2/gaborator/version.h b/gaborator/gaborator-1.7/gaborator/version.h similarity index 73% rename from gaborator/gaborator-1.2/gaborator/version.h rename to gaborator/gaborator-1.7/gaborator/version.h index ed5c915..41ddc8a 100644 --- a/gaborator/gaborator-1.2/gaborator/version.h +++ b/gaborator/gaborator-1.7/gaborator/version.h @@ -1,7 +1,7 @@ // // Version number // -// Copyright (C) 2015-2018 Andreas Gustafsson. This file is part of +// Copyright (C) 2015-2021 Andreas Gustafsson. This file is part of // the Gaborator library source distribution. See the file LICENSE at // the top level of the distribution for license information. // @@ -10,6 +10,6 @@ #define _GABORATOR_VERSION_H #define GABORATOR_VERSION_MAJOR 1 -#define GABORATOR_VERSION_MINOR 2 +#define GABORATOR_VERSION_MINOR 7 #endif diff --git a/gaborator/jgaborator.cc b/gaborator/jgaborator.cc index b3bd43b..57ad32a 100644 --- a/gaborator/jgaborator.cc +++ b/gaborator/jgaborator.cc @@ -1,5 +1,5 @@ #include "jgaborator.h" -#include "gaborator-1.2/gaborator/gaborator.h" +#include "gaborator-1.7/gaborator/gaborator.h" #include #include diff --git a/gaborator/jgaborator.h b/gaborator/jgaborator.h index 185b5a7..8331adc 100644 --- a/gaborator/jgaborator.h +++ b/gaborator/jgaborator.h @@ -5,8 +5,6 @@ #include #include - - #ifndef _Included_be_ugent_jgaborator_JGaborator #define _Included_be_ugent_jgaborator_JGaborator #ifdef __cplusplus diff --git a/gaborator/precompile_for_targets.rb b/gaborator/precompile_for_targets.rb new file mode 100644 index 0000000..c327552 --- /dev/null +++ b/gaborator/precompile_for_targets.rb @@ -0,0 +1,16 @@ +platforms = ["aarch64-linux-musl","aarch64-windows-gnu","aarch64-macos-gnu","i386-linux-gnu","i386-windows-gnu","riscv64-linux-musl","x86_64-linux-musl","x86_64-windows-gnu","x86_64-macos-gnu"] + +extensions = {"windows" => "dll","macos" => "dylib","linux"=> "so"} +basename = "libjgaborator" +output_folder = "../src/main/resources/jni/" + +platforms.each do |platform| + + os = platform.split("-")[1] + arch = platform.split("-")[0] + extension = extensions[os] + + ouput_file = File.join(output_folder,"#{os}_#{arch}_#{basename}.#{extension}") + puts ouput_file + system("export ZIG_OUTPUT_FILE=#{ouput_file} && export ZIG_TARGET_PLATFORM=#{platform} && make zig") +end \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..744e882 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/TarsosDSP-2.4.jar b/lib/TarsosDSP-2.4.jar deleted file mode 100644 index 6a7808c..0000000 Binary files a/lib/TarsosDSP-2.4.jar and /dev/null differ diff --git a/build/JGaborator-0.6.jar b/media/JGaborator-0.6.jar similarity index 100% rename from build/JGaborator-0.6.jar rename to media/JGaborator-0.6.jar diff --git a/build/JNI_bridge_schema.drawio b/media/JNI_bridge_schema.drawio similarity index 100% rename from build/JNI_bridge_schema.drawio rename to media/JNI_bridge_schema.drawio diff --git a/build/build.xml b/media/build.xml similarity index 100% rename from build/build.xml rename to media/build.xml diff --git a/build/jgaborator.png b/media/jgaborator.png similarity index 100% rename from build/jgaborator.png rename to media/jgaborator.png diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..7e14dc3 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'JGaborator' + diff --git a/src/be/ugent/jgaborator/util/NativeUtils.java b/src/be/ugent/jgaborator/util/NativeUtils.java deleted file mode 100644 index f60fe90..0000000 --- a/src/be/ugent/jgaborator/util/NativeUtils.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Class NativeUtils is published under the The MIT License: - * - * Copyright (c) 2012 Adam Heinrich - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package be.ugent.jgaborator.util; - -import java.io.*; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.ProviderNotFoundException; -import java.nio.file.StandardCopyOption; - -/** - * A simple library class which helps with loading dynamic libraries stored in the - * JAR archive. These libraries usually contain implementation of some methods in - * native code (using JNI - Java Native Interface). - * - * @see http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar - * @see https://github.com/adamheinrich/native-utils - * - */ -public class NativeUtils { - - /** - * The minimum length a prefix for a file has to have according to {@link File#createTempFile(String, String)}}. - */ - private static final int MIN_PREFIX_LENGTH = 3; - public static final String NATIVE_FOLDER_PATH_PREFIX = "nativeutils"; - - /** - * Temporary directory which will contain the DLLs. - */ - private static File temporaryDir; - - /** - * Private constructor - this class will never be instanced - */ - private NativeUtils() { - } - - /** - * Loads library from current JAR archive - * - * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after - * exiting. - * Method uses String as filename because the pathname is "abstract", not system-dependent. - * - * @param path The path of file inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext - * @throws IOException If temporary file creation or read/write operation fails - * @throws IllegalArgumentException If source file (param path) does not exist - * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters - * (restriction of {@link File#createTempFile(java.lang.String, java.lang.String)}). - * @throws FileNotFoundException If the file could not be found inside the JAR. - */ - public static void loadLibraryFromJar(String path) throws IOException { - - if (null == path || !path.startsWith("/")) { - throw new IllegalArgumentException("The path has to be absolute (start with '/')."); - } - - // Obtain filename from path - String[] parts = path.split("/"); - String filename = (parts.length > 1) ? parts[parts.length - 1] : null; - - // Check if the filename is okay - if (filename == null || filename.length() < MIN_PREFIX_LENGTH) { - throw new IllegalArgumentException("The filename has to be at least 3 characters long."); - } - - // Prepare temporary file - if (temporaryDir == null) { - temporaryDir = createTempDirectory(NATIVE_FOLDER_PATH_PREFIX); - temporaryDir.deleteOnExit(); - } - - File temp = new File(temporaryDir, filename); - - try (InputStream is = NativeUtils.class.getResourceAsStream(path)) { - Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - temp.delete(); - throw e; - } catch (NullPointerException e) { - temp.delete(); - throw new FileNotFoundException("File " + path + " was not found inside JAR."); - } - - try { - System.load(temp.getAbsolutePath()); - } finally { - if (isPosixCompliant()) { - // Assume POSIX compliant file system, can be deleted after loading - temp.delete(); - } else { - // Assume non-POSIX, and don't delete until last file descriptor closed - temp.deleteOnExit(); - } - } - } - - private static boolean isPosixCompliant() { - try { - return FileSystems.getDefault() - .supportedFileAttributeViews() - .contains("posix"); - } catch (FileSystemNotFoundException - | ProviderNotFoundException - | SecurityException e) { - return false; - } - } - - private static File createTempDirectory(String prefix) throws IOException { - String tempDir = System.getProperty("java.io.tmpdir"); - File generatedDir = new File(tempDir, prefix + System.nanoTime()); - - if (!generatedDir.mkdir()) - throw new IOException("Failed to create temp directory " + generatedDir.getName()); - - return generatedDir; - } -} \ No newline at end of file diff --git a/src/be/ugent/jgaborator/JGaborator.java b/src/main/java/be/ugent/jgaborator/JGaborator.java similarity index 92% rename from src/be/ugent/jgaborator/JGaborator.java rename to src/main/java/be/ugent/jgaborator/JGaborator.java index d766c22..f19296b 100644 --- a/src/be/ugent/jgaborator/JGaborator.java +++ b/src/main/java/be/ugent/jgaborator/JGaborator.java @@ -1,7 +1,6 @@ package be.ugent.jgaborator; import java.io.IOException; -import java.io.FileNotFoundException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -14,7 +13,7 @@ import be.tarsos.dsp.AudioEvent; import be.tarsos.dsp.AudioProcessor; -import be.ugent.jgaborator.util.NativeUtils; +import be.ugent.jgaborator.util.ZigNativeUtils; public class JGaborator implements AudioProcessor{ @@ -43,31 +42,20 @@ public class JGaborator implements AudioProcessor{ static { // Load native library at runtime try { - System.loadLibrary("jgaborator"); - }catch (UnsatisfiedLinkError e){ - System.err.println("Could not load jgaborator JNI library. Will attempt to use a version packed in the JAR archive"); - System.err.println(" info : " + e.getMessage()); - try { - - String arch = System.getProperty("os.arch"); - - try{ - NativeUtils.loadLibraryFromJar("/jni/" + arch + "/" + System.mapLibraryName("jgaborator")); - }catch (FileNotFoundException ex) { + System.loadLibrary("sjgaborator"); - } - - NativeUtils.loadLibraryFromJar("/jni/" + System.mapLibraryName("jgaborator")); - - - System.err.println("Loaded JNI jgaborator library from JAR archive."); + }catch (UnsatisfiedLinkError e){ + System.err.println("Could not load 'jgaborator' JNI library. Will attempt to use a precompiled version packed in the JAR archive"); + try{ + ZigNativeUtils.loadLibraryFromJarWithOSDetection("/jni/" + System.mapLibraryName("jgaborator")); } catch (IOException e1) { - e1.printStackTrace(); } } } + + /** * @param blocksize The size of a block of audio * @param samplerate The sample rate of the audio in Hz. For example 8000Hz or 44100Hz diff --git a/src/be/ugent/jgaborator/ui/FileDrop.java b/src/main/java/be/ugent/jgaborator/ui/FileDrop.java similarity index 100% rename from src/be/ugent/jgaborator/ui/FileDrop.java rename to src/main/java/be/ugent/jgaborator/ui/FileDrop.java diff --git a/src/be/ugent/jgaborator/ui/FrequencyAxisLayer.java b/src/main/java/be/ugent/jgaborator/ui/FrequencyAxisLayer.java similarity index 100% rename from src/be/ugent/jgaborator/ui/FrequencyAxisLayer.java rename to src/main/java/be/ugent/jgaborator/ui/FrequencyAxisLayer.java diff --git a/src/be/ugent/jgaborator/ui/GaborLayer.java b/src/main/java/be/ugent/jgaborator/ui/GaborLayer.java similarity index 100% rename from src/be/ugent/jgaborator/ui/GaborLayer.java rename to src/main/java/be/ugent/jgaborator/ui/GaborLayer.java diff --git a/src/be/ugent/jgaborator/ui/JGaboratorBrowser.java b/src/main/java/be/ugent/jgaborator/ui/JGaboratorBrowser.java similarity index 99% rename from src/be/ugent/jgaborator/ui/JGaboratorBrowser.java rename to src/main/java/be/ugent/jgaborator/ui/JGaboratorBrowser.java index 162b746..c800c95 100644 --- a/src/be/ugent/jgaborator/ui/JGaboratorBrowser.java +++ b/src/main/java/be/ugent/jgaborator/ui/JGaboratorBrowser.java @@ -54,10 +54,10 @@ public class JGaboratorBrowser extends JFrame{ public JGaboratorBrowser() { minFrequency = 110; - maxFrequency = 3520; + maxFrequency = 3520*2; refFrequency = 440; - bandsPerOctave = 12; - sampleRate = 8000; + bandsPerOctave = 48; + sampleRate = 16000; resolution = 64; stepSize = 4096* 2 * 2; diff --git a/src/main/java/be/ugent/jgaborator/util/ZigNativeUtils.java b/src/main/java/be/ugent/jgaborator/util/ZigNativeUtils.java new file mode 100644 index 0000000..cca1881 --- /dev/null +++ b/src/main/java/be/ugent/jgaborator/util/ZigNativeUtils.java @@ -0,0 +1,391 @@ +/* + * Class NativeUtils is published under the The MIT License: + * + * Copyright (c) 2012 Adam Heinrich + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package be.ugent.jgaborator.util; + +import java.io.*; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.ProviderNotFoundException; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; + +/** + * A simple library class which helps with loading dynamic libraries stored in the + * JAR archive. These libraries usually contain implementation of some methods in + * native code (using JNI - Java Native Interface). + * + * @see http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar + * @see https://github.com/adamheinrich/native-utils + * + */ +public class ZigNativeUtils { + + /** + * The minimum length a prefix for a file has to have according to {@link File#createTempFile(String, String)}}. + */ + private static final int MIN_PREFIX_LENGTH = 3; + public static final String NATIVE_FOLDER_PATH_PREFIX = "zignativeutils"; + + /** + * Temporary directory which will contain the DLLs. + */ + private static File temporaryDir; + + /** + * Private constructor - this class will never be instanced + */ + private ZigNativeUtils() { + } + + /** + * + * @return + * @see https://github.com/trustin/os-maven-plugin/blob/master/src/main/java/kr/motd/maven/os/Detector.java#L163 + * @see https://github.com/trustin/os-maven-plugin/blob/master/src/main/java/kr/motd/maven/os/Detector.java#L192 + */ + private static String ziggifyOS(){ + String os = System.getProperty("os.name").toLowerCase(); + if(os.startsWith("mac") || os.startsWith("osx")){ + os = "macos"; + }else if (os.contains("indows")){ + os = "windows"; + }else if (os.contains("linux")){ + os = "linux"; + }else if (os.startsWith("freebsd")) { + os = "freebsd"; + } else if (os.startsWith("openbsd")) { + os = "openbsd"; + } else if (os.startsWith("netbsd")) { + os = "netbsd"; + } else { + System.err.println("OS " + os + " not recognized will. Will use prefix '" + os + "_'" ); + } + return os; + } + + private static String ziggifyArch() { + String value = System.getProperty("os.arch").toLowerCase(); + if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) { + return "x86_64"; + } + if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) { + return "i386"; + } + if (value.matches("^(ia64w?|itanium64)$")) { + return "itanium_64"; + } + if ("ia64n".equals(value)) { + return "itanium_32"; + } + if (value.matches("^(sparc|sparc32)$")) { + return "sparc_32"; + } + if (value.matches("^(sparcv9|sparc64)$")) { + return "sparc_64"; + } + if (value.matches("^(arm|arm32)$")) { + return "arm_32"; + } + if ("aarch64".equals(value)) { + return "aarch64"; + } + if (value.matches("^(mips|mips32)$")) { + return "mips"; + } + if (value.matches("^(mipsel|mips32el)$")) { + return "mipsel"; + } + if ("mips64".equals(value)) { + return "mips64"; + } + if ("mips64el".equals(value)) { + return "mips64el"; + } + if (value.matches("^(ppc|ppc32)$")) { + return "powerpc"; + } + if (value.matches("^(ppcle|ppc32le)$")) { + return "ppcle_32"; + } + if ("ppc64".equals(value)) { + return "ppc_64"; + } + if ("ppc64le".equals(value)) { + return "ppcle_64"; + } + if ("s390".equals(value)) { + return "s390_32"; + } + if ("s390x".equals(value)) { + return "s390_64"; + } + if (value.matches("^(riscv|riscv32)$")) { + return "riscv"; + } + if ("riscv64".equals(value)) { + return "riscv64"; + } + if ("e2k".equals(value)) { + return "e2k"; + } + if ("loongarch64".equals(value)) { + return "loongarch_64"; + } + + return value; + } + + private static String makeZigPrefix(){ + String os = ziggifyOS(); + String arch = ziggifyArch(); + + if(!Arrays.asList(zigOperatingSystems).contains(os)){ + System.err.println("OS " + os + " not in the list of OSes recognized by Zig:"); + for(String zigOS : Arrays.asList(zigOperatingSystems)){ + System.err.println("\t" + zigOS); + }; + } + + if(!Arrays.asList(zigArchitectures).contains(arch)){ + System.err.println("Architecture " + arch + " not in the list of architectures recognized by Zig:"); + for(String zigArch : Arrays.asList(zigArchitectures)){ + System.err.println("\t" + zigArch); + }; + } + + return os + "_" + arch; + } + + + /** + * Load a library with OS and architecture detection. The idea is that libraries are found + * @param path + * @throws IOException + */ + public static void loadLibraryFromJarWithOSDetection(String path) throws IOException{ + String[] parts = path.split("/"); + String original_filename = (parts.length > 1) ? parts[parts.length - 1] : null; + String prefixed_filename = makeZigPrefix() + "_" + original_filename; + String prefixed_path = path.replace(original_filename,prefixed_filename); + + System.err.println("Try to load JNI library " + prefixed_path + " from JAR archive."); + ZigNativeUtils.loadLibraryFromJar(prefixed_path); + System.err.println("Native library library " + prefixed_path + " loaded!"); + } + + + /** + * Loads library from current JAR archive + * + * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after + * exiting. + * Method uses String as filename because the pathname is "abstract", not system-dependent. + * + * @param path The path of file inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext + * @throws IOException If temporary file creation or read/write operation fails + * @throws IllegalArgumentException If source file (param path) does not exist + * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters + * (restriction of {@link File#createTempFile(java.lang.String, java.lang.String)}). + * @throws FileNotFoundException If the file could not be found inside the JAR. + */ + public static void loadLibraryFromJar(String path) throws IOException { + + if (null == path || !path.startsWith("/")) { + throw new IllegalArgumentException("The path has to be absolute (start with '/')."); + } + + // Obtain filename from path + String[] parts = path.split("/"); + String filename = (parts.length > 1) ? parts[parts.length - 1] : null; + + // Check if the filename is okay + if (filename == null || filename.length() < MIN_PREFIX_LENGTH) { + throw new IllegalArgumentException("The filename has to be at least 3 characters long."); + } + + // Prepare temporary file + if (temporaryDir == null) { + temporaryDir = createTempDirectory(NATIVE_FOLDER_PATH_PREFIX); + temporaryDir.deleteOnExit(); + } + + File temp = new File(temporaryDir, filename); + + try (InputStream is = ZigNativeUtils.class.getResourceAsStream(path)) { + Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + temp.delete(); + throw e; + } catch (NullPointerException e) { + temp.delete(); + throw new FileNotFoundException("File " + path + " was not found inside JAR."); + } + + try { + System.load(temp.getAbsolutePath()); + } finally { + if (isPosixCompliant()) { + // Assume POSIX compliant file system, can be deleted after loading + temp.delete(); + } else { + // Assume non-POSIX, and don't delete until last file descriptor closed + temp.deleteOnExit(); + } + } + } + + private static boolean isPosixCompliant() { + try { + return FileSystems.getDefault() + .supportedFileAttributeViews() + .contains("posix"); + } catch (FileSystemNotFoundException + | ProviderNotFoundException + | SecurityException e) { + return false; + } + } + + private static File createTempDirectory(String prefix) throws IOException { + String tempDir = System.getProperty("java.io.tmpdir"); + File generatedDir = new File(tempDir, prefix + System.nanoTime()); + + if (!generatedDir.mkdir()) + throw new IOException("Failed to create temp directory " + generatedDir.getName()); + + return generatedDir; + } + + /** + * A list of operating systems known by Zig. + * Generate with command 'zig targets' + * @see https://ziglang.org/ + */ + private final static String[] zigOperatingSystems = {"freestanding", + "ananas", + "cloudabi", + "dragonfly", + "freebsd", + "fuchsia", + "ios", + "kfreebsd", + "linux", + "lv2", + "macos", + "netbsd", + "openbsd", + "solaris", + "windows", + "zos", + "haiku", + "minix", + "rtems", + "nacl", + "aix", + "cuda", + "nvcl", + "amdhsa", + "ps4", + "elfiamcu", + "tvos", + "watchos", + "mesa3d", + "contiki", + "amdpal", + "hermit", + "hurd", + "wasi", + "emscripten", + "uefi", + "opencl", + "glsl450", + "vulkan", + "plan9", + "other"}; + /** + * A list of architectures known by Zig. + * Generate with command 'zig targets' + * @see https://ziglang.org/ + */ + private static final String[] zigArchitectures = {"arm", + "armeb", + "aarch64", + "aarch64_be", + "aarch64_32", + "arc", + "avr", + "bpfel", + "bpfeb", + "csky", + "hexagon", + "m68k", + "mips", + "mipsel", + "mips64", + "mips64el", + "msp430", + "powerpc", + "powerpcle", + "powerpc64", + "powerpc64le", + "r600", + "amdgcn", + "riscv32", + "riscv64", + "sparc", + "sparcv9", + "sparcel", + "s390x", + "tce", + "tcele", + "thumb", + "thumbeb", + "i386", + "x86_64", + "xcore", + "nvptx", + "nvptx64", + "le32", + "le64", + "amdil", + "amdil64", + "hsail", + "hsail64", + "spir", + "spir64", + "kalimba", + "shave", + "lanai", + "wasm32", + "wasm64", + "renderscript32", + "renderscript64", + "ve", + "spu_2", + "spirv32", + "spirv64" + }; + +} \ No newline at end of file diff --git a/src/main/resources/PianoFlanger.ogg b/src/main/resources/PianoFlanger.ogg new file mode 100644 index 0000000..ce7ab72 Binary files /dev/null and b/src/main/resources/PianoFlanger.ogg differ diff --git a/src/main/resources/jni/linux_aarch64_libjgaborator.so b/src/main/resources/jni/linux_aarch64_libjgaborator.so new file mode 100755 index 0000000..d072c1a Binary files /dev/null and b/src/main/resources/jni/linux_aarch64_libjgaborator.so differ diff --git a/src/main/resources/jni/linux_i386_libjgaborator.so b/src/main/resources/jni/linux_i386_libjgaborator.so new file mode 100755 index 0000000..e523a21 Binary files /dev/null and b/src/main/resources/jni/linux_i386_libjgaborator.so differ diff --git a/src/main/resources/jni/linux_riscv64_libjgaborator.so b/src/main/resources/jni/linux_riscv64_libjgaborator.so new file mode 100755 index 0000000..acb1323 Binary files /dev/null and b/src/main/resources/jni/linux_riscv64_libjgaborator.so differ diff --git a/src/main/resources/jni/linux_x86_64_libjgaborator.so b/src/main/resources/jni/linux_x86_64_libjgaborator.so new file mode 100755 index 0000000..e5f6a0b Binary files /dev/null and b/src/main/resources/jni/linux_x86_64_libjgaborator.so differ diff --git a/src/main/resources/jni/macos_aarch64_libjgaborator.dylib b/src/main/resources/jni/macos_aarch64_libjgaborator.dylib new file mode 100755 index 0000000..b75c418 Binary files /dev/null and b/src/main/resources/jni/macos_aarch64_libjgaborator.dylib differ diff --git a/src/main/resources/jni/macos_x86_64_libjgaborator.dylib b/src/main/resources/jni/macos_x86_64_libjgaborator.dylib new file mode 100755 index 0000000..fae3f14 Binary files /dev/null and b/src/main/resources/jni/macos_x86_64_libjgaborator.dylib differ diff --git a/src/main/resources/jni/windows_aarch64_libjgaborator.dll b/src/main/resources/jni/windows_aarch64_libjgaborator.dll new file mode 100755 index 0000000..3dd7093 Binary files /dev/null and b/src/main/resources/jni/windows_aarch64_libjgaborator.dll differ diff --git a/src/main/resources/jni/windows_i386_libjgaborator.dll b/src/main/resources/jni/windows_i386_libjgaborator.dll new file mode 100755 index 0000000..90ea29d Binary files /dev/null and b/src/main/resources/jni/windows_i386_libjgaborator.dll differ diff --git a/src/main/resources/jni/windows_x86_64_libjgaborator.dll b/src/main/resources/jni/windows_x86_64_libjgaborator.dll new file mode 100755 index 0000000..6d573d5 Binary files /dev/null and b/src/main/resources/jni/windows_x86_64_libjgaborator.dll differ