// // Constant Q spectrum analysis and resynthesis // // Copyright (C) 2015-2020 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-sys/gaborator/fft.h" #include "gaborator-sys/gaborator/gaussian.h" #include "gaborator-sys/gaborator/linear_transform.h" #include "gaborator-sys/gaborator/pod_vector.h" #include "gaborator-sys/gaborator/pool.h" #include "gaborator-sys/gaborator/ref.h" #include "gaborator-sys/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: // 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; } 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; } // 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) unsigned int step; // Signal samples per coefficient sample unsigned int step_log2; // log2(step) 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 unsigned int time_support; // Filter support in time domain, in octave subsamples }; // 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 (unsigned int i = 0; i < bands.size(); i++) { bands[i].band_offset = offset; offset += bands[i].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 oct_tmp = gbno / meta.bands_per_octave; int obno_tmp = gbno % meta.bands_per_octave; // Octave 0 is twice the size if (oct_tmp < 2) { if (oct_tmp == 1) { // Octave 1 gets appended to octave 0 obno_tmp += meta.bands_per_octave; oct_tmp--; } } else { // Because octave 0 is twice the size, the // other octave numbers are one less oct_tmp--; } oct = oct_tmp; // Now determine the band within the octave. // obno_tmp counts down, but obno counts up. obno = (unsigned int)meta.octaves[oct].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) { (void)oct; 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_) { // Use the analysis structure of the largest plan meta = anl_.anl_plans[anl_.anl_plans.size() - 1]->cmeta; // 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(); } ref 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; } // 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() { } // Zone number, 0..3 unsigned int zno; // Total number of bands, including DC band if lowest octave unsigned int n_bands; // 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; unsigned int max_step_log2; }; 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: phase_convention::absolute means the phase of a // coefficient at time tc is relative to e(i tau f t), and // phase_convention::relative means it is relative to // e(i tau f (t - tc)) enum class phase_convention { absolute, relative }; enum class bandwidth_formula { v1, v2 }; // 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, phase_convention phase_ = phase_convention::absolute, bandwidth_formula bandwidth_ = bandwidth_formula::v1): bands_per_octave(bands_per_octave_), ff_min(ff_min_), ff_ref(ff_ref_), overlap(overlap_), max_error(max_error_), phase(phase_), bandwidth(bandwidth_), coef_scale(1.0), synthesis(true) { } // 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); #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 == bandwidth_formula::v1 ? 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; phase_convention phase; bandwidth_formula bandwidth; double coef_scale; bool synthesis; // Synthesis is supported }; // 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 (size_t i = 0; i < v.size(); i++) v[i] *= 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 { zeropad_source(const S &source_, int64_t src_i0_, int64_t src_i1_): source(source_), src_i0(src_i0_), src_i1(src_i1_) { } T *operator()(int64_t i0, int64_t i1, T *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; // 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); } template struct analyzer: public refcounted { typedef complex C; struct plan; analyzer(const parameters ¶ms_): params(params_), 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 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); // 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 linar 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 = linear_transform(a, b); bandpass_band_log2ff = log2ff_bandpass_band.inverse(); // 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); // Figure out the number of octaves, keeping in mind that // the top octave is twice the size, and DC band is added // to the bottom octave even if that makes it larger than // the others. n_octaves = (n_bandpass_bands_total + params.bands_per_octave - 1) / params.bands_per_octave; if (n_octaves > 1) n_octaves--; { // 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 actual filter // coefficients are plan-dependent and will be calculated // later. // 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 + gaussian_support(ff_sd(0.25), params.max_error); 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); // 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_ff = ff; ds_ff_sd = ff_sd; ds_time_support = gaussian_support(time_sd, params.max_error); } band0_time_analysis_support = gaussian_support(band0_time_sd, params.max_error); band0_time_synthesis_support = band0_time_analysis_support * synthesis_support_multiplier(); // Make analysis plans unsigned int max_support = std::max(ceil(band0_time_analysis_support), ds_time_support * 2); unsigned int size = next_power_of_two(max_support * 2); plan *p; for (;;) { p = new plan(this, false, size, max_support); if (p->ok) break; delete p; size *= 2; } anl_plans.push_back(p); // Smallest possible plan size *= 2; p = new plan(this, false, size, 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(band0_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); } // 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); } cmeta_any = anl_plans[0]->cmeta; } // 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 { return analysis_support(n_bands_total - 2); // Last before DC } // 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 { // Empirical formula return params.synthesis ? 1.8 / params.overlap : 1; } // 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); } // 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)); } // Return the fractional frequency of bandpass band number "obno" // in octave "oct", scaling according to the octave double bandpass_band_ff(int oct, int obno) const { return bandpass_band_ff(bno_merge(oct, obno)); } const plan &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 *plans[i]; } void synthesize_one_slice(int oct, const plan &plan, 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 { zone &z = *plan.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(); // 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 + bp->sftsize, coefbuf.data()); C *indata = coefbuf.data(); pod_vector &sdata(buf2); T scale_factor = (T)1 / ((T)params.coef_scale * 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 * plan.inv_fftsize_double; double arg = (params.phase == phase_convention::absolute) ? tau * t0 * ff : 0; 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) & (plan.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[(plan.fftsize - 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, coefs &msc) const { const plan &plan(choose_plan(anl_plans, t1 - t0)); // 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 = *plan.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 = ds_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); // 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(); 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 = bp->fq_offset_int; int end_index = bp->fq_offset_int + bp->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, bp->kernel.data(), bp->sftsize); } else { // Slow path for (size_t i = 0; i < bp->sftsize; i++) sdata[i] = get_real_spectrum_coef(spectrum.data(), (int)(start_index + i), plan.fftsize) * bp->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)); bp->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 = bp->center * plan.inv_fftsize_double; double arg; if (params.phase == phase_convention::absolute) 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(), bp->shift_kernel.data(), phase_times_scale, bp->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. coef_index_t ii0 = std::max(ii, (t0 - (int)bp->time_support) >> coef_shift); coef_index_t ii1 = std::min(ii + bp->sftsize, ((t1 + (int)bp->time_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, 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 plan &plan(choose_plan(syn_plans, t1 - t0)); // 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, plan, 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; (void)level; buffers buf(fftsize_max, sftsize_max); analyze_sliced(buf, 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 { 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; double band0_log2ff; linear_transform log2ff_bandpass_band; linear_transform bandpass_band_log2ff; unsigned int n_bandpass_bands_total; struct plan: public refcounted { plan(const plan &) = delete; plan(analyzer *anl, bool synthesis_, unsigned int fftsize_, double support_): ok(false), synthesis(synthesis_), fftsize(fftsize_), max_step_log2(0), 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 // 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) ? anl->params.bands_per_octave * 2 : anl->params.bands_per_octave; base += max_bands_this_octave; int bands_remaining = anl->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)anl->params.bands_per_octave); if (zno < 2 || dc_zone || dc_adjacent_zone) { make_zone(anl, zno, base - (tbno + bands_this_octave), base - tbno, dc_zone, bands_below); zno++; } octaves.push_back(octave()); octaves.back().z = zones[zno - 1].get(); tbno += bands_this_octave; if (dc_zone) break; } assert(octaves.size() == anl->n_octaves); // 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. gaussian_windowed_lowpass(anl->ds_ff, anl->ds_ff_sd, dsparams.kernel.begin(), dsparams.kernel.end(), true); 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 if (! synthesis) { unsigned int align = 1 << max_step_log2; 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; // Initialize coefficient metadata cmeta = new coefs_meta; cmeta->n_octaves = anl->n_octaves; cmeta->n_bands_total = anl->n_bands_total; cmeta->bands_per_octave = anl->params.bands_per_octave; cmeta->slice_len = fftsize >> 1; 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++) { bv[i].slice_len = z->bandparams[i]->sftsize >> 1; bv[i].slice_len_log2 = z->bandparams[i]->sftsize_log2 - 1; bv[i].step_log2 = fftsize_log2 - z->bandparams[i]->sftsize_log2; } cmeta->zones[zi].init(bv); } cmeta->octaves.resize(octaves.size()); tbno = 0; for (unsigned int i = 0; i < anl->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(); } ok = true; } void make_zone(analyzer *anl, unsigned int zno, int bb, int be, bool dc_zone, int bands_below) { assert(zones.size() == zno); zone *z = new zone(); z->zno = zno; 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(anl, 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(anl, 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(anl, i, false)); } // The actual bandpass bands of this zone for (int i = bb; i < be; i++) push(z->bandparams, make_band(anl, 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)anl->params.bands_per_octave * 2; i++) push(z->mock_bandparams, make_band(anl, i, false)); z->n_bands = (unsigned int)z->bandparams.size(); // Find the largest coefficient step unsigned int m = 0; 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 complex exponentials for non-integer center // frequency adjustment and phase convention 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++) { double center = (anl->params.phase == phase_convention::absolute) ? bp->center : 0; double arg = tau * ((double)i / bp->sftsize) * -(center - bp->icenter); C t(cos(arg), sin(arg)); // Apply ifftshift of spectrum in time domain if (i & 1) t = -t; bp->shift_kernel[i] = t; } } if (synthesis) { // Accumulate window power for calculating dual z->power.resize(fftsize); std::fill(z->power.data(), z->power.data() + fftsize, (T)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 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)]; } for (unsigned int i = 0; i < bp->sftsize; i++) { C t = bp->shift_kernel[i]; // Undo time-domain ifftshift if (i & 1) t = -t; bp->shift_kernel_conj[i] = conj(t); } } } // 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; } } } // 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(analyzer *anl, 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 = anl->rbno_ff(rbno); // Standard deviation of the bandpass Gaussian, // as a fractional frequency double ffsd = anl->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, anl->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)); unsigned int sftsize = next_power_of_two(fq_2support); // If there is a very small number of bandpass bands, the // taper of the lowpass band may extend past the Nyquist // frequency, causing sftsize to become greater than fftsize. // This would mean sampling the lowpass band at a higher sample // rate than the original signal, which would be a bit silly, // and having sftsize > fftsize will break things by causing // shift amounts to go negative, so instead we force sftsize // = fftsize but remember the original sftsize. The lowpass // kernel is calculated using the original sftsize and allowed // to alias over the truncated sftsize. unsigned int orig_sftsize = sftsize; if (sftsize > fftsize) { assert(dc); sftsize = fftsize; } band_params *bp = new band_params; bp->dc = dc; bp->sftsize = sftsize; bp->sftsize_log2 = whichp2(bp->sftsize); bp->step_log2 = fftsize_log2 - bp->sftsize_log2; bp->step = 1U << bp->step_log2; sftsize_max = std::max(sftsize_max, bp->sftsize); bp->sft = pool, int>::shared.get(bp->sftsize); bp->kernel.resize(bp->sftsize); bp->shift_kernel.resize(bp->sftsize); if (synthesis) { bp->dual_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. The cast to double is necessary to get // a float division in pathological cases where orig_sftsize // is larger than fftsize. double scale = (double)fftsize / orig_sftsize; pod_vector win(orig_sftsize); gaussian_windowed_lowpass( ff * scale, ffsd * scale, win.begin(), win.end()); for (unsigned int i = 0; i < bp->kernel.size(); i++) bp->kernel[i] = 0; for (unsigned int i = 0; i < win.size(); i++) bp->kernel[i & (bp->kernel.size() - 1)] += win[i]; 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; } } double time_sd = sd_f2t(ffsd); double time_support = gaussian_support(time_sd, anl->params.max_error); bp->time_support = ceil(time_support); return bp; } // 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; unsigned int max_step_log2; // 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 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 ref cmeta; // Coefficient metadata }; 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 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 // The largest time-domain support of any bandpass filter, // in octave subsamples. This is determined by the "band0" // filter (near fs/8). double band0_time_analysis_support; double band0_time_synthesis_support; 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