logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
20
crates/denoise/Cargo.toml
Normal file
20
crates/denoise/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "denoise"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
candle-core = { version = "0.9.1", git ="https://github.com/zed-industries/candle", branch = "9.1-patched" }
|
||||
candle-onnx = { version = "0.9.1", git ="https://github.com/zed-industries/candle", branch = "9.1-patched" }
|
||||
log.workspace = true
|
||||
|
||||
rodio = { workspace = true, features = ["wav_output"] }
|
||||
|
||||
rustfft = { version = "6.2.0", features = ["avx"] }
|
||||
realfft = "3.4.0"
|
||||
thiserror.workspace = true
|
||||
1
crates/denoise/LICENSE-GPL
Symbolic link
1
crates/denoise/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
20
crates/denoise/README.md
Normal file
20
crates/denoise/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Real time streaming audio denoising using a [Dual-Signal Transformation LSTM Network for Real-Time Noise Suppression](https://arxiv.org/abs/2005.07551).
|
||||
|
||||
Trivial to build as it uses the native rust Candle crate for inference. Easy to integrate into any Rodio pipeline.
|
||||
|
||||
```rust
|
||||
# use rodio::{nz, source::UniformSourceIterator, wav_to_file};
|
||||
let file = std::fs::File::open("clips_airconditioning.wav")?;
|
||||
let decoder = rodio::Decoder::try_from(file)?;
|
||||
let resampled = UniformSourceIterator::new(decoder, nz!(1), nz!(16_000));
|
||||
|
||||
let mut denoised = denoise::Denoiser::try_new(resampled)?;
|
||||
wav_to_file(&mut denoised, "denoised.wav")?;
|
||||
Result::Ok<(), Box<dyn std::error::Error>>
|
||||
```
|
||||
|
||||
## Acknowledgements & License
|
||||
|
||||
The trained models in this repo are optimized versions of the models in the [breizhn/DTLN](https://github.com/breizhn/DTLN?tab=readme-ov-file#model-conversion-and-real-time-processing-with-onnx). These are licensed under MIT.
|
||||
|
||||
The FFT code was adapted from Datadog's [dtln-rs Repo](https://github.com/DataDog/dtln-rs/tree/main) also licensed under MIT.
|
||||
11
crates/denoise/examples/denoise.rs
Normal file
11
crates/denoise/examples/denoise.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use rodio::{nz, source::UniformSourceIterator, wav_to_file};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file = std::fs::File::open("airconditioning.wav")?;
|
||||
let decoder = rodio::Decoder::try_from(file)?;
|
||||
let resampled = UniformSourceIterator::new(decoder, nz!(1), nz!(16_000));
|
||||
|
||||
let mut denoised = denoise::Denoiser::try_new(resampled)?;
|
||||
wav_to_file(&mut denoised, "denoised.wav")?;
|
||||
Ok(())
|
||||
}
|
||||
23
crates/denoise/examples/enable_disable.rs
Normal file
23
crates/denoise/examples/enable_disable.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use rodio::Source;
|
||||
use rodio::wav_to_file;
|
||||
use rodio::{nz, source::UniformSourceIterator};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file = std::fs::File::open("clips_airconditioning.wav")?;
|
||||
let decoder = rodio::Decoder::try_from(file)?;
|
||||
let resampled = UniformSourceIterator::new(decoder, nz!(1), nz!(16_000));
|
||||
|
||||
let mut enabled = true;
|
||||
let denoised = denoise::Denoiser::try_new(resampled)?.periodic_access(
|
||||
Duration::from_secs(2),
|
||||
|denoised| {
|
||||
enabled = !enabled;
|
||||
denoised.set_enabled(enabled);
|
||||
},
|
||||
);
|
||||
|
||||
wav_to_file(denoised, "processed.wav")?;
|
||||
Ok(())
|
||||
}
|
||||
BIN
crates/denoise/models/model_1_converted_simplified.onnx
Normal file
BIN
crates/denoise/models/model_1_converted_simplified.onnx
Normal file
Binary file not shown.
BIN
crates/denoise/models/model_2_converted_simplified.onnx
Normal file
BIN
crates/denoise/models/model_2_converted_simplified.onnx
Normal file
Binary file not shown.
204
crates/denoise/src/engine.rs
Normal file
204
crates/denoise/src/engine.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
/// use something like https://netron.app/ to inspect the models and understand
|
||||
/// the flow
|
||||
use std::collections::HashMap;
|
||||
|
||||
use candle_core::{Device, IndexOp, Tensor};
|
||||
use candle_onnx::onnx::ModelProto;
|
||||
use candle_onnx::prost::Message;
|
||||
use realfft::RealFftPlanner;
|
||||
use rustfft::num_complex::Complex;
|
||||
|
||||
pub struct Engine {
|
||||
spectral_model: ModelProto,
|
||||
signal_model: ModelProto,
|
||||
|
||||
fft_planner: RealFftPlanner<f32>,
|
||||
fft_scratch: Vec<Complex<f32>>,
|
||||
spectrum: [Complex<f32>; FFT_OUT_SIZE],
|
||||
signal: [f32; BLOCK_LEN],
|
||||
|
||||
in_magnitude: [f32; FFT_OUT_SIZE],
|
||||
in_phase: [f32; FFT_OUT_SIZE],
|
||||
|
||||
spectral_memory: Tensor,
|
||||
signal_memory: Tensor,
|
||||
|
||||
in_buffer: [f32; BLOCK_LEN],
|
||||
out_buffer: [f32; BLOCK_LEN],
|
||||
}
|
||||
|
||||
// 32 ms @ 16khz per DTLN docs: https://github.com/breizhn/DTLN
|
||||
pub const BLOCK_LEN: usize = 512;
|
||||
// 8 ms @ 16khz per DTLN docs.
|
||||
pub const BLOCK_SHIFT: usize = 128;
|
||||
pub const FFT_OUT_SIZE: usize = BLOCK_LEN / 2 + 1;
|
||||
|
||||
impl Engine {
|
||||
pub fn new() -> Self {
|
||||
let mut fft_planner = RealFftPlanner::new();
|
||||
let fft_planned = fft_planner.plan_fft_forward(BLOCK_LEN);
|
||||
let scratch_len = fft_planned.get_scratch_len();
|
||||
Self {
|
||||
// Models are 1.5MB and 2.5MB respectively. Its worth the binary
|
||||
// size increase not to have to distribute the models separately.
|
||||
spectral_model: ModelProto::decode(
|
||||
include_bytes!("../models/model_1_converted_simplified.onnx").as_slice(),
|
||||
)
|
||||
.expect("The model should decode"),
|
||||
signal_model: ModelProto::decode(
|
||||
include_bytes!("../models/model_2_converted_simplified.onnx").as_slice(),
|
||||
)
|
||||
.expect("The model should decode"),
|
||||
fft_planner,
|
||||
fft_scratch: vec![Complex::ZERO; scratch_len],
|
||||
spectrum: [Complex::ZERO; FFT_OUT_SIZE],
|
||||
signal: [0f32; BLOCK_LEN],
|
||||
|
||||
in_magnitude: [0f32; FFT_OUT_SIZE],
|
||||
in_phase: [0f32; FFT_OUT_SIZE],
|
||||
|
||||
spectral_memory: Tensor::from_slice::<_, f32>(
|
||||
&[0f32; 512],
|
||||
(1, 2, BLOCK_SHIFT, 2),
|
||||
&Device::Cpu,
|
||||
)
|
||||
.expect("Tensor has the correct dimensions"),
|
||||
signal_memory: Tensor::from_slice::<_, f32>(
|
||||
&[0f32; 512],
|
||||
(1, 2, BLOCK_SHIFT, 2),
|
||||
&Device::Cpu,
|
||||
)
|
||||
.expect("Tensor has the correct dimensions"),
|
||||
out_buffer: [0f32; BLOCK_LEN],
|
||||
in_buffer: [0f32; BLOCK_LEN],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a clunk of samples and get the denoised chunk 4 feeds later
|
||||
pub fn feed(&mut self, samples: &[f32]) -> [f32; BLOCK_SHIFT] {
|
||||
/// The name of the output node of the onnx network
|
||||
/// [Dual-Signal Transformation LSTM Network for Real-Time Noise Suppression](https://arxiv.org/abs/2005.07551).
|
||||
const MEMORY_OUTPUT: &'static str = "Identity_1";
|
||||
|
||||
debug_assert_eq!(samples.len(), BLOCK_SHIFT);
|
||||
|
||||
// place new samples at the end of the `in_buffer`
|
||||
self.in_buffer.copy_within(BLOCK_SHIFT.., 0);
|
||||
self.in_buffer[(BLOCK_LEN - BLOCK_SHIFT)..].copy_from_slice(&samples);
|
||||
|
||||
// run inference
|
||||
let inputs = self.spectral_inputs();
|
||||
let mut spectral_outputs = candle_onnx::simple_eval(&self.spectral_model, inputs)
|
||||
.expect("The embedded file must be valid");
|
||||
self.spectral_memory = spectral_outputs
|
||||
.remove(MEMORY_OUTPUT)
|
||||
.expect("The model has an output named Identity_1");
|
||||
let inputs = self.signal_inputs(spectral_outputs);
|
||||
let mut signal_outputs = candle_onnx::simple_eval(&self.signal_model, inputs)
|
||||
.expect("The embedded file must be valid");
|
||||
self.signal_memory = signal_outputs
|
||||
.remove(MEMORY_OUTPUT)
|
||||
.expect("The model has an output named Identity_1");
|
||||
let model_output = model_outputs(signal_outputs);
|
||||
|
||||
// place processed samples at the start of the `out_buffer`
|
||||
// shift the rest left, fill the end with zeros. Zeros are needed as
|
||||
// the out buffer is part of the input of the network
|
||||
self.out_buffer.copy_within(BLOCK_SHIFT.., 0);
|
||||
self.out_buffer[BLOCK_LEN - BLOCK_SHIFT..].fill(0f32);
|
||||
for (a, b) in self.out_buffer.iter_mut().zip(model_output) {
|
||||
*a += b;
|
||||
}
|
||||
|
||||
// samples at the front of the `out_buffer` are now denoised
|
||||
self.out_buffer[..BLOCK_SHIFT]
|
||||
.try_into()
|
||||
.expect("len is correct")
|
||||
}
|
||||
|
||||
fn spectral_inputs(&mut self) -> HashMap<String, Tensor> {
|
||||
// Prepare FFT input
|
||||
let fft = self.fft_planner.plan_fft_forward(BLOCK_LEN);
|
||||
|
||||
// Perform real-to-complex FFT
|
||||
let mut fft_in = self.in_buffer;
|
||||
fft.process_with_scratch(&mut fft_in, &mut self.spectrum, &mut self.fft_scratch)
|
||||
.expect("The fft should run, there is enough scratch space");
|
||||
|
||||
// Generate magnitude and phase
|
||||
for ((magnitude, phase), complex) in self
|
||||
.in_magnitude
|
||||
.iter_mut()
|
||||
.zip(self.in_phase.iter_mut())
|
||||
.zip(self.spectrum)
|
||||
{
|
||||
*magnitude = complex.norm();
|
||||
*phase = complex.arg();
|
||||
}
|
||||
|
||||
const SPECTRUM_INPUT: &str = "input_2";
|
||||
const MEMORY_INPUT: &str = "input_3";
|
||||
let spectrum =
|
||||
Tensor::from_slice::<_, f32>(&self.in_magnitude, (1, 1, FFT_OUT_SIZE), &Device::Cpu)
|
||||
.expect("the in magnitude has enough elements to fill the Tensor");
|
||||
|
||||
let inputs = HashMap::from([
|
||||
(SPECTRUM_INPUT.to_string(), spectrum),
|
||||
(MEMORY_INPUT.to_string(), self.spectral_memory.clone()),
|
||||
]);
|
||||
inputs
|
||||
}
|
||||
|
||||
fn signal_inputs(&mut self, outputs: HashMap<String, Tensor>) -> HashMap<String, Tensor> {
|
||||
let magnitude_weight = model_outputs(outputs);
|
||||
|
||||
// Apply mask and reconstruct complex spectrum
|
||||
let mut spectrum = [Complex::I; FFT_OUT_SIZE];
|
||||
for i in 0..FFT_OUT_SIZE {
|
||||
let magnitude = self.in_magnitude[i] * magnitude_weight[i];
|
||||
let phase = self.in_phase[i];
|
||||
let real = magnitude * phase.cos();
|
||||
let imag = magnitude * phase.sin();
|
||||
spectrum[i] = Complex::new(real, imag);
|
||||
}
|
||||
|
||||
// Handle DC component (i = 0)
|
||||
let magnitude = self.in_magnitude[0] * magnitude_weight[0];
|
||||
spectrum[0] = Complex::new(magnitude, 0.0);
|
||||
|
||||
// Handle Nyquist component (i = N/2)
|
||||
let magnitude = self.in_magnitude[FFT_OUT_SIZE - 1] * magnitude_weight[FFT_OUT_SIZE - 1];
|
||||
spectrum[FFT_OUT_SIZE - 1] = Complex::new(magnitude, 0.0);
|
||||
|
||||
// Perform complex-to-real IFFT
|
||||
let ifft = self.fft_planner.plan_fft_inverse(BLOCK_LEN);
|
||||
ifft.process_with_scratch(&mut spectrum, &mut self.signal, &mut self.fft_scratch)
|
||||
.expect("The fft should run, there is enough scratch space");
|
||||
|
||||
// Normalize the IFFT output
|
||||
for real in &mut self.signal {
|
||||
*real /= BLOCK_LEN as f32;
|
||||
}
|
||||
|
||||
const SIGNAL_INPUT: &str = "input_4";
|
||||
const SIGNAL_MEMORY: &str = "input_5";
|
||||
let signal_input =
|
||||
Tensor::from_slice::<_, f32>(&self.signal, (1, 1, BLOCK_LEN), &Device::Cpu).unwrap();
|
||||
|
||||
HashMap::from([
|
||||
(SIGNAL_INPUT.to_string(), signal_input),
|
||||
(SIGNAL_MEMORY.to_string(), self.signal_memory.clone()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// Both models put their outputs in the same location
|
||||
fn model_outputs(mut outputs: HashMap<String, Tensor>) -> Vec<f32> {
|
||||
const NON_MEMORY_OUTPUT: &str = "Identity";
|
||||
outputs
|
||||
.remove(NON_MEMORY_OUTPUT)
|
||||
.expect("The model has this output")
|
||||
.i((0, 0))
|
||||
.and_then(|tensor| tensor.to_vec1())
|
||||
.expect("The tensor has the correct dimensions")
|
||||
}
|
||||
269
crates/denoise/src/lib.rs
Normal file
269
crates/denoise/src/lib.rs
Normal file
@@ -0,0 +1,269 @@
|
||||
mod engine;
|
||||
|
||||
use core::fmt;
|
||||
use std::{collections::VecDeque, sync::mpsc, thread};
|
||||
|
||||
pub use engine::Engine;
|
||||
use rodio::{ChannelCount, Sample, SampleRate, Source, nz};
|
||||
|
||||
use crate::engine::BLOCK_SHIFT;
|
||||
|
||||
const SUPPORTED_SAMPLE_RATE: SampleRate = nz!(16_000);
|
||||
const SUPPORTED_CHANNEL_COUNT: ChannelCount = nz!(1);
|
||||
|
||||
pub struct Denoiser<S: Source> {
|
||||
inner: S,
|
||||
input_tx: mpsc::Sender<[Sample; BLOCK_SHIFT]>,
|
||||
denoised_rx: mpsc::Receiver<[Sample; BLOCK_SHIFT]>,
|
||||
ready: [Sample; BLOCK_SHIFT],
|
||||
next: usize,
|
||||
state: IterState,
|
||||
// When disabled instead of reading denoised sub-blocks from the engine through
|
||||
// `denoised_rx` we read unprocessed from this queue. This maintains the same
|
||||
// latency so we can 'trivially' re-enable
|
||||
queued: Queue,
|
||||
}
|
||||
|
||||
impl<S: Source> fmt::Debug for Denoiser<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Denoiser")
|
||||
.field("state", &self.state)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
struct Queue(VecDeque<[Sample; BLOCK_SHIFT]>);
|
||||
|
||||
impl Queue {
|
||||
fn new() -> Self {
|
||||
Self(VecDeque::new())
|
||||
}
|
||||
fn push(&mut self, block: [Sample; BLOCK_SHIFT]) {
|
||||
self.0.push_back(block);
|
||||
self.0.resize(4, [0f32; BLOCK_SHIFT]);
|
||||
}
|
||||
fn pop(&mut self) -> [Sample; BLOCK_SHIFT] {
|
||||
debug_assert!(self.0.len() == 4);
|
||||
self.0.pop_front().expect(
|
||||
"There is no State where the queue is popped while there are less then 4 entries",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum IterState {
|
||||
Enabled,
|
||||
StartingMidAudio { fed_to_denoiser: usize },
|
||||
Disabled,
|
||||
Startup { enabled: bool },
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DenoiserError {
|
||||
#[error("This denoiser only works on sources with samplerate 16000")]
|
||||
UnsupportedSampleRate,
|
||||
#[error("This denoiser only works on mono sources (1 channel)")]
|
||||
UnsupportedChannelCount,
|
||||
}
|
||||
|
||||
// todo dvdsk needs constant source upstream in rodio
|
||||
impl<S: Source> Denoiser<S> {
|
||||
pub fn try_new(source: S) -> Result<Self, DenoiserError> {
|
||||
if source.sample_rate() != SUPPORTED_SAMPLE_RATE {
|
||||
return Err(DenoiserError::UnsupportedSampleRate);
|
||||
}
|
||||
if source.channels() != SUPPORTED_CHANNEL_COUNT {
|
||||
return Err(DenoiserError::UnsupportedChannelCount);
|
||||
}
|
||||
|
||||
let (input_tx, input_rx) = mpsc::channel();
|
||||
let (denoised_tx, denoised_rx) = mpsc::channel();
|
||||
|
||||
thread::Builder::new()
|
||||
.name("NeuralDenoiser".to_owned())
|
||||
.spawn(move || {
|
||||
run_neural_denoiser(denoised_tx, input_rx);
|
||||
})
|
||||
.expect("Should be ablet to spawn threads");
|
||||
|
||||
Ok(Self {
|
||||
inner: source,
|
||||
input_tx,
|
||||
denoised_rx,
|
||||
ready: [0.0; BLOCK_SHIFT],
|
||||
state: IterState::Startup { enabled: true },
|
||||
next: BLOCK_SHIFT,
|
||||
queued: Queue::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.state = match (enabled, self.state) {
|
||||
(false, IterState::StartingMidAudio { .. }) | (false, IterState::Enabled) => {
|
||||
IterState::Disabled
|
||||
}
|
||||
(false, IterState::Startup { enabled: true }) => IterState::Startup { enabled: false },
|
||||
(true, IterState::Disabled) => IterState::StartingMidAudio { fed_to_denoiser: 0 },
|
||||
(_, state) => state,
|
||||
};
|
||||
}
|
||||
|
||||
fn feed(&self, sub_block: [f32; BLOCK_SHIFT]) {
|
||||
self.input_tx.send(sub_block).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn run_neural_denoiser(
|
||||
denoised_tx: mpsc::Sender<[f32; BLOCK_SHIFT]>,
|
||||
input_rx: mpsc::Receiver<[f32; BLOCK_SHIFT]>,
|
||||
) {
|
||||
let mut engine = Engine::new();
|
||||
// until tx is dropped
|
||||
while let Ok(sub_block) = input_rx.recv() {
|
||||
let denoised_sub_block = engine.feed(&sub_block);
|
||||
if denoised_tx.send(denoised_sub_block).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Source> Source for Denoiser<S> {
|
||||
fn current_span_len(&self) -> Option<usize> {
|
||||
self.inner.current_span_len()
|
||||
}
|
||||
|
||||
fn channels(&self) -> rodio::ChannelCount {
|
||||
self.inner.channels()
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> rodio::SampleRate {
|
||||
self.inner.sample_rate()
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<std::time::Duration> {
|
||||
self.inner.total_duration()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Source> Iterator for Denoiser<S> {
|
||||
type Item = Sample;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.next += 1;
|
||||
if self.next < self.ready.len() {
|
||||
let sample = self.ready[self.next];
|
||||
return Some(sample);
|
||||
}
|
||||
|
||||
// This is a separate function to prevent it from being inlined
|
||||
// as this code only runs once every 128 samples
|
||||
self.prepare_next_ready()
|
||||
.inspect_err(|_| {
|
||||
log::error!("Denoise engine crashed");
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Could not send or receive from denoise thread. It must have crashed")]
|
||||
struct DenoiseEngineCrashed;
|
||||
|
||||
impl<S: Source> Denoiser<S> {
|
||||
#[cold]
|
||||
fn prepare_next_ready(&mut self) -> Result<Option<f32>, DenoiseEngineCrashed> {
|
||||
self.state = match self.state {
|
||||
IterState::Startup { enabled } => {
|
||||
// guaranteed to be coming from silence
|
||||
for _ in 0..3 {
|
||||
let Some(sub_block) = read_sub_block(&mut self.inner) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.queued.push(sub_block);
|
||||
self.input_tx
|
||||
.send(sub_block)
|
||||
.map_err(|_| DenoiseEngineCrashed)?;
|
||||
}
|
||||
let Some(sub_block) = read_sub_block(&mut self.inner) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.queued.push(sub_block);
|
||||
self.input_tx
|
||||
.send(sub_block)
|
||||
.map_err(|_| DenoiseEngineCrashed)?;
|
||||
// throw out old blocks that are denoised silence
|
||||
let _ = self.denoised_rx.iter().take(3).count();
|
||||
self.ready = self.denoised_rx.recv().map_err(|_| DenoiseEngineCrashed)?;
|
||||
|
||||
let Some(sub_block) = read_sub_block(&mut self.inner) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.queued.push(sub_block);
|
||||
self.feed(sub_block);
|
||||
|
||||
if enabled {
|
||||
IterState::Enabled
|
||||
} else {
|
||||
IterState::Disabled
|
||||
}
|
||||
}
|
||||
IterState::Enabled => {
|
||||
self.ready = self.denoised_rx.recv().map_err(|_| DenoiseEngineCrashed)?;
|
||||
let Some(sub_block) = read_sub_block(&mut self.inner) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.queued.push(sub_block);
|
||||
self.input_tx
|
||||
.send(sub_block)
|
||||
.map_err(|_| DenoiseEngineCrashed)?;
|
||||
IterState::Enabled
|
||||
}
|
||||
IterState::Disabled => {
|
||||
// Need to maintain the same 512 samples delay such that
|
||||
// we can re-enable at any point.
|
||||
self.ready = self.queued.pop();
|
||||
let Some(sub_block) = read_sub_block(&mut self.inner) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.queued.push(sub_block);
|
||||
IterState::Disabled
|
||||
}
|
||||
IterState::StartingMidAudio {
|
||||
fed_to_denoiser: mut sub_blocks_fed,
|
||||
} => {
|
||||
self.ready = self.queued.pop();
|
||||
let Some(sub_block) = read_sub_block(&mut self.inner) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.queued.push(sub_block);
|
||||
self.input_tx
|
||||
.send(sub_block)
|
||||
.map_err(|_| DenoiseEngineCrashed)?;
|
||||
sub_blocks_fed += 1;
|
||||
if sub_blocks_fed > 4 {
|
||||
// throw out partially denoised blocks,
|
||||
// next will be correctly denoised
|
||||
let _ = self.denoised_rx.iter().take(3).count();
|
||||
IterState::Enabled
|
||||
} else {
|
||||
IterState::StartingMidAudio {
|
||||
fed_to_denoiser: sub_blocks_fed,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.next = 0;
|
||||
Ok(Some(self.ready[0]))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_sub_block(s: &mut impl Source) -> Option<[f32; BLOCK_SHIFT]> {
|
||||
let mut res = [0f32; BLOCK_SHIFT];
|
||||
for sample in &mut res {
|
||||
*sample = s.next()?;
|
||||
}
|
||||
Some(res)
|
||||
}
|
||||
Reference in New Issue
Block a user