Compare commits
111 Commits
remote-pro
...
mic-denois
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8bc259404 | ||
|
|
90a32d0532 | ||
|
|
72cdc13351 | ||
|
|
20a219e383 | ||
|
|
5e9a6bd8ac | ||
|
|
a9696faf61 | ||
|
|
7ed243c7ce | ||
|
|
319c092a9d | ||
|
|
a40a947895 | ||
|
|
58efd50cee | ||
|
|
654053ec03 | ||
|
|
c1bcdf7d4e | ||
|
|
839b5cbaad | ||
|
|
90b8fc8f1e | ||
|
|
c13ad0c8fd | ||
|
|
2b18032df5 | ||
|
|
a58d11c7b2 | ||
|
|
018ee29e2d | ||
|
|
69a353a9fb | ||
|
|
d77cefd79a | ||
|
|
9f3b251f77 | ||
|
|
674b9451e9 | ||
|
|
3d2e5f43f4 | ||
|
|
b0d16cdba9 | ||
|
|
d48f8f149b | ||
|
|
f6ce757818 | ||
|
|
933c43ad1d | ||
|
|
2dba9eed56 | ||
|
|
0a64212630 | ||
|
|
dfc5fdac23 | ||
|
|
a1f1522cfa | ||
|
|
aeb2bd0429 | ||
|
|
d8176c9a14 | ||
|
|
047c873a62 | ||
|
|
448af397e6 | ||
|
|
27b59c0dd1 | ||
|
|
93968f2563 | ||
|
|
0ade84e5e6 | ||
|
|
16974e64aa | ||
|
|
0968cc8256 | ||
|
|
b66b5850ec | ||
|
|
e0ac8f7a9d | ||
|
|
e9b643e999 | ||
|
|
22449cd1e5 | ||
|
|
f053237454 | ||
|
|
8b62dcb891 | ||
|
|
7697cc2f37 | ||
|
|
72fb69f464 | ||
|
|
dcbeb86253 | ||
|
|
1cbd8612a2 | ||
|
|
76d1b2291b | ||
|
|
4e0a78813d | ||
|
|
ef9f28926c | ||
|
|
55fa4a33b8 | ||
|
|
b3b71a6e5c | ||
|
|
34b57b3be7 | ||
|
|
e673c3657a | ||
|
|
e489dba802 | ||
|
|
34d727aa72 | ||
|
|
17c44d9203 | ||
|
|
21d6f807d1 | ||
|
|
f1860abbc8 | ||
|
|
b3362338a4 | ||
|
|
f82224a46f | ||
|
|
127270d71a | ||
|
|
877c1450df | ||
|
|
30dc8e0b6b | ||
|
|
7d7e67a20a | ||
|
|
0a8a82e3b6 | ||
|
|
6eeffe93a9 | ||
|
|
7de2084bcf | ||
|
|
b61c73aac6 | ||
|
|
fc692b46d7 | ||
|
|
6e93d7060b | ||
|
|
8195364b36 | ||
|
|
034ff4ec9f | ||
|
|
27da40941d | ||
|
|
ed270c8646 | ||
|
|
debd8701cd | ||
|
|
d9e7cec281 | ||
|
|
742d196f35 | ||
|
|
31aeb56e3a | ||
|
|
48664bf514 | ||
|
|
7eaaf63791 | ||
|
|
63a0cc19fb | ||
|
|
53e740947d | ||
|
|
37000a261e | ||
|
|
425ecbbef7 | ||
|
|
81af11cc58 | ||
|
|
f5c93f2f1a | ||
|
|
286fb0437a | ||
|
|
809d2810c1 | ||
|
|
d50548ab1a | ||
|
|
c2a1829d4a | ||
|
|
93c129beb8 | ||
|
|
231870e1cc | ||
|
|
e650020f26 | ||
|
|
fdaaa558c9 | ||
|
|
f339c75ed3 | ||
|
|
23d594cea7 | ||
|
|
947fa3cfc8 | ||
|
|
6254673c91 | ||
|
|
72bf0910f6 | ||
|
|
2932ee576e | ||
|
|
eeb5e24bb1 | ||
|
|
6d883e31cd | ||
|
|
faf8947bb0 | ||
|
|
d2ab0f9941 | ||
|
|
4b4d711e07 | ||
|
|
b2bd97c16e | ||
|
|
9edebc58aa |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1405,6 +1405,7 @@ dependencies = [
|
||||
"async-tar",
|
||||
"collections",
|
||||
"crossbeam",
|
||||
"denoise",
|
||||
"gpui",
|
||||
"libwebrtc",
|
||||
"log",
|
||||
@@ -20747,7 +20748,6 @@ dependencies = [
|
||||
"nix 0.29.0",
|
||||
"nix 0.30.1",
|
||||
"nom 7.1.3",
|
||||
"num",
|
||||
"num-bigint",
|
||||
"num-bigint-dig",
|
||||
"num-complex",
|
||||
|
||||
@@ -782,6 +782,8 @@ codegen-units = 16
|
||||
codegen-units = 16
|
||||
|
||||
[profile.dev.package]
|
||||
# Without optimizations we get audio artifacts (crackling)
|
||||
audio = { opt-level = 3 }
|
||||
taffy = { opt-level = 3 }
|
||||
cranelift-codegen = { opt-level = 3 }
|
||||
cranelift-codegen-meta = { opt-level = 3 }
|
||||
|
||||
@@ -410,18 +410,36 @@
|
||||
},
|
||||
"audio": {
|
||||
// Opt into the new audio system.
|
||||
"experimental.rodio_audio": false,
|
||||
"experimental.rodio_audio": true,
|
||||
// Requires 'rodio_audio: true'
|
||||
//
|
||||
// Use the new audio systems automatic gain control for your microphone.
|
||||
// This affects how loud you sound to others.
|
||||
"experimental.control_input_volume": false,
|
||||
// Automatically increase or decrease you microphone's volume. This affects how
|
||||
// loud you sound to others.
|
||||
//
|
||||
// Recommended: off (default)
|
||||
// Microphones are too quite in zed, until everyone is on experimental
|
||||
// audio and has auto speaker volume on this will make you very loud
|
||||
// compared to other speakers.
|
||||
"experimental.auto_microphone_volume": false,
|
||||
// Requires 'rodio_audio: true'
|
||||
//
|
||||
// Use the new audio systems automatic gain control on everyone in the
|
||||
// call. This makes call members who are too quite louder and those who are
|
||||
// too loud quieter. This only affects how things sound for you.
|
||||
"experimental.control_output_volume": false
|
||||
// Automatically increate or decrease the volume of other call members.
|
||||
// This only affects how things sound for you.
|
||||
"experimental.auto_speaker_volume": true,
|
||||
// Requires 'rodio_audio: true'
|
||||
//
|
||||
// Remove background noises. Works great for typing, cars, dogs, AC. Does
|
||||
// not work well on music.
|
||||
"experimental.denoise": true,
|
||||
// Requires 'rodio_audio: true'
|
||||
//
|
||||
// Use audio parameters compatible with the previous versions of
|
||||
// experimental audio and non-experimental audio. When this is false you
|
||||
// will sound strange to anyone not on the latest experimental audio. In
|
||||
// the future we will migrate by setting this to false
|
||||
//
|
||||
// You need to rejoin a call for this setting to apply
|
||||
"experimental.legacy_audio_compatible": true
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
|
||||
@@ -17,6 +17,7 @@ anyhow.workspace = true
|
||||
async-tar.workspace = true
|
||||
collections.workspace = true
|
||||
crossbeam.workspace = true
|
||||
denoise = { path = "../denoise" }
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
@@ -9,7 +9,7 @@ mod non_windows_and_freebsd_deps {
|
||||
pub(super) use log::info;
|
||||
pub(super) use parking_lot::Mutex;
|
||||
pub(super) use rodio::cpal::Sample;
|
||||
pub(super) use rodio::source::{LimitSettings, UniformSourceIterator};
|
||||
pub(super) use rodio::source::LimitSettings;
|
||||
pub(super) use std::sync::Arc;
|
||||
}
|
||||
|
||||
@@ -31,18 +31,20 @@ pub use rodio_ext::RodioExt;
|
||||
|
||||
use crate::audio_settings::LIVE_SETTINGS;
|
||||
|
||||
// NOTE: We used to use WebRTC's mixer which only supported
|
||||
// 16kHz, 32kHz and 48kHz. As 48 is the most common "next step up"
|
||||
// for audio output devices like speakers/bluetooth, we just hard-code
|
||||
// this; and downsample when we need to.
|
||||
// We are migrating to 16kHz sample rate from 48kHz. In the future
|
||||
// once we are reasonably sure most users have upgraded we will
|
||||
// remove the LEGACY parameters.
|
||||
//
|
||||
// Since most noise cancelling requires 16kHz we will move to
|
||||
// that in the future.
|
||||
pub const SAMPLE_RATE: NonZero<u32> = nz!(48000);
|
||||
pub const CHANNEL_COUNT: NonZero<u16> = nz!(2);
|
||||
// We migrate to 16kHz because it is sufficient for speech and required
|
||||
// by the denoiser and future Speech to Text layers.
|
||||
pub const SAMPLE_RATE: NonZero<u32> = nz!(16000);
|
||||
pub const CHANNEL_COUNT: NonZero<u16> = nz!(1);
|
||||
pub const BUFFER_SIZE: usize = // echo canceller and livekit want 10ms of audio
|
||||
(SAMPLE_RATE.get() as usize / 100) * CHANNEL_COUNT.get() as usize;
|
||||
|
||||
pub const LEGACY_SAMPLE_RATE: NonZero<u32> = nz!(48000);
|
||||
pub const LEGACY_CHANNEL_COUNT: NonZero<u16> = nz!(2);
|
||||
|
||||
pub const REPLAY_DURATION: Duration = Duration::from_secs(30);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
@@ -106,6 +108,11 @@ impl Global for Audio {}
|
||||
|
||||
impl Audio {
|
||||
fn ensure_output_exists(&mut self) -> Result<&Mixer> {
|
||||
#[cfg(debug_assertions)]
|
||||
log::warn!(
|
||||
"Audio does not sound correct without optimizations. Use a release build to debug audio issues"
|
||||
);
|
||||
|
||||
if self.output_handle.is_none() {
|
||||
self.output_handle = Some(
|
||||
OutputStreamBuilder::open_default_stream()
|
||||
@@ -160,13 +167,20 @@ impl Audio {
|
||||
let stream = rodio::microphone::MicrophoneBuilder::new()
|
||||
.default_device()?
|
||||
.default_config()?
|
||||
.prefer_sample_rates([SAMPLE_RATE, SAMPLE_RATE.saturating_mul(nz!(2))])
|
||||
// .prefer_channel_counts([nz!(1), nz!(2)])
|
||||
.prefer_sample_rates([
|
||||
SAMPLE_RATE, // sample rates trivially resamplable to `SAMPLE_RATE`
|
||||
SAMPLE_RATE.saturating_mul(nz!(2)),
|
||||
SAMPLE_RATE.saturating_mul(nz!(3)),
|
||||
SAMPLE_RATE.saturating_mul(nz!(4)),
|
||||
])
|
||||
.prefer_channel_counts([nz!(2)])
|
||||
.prefer_buffer_sizes(512..)
|
||||
.open_stream()?;
|
||||
info!("Opened microphone: {:?}", stream.config());
|
||||
|
||||
let (replay, stream) = UniformSourceIterator::new(stream, CHANNEL_COUNT, SAMPLE_RATE)
|
||||
let (replay, stream) = stream
|
||||
.possibly_disconnected_channels_to_mono()
|
||||
.constant_samplerate(SAMPLE_RATE)
|
||||
.limit(LimitSettings::live_performance())
|
||||
.process_buffer::<BUFFER_SIZE, _>(move |buffer| {
|
||||
let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample());
|
||||
@@ -187,15 +201,24 @@ impl Audio {
|
||||
}
|
||||
}
|
||||
})
|
||||
.automatic_gain_control(1.0, 4.0, 0.0, 5.0)
|
||||
.denoise()
|
||||
.context("Could not set up denoiser")?
|
||||
.periodic_access(Duration::from_millis(100), move |denoise| {
|
||||
denoise.set_enabled(LIVE_SETTINGS.denoise.load(Ordering::Relaxed));
|
||||
})
|
||||
.automatic_gain_control(1.0, 2.0, 0.0, 5.0)
|
||||
.periodic_access(Duration::from_millis(100), move |agc_source| {
|
||||
agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed));
|
||||
agc_source
|
||||
.set_enabled(LIVE_SETTINGS.auto_microphone_volume.load(Ordering::Relaxed));
|
||||
})
|
||||
.replayable(REPLAY_DURATION)?;
|
||||
|
||||
voip_parts
|
||||
.replays
|
||||
.add_voip_stream("local microphone".to_string(), replay);
|
||||
|
||||
let stream = stream.constant_params(LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE);
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
@@ -206,9 +229,10 @@ impl Audio {
|
||||
cx: &mut App,
|
||||
) -> anyhow::Result<()> {
|
||||
let (replay_source, source) = source
|
||||
.automatic_gain_control(1.0, 4.0, 0.0, 5.0)
|
||||
.constant_params(CHANNEL_COUNT, SAMPLE_RATE)
|
||||
.automatic_gain_control(1.0, 2.0, 0.0, 5.0)
|
||||
.periodic_access(Duration::from_millis(100), move |agc_source| {
|
||||
agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed));
|
||||
agc_source.set_enabled(LIVE_SETTINGS.auto_speaker_volume.load(Ordering::Relaxed));
|
||||
})
|
||||
.replayable(REPLAY_DURATION)
|
||||
.expect("REPLAY_DURATION is longer than 100ms");
|
||||
|
||||
@@ -7,18 +7,38 @@ use util::MergeFrom as _;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AudioSettings {
|
||||
/// Opt into the new audio system.
|
||||
///
|
||||
/// You need to rejoin a call for this setting to apply
|
||||
pub rodio_audio: bool, // default is false
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control for your microphone.
|
||||
/// This affects how loud you sound to others.
|
||||
pub control_input_volume: bool,
|
||||
/// Automatically increase or decrease you microphone's volume. This affects how
|
||||
/// loud you sound to others.
|
||||
///
|
||||
/// Recommended: off (default)
|
||||
/// Microphones are too quite in zed, until everyone is on experimental
|
||||
/// audio and has auto speaker volume on this will make you very loud
|
||||
/// compared to other speakers.
|
||||
pub auto_microphone_volume: bool,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control on everyone in the
|
||||
/// call. This makes call members who are too quite louder and those who are
|
||||
/// too loud quieter. This only affects how things sound for you.
|
||||
pub control_output_volume: bool,
|
||||
/// Automatically increate or decrease the volume of other call members.
|
||||
/// This only affects how things sound for you.
|
||||
pub auto_speaker_volume: bool,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Remove background noises. Works great for typing, cars, dogs, AC. Does
|
||||
/// not work well on music.
|
||||
pub denoise: bool,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use audio parameters compatible with the previous versions of
|
||||
/// experimental audio and non-experimental audio. When this is false you
|
||||
/// will sound strange to anyone not on the latest experimental audio. In
|
||||
/// the future we will migrate by setting this to false
|
||||
///
|
||||
/// You need to rejoin a call for this setting to apply
|
||||
pub legacy_audio_compatible: bool,
|
||||
}
|
||||
|
||||
/// Configuration of audio in Zed
|
||||
@@ -26,9 +46,11 @@ impl Settings for AudioSettings {
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let audio = &content.audio.as_ref().unwrap();
|
||||
AudioSettings {
|
||||
control_input_volume: audio.control_input_volume.unwrap(),
|
||||
control_output_volume: audio.control_output_volume.unwrap(),
|
||||
rodio_audio: audio.rodio_audio.unwrap(),
|
||||
auto_microphone_volume: audio.auto_microphone_volume.unwrap(),
|
||||
auto_speaker_volume: audio.auto_speaker_volume.unwrap(),
|
||||
denoise: audio.denoise.unwrap(),
|
||||
legacy_audio_compatible: audio.legacy_audio_compatible.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,47 +58,68 @@ impl Settings for AudioSettings {
|
||||
let Some(audio) = content.audio.as_ref() else {
|
||||
return;
|
||||
};
|
||||
self.control_input_volume
|
||||
.merge_from(&audio.control_input_volume);
|
||||
self.control_output_volume
|
||||
.merge_from(&audio.control_output_volume);
|
||||
self.rodio_audio.merge_from(&audio.rodio_audio);
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
) {
|
||||
self.auto_microphone_volume
|
||||
.merge_from(&audio.auto_microphone_volume);
|
||||
self.auto_speaker_volume
|
||||
.merge_from(&audio.auto_speaker_volume);
|
||||
self.denoise.merge_from(&audio.denoise);
|
||||
self.legacy_audio_compatible
|
||||
.merge_from(&audio.legacy_audio_compatible);
|
||||
}
|
||||
}
|
||||
|
||||
/// See docs on [LIVE_SETTINGS]
|
||||
pub(crate) struct LiveSettings {
|
||||
pub(crate) control_input_volume: AtomicBool,
|
||||
pub(crate) control_output_volume: AtomicBool,
|
||||
pub(crate) auto_microphone_volume: AtomicBool,
|
||||
pub(crate) auto_speaker_volume: AtomicBool,
|
||||
pub(crate) denoise: AtomicBool,
|
||||
}
|
||||
|
||||
impl LiveSettings {
|
||||
pub(crate) fn initialize(&self, cx: &mut App) {
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
LIVE_SETTINGS.control_input_volume.store(
|
||||
AudioSettings::get_global(cx).control_input_volume,
|
||||
LIVE_SETTINGS.auto_microphone_volume.store(
|
||||
AudioSettings::get_global(cx).auto_microphone_volume,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
LIVE_SETTINGS.control_output_volume.store(
|
||||
AudioSettings::get_global(cx).control_output_volume,
|
||||
LIVE_SETTINGS.auto_speaker_volume.store(
|
||||
AudioSettings::get_global(cx).auto_speaker_volume,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
|
||||
let denoise_enabled = AudioSettings::get_global(cx).denoise;
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
static DENOISE_WARNING_SEND: AtomicBool = AtomicBool::new(false);
|
||||
if denoise_enabled && !DENOISE_WARNING_SEND.load(Ordering::Relaxed) {
|
||||
DENOISE_WARNING_SEND.store(true, Ordering::Relaxed);
|
||||
log::warn!("Denoise does not work on debug builds, not enabling")
|
||||
}
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
LIVE_SETTINGS
|
||||
.denoise
|
||||
.store(denoise_enabled, Ordering::Relaxed);
|
||||
})
|
||||
.detach();
|
||||
|
||||
let init_settings = AudioSettings::get_global(cx);
|
||||
LIVE_SETTINGS
|
||||
.control_input_volume
|
||||
.store(init_settings.control_input_volume, Ordering::Relaxed);
|
||||
.auto_microphone_volume
|
||||
.store(init_settings.auto_microphone_volume, Ordering::Relaxed);
|
||||
LIVE_SETTINGS
|
||||
.control_output_volume
|
||||
.store(init_settings.control_output_volume, Ordering::Relaxed);
|
||||
.auto_speaker_volume
|
||||
.store(init_settings.auto_speaker_volume, Ordering::Relaxed);
|
||||
let denoise_enabled = AudioSettings::get_global(cx).denoise;
|
||||
#[cfg(debug_assertions)]
|
||||
if denoise_enabled {
|
||||
log::warn!("Denoise does not work on debug builds, not enabling")
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
LIVE_SETTINGS
|
||||
.denoise
|
||||
.store(denoise_enabled, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +128,7 @@ impl LiveSettings {
|
||||
/// real time and must each run in a dedicated OS thread, therefore we can not
|
||||
/// use the background executor.
|
||||
pub(crate) static LIVE_SETTINGS: LiveSettings = LiveSettings {
|
||||
control_input_volume: AtomicBool::new(true),
|
||||
control_output_volume: AtomicBool::new(true),
|
||||
auto_microphone_volume: AtomicBool::new(true),
|
||||
auto_speaker_volume: AtomicBool::new(true),
|
||||
denoise: AtomicBool::new(true),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::{
|
||||
f32,
|
||||
num::NonZero,
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -7,12 +9,22 @@ use std::{
|
||||
};
|
||||
|
||||
use crossbeam::queue::ArrayQueue;
|
||||
use rodio::{ChannelCount, Sample, SampleRate, Source};
|
||||
use denoise::{Denoiser, DenoiserError};
|
||||
use log::warn;
|
||||
use rodio::{
|
||||
ChannelCount, Sample, SampleRate, Source, conversions::SampleRateConverter, nz,
|
||||
source::UniformSourceIterator,
|
||||
};
|
||||
|
||||
const MAX_CHANNELS: usize = 8;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Replay duration is too short must be >= 100ms")]
|
||||
pub struct ReplayDurationTooShort;
|
||||
|
||||
// These all require constant sources (so the span is infinitely long)
|
||||
// this is not guaranteed by rodio however we know it to be true in all our
|
||||
// applications. Rodio desperately needs a constant source concept.
|
||||
pub trait RodioExt: Source + Sized {
|
||||
fn process_buffer<const N: usize, F>(self, callback: F) -> ProcessBuffer<N, Self, F>
|
||||
where
|
||||
@@ -25,6 +37,14 @@ pub trait RodioExt: Source + Sized {
|
||||
duration: Duration,
|
||||
) -> Result<(Replay, Replayable<Self>), ReplayDurationTooShort>;
|
||||
fn take_samples(self, n: usize) -> TakeSamples<Self>;
|
||||
fn denoise(self) -> Result<Denoiser<Self>, DenoiserError>;
|
||||
fn constant_params(
|
||||
self,
|
||||
channel_count: ChannelCount,
|
||||
sample_rate: SampleRate,
|
||||
) -> UniformSourceIterator<Self>;
|
||||
fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate<Self>;
|
||||
fn possibly_disconnected_channels_to_mono(self) -> ToMono<Self>;
|
||||
}
|
||||
|
||||
impl<S: Source> RodioExt for S {
|
||||
@@ -101,8 +121,149 @@ impl<S: Source> RodioExt for S {
|
||||
left_to_take: n,
|
||||
}
|
||||
}
|
||||
fn denoise(self) -> Result<Denoiser<Self>, DenoiserError> {
|
||||
let res = Denoiser::try_new(self);
|
||||
res
|
||||
}
|
||||
fn constant_params(
|
||||
self,
|
||||
channel_count: ChannelCount,
|
||||
sample_rate: SampleRate,
|
||||
) -> UniformSourceIterator<Self> {
|
||||
UniformSourceIterator::new(self, channel_count, sample_rate)
|
||||
}
|
||||
fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate<Self> {
|
||||
ConstantSampleRate::new(self, sample_rate)
|
||||
}
|
||||
fn possibly_disconnected_channels_to_mono(self) -> ToMono<Self> {
|
||||
ToMono::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstantSampleRate<S: Source> {
|
||||
inner: SampleRateConverter<S>,
|
||||
channels: ChannelCount,
|
||||
sample_rate: SampleRate,
|
||||
}
|
||||
|
||||
impl<S: Source> ConstantSampleRate<S> {
|
||||
fn new(source: S, target_rate: SampleRate) -> Self {
|
||||
let input_sample_rate = source.sample_rate();
|
||||
let channels = source.channels();
|
||||
let inner = SampleRateConverter::new(source, input_sample_rate, target_rate, channels);
|
||||
Self {
|
||||
inner,
|
||||
channels,
|
||||
sample_rate: target_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Source> Iterator for ConstantSampleRate<S> {
|
||||
type Item = rodio::Sample;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Source> Source for ConstantSampleRate<S> {
|
||||
fn current_span_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
fn channels(&self) -> ChannelCount {
|
||||
self.channels
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> SampleRate {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None // not supported (not used by us)
|
||||
}
|
||||
}
|
||||
|
||||
const TYPICAL_NOISE_FLOOR: Sample = 1e-3;
|
||||
|
||||
/// constant source, only works on a single span
|
||||
pub struct ToMono<S> {
|
||||
inner: S,
|
||||
input_channel_count: ChannelCount,
|
||||
connected_channels: ChannelCount,
|
||||
/// running mean of second channel 'volume'
|
||||
means: [f32; MAX_CHANNELS],
|
||||
}
|
||||
impl<S: Source> ToMono<S> {
|
||||
fn new(input: S) -> Self {
|
||||
let channels = input
|
||||
.channels()
|
||||
.min(const { NonZero::<u16>::new(MAX_CHANNELS as u16).unwrap() });
|
||||
if channels < input.channels() {
|
||||
warn!("Ignoring input channels {}..", channels.get());
|
||||
}
|
||||
|
||||
Self {
|
||||
connected_channels: channels,
|
||||
input_channel_count: channels,
|
||||
inner: input,
|
||||
means: [TYPICAL_NOISE_FLOOR; MAX_CHANNELS],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Source> Source for ToMono<S> {
|
||||
fn current_span_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
fn channels(&self) -> ChannelCount {
|
||||
rodio::nz!(1)
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> SampleRate {
|
||||
self.inner.sample_rate()
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
self.inner.total_duration()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_mean(mean: &mut f32, sample: Sample) {
|
||||
const HISTORY: f32 = 500.0;
|
||||
*mean *= (HISTORY - 1.0) / HISTORY;
|
||||
*mean += sample.abs() / HISTORY;
|
||||
}
|
||||
|
||||
impl<S: Source> Iterator for ToMono<S> {
|
||||
type Item = Sample;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut mono_sample = 0f32;
|
||||
let mut active_channels = 0;
|
||||
for channel in 0..self.input_channel_count.get() as usize {
|
||||
let sample = self.inner.next()?;
|
||||
mono_sample += sample;
|
||||
|
||||
update_mean(&mut self.means[channel], sample);
|
||||
if self.means[channel] > TYPICAL_NOISE_FLOOR / 10.0 {
|
||||
active_channels += 1;
|
||||
}
|
||||
}
|
||||
mono_sample /= self.connected_channels.get() as f32;
|
||||
self.connected_channels = NonZero::new(active_channels).unwrap_or(nz!(1));
|
||||
|
||||
Some(mono_sample)
|
||||
}
|
||||
}
|
||||
|
||||
/// constant source, only works on a single span
|
||||
pub struct TakeSamples<S> {
|
||||
inner: S,
|
||||
left_to_take: usize,
|
||||
@@ -147,6 +308,7 @@ impl<S: Source> Source for TakeSamples<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// constant source, only works on a single span
|
||||
#[derive(Debug)]
|
||||
struct ReplayQueue {
|
||||
inner: ArrayQueue<Vec<Sample>>,
|
||||
@@ -193,6 +355,7 @@ impl ReplayQueue {
|
||||
}
|
||||
}
|
||||
|
||||
/// constant source, only works on a single span
|
||||
pub struct ProcessBuffer<const N: usize, S, F>
|
||||
where
|
||||
S: Source + Sized,
|
||||
@@ -260,6 +423,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// constant source, only works on a single span
|
||||
pub struct InspectBuffer<const N: usize, S, F>
|
||||
where
|
||||
S: Source + Sized,
|
||||
@@ -324,6 +488,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// constant source, only works on a single span
|
||||
#[derive(Debug)]
|
||||
pub struct Replayable<S: Source> {
|
||||
inner: S,
|
||||
@@ -375,6 +540,7 @@ impl<S: Source> Source for Replayable<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// constant source, only works on a single span
|
||||
#[derive(Debug)]
|
||||
pub struct Replay {
|
||||
rx: Arc<ReplayQueue>,
|
||||
|
||||
@@ -138,13 +138,13 @@ impl Engine {
|
||||
|
||||
const SPECTRUM_INPUT: &str = "input_2";
|
||||
const MEMORY_INPUT: &str = "input_3";
|
||||
let memory_input =
|
||||
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([
|
||||
(MEMORY_INPUT.to_string(), memory_input),
|
||||
(SPECTRUM_INPUT.to_string(), self.spectral_memory.clone()),
|
||||
(SPECTRUM_INPUT.to_string(), spectrum),
|
||||
(MEMORY_INPUT.to_string(), self.spectral_memory.clone()),
|
||||
]);
|
||||
inputs
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ impl<S: Source> Denoiser<S> {
|
||||
.spawn(move || {
|
||||
run_neural_denoiser(denoised_tx, input_rx);
|
||||
})
|
||||
.unwrap();
|
||||
.expect("Should be a able to spawn threads");
|
||||
|
||||
Ok(Self {
|
||||
inner: source,
|
||||
@@ -264,6 +264,7 @@ impl<S: Source> Denoiser<S> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_sub_block(s: &mut impl Source) -> Option<[f32; BLOCK_SHIFT]> {
|
||||
let mut res = [0f32; BLOCK_SHIFT];
|
||||
for sample in &mut res {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use audio::AudioSettings;
|
||||
use collections::HashMap;
|
||||
use futures::{SinkExt, channel::mpsc};
|
||||
@@ -12,7 +12,10 @@ use settings::Settings;
|
||||
|
||||
mod playback;
|
||||
|
||||
use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication};
|
||||
use crate::{
|
||||
LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication,
|
||||
livekit_client::playback::Speaker,
|
||||
};
|
||||
pub use playback::AudioStream;
|
||||
pub(crate) use playback::{RemoteVideoFrame, play_remote_video_track};
|
||||
|
||||
@@ -132,11 +135,20 @@ impl Room {
|
||||
track: &RemoteAudioTrack,
|
||||
cx: &mut App,
|
||||
) -> Result<playback::AudioStream> {
|
||||
let speaker: Speaker =
|
||||
serde_urlencoded::from_str(&track.0.name()).unwrap_or_else(|_| Speaker {
|
||||
name: track.0.name(),
|
||||
is_staff: false,
|
||||
legacy_audio_compatible: true,
|
||||
});
|
||||
|
||||
if AudioSettings::get_global(cx).rodio_audio {
|
||||
info!("Using experimental.rodio_audio audio pipeline for output");
|
||||
playback::play_remote_audio_track(&track.0, cx)
|
||||
} else {
|
||||
playback::play_remote_audio_track(&track.0, speaker, cx)
|
||||
} else if speaker.legacy_audio_compatible {
|
||||
Ok(self.playback.play_remote_audio_track(&track.0))
|
||||
} else {
|
||||
Err(anyhow!("Client version too old to play audio in call"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use audio::{AudioSettings, CHANNEL_COUNT, SAMPLE_RATE};
|
||||
use audio::{AudioSettings, CHANNEL_COUNT, LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE, SAMPLE_RATE};
|
||||
use cpal::traits::{DeviceTrait, StreamTrait as _};
|
||||
use futures::channel::mpsc::UnboundedSender;
|
||||
use futures::{Stream, StreamExt as _};
|
||||
@@ -43,12 +43,17 @@ pub(crate) struct AudioStack {
|
||||
|
||||
pub(crate) fn play_remote_audio_track(
|
||||
track: &livekit::track::RemoteAudioTrack,
|
||||
speaker: Speaker,
|
||||
cx: &mut gpui::App,
|
||||
) -> Result<AudioStream> {
|
||||
let stream = source::LiveKitStream::new(
|
||||
cx.background_executor(),
|
||||
track,
|
||||
speaker.legacy_audio_compatible,
|
||||
);
|
||||
|
||||
let stop_handle = Arc::new(AtomicBool::new(false));
|
||||
let stop_handle_clone = stop_handle.clone();
|
||||
let stream = source::LiveKitStream::new(cx.background_executor(), track);
|
||||
|
||||
let stream = stream
|
||||
.stoppable()
|
||||
.periodic_access(Duration::from_millis(50), move |s| {
|
||||
@@ -57,10 +62,6 @@ pub(crate) fn play_remote_audio_track(
|
||||
}
|
||||
});
|
||||
|
||||
let speaker: Speaker = serde_urlencoded::from_str(&track.name()).unwrap_or_else(|_| Speaker {
|
||||
name: track.name(),
|
||||
is_staff: false,
|
||||
});
|
||||
audio::Audio::play_voip_stream(stream, speaker.name, speaker.is_staff, cx)
|
||||
.context("Could not play audio")?;
|
||||
|
||||
@@ -152,17 +153,32 @@ impl AudioStack {
|
||||
is_staff: bool,
|
||||
cx: &AsyncApp,
|
||||
) -> Result<(crate::LocalAudioTrack, AudioStream)> {
|
||||
let source = NativeAudioSource::new(
|
||||
// n.b. this struct's options are always ignored, noise cancellation is provided by apm.
|
||||
AudioSourceOptions::default(),
|
||||
SAMPLE_RATE.get(),
|
||||
CHANNEL_COUNT.get().into(),
|
||||
10,
|
||||
);
|
||||
let legacy_audio_compatible =
|
||||
AudioSettings::try_read_global(cx, |setting| setting.legacy_audio_compatible)
|
||||
.unwrap_or_default();
|
||||
|
||||
let source = if legacy_audio_compatible {
|
||||
NativeAudioSource::new(
|
||||
// n.b. this struct's options are always ignored, noise cancellation is provided by apm.
|
||||
AudioSourceOptions::default(),
|
||||
LEGACY_SAMPLE_RATE.get(),
|
||||
LEGACY_CHANNEL_COUNT.get().into(),
|
||||
10,
|
||||
)
|
||||
} else {
|
||||
NativeAudioSource::new(
|
||||
// n.b. this struct's options are always ignored, noise cancellation is provided by apm.
|
||||
AudioSourceOptions::default(),
|
||||
SAMPLE_RATE.get(),
|
||||
CHANNEL_COUNT.get().into(),
|
||||
10,
|
||||
)
|
||||
};
|
||||
|
||||
let track_name = serde_urlencoded::to_string(Speaker {
|
||||
name: user_name,
|
||||
is_staff,
|
||||
legacy_audio_compatible,
|
||||
})
|
||||
.context("Could not encode user information in track name")?;
|
||||
|
||||
@@ -186,22 +202,32 @@ impl AudioStack {
|
||||
let capture_task = if rodio_pipeline {
|
||||
info!("Using experimental.rodio_audio audio pipeline");
|
||||
let voip_parts = audio::VoipParts::new(cx)?;
|
||||
// Audio needs to run real-time and should never be paused. That is why we are using a
|
||||
// normal std::thread and not a background task
|
||||
// Audio needs to run real-time and should never be paused. That is
|
||||
// why we are using a normal std::thread and not a background task
|
||||
thread::Builder::new()
|
||||
.name("AudioCapture".to_string())
|
||||
.name("MicrophoneToLivekit".to_string())
|
||||
.spawn(move || {
|
||||
// microphone is non send on mac
|
||||
let microphone = audio::Audio::open_microphone(voip_parts)?;
|
||||
let microphone = match audio::Audio::open_microphone(voip_parts) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
log::error!("Could not open microphone: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
send_to_livekit(frame_tx, microphone);
|
||||
Ok::<(), anyhow::Error>(())
|
||||
})
|
||||
.unwrap();
|
||||
.expect("should be able to spawn threads");
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
self.executor.spawn(async move {
|
||||
Self::capture_input(apm, frame_tx, SAMPLE_RATE.get(), CHANNEL_COUNT.get().into())
|
||||
.await
|
||||
Self::capture_input(
|
||||
apm,
|
||||
frame_tx,
|
||||
LEGACY_SAMPLE_RATE.get(),
|
||||
LEGACY_CHANNEL_COUNT.get().into(),
|
||||
)
|
||||
.await
|
||||
})
|
||||
};
|
||||
|
||||
@@ -389,25 +415,30 @@ impl AudioStack {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Speaker {
|
||||
name: String,
|
||||
is_staff: bool,
|
||||
pub struct Speaker {
|
||||
pub name: String,
|
||||
pub is_staff: bool,
|
||||
pub legacy_audio_compatible: bool,
|
||||
}
|
||||
|
||||
fn send_to_livekit(frame_tx: UnboundedSender<AudioFrame<'static>>, mut microphone: impl Source) {
|
||||
use cpal::Sample;
|
||||
let sample_rate = microphone.sample_rate().get();
|
||||
let num_channels = microphone.channels().get() as u32;
|
||||
let buffer_size = sample_rate / 100 * num_channels;
|
||||
|
||||
loop {
|
||||
let sampled: Vec<_> = microphone
|
||||
.by_ref()
|
||||
.take(audio::BUFFER_SIZE)
|
||||
.take(buffer_size as usize)
|
||||
.map(|s| s.to_sample())
|
||||
.collect();
|
||||
|
||||
if frame_tx
|
||||
.unbounded_send(AudioFrame {
|
||||
sample_rate: SAMPLE_RATE.get(),
|
||||
num_channels: CHANNEL_COUNT.get() as u32,
|
||||
samples_per_channel: sampled.len() as u32 / CHANNEL_COUNT.get() as u32,
|
||||
sample_rate,
|
||||
num_channels,
|
||||
samples_per_channel: sampled.len() as u32 / num_channels,
|
||||
data: Cow::Owned(sampled),
|
||||
})
|
||||
.is_err()
|
||||
|
||||
@@ -3,17 +3,19 @@ use std::num::NonZero;
|
||||
use futures::StreamExt;
|
||||
use libwebrtc::{audio_stream::native::NativeAudioStream, prelude::AudioFrame};
|
||||
use livekit::track::RemoteAudioTrack;
|
||||
use rodio::{Source, buffer::SamplesBuffer, conversions::SampleTypeConverter, nz};
|
||||
use rodio::{
|
||||
ChannelCount, SampleRate, Source, buffer::SamplesBuffer, conversions::SampleTypeConverter,
|
||||
};
|
||||
|
||||
use audio::{CHANNEL_COUNT, SAMPLE_RATE};
|
||||
use audio::{CHANNEL_COUNT, LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE, SAMPLE_RATE};
|
||||
|
||||
fn frame_to_samplesbuffer(frame: AudioFrame) -> SamplesBuffer {
|
||||
let samples = frame.data.iter().copied();
|
||||
let samples = SampleTypeConverter::<_, _>::new(samples);
|
||||
let samples: Vec<f32> = samples.collect();
|
||||
SamplesBuffer::new(
|
||||
nz!(2), // frame always has two channels
|
||||
NonZero::new(frame.sample_rate).expect("audio frame sample rate is nonzero"),
|
||||
NonZero::new(frame.num_channels as u16).expect("zero channels is nonsense"),
|
||||
NonZero::new(frame.sample_rate).expect("samplerate zero is nonsense"),
|
||||
samples,
|
||||
)
|
||||
}
|
||||
@@ -22,14 +24,26 @@ pub struct LiveKitStream {
|
||||
// shared_buffer: SharedBuffer,
|
||||
inner: rodio::queue::SourcesQueueOutput,
|
||||
_receiver_task: gpui::Task<()>,
|
||||
channel_count: ChannelCount,
|
||||
sample_rate: SampleRate,
|
||||
}
|
||||
|
||||
impl LiveKitStream {
|
||||
pub fn new(executor: &gpui::BackgroundExecutor, track: &RemoteAudioTrack) -> Self {
|
||||
pub fn new(
|
||||
executor: &gpui::BackgroundExecutor,
|
||||
track: &RemoteAudioTrack,
|
||||
legacy: bool,
|
||||
) -> Self {
|
||||
let (channel_count, sample_rate) = if legacy {
|
||||
(LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RATE)
|
||||
} else {
|
||||
(CHANNEL_COUNT, SAMPLE_RATE)
|
||||
};
|
||||
|
||||
let mut stream = NativeAudioStream::new(
|
||||
track.rtc_track(),
|
||||
SAMPLE_RATE.get() as i32,
|
||||
CHANNEL_COUNT.get().into(),
|
||||
sample_rate.get() as i32,
|
||||
channel_count.get().into(),
|
||||
);
|
||||
let (queue_input, queue_output) = rodio::queue::queue(true);
|
||||
// spawn rtc stream
|
||||
@@ -45,6 +59,8 @@ impl LiveKitStream {
|
||||
LiveKitStream {
|
||||
_receiver_task: receiver_task,
|
||||
inner: queue_output,
|
||||
sample_rate,
|
||||
channel_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,17 +79,11 @@ impl Source for LiveKitStream {
|
||||
}
|
||||
|
||||
fn channels(&self) -> rodio::ChannelCount {
|
||||
// This must be hardcoded because the playback source assumes constant
|
||||
// sample rate and channel count. The queue upon which this is build
|
||||
// will however report different counts and rates. Even though we put in
|
||||
// only items with our (constant) CHANNEL_COUNT & SAMPLE_RATE this will
|
||||
// play silence on one channel and at 44100 which is not what our
|
||||
// constants are.
|
||||
CHANNEL_COUNT
|
||||
self.channel_count
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> rodio::SampleRate {
|
||||
SAMPLE_RATE // see comment on channels
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<std::time::Duration> {
|
||||
|
||||
@@ -288,21 +288,43 @@ pub enum TitleBarVisibility {
|
||||
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct AudioSettingsContent {
|
||||
/// Opt into the new audio system.
|
||||
#[serde(rename = "experimental.rodio_audio", default)]
|
||||
pub rodio_audio: Option<bool>,
|
||||
///
|
||||
/// You need to rejoin a call for this setting to apply
|
||||
#[serde(rename = "experimental.rodio_audio")]
|
||||
pub rodio_audio: Option<bool>, // default is false
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control for your microphone.
|
||||
/// This affects how loud you sound to others.
|
||||
#[serde(rename = "experimental.control_input_volume", default)]
|
||||
pub control_input_volume: Option<bool>,
|
||||
/// Automatically increase or decrease you microphone's volume. This affects how
|
||||
/// loud you sound to others.
|
||||
///
|
||||
/// Recommended: off (default)
|
||||
/// Microphones are too quite in zed, until everyone is on experimental
|
||||
/// audio and has auto speaker volume on this will make you very loud
|
||||
/// compared to other speakers.
|
||||
#[serde(rename = "experimental.auto_microphone_volume")]
|
||||
pub auto_microphone_volume: Option<bool>,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control on everyone in the
|
||||
/// call. This makes call members who are too quite louder and those who are
|
||||
/// too loud quieter. This only affects how things sound for you.
|
||||
#[serde(rename = "experimental.control_output_volume", default)]
|
||||
pub control_output_volume: Option<bool>,
|
||||
/// Automatically increate or decrease the volume of other call members.
|
||||
/// This only affects how things sound for you.
|
||||
#[serde(rename = "experimental.auto_speaker_volume")]
|
||||
pub auto_speaker_volume: Option<bool>,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Remove background noises. Works great for typing, cars, dogs, AC. Does
|
||||
/// not work well on music.
|
||||
#[serde(rename = "experimental.denoise")]
|
||||
pub denoise: Option<bool>,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use audio parameters compatible with the previous versions of
|
||||
/// experimental audio and non-experimental audio. When this is false you
|
||||
/// will sound strange to anyone not on the latest experimental audio. In
|
||||
/// the future we will migrate by setting this to false
|
||||
///
|
||||
/// You need to rejoin a call for this setting to apply
|
||||
#[serde(rename = "experimental.legacy_audio_compatible")]
|
||||
pub legacy_audio_compatible: Option<bool>,
|
||||
}
|
||||
|
||||
/// Control what info is collected by Zed.
|
||||
|
||||
@@ -802,7 +802,7 @@ impl SettingsStore {
|
||||
pub fn local_settings(
|
||||
&self,
|
||||
root_id: WorktreeId,
|
||||
) -> impl '_ + Iterator<Item = (Arc<Path>, &ProjectSettingsContent)> {
|
||||
) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
|
||||
self.local_settings
|
||||
.range(
|
||||
(root_id, Path::new("").into())
|
||||
@@ -811,7 +811,7 @@ impl SettingsStore {
|
||||
Path::new("").into(),
|
||||
),
|
||||
)
|
||||
.map(|((_, path), content)| (path.clone(), &content.project))
|
||||
.map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
|
||||
}
|
||||
|
||||
pub fn local_editorconfig_settings(
|
||||
|
||||
@@ -90,7 +90,6 @@ mime_guess = { version = "2" }
|
||||
miniz_oxide = { version = "0.8", features = ["simd"] }
|
||||
nom = { version = "7" }
|
||||
num-bigint = { version = "0.4" }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
num-integer = { version = "0.1", features = ["i128"] }
|
||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
||||
num-rational = { version = "0.4", features = ["num-bigint-std"] }
|
||||
@@ -229,7 +228,6 @@ mime_guess = { version = "2" }
|
||||
miniz_oxide = { version = "0.8", features = ["simd"] }
|
||||
nom = { version = "7" }
|
||||
num-bigint = { version = "0.4" }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
num-integer = { version = "0.1", features = ["i128"] }
|
||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
||||
num-rational = { version = "0.4", features = ["num-bigint-std"] }
|
||||
@@ -308,7 +306,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1"
|
||||
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
|
||||
naga = { version = "25", features = ["msl-out", "wgsl-in"] }
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
num = { version = "0.4" }
|
||||
objc2 = { version = "0.6" }
|
||||
objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] }
|
||||
@@ -338,7 +335,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1"
|
||||
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
|
||||
naga = { version = "25", features = ["msl-out", "wgsl-in"] }
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
num = { version = "0.4" }
|
||||
objc2 = { version = "0.6" }
|
||||
objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] }
|
||||
@@ -369,7 +365,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1"
|
||||
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
|
||||
naga = { version = "25", features = ["msl-out", "wgsl-in"] }
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
num = { version = "0.4" }
|
||||
objc2 = { version = "0.6" }
|
||||
objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] }
|
||||
@@ -399,7 +394,6 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1"
|
||||
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
|
||||
naga = { version = "25", features = ["msl-out", "wgsl-in"] }
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
num = { version = "0.4" }
|
||||
objc2 = { version = "0.6" }
|
||||
objc2-core-foundation = { version = "0.3", default-features = false, features = ["CFArray", "CFCGTypes", "CFData", "CFDate", "CFDictionary", "CFRunLoop", "CFString", "CFURL", "objc2", "std"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["NSArray", "NSAttributedString", "NSBundle", "NSCoder", "NSData", "NSDate", "NSDictionary", "NSEnumerator", "NSError", "NSGeometry", "NSNotification", "NSNull", "NSObjCRuntime", "NSObject", "NSProcessInfo", "NSRange", "NSRunLoop", "NSString", "NSURL", "NSUndoManager", "NSValue", "objc2-core-foundation", "std"] }
|
||||
@@ -442,6 +436,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] }
|
||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
@@ -483,6 +478,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] }
|
||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
@@ -522,6 +518,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] }
|
||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
@@ -563,6 +560,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] }
|
||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
@@ -587,7 +585,6 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f
|
||||
getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] }
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
|
||||
num = { version = "0.4" }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "fs", "net"] }
|
||||
@@ -613,7 +610,6 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f
|
||||
getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] }
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" }
|
||||
num = { version = "0.4" }
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
@@ -655,6 +651,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] }
|
||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
@@ -696,6 +693,7 @@ nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "m
|
||||
nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] }
|
||||
nix-fa1f6196edfd7249 = { package = "nix", version = "0.30", features = ["fs", "socket", "uio", "user"] }
|
||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||
num-complex = { version = "0.4", features = ["bytemuck"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
prost-5ef9efb8ec2df382 = { package = "prost", version = "0.12", features = ["prost-derive"] }
|
||||
|
||||
Reference in New Issue
Block a user