Compare commits

...

9 Commits

6 changed files with 364 additions and 955 deletions

819
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,19 @@ serde_json = "1.0.146"
[dependencies.bevy] [dependencies.bevy]
version = "0.17.3" version = "0.17.3"
default-features = false
features = [ features = [
"bevy_winit", "bevy_log",
"bevy_window",
"bevy_winit",
"bevy_render",
"bevy_core_pipeline",
"bevy_sprite",
"bevy_text",
"bevy_ui",
"bevy_asset",
"bevy_picking",
"multi_threaded",
] ]
[profile.dev.package."*"] [profile.dev.package."*"]
@@ -22,5 +33,20 @@ opt-level = 2
debug = false debug = false
[target.'cfg(target_os = "linux")'.dependencies.bevy] [target.'cfg(target_os = "linux")'.dependencies.bevy]
default-features = false
version = "0.17.3" version = "0.17.3"
features = ["wayland", "x11", "bevy_winit"] features = [
"bevy_log",
"bevy_window",
"bevy_winit",
"bevy_render",
"bevy_core_pipeline",
"bevy_sprite",
"bevy_text",
"bevy_ui",
"multi_threaded",
"bevy_asset",
"bevy_picking",
"wayland",
"x11"
]

View File

@@ -1,4 +1,5 @@
cargo run cargo run
pactl list modules short | grep "module-loopback" | cut -f1 | xargs -L1 pactl unload-module
pactl list modules short | grep "Virtual_Microphone" | cut -f1 | xargs -L1 pactl unload-module pactl list modules short | grep "Virtual_Microphone" | cut -f1 | xargs -L1 pactl unload-module
pactl list modules short | grep "Virtual_Mic_Source" | cut -f1 | xargs -L1 pactl unload-module pactl list modules short | grep "Virtual_Mic_Source" | cut -f1 | xargs -L1 pactl unload-module

122
src/linux_lib.rs Normal file
View File

@@ -0,0 +1,122 @@
use std::process::Command;
use serde_json::Value;
use rodio::{OutputStream, OutputStreamBuilder, cpal::{self, traits::HostTrait}};
fn pactl_list(sink_type: &str) -> Value {
let command_output = Command::new("pactl")
.args(&["-f", "json", "list", sink_type])
.output()
.expect("Failed to execute process");
if command_output.status.success() {
serde_json::from_str(str::from_utf8(&command_output.stdout).expect("Failed to convert to string")).expect("Failed to parse sink JSON output")
}
else {
Value::Null{}
}
}
pub fn get_sink_by_index(sink_type: &str, index: String) -> Value {
let sinks = pactl_list(sink_type);
for sink in sinks.as_array().unwrap_or(&vec![]) {
if sink["index"].as_u64().expect("sink index is not a number").to_string() == index {
return sink.clone();
}
}
return Value::Null{};
}
fn find_soundboard_sinks() -> Vec<Value> {
let sink_inputs = pactl_list("sink-inputs");
sink_inputs.as_array()
.unwrap_or(&vec![])
.iter()
.filter(|sink| {sink["properties"]["node.name"] == "alsa_playback.soundboard"})
.cloned()
.collect()
}
pub fn move_playback_to_sink() {
let soundboard_sinks = find_soundboard_sinks();
for sink in soundboard_sinks {
let index = sink["index"].as_u64().expect("sink index is not a number").to_string();
Command::new("pactl")
.args(&["move-sink-input", index.as_str(), "VirtualMic"]) // as_str is needed here as you cannot instantly dereference a growing String (Rust...)
.output()
.expect("Failed to execute process");
}
}
pub fn list_outputs() -> Vec<(String, String)> {
let source_outputs = pactl_list("source-outputs");
return source_outputs.as_array().unwrap_or(&vec![]).iter().filter_map(|sink| {
let app_name = sink["properties"]["application.name"].as_str()?;
let binary = sink["properties"]["application.process.binary"].as_str().unwrap_or("Unknown");
let index = sink["index"].as_u64().expect("sink index is not a number").to_string();
Some((format!("{} ({})", app_name, binary), index))
}).collect();
}
pub fn move_index_to_virtualmic(index: String) {
Command::new("pactl")
.args(&["move-source-output", index.as_str(), "VirtualMicSource"]) // as_str is needed here as you cannot instantly dereference a growing String (Rust...)
.output()
.expect("Failed to execute process");
}
pub fn create_virtual_mic_linux() -> OutputStream {
Command::new("pactl")
.args(&["load-module", "module-null-sink", "sink_name=VirtualMic", "sink_properties=device.description=\"Virtual_Microphone\""])
.output()
.expect("Failed to create VirtualMic");
Command::new("pactl")
.args(&["load-module", "module-remap-source", "master=VirtualMic.monitor", "source_name=VirtualMicSource", "source_properties=device.description=\"Virtual_Mic_Source\""])
.output()
.expect("Failed to create VirtualMicSource");
Command::new("pactl")
.args(&["load-module", "module-loopback", "source=VirtualMic.monitor", "sink=@DEFAULT_SINK@", "latency_msec=1"])
.output()
.expect("Failed to create loopback");
Command::new("pactl")
.args(&["set-sink-volume", "VirtualMic", "100%"])
.output()
.expect("Failed to set volume");
let host = cpal::host_from_id(cpal::HostId::Alsa).expect("Could not initialize ALSA");
let device = host.default_output_device().expect("Could not get default output device");
let stream = OutputStreamBuilder::from_device(device)
.expect("Unable to open VirtualMic")
.open_stream()
.expect("Failed to open stream");
move_playback_to_sink();
return stream;
}
pub fn reload_sound() {
let script = r#"
pactl list modules short | grep "module-loopback" | cut -f1 | xargs -L1 pactl unload-module
pactl list modules short | grep "Virtual_Microphone" | cut -f1 | xargs -L1 pactl unload-module
pactl list modules short | grep "Virtual_Mic_Source" | cut -f1 | xargs -L1 pactl unload-module
"#;
let output = Command::new("sh")
.arg("-c")
.arg(script)
.output()
.expect("Failed to execute process");
if output.status.success() {
println!("Modules unloaded successfully.");
} else {
println!("Error: {}", String::from_utf8_lossy(&output.stderr));
}
}

View File

@@ -1,19 +1,23 @@
use bevy::{ use bevy::{log::Level, prelude::*};
log::{Level, LogPlugin},
prelude::*,
};
use std::{collections::HashMap, fs::File, io::BufReader, path::Path, process::Command}; use std::{collections::HashMap, fs::File, io::BufReader, path::Path};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use bevy_egui::{ use bevy_egui::{EguiContextSettings, EguiContexts, EguiPrimaryContextPass, EguiStartupSet, egui};
EguiContextSettings, EguiContexts, EguiPlugin, EguiPrimaryContextPass, EguiStartupSet, egui,
use egui::ecolor::Color32;
#[cfg(target_os = "linux")]
mod linux_lib;
#[cfg(target_os = "windows")]
mod windows_lib;
use rodio::{
Decoder, OutputStream, Sink, Source, cpal::{self, traits::HostTrait}, OutputStreamBuilder
}; };
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink, Source, cpal::{self, Device, Host, traits::HostTrait, traits::DeviceTrait}};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct JSONData { struct JSONData {
tabs: Vec<String>, tabs: Vec<String>,
@@ -23,14 +27,16 @@ struct JSONData {
struct PlayingSound { struct PlayingSound {
file_path: String, file_path: String,
length: f32, length: f32,
virtual_sink: Sink, sink: Sink,
// normal_sink: Sink #[cfg(target_os = "windows")]
normal_sink: Sink
} }
struct SoundSystem { struct SoundSystem {
virtual_mic_stream: OutputStream, #[cfg(target_os = "windows")]
// normal_output_stream: OutputStream, normal_output_stream: OutputStream,
paused: bool output_stream: OutputStream,
paused: bool,
} }
#[derive(Resource)] #[derive(Resource)]
@@ -39,113 +45,71 @@ struct AppState {
json_data: JSONData, json_data: JSONData,
current_directory: String, current_directory: String,
currently_playing: Vec<PlayingSound>, currently_playing: Vec<PlayingSound>,
sound_system: SoundSystem sound_system: SoundSystem,
virt_outputs: Vec<(String, String)>,
virt_output_index_switch: String,
virt_output_index: String,
} }
const ALLOWED_FILE_EXTENSIONS: [&str; 4] = ["mp3", "wav", "flac", "ogg"]; const ALLOWED_FILE_EXTENSIONS: [&str; 4] = ["mp3", "wav", "flac", "ogg"];
fn move_playback_to_sink() { fn create_virtual_mic() -> SoundSystem {
let command_output = Command::new("pactl")
.args(&["-f", "json", "list", "sink-inputs"])
.output()
.expect("Failed to execute process");
if command_output.status.success() {
let sink_json: Value = serde_json::from_str(str::from_utf8(&command_output.stdout).expect("Failed to convert to string")).expect("Failed to parse sink JSON output");
for device in sink_json.as_array().unwrap_or(&vec![]) {
if device["properties"]["node.name"] == "alsa_playback.soundboard" {
let index = device["index"].as_u64().expect("Device index is not a number").to_string();
Command::new("pactl")
.args(&["move-sink-input", index.as_str(), "VirtualMic"]) // as_str is needed here as you cannot instantly dereference a growing String (Rust...)
.output()
.expect("Failed to execute process");
}
}
}
}
fn create_virtual_mic() -> OutputStream {
let host: Host;
// let original_host: Host;
// let normal_output: Device;
let virtual_mic: Device;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
host = cpal::host_from_id(cpal::HostId::Wasapi).expect("Could not initialize audio routing using WasAPI"); let (normal, virtual_mic) = windows_lib::create_virtual_mic_windows();
virtual_mic = host.output_devices().expect("Could not list Output devices").find(|device| { return SoundSystem {
device.name().ok().map(|name|{ output_stream: virtual_mic,
name.contains("CABLE Input") || name.contains("VB-Audio") normal_output_stream: normal,
}).unwrap_or(false) paused: false,
}).expect("Could not get default output device"); };
// normal_output = host.default_output_device().expect("Could not get default output device");
return OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream");
// return (OutputStreamBuilder::from_device(normal_output).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"), OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"));
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
// original_host = cpal::host_from_id(cpal::HostId::Alsa).expect("Could not initialize audio routing using ALSA"); return SoundSystem {
// normal_output = original_host.default_output_device().expect("Could not get default output device"); output_stream: linux_lib::create_virtual_mic_linux(),
paused: false,
Command::new("pactl") };
.args(&["load-module", "module-null-sink", "sink_name=VirtualMic", "sink_properties=device.description=\"Virtual_Microphone\""])
.output()
.expect("Failed to execute process");
Command::new("pactl")
.args(&["load-module", "module-remap-source", "master=VirtualMic.monitor", "source_name=VirtualMicSource", "source_properties=device.description=\"Virtual_Mic_Source\""])
.output()
.expect("Failed to execute process");
host = cpal::host_from_id(cpal::HostId::Alsa).expect("Could not initialize audio routing using ALSA"); // Alsa needed so pulse default works
virtual_mic = host.default_output_device().expect("Could not get default output device");
let virtual_mic_stream = OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream");
move_playback_to_sink();
return virtual_mic_stream;
// return (OutputStreamBuilder::from_device(normal_output).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"), OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"));
}
#[allow(unreachable_code)] {
println!("Unknown/unsupported OS. Audio support may not work or may route to default output (headset, headphones, etc).");
host = cpal::default_host();
virtual_mic = host.default_output_device().expect("Could not get default output device");
return OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream")
// normal_output = host.default_output_device().expect("Could not get default output device");
// return (OutputStreamBuilder::from_device(normal_output).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"), OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"));
} }
} #[allow(unreachable_code)]
{
fn reload_sound() -> OutputStream { let host = cpal::default_host();
if cfg!(target_os = "linux"){ let device = host.default_output_device().expect("Could not get default output device");
let script = r#" SoundSystem {
pactl list modules short | grep "Virtual_Microphone" | cut -f1 | xargs -L1 pactl unload-module output_stream: OutputStreamBuilder::from_device(device)
pactl list modules short | grep "Virtual_Mic_Source" | cut -f1 | xargs -L1 pactl unload-module .expect("Unable to open device")
"#; .open_stream()
.expect("Failed to open stream"),
let output = Command::new("sh") paused: false,
.arg("-c")
.arg(script)
.output()
.expect("Failed to execute process");
if output.status.success() {
println!("Modules unloaded successfully.");
} else {
println!("Error: {}", String::from_utf8_lossy(&output.stderr));
} }
} }
}
fn reload_sound() -> SoundSystem {
#[cfg(target_os = "linux")]
linux_lib::reload_sound();
return create_virtual_mic(); return create_virtual_mic();
} }
fn main() { fn list_outputs() -> Vec<(String, String)> {
let virtual_mic_stream = create_virtual_mic(); #[cfg(target_os = "windows")]
// let (normal_output_stream, virtual_mic_stream) = create_virtual_mic(); return Vec::from([("Select in apps".to_string(), String::from("9999999"))]);
#[cfg(target_os = "linux")]
return linux_lib::list_outputs();
#[allow(unreachable_code)]
return Vec::new();
}
fn main() {
App::new() App::new()
.insert_resource(ClearColor(Color::BLACK)) .insert_resource(ClearColor(Color::BLACK))
.add_plugins( .add_plugins(
DefaultPlugins DefaultPlugins
.set(LogPlugin { .set(bevy::log::LogPlugin {
filter: "warn,ui=info".to_string(), filter: "warn,ui=info".to_string(),
level: Level::INFO, level: Level::INFO,
..Default::default() ..Default::default()
@@ -159,17 +123,16 @@ fn main() {
..default() ..default()
}), }),
) )
.add_plugins(EguiPlugin::default()) .add_plugins(bevy_egui::EguiPlugin::default())
.insert_resource(AppState { .insert_resource(AppState {
loaded_files: HashMap::new(), loaded_files: HashMap::new(),
json_data: JSONData { tabs: Vec::new() }, json_data: JSONData { tabs: Vec::new() },
current_directory: String::new(), current_directory: String::new(),
currently_playing: Vec::new(), currently_playing: Vec::new(),
sound_system: SoundSystem { sound_system: create_virtual_mic(),
virtual_mic_stream, virt_outputs: Vec::new(),
// normal_output_stream, virt_output_index_switch: String::from("0"),
paused: false virt_output_index: String::from("999"),
}
}) })
.add_systems( .add_systems(
PreStartup, PreStartup,
@@ -178,12 +141,28 @@ fn main() {
.add_systems(Startup, load_system) .add_systems(Startup, load_system)
.add_systems( .add_systems(
EguiPrimaryContextPass, EguiPrimaryContextPass,
(ui_system, update_ui_scale_factor_system), (ui_system, update_ui_scale_factor_system, update_virtualmic),
) )
.run(); .run();
} }
fn update_virtualmic(mut app_state: ResMut<AppState>) {
if app_state.virt_outputs.is_empty() {
return;
}
if app_state.virt_output_index != app_state.virt_output_index_switch {
app_state.virt_output_index = app_state.virt_output_index_switch.clone();
#[cfg(target_os = "linux")]
linux_lib::move_index_to_virtualmic(app_state.virt_output_index_switch.clone());
}
}
fn load_system(mut app_state: ResMut<AppState>) { fn load_system(mut app_state: ResMut<AppState>) {
app_state.virt_outputs = list_outputs();
if !app_state.virt_outputs.is_empty() {
app_state.virt_output_index_switch = app_state.virt_outputs[0].1.clone();
}
load_data(&mut app_state); load_data(&mut app_state);
} }
@@ -209,7 +188,15 @@ fn load_data(app_state: &mut AppState) {
.filter_map(|entry| { .filter_map(|entry| {
entry.ok().and_then(|e| { entry.ok().and_then(|e| {
let path = e.path(); let path = e.path();
if path.is_file() && ALLOWED_FILE_EXTENSIONS.contains(&path.extension().expect("Could not find extension").to_str().expect("Could not convert extension to string")) { if path.is_file()
&& ALLOWED_FILE_EXTENSIONS.contains(
&path
.extension()
.unwrap_or_default()
.to_str()
.expect("Could not convert extension to string"),
)
{
path.to_str().map(|s| s.to_string()) path.to_str().map(|s| s.to_string())
} else { } else {
None None
@@ -227,33 +214,37 @@ fn setup_camera_system(mut commands: Commands) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
} }
fn update_ui_scale_factor_system( fn update_ui_scale_factor_system(egui_context: Single<(&mut EguiContextSettings, &Camera)>) {
egui_context: Single<(&mut EguiContextSettings, &Camera)>,
) {
let (mut egui_settings, camera) = egui_context.into_inner(); let (mut egui_settings, camera) = egui_context.into_inner();
egui_settings.scale_factor = 1.5 / camera.target_scaling_factor().unwrap_or(1.5); egui_settings.scale_factor = 1.5 / camera.target_scaling_factor().unwrap_or(1.5);
} }
fn play_sound(file_path: String, app_state: &mut AppState) { fn play_sound(file_path: String, app_state: &mut AppState) {
let virtual_file = File::open(&file_path).unwrap(); let file = File::open(&file_path).unwrap();
let virtual_src = Decoder::new(BufReader::new(virtual_file)).unwrap(); let src = Decoder::new(BufReader::new(file)).unwrap();
let virtual_sink = Sink::connect_new(&app_state.sound_system.virtual_mic_stream.mixer()); let sink = Sink::connect_new(&app_state.sound_system.output_stream.mixer());
let length = virtual_src.total_duration().expect("Could not get source duration").as_secs_f32(); let length = src
virtual_sink.append(virtual_src); .total_duration()
virtual_sink.play(); .expect("Could not get source duration")
.as_secs_f32();
// let normal_file = File::open(&file_path).unwrap(); sink.append(src);
// let normal_src = Decoder::new(BufReader::new(normal_file)).unwrap();
// let normal_sink = Sink::connect_new(&app_state.sound_system.normal_output_stream.mixer());
// normal_sink.append(normal_src);
// normal_sink.play();
#[cfg(target_os = "windows")]
app_state.currently_playing.push(PlayingSound { {
file_path: file_path.clone(), let file2 = File::open(&file_path).unwrap();
let src2 = Decoder::new(BufReader::new(file2)).unwrap();
let normal_sink = Sink::connect_new(&app_state.sound_system.normal_output_stream.mixer());
sink2.append(src2);
}
sink.play();
app_state.currently_playing.push(PlayingSound {
file_path: file_path.clone(),
length, length,
virtual_sink, sink,
// normal_sink #[cfg(target_os = "windows")]
normal_sink
}) })
} }
@@ -269,11 +260,40 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
ui.separator(); ui.separator();
let available_width = ui.available_width();
let available_height = ui.available_height(); let available_height = ui.available_height();
let outputs = app_state.virt_outputs.clone();
let mut mic_name = "Select inside apps".to_string();
#[cfg(target_os = "linux")]
{
let output_index = app_state.virt_output_index.clone();
let output_sink = linux_lib::get_sink_by_index("source-outputs", output_index);
if let Some(app_name) = output_sink["properties"]["application.name"].as_str() {
mic_name = app_name.to_string();
}
}
ui.label("Virtual Mic Output");
egui::ComboBox::from_id_salt("Virtual Mic Output")
.selected_text(mic_name)
.width(available_width)
.height(available_height / 15.0)
.show_ui(ui, |ui| {
for output in &outputs {
ui.selectable_value(
&mut app_state.virt_output_index_switch,
output.1.clone(),
output.0.clone(),
);
}
});
if ui if ui
.add_sized( .add_sized(
[ui.available_width(), available_height / 15.0], [available_width, available_height / 15.0],
egui::Button::new("Add folder"), egui::Button::new("Add folder"),
) )
.clicked() .clicked()
@@ -297,7 +317,7 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
if ui if ui
.add_sized( .add_sized(
[ui.available_width(), available_height / 15.0], [available_width, available_height / 15.0],
egui::Button::new("Reload content"), egui::Button::new("Reload content"),
) )
.clicked() .clicked()
@@ -308,7 +328,7 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
if ui if ui
.add_sized( .add_sized(
[ui.available_width(), available_height / 15.0], [available_width, available_height / 15.0],
egui::Button::new("Youtube downloader"), egui::Button::new("Youtube downloader"),
) )
.clicked() .clicked()
@@ -318,14 +338,13 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
if ui if ui
.add_sized( .add_sized(
[ui.available_width(), available_height / 15.0], [available_width, available_height / 15.0],
egui::Button::new("Reload sound system"), egui::Button::new("Reload sound system"),
) )
.clicked() .clicked()
{ {
app_state.currently_playing.clear(); app_state.currently_playing.clear();
app_state.sound_system.virtual_mic_stream = reload_sound(); app_state.sound_system = reload_sound();
// (app_state.sound_system.normal_output_stream, app_state.sound_system.virtual_mic_stream) = reload_sound();
println!("Sucessfully reloaded sound system!"); println!("Sucessfully reloaded sound system!");
} }
}); });
@@ -334,14 +353,18 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
ui.horizontal(|ui| { ui.horizontal(|ui| {
if app_state.sound_system.paused { if app_state.sound_system.paused {
ui.heading("Paused"); ui.heading("Paused");
} } else {
else {
ui.heading("Playing"); ui.heading("Playing");
} }
ui.vertical(|ui| { ui.vertical(|ui| {
for playing_sound in &app_state.currently_playing { for playing_sound in &app_state.currently_playing {
ui.label(format!("{} - {:.2} / {:.2}", playing_sound.file_path, playing_sound.virtual_sink.get_pos().as_secs_f32(), playing_sound.length)); ui.label(format!(
"{} - {:.2} / {:.2}",
playing_sound.file_path,
playing_sound.sink.get_pos().as_secs_f32(),
playing_sound.length
));
} }
}) })
}); });
@@ -349,15 +372,22 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
let available_height = ui.available_height(); let available_height = ui.available_height();
ui.horizontal(|ui| { ui.horizontal(|ui| {
let available_width = ui.available_width(); let available_width = ui.available_width();
let current_directories = app_state.loaded_files.keys().cloned().collect::<Vec<_>>(); let current_directories = app_state.loaded_files.keys().cloned().collect::<Vec<_>>();
for directory in current_directories.clone() { for directory in current_directories.clone() {
let mut button = egui::Button::new(&directory);
if directory == app_state.current_directory {
button = button.fill(Color32::BLACK);
}
if ui if ui
.add_sized( .add_sized(
[available_width / current_directories.len() as f32, available_height / 15.0], [
egui::Button::new(&directory), available_width / current_directories.len() as f32,
available_height / 15.0,
],
button,
) )
.clicked() .clicked()
{ {
@@ -366,10 +396,6 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
} }
}); });
ui.add_space(available_height / 50.0); ui.add_space(available_height / 50.0);
ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
ui.label(egui::RichText::new(format!("The current directory is {}", app_state.current_directory)).font(egui::FontId::proportional(20.0)));
});
ui.add_space(available_height / 50.0);
if app_state.current_directory.chars().count() > 0 { if app_state.current_directory.chars().count() > 0 {
let files = app_state let files = app_state
.loaded_files .loaded_files
@@ -380,10 +406,13 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
egui::ScrollArea::vertical().show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| {
for element in files { for element in files {
if let Some(filename) = element.split("/").collect::<Vec<_>>().last() { if let Some(filename) = element.split("/").collect::<Vec<_>>().last() {
if ui.add_sized( if ui
[ui.available_width(), available_height / 15.0], .add_sized(
egui::Button::new(*filename), [ui.available_width(), available_height / 15.0],
).clicked() { egui::Button::new(*filename),
)
.clicked()
{
let path = Path::new(&app_state.current_directory) let path = Path::new(&app_state.current_directory)
.join(filename) .join(filename)
.to_string_lossy() .to_string_lossy()
@@ -395,10 +424,10 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
}); });
} }
}); });
app_state.currently_playing.retain(|playing_sound| { app_state.currently_playing.retain(|playing_sound| {
playing_sound.virtual_sink.get_pos().as_secs_f32() <= (playing_sound.length - 0.01) // 0.01 offset needed here because of floating point errors and so its not exact playing_sound.sink.get_pos().as_secs_f32() <= (playing_sound.length - 0.01) // 0.01 offset needed here because of floating point errors and so its not exact
}); });
Ok(()) Ok(())
} }

14
src/windows_lib.rs Normal file
View File

@@ -0,0 +1,14 @@
use rodio::{OutputStream, OutputStreamBuilder, cpal::{self, traits::HostTrait, traits::DeviceTrait}};
pub fn create_virtual_mic_windows() -> (OutputStream, OutputStream) {
let host = cpal::host_from_id(cpal::HostId::Wasapi).expect("Could not initialize audio routing using WasAPI");
let virtual_mic = host.output_devices().expect("Could not list Output devices").find(|device| {
device.name().ok().map(|name|{
name.contains("CABLE Input") || name.contains("VB-Audio")
}).unwrap_or(false)
}).expect("Could not get default output device");
normal_output = host.default_output_device().expect("Could not get default output device");
return (OutputStreamBuilder::from_device(normal_output).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"), OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream"));
}