Skip to content

Commit

Permalink
Frequency domain resynthesis.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamiPerttu committed Dec 26, 2023
1 parent 7099e69 commit 5c57743
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- `AudioNode` now requires `Send` and `Sync`.
- Feedback units `Feedback64` and `Feedback32`.
- `Shape::Atan` was contributed by Serdnad.
- New opcode `resynth` for frequency domain resynthesis.

### Version 0.15

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@ The type parameters in the table refer to the hacker preludes.
| `resample(node)` | 1 (speed) | `node` | Resample generator `node` using cubic interpolation at speed obtained from the input, where 1 is the original speed. |
| `resonator()` | 3 (audio, frequency, bandwidth) | 1 | Constant-gain bandpass resonator (2nd order). |
| `resonator_hz(f, bw)` | 1 | 1 | Constant-gain bandpass resonator (2nd order) with center frequency `f` Hz and bandwidth `bw` Hz. |
| `resynth::<I, O, _>(w, f)` | `I` | `O` | Frequency domain resynthesis with window length `w` and processing function `f` |
| `reverb_stereo(r, t)` | 2 | 2 | Stereo reverb with room size `r` meters (10 is average) and reverberation time `t` seconds. |
| `reverse::<N>()` | `N` | `N` | Reverse channel order, e.g., swap left and right channels. |
| `rossler()` | 1 (frequency) | 1 | [Rössler dynamical system](https://en.wikipedia.org/wiki/R%C3%B6ssler_attractor) oscillator. |
Expand Down
6 changes: 3 additions & 3 deletions src/feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ where
}

fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame {
Routing::Arbitrary.propagate(input, self.outputs())
Routing::Arbitrary(0.0).propagate(input, self.outputs())
}

fn ping(&mut self, probe: bool, hash: AttoHash) -> AttoHash {
Expand Down Expand Up @@ -264,7 +264,7 @@ where
}

fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame {
Routing::Arbitrary.propagate(input, self.outputs())
Routing::Arbitrary(0.0).propagate(input, self.outputs())
}

fn ping(&mut self, probe: bool, hash: AttoHash) -> AttoHash {
Expand Down Expand Up @@ -437,7 +437,7 @@ impl AudioUnit48 for Feedback48 {
}

fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame {
Routing::Arbitrary.propagate(input, self.outputs())
Routing::Arbitrary(0.0).propagate(input, self.outputs())
}

fn get_id(&self) -> u64 {
Expand Down
33 changes: 32 additions & 1 deletion src/hacker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ where
O: ConstantFrame<Sample = f64>,
O::Size: Size<f64>,
{
An(Map::new(f, Routing::Arbitrary))
An(Map::new(f, Routing::Arbitrary(0.0)))
}

/// Keeps a signal zero centered.
Expand Down Expand Up @@ -2171,3 +2171,34 @@ pub fn snoop(capacity: usize) -> (Snoop<f64>, An<SnoopBackend<f64>>) {
let (snoop, backend) = Snoop::new(capacity);
(snoop, An(backend))
}

/// Frequency domain resynthesizer. The number of inputs is `I` and the number of outputs is `O`.
/// The window length (in samples) must be a power of two and at least four.
/// Processes windows of input samples transformed into the frequency domain.
/// The processing function is issued arguments (time, window).
/// It processes frequency domain inputs into frequency domain outputs.
/// The latency in samples is equal to window length.
/// If any output is a copy of an input, then the input will be reconstructed exactly
/// once all windows are overlapping, which takes `window_length` samples.
/// -Input(s): input signals.
/// -Output(s): processed signals.
///
/// ### Example: FFT Lowpass Filter
/// ```
/// use fundsp::hacker::*;
/// let cutoff = 1000.0;
/// let resynth = resynth::<U1, U1, _>(1024, |_time, fft|
/// for i in 0 .. fft.bins() {
/// if fft.frequency(i) <= cutoff {
/// fft.set(0, i, fft.at(0, i));
/// }
/// });
/// ```
pub fn resynth<I, O, F>(window_length: usize, processing: F) -> An<Resynth<I, O, f64, F>>
where
I: Size<f64>,
O: Size<f64>,
F: FnMut(f32, &mut FftWindow) + Clone + Send + Sync,
{
An(Resynth::new(window_length, processing))
}
33 changes: 32 additions & 1 deletion src/hacker32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ where
O: ConstantFrame<Sample = f32>,
O::Size: Size<f32>,
{
An(Map::new(f, Routing::Arbitrary))
An(Map::new(f, Routing::Arbitrary(0.0)))
}

/// Keeps a signal zero centered.
Expand Down Expand Up @@ -2173,3 +2173,34 @@ pub fn snoop(capacity: usize) -> (Snoop<f32>, An<SnoopBackend<f32>>) {
let (snoop, backend) = Snoop::new(capacity);
(snoop, An(backend))
}

/// Frequency domain resynthesizer. The number of inputs is `I` and the number of outputs is `O`.
/// The window length (in samples) must be a power of two and at least four.
/// Processes windows of input samples transformed into the frequency domain.
/// The processing function is issued arguments (time, window).
/// It processes frequency domain inputs into frequency domain outputs.
/// The latency in samples is equal to window length.
/// If any output is a copy of an input, then the input will be reconstructed exactly
/// once all windows are overlapping, which takes `window_length` samples.
/// -Input(s): input signals.
/// -Output(s): processed signals.
///
/// ### Example: FFT Lowpass Filter
/// ```
/// use fundsp::hacker32::*;
/// let cutoff = 1000.0;
/// let resynth = resynth::<U1, U1, _>(1024, |_time, fft|
/// for i in 0 .. fft.bins() {
/// if fft.frequency(i) <= cutoff {
/// fft.set(0, i, fft.at(0, i));
/// }
/// });
/// ```
pub fn resynth<I, O, F>(window_length: usize, processing: F) -> An<Resynth<I, O, f32, F>>
where
I: Size<f32>,
O: Size<f32>,
F: FnMut(f32, &mut FftWindow) + Clone + Send + Sync,
{
An(Resynth::new(window_length, processing))
}
34 changes: 33 additions & 1 deletion src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ where
O: ConstantFrame<Sample = T>,
O::Size: Size<T>,
{
An(Map::new(f, Routing::Arbitrary))
An(Map::new(f, Routing::Arbitrary(0.0)))
}

/// Keeps a signal zero centered.
Expand Down Expand Up @@ -2676,3 +2676,35 @@ pub fn snoop<T: Float>(capacity: usize) -> (Snoop<T>, An<SnoopBackend<T>>) {
let (snoop, backend) = Snoop::new(capacity);
(snoop, An(backend))
}

/// Frequency domain resynthesizer. The number of inputs is `I` and the number of outputs is `O`.
/// The window length (in samples) must be a power of two and at least four.
/// Processes windows of input samples transformed into the frequency domain.
/// The processing function is issued arguments (time, window).
/// It processes frequency domain inputs into frequency domain outputs.
/// The latency in samples is equal to window length.
/// If any output is a copy of an input, then the input will be reconstructed exactly
/// once all windows are overlapping, which takes `window_length` samples.
/// -Input(s): input signals.
/// -Output(s): processed signals.
///
/// ### Example: FFT Lowpass Filter
/// ```
/// use fundsp::prelude::*;
/// let cutoff = 1000.0;
/// let resynth = resynth::<U1, U1, f32, _>(1024, |_time, fft|
/// for i in 0 .. fft.bins() {
/// if fft.frequency(i) <= cutoff {
/// fft.set(0, i, fft.at(0, i));
/// }
/// });
/// ```
pub fn resynth<I, O, T, F>(window_length: usize, processing: F) -> An<Resynth<I, O, T, F>>
where
I: Size<T>,
O: Size<T>,
T: Float,
F: FnMut(f32, &mut FftWindow) + Clone + Send + Sync,
{
An(Resynth::new(window_length, processing))
}
Loading

0 comments on commit 5c57743

Please sign in to comment.