mirror of
https://github.com/csd4ni3l/soundboard.git
synced 2026-04-17 16:07:22 +02:00
Compare commits
2 Commits
ea2fa978ca
...
4fff1d4709
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fff1d4709 | ||
|
|
6fcd4a4154 |
@@ -1,4 +1,4 @@
|
|||||||
Soundboard made in Rust & Bevy. My first Rust project.
|
Soundboard made in Rust & Bevy. My first Rust project.
|
||||||
|
|
||||||
For compilation on Linux, you will need the mold linker to speed things up.
|
For compilation on Linux, you will need the mold linker and clang to speed things up.
|
||||||
On an arch machine for example, do `sudo pacman -S mold`
|
On an arch machine for example, do `sudo pacman -S mold`
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
#!/bin/bash
|
cargo run
|
||||||
pactl load-module module-null-sink sink_name=VirtualMic sink_properties=device.description="Virtual_Microphone"
|
|
||||||
pactl load-module module-remap-source master=VirtualMic.monitor source_name=VirtualMicSource source_properties=device.description="Virtual_Mic_Source"
|
|
||||||
|
|
||||||
PULSE_SINK=VirtualMic cargo run
|
|
||||||
|
|
||||||
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
|
||||||
7
soundboard.desktop
Normal file
7
soundboard.desktop
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=csd4ni3l Soundboard
|
||||||
|
Comment=A simple soundboard made in Rust
|
||||||
|
Exec=/opt/cssoundboard/run_linux.sh
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Application;
|
||||||
124
src/main.rs
124
src/main.rs
@@ -3,7 +3,7 @@ use bevy::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{collections::HashMap, fs::File, io::BufReader, path::Path};
|
use std::{collections::HashMap, fs::File, io::BufReader, path::Path, process::Command};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ use bevy_egui::{
|
|||||||
EguiContextSettings, EguiContexts, EguiPlugin, EguiPrimaryContextPass, EguiStartupSet, egui,
|
EguiContextSettings, EguiContexts, EguiPlugin, EguiPrimaryContextPass, EguiStartupSet, egui,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink, mixer::Mixer, cpal, cpal::traits::{HostTrait, DeviceTrait}};
|
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Sink, Source, cpal::{self, traits::HostTrait}};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct JSONData {
|
struct JSONData {
|
||||||
@@ -20,11 +20,12 @@ struct JSONData {
|
|||||||
|
|
||||||
struct PlayingSound {
|
struct PlayingSound {
|
||||||
file_path: String,
|
file_path: String,
|
||||||
start_time: f32,
|
length: f32,
|
||||||
|
sink: Sink
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SoundSystem {
|
struct SoundSystem {
|
||||||
sink: Sink,
|
stream_handle: OutputStream,
|
||||||
paused: bool
|
paused: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,19 +38,73 @@ struct AppState {
|
|||||||
sound_system: SoundSystem
|
sound_system: SoundSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
const ALLOWED_FILE_EXTENSIONS: [&str; 4] = ["mp3", "wav", "flac", "ogg"];
|
const ALLOWED_FILE_EXTENSIONS: [&str; 4] = ["mp3", "wav", "flac", "ogg"];
|
||||||
|
|
||||||
fn main() {
|
fn create_virtual_mic() -> OutputStream {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
panic!("Windows is currently unsupported.");
|
||||||
|
}
|
||||||
|
else if cfg!(target_os = "macos") {
|
||||||
|
panic!("MacOS is and will most likely stay unsupported.");
|
||||||
|
}
|
||||||
|
else if cfg!(target_os = "linux") {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
panic!("I have no idea what OS you are on but it's not mainstream enough.");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("PULSE_SINK", "VirtualMic");
|
||||||
|
}
|
||||||
|
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let virtual_mic = host.default_output_device().expect("Could not get default output device");
|
let virtual_mic = host.default_output_device().expect("Could not get default output device");
|
||||||
|
|
||||||
println!("Using device: {}", virtual_mic.name().unwrap_or_default());
|
return OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream");
|
||||||
|
}
|
||||||
|
|
||||||
let stream_handle = OutputStreamBuilder::from_device(virtual_mic).expect("Unable to open default audio device").open_stream().expect("Failed to open stream");
|
fn recreate_virtual_mic() -> OutputStream {
|
||||||
let mixer = stream_handle.mixer();
|
if cfg!(target_os = "windows") {
|
||||||
let sink = Sink::connect_new(&mixer);
|
panic!("Windows is currently unsupported.");
|
||||||
|
}
|
||||||
|
else if cfg!(target_os = "macos") {
|
||||||
|
panic!("MacOS is and will most likely stay unsupported.");
|
||||||
|
}
|
||||||
|
else if cfg!(target_os = "linux"){
|
||||||
|
let script = r#"
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
panic!("I have no idea what OS you are on but it's not mainstream enough.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return create_virtual_mic();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let stream_handle = create_virtual_mic();
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.insert_resource(ClearColor(Color::BLACK))
|
.insert_resource(ClearColor(Color::BLACK))
|
||||||
@@ -76,7 +131,7 @@ fn main() {
|
|||||||
current_directory: String::new(),
|
current_directory: String::new(),
|
||||||
currently_playing: Vec::new(),
|
currently_playing: Vec::new(),
|
||||||
sound_system: SoundSystem {
|
sound_system: SoundSystem {
|
||||||
sink,
|
stream_handle,
|
||||||
paused: false
|
paused: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -146,16 +201,14 @@ fn update_ui_scale_factor_system(
|
|||||||
fn play_sound(file_path: String, app_state: &mut AppState) {
|
fn play_sound(file_path: String, app_state: &mut AppState) {
|
||||||
let file = BufReader::new(File::open(&file_path).unwrap());
|
let file = BufReader::new(File::open(&file_path).unwrap());
|
||||||
let src = Decoder::new(file).unwrap();
|
let src = Decoder::new(file).unwrap();
|
||||||
app_state.sound_system.sink.append(src);
|
let length = src.total_duration().expect("Could not get source duration").as_secs_f32();
|
||||||
|
let sink = Sink::connect_new(&app_state.sound_system.stream_handle.mixer());
|
||||||
let start = SystemTime::now();
|
sink.append(src);
|
||||||
let since_the_epoch = start
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.expect("time should go forward");
|
|
||||||
|
|
||||||
app_state.currently_playing.push(PlayingSound {
|
app_state.currently_playing.push(PlayingSound {
|
||||||
file_path: file_path.clone(),
|
file_path: file_path.clone(),
|
||||||
start_time: since_the_epoch.as_secs_f32(),
|
length: length,
|
||||||
|
sink: sink
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +270,35 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
|
|||||||
{
|
{
|
||||||
println!("Youtube downloader!");
|
println!("Youtube downloader!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.add_sized(
|
||||||
|
[ui.available_width(), available_height / 15.0],
|
||||||
|
egui::Button::new("Recreate Virtual Mic"),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
app_state.currently_playing.clear();
|
||||||
|
app_state.sound_system.stream_handle = recreate_virtual_mic();
|
||||||
|
println!("Recreated Virtual microphone!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::TopBottomPanel::bottom("currently_playing").show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if app_state.sound_system.paused {
|
||||||
|
ui.heading("Paused");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui.heading("Playing");
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
for playing_sound in &app_state.currently_playing {
|
||||||
|
ui.label(format!("{} - {:.2} / {:.2}", playing_sound.file_path, playing_sound.sink.get_pos().as_secs_f32(), playing_sound.length));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
@@ -267,6 +349,10 @@ fn ui_system(mut contexts: EguiContexts, mut app_state: ResMut<AppState>) -> Res
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app_state.currently_playing.retain(|playing_sound| {
|
||||||
|
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(())
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user