1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Gaborator is a C++ library for converting audio samples to a special spectral representation
//! that uses different FTT sizes based on whether it is bass or treble (oversimplifying here).
//! The transformation is reversible.
//! See [the website](https://www.gaborator.com/) for more info.
//!
//! This crate is a [cxx](https://cxx.rs/)-based wrapper of this library, allowing Rust code to use Gaborator (although with reduced efficiency).
//!
//! Limitations:
//!
//! * `f32` only
//! * Not performance-minded
//! * Some overridable or low-level details not exposed
//! * No visualisation
//! * Crate soundness may be iffy - I was just followed the path of least resistance.
//! * Arithmentic overflows in buffer length calculations are not checked.
//! * Not really tested, apart from included examples. For example, streaming should be supported, but I haven't tried it myself.
//!
//! Currently based on Gaborator version 1.6. Source code of the Gaborator is included into the crate.
//! 
//! Availble examples:
//! 
//! * Phase information randomizer, creating sort-of-reverberation audio effect.
//! * Converts the analyzed sound to (sample,band,magnitude,phase) CSV file and back.
//!
//! License of Gaborator is Affero GPL 3.0.
//!
//! Glue code (sans doccomments copied from Gaborator) in this crate may be considered
//! to be licensed as either MIT or AGPL-3.0, at your option.

#![deny(missing_docs)]

pub extern crate cxx;

#[cxx::bridge(namespace = "gabbridge")]
mod ffi {
    /// Corresponds to `gaborator::parameters`.
    pub struct Params {
        /// The number of frequency bands per octave.
        /// Values from 6 to 384 (inclusive) are supported.
        /// Values outside this range may not work, or may cause degraded performance.
        pub bands_per_octave: u32,

        /// 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.
        pub ff_min: f64,

        /// 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. 
        ///
        /// Default value in C++ code is `1.0`.
        pub ff_ref: f64,

        /// A field not documented in Gaborator reference.
        ///
        /// Default value in C++ code is `0.7`.
        pub overlap: f64,
    }

    /// Complex point, representing one coefficient.
    /// Magnitude is loudness at this point, argument is phase.
    #[derive(Copy,Clone,Debug,PartialEq,PartialOrd,Default)]
    pub struct Coef {
        /// Real part of the complex number
        pub re: f32,
        /// Imaginatry part of the complex number
        pub im: f32,
    }

    /// Additional data for `read_coefficients_with_meta` or `write_coefficients_with_meta`
    #[derive(Copy,Clone,Debug,PartialEq,PartialOrd,Default,Eq,Ord,Hash)]
    pub struct CoefMeta {
        /// The band number of the frequency band the coefficients pertain to.
        /// This may be either a bandpass band or the lowpass band.
        band: i32,

        /// The point in time the coefficients pertain to, in samples
        sample_time: i64,
    }

    extern "Rust" {
        type ProcessOrFillCallback<'a>;

        fn process_or_write_callback(cb: &mut ProcessOrFillCallback, meta: CoefMeta, coef: &mut Coef);
    }

    unsafe extern "C++" {
        include!("gaborator-sys/src/gabbridge.h");

        /// Represents C++'s `gaborator::analyzer<float>`. Use `new_analyzer` function to create it.
        pub type Analyzer;

        /// Reprepresents C++'s `gaborator::coefs<float>`. Create it using `create_coefs`.
        /// Can be memory-hungry.
        /// 
        /// (I'm not sure whether this can be dropped after `Analyzer`.
        /// I see some mention of reference counting withing Gaborator library,
        /// but have not checked in detail.)
        pub type Coefs;

        /// Create new instance of Gaborator analyzer/synthesizer based on supplied parameters
        pub fn new_analyzer(params: &Params) -> UniquePtr<Analyzer>;

        /// 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.
        pub fn get_analysis_support_len(b: &Analyzer) -> usize;

        /// 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.
        pub fn get_synthesis_support_len(b: &Analyzer) -> usize;

        /// Create `Coefs` - the holder of analyzed data. 
        pub fn create_coefs(b: &Analyzer) -> UniquePtr<Coefs>;

        /// 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.
        pub fn forget_before(b: &Analyzer, c: Pin<&mut Coefs>, limit: i64, clean_cut: bool);


        /// Read or write values within `Coefs`, skipping over non-existent entries.
        /// Corresponds to `process` function of Gaborator.
        /// `from_band` and `to_band` may be given INT_MIN / INT_MAX values, that would mean all bands.
        /// `from_sample_time` and `to_sample_time` can also be given INT64_MIN / INT64_MAX value to mean all available data.
        pub fn process(
            coefs: Pin<&mut Coefs>,
            from_band: i32,
            to_band: i32,
            from_sample_time: i64,
            to_sample_time: i64,
            callback: &mut ProcessOrFillCallback,
        );

        /// Write values to `Coefs`, creating non-existent entries as needed.
        /// Corresponds to `fill` function of Gaborator.
        /// `from_band` and `to_band` may be given INT_MIN / INT_MAX values, that would mean all bands.
        /// `from_sample_time` / `to_sample_time` should not be set to overtly large range, lest memory will be exhausted.
        pub fn fill(
            coefs: Pin<&mut Coefs>,
            from_band: i32,
            to_band: i32,
            from_sample_time: i64,
            to_sample_time: i64,
            callback: &mut ProcessOrFillCallback,
        );

        /// Spectrum analyze the samples at `signal` and add the resulting coefficients to `coefs`.
        /// `t1` parameter from Gaborator's `analyze` method is caluclated based on supplied slice size.
        ///
        /// If the `coefs` object already contains some coefficients, the new coefficients are summed to those already present.
        pub fn analyze(
            b : &Analyzer,
            signal: &[f32],
            signal_begin_sample_number: i64,
            coefs: Pin<&mut Coefs>,
        );
            
        /// Synthesize signal samples from the coefficients `coef` and store them at `signal`. 
        /// `t1` parameter from Gaborator's `synthesize` method is caluclated based on supplied slice size.
        /// 
        /// The time range may extend outside the range analyzed using analyze(), in which case
        /// the signal is assumed to be zero in the un-analyzed range.
        pub fn synthesize(
            b : &Analyzer,
            coefs: &Coefs,
            signal_begin_sample_number: i64,
            signal: &mut [f32],
        );

        /// Return the smallest valid bandpass band number, corresponding to the highest-frequency bandpass filter.
        /// 
        /// 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.
        pub fn  bandpass_bands_begin(b : &Analyzer) -> i32;

        /// Return the bandpass band number one past the highest valid bandpass band number,
        /// corresponding to one past the lowest-frequency bandpass filter. 
        pub fn  bandpass_bands_end(b : &Analyzer) -> i32;

        /// Return the band number of the lowpass band. 
        pub fn  band_lowpass(b : &Analyzer)  -> i32;

        /// 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. 
        pub fn  band_ref(b : &Analyzer) -> i32;

        /// Return the center frequency of band number `band`, in units of the sampling frequency. 
        pub fn  band_ff(b : &Analyzer, band: i32) -> f64;
    }
}


pub use ffi::*;
/// Wrapper for your callback function for `fill` or `process`.
///
/// Example:
/// 
/// ```no_build
/// gaborator_sys::process(coefs.pin_mut(), -100000, 100000, -100000, 100000, &mut gaborator_sys::ProcessOrFillCallback(Box::new(
///        |_meta,_coef| {
///            // read _meta, read or write _coef
///        }
///    ))); 
/// ```
pub struct ProcessOrFillCallback<'a>(pub Box<dyn FnMut(CoefMeta, &mut Coef) + 'a>);

fn process_or_write_callback(cb: &mut ProcessOrFillCallback, meta: CoefMeta, coef: &mut Coef) {
    cb.0(meta, coef);
}