generated from GarandPLG/rust-flake-template
Add audio subsystem with mute and volume controls
Update flake.lock dependencies to latest revisions
This commit is contained in:
Generated
+12
-12
@@ -8,11 +8,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762929886,
|
||||
"narHash": "sha256-TQZ3Ugb1FoHpTSc8KLrzN4njIZU4FemAMHyS4M3mt6s=",
|
||||
"lastModified": 1775462255,
|
||||
"narHash": "sha256-YRzdvh6nvMebcgO2nDpr8dqVwKHpp1BBRUHeMxX9UAY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "6998514dce2c365142a0a119a95ef95d89b84086",
|
||||
"rev": "f90343f1ed330243d4bbdbce51acbd93b776a797",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -31,11 +31,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1752689277,
|
||||
"narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=",
|
||||
"lastModified": 1769799857,
|
||||
"narHash": "sha256-88IFXZ7Sa1vxbz5pty0Io5qEaMQMMUPMonLa3Ls/ss4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "0e72363d0938b0208d6c646d10649164c43f4d64",
|
||||
"rev": "9d4ed44d8b8cecdceb1d6fd76e74123d90ae6339",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -46,11 +46,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1762844143,
|
||||
"narHash": "sha256-SlybxLZ1/e4T2lb1czEtWVzDCVSTvk9WLwGhmxFmBxI=",
|
||||
"lastModified": 1775036866,
|
||||
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9da7f1cf7f8a6e2a7cb3001b048546c92a8258b4",
|
||||
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -70,11 +70,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1762860488,
|
||||
"narHash": "sha256-rMfWMCOo/pPefM2We0iMBLi2kLBAnYoB9thi4qS7uk4=",
|
||||
"lastModified": 1775429583,
|
||||
"narHash": "sha256-bFC/p7Ywyd9QIr9DbU3Q75c7AcaCm9wVmEvcI3702cY=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "2efc80078029894eec0699f62ec8d5c1a56af763",
|
||||
"rev": "38fb8f92ac15853d7fa9fb47fc2d81fdd5cd6c7e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -48,7 +48,10 @@
|
||||
};
|
||||
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
env.RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
env = {
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; [alsa-lib]);
|
||||
};
|
||||
buildInputs = with pkgs; [
|
||||
rustToolchain
|
||||
alsa-lib
|
||||
|
||||
Binary file not shown.
+9
-3
@@ -1,11 +1,15 @@
|
||||
use crate::{
|
||||
app::{GameStates, handle_keybindings, threads::AppEvent, view::View},
|
||||
app::{
|
||||
GameStates, handle_keybindings,
|
||||
threads::{AppEvent, AudioCmd},
|
||||
view::View,
|
||||
},
|
||||
cli::Cli,
|
||||
};
|
||||
use ratatui::{DefaultTerminal, Frame, crossterm::event::KeyEvent, layout::Rect};
|
||||
use std::{
|
||||
io::Result,
|
||||
sync::mpsc::{Receiver, RecvTimeoutError},
|
||||
sync::mpsc::{Receiver, RecvTimeoutError, Sender},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -15,16 +19,18 @@ pub struct App {
|
||||
pub window_area: Rect,
|
||||
pub args: Cli,
|
||||
pub states: Option<GameStates>,
|
||||
pub audio_tx: Sender<AudioCmd>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(args: Cli) -> Self {
|
||||
pub fn new(args: Cli, audio_tx: Sender<AudioCmd>) -> Self {
|
||||
Self {
|
||||
exit: false,
|
||||
view: args.view,
|
||||
window_area: Rect::default(),
|
||||
args: args,
|
||||
states: None,
|
||||
audio_tx,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::app::{
|
||||
App, View,
|
||||
keybindings::{Action, event_to_action},
|
||||
threads::AudioCmd,
|
||||
};
|
||||
use ratatui::crossterm::event::KeyEvent;
|
||||
|
||||
@@ -8,6 +9,9 @@ pub fn common_keybindings(app: &mut App, action: Action) {
|
||||
match action {
|
||||
Action::Quit | Action::Quit2 => app.exit = true,
|
||||
Action::Esc => app.view = View::MainMenu,
|
||||
Action::Mute => if let Err(_) = app.audio_tx.send(AudioCmd::Mute) {},
|
||||
Action::VolumeUp => if let Err(_) = app.audio_tx.send(AudioCmd::VolumeUp) {},
|
||||
Action::VolumeDown => if let Err(_) = app.audio_tx.send(AudioCmd::VolumeDown) {},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,12 @@ pub enum Action {
|
||||
ZoomIn,
|
||||
/// Zoom the view out.
|
||||
ZoomOut,
|
||||
/// Mute music.
|
||||
Mute,
|
||||
/// Volume up.
|
||||
VolumeUp,
|
||||
/// Volume down.
|
||||
VolumeDown,
|
||||
/// Matches any character key; the inner `char` is the actual key pressed.
|
||||
WildCard(char),
|
||||
}
|
||||
@@ -61,6 +67,8 @@ pub enum Group {
|
||||
Input,
|
||||
/// Zoom related bindings.
|
||||
Zoom,
|
||||
/// Music related bindings.
|
||||
Music,
|
||||
/// Quit related bindings.
|
||||
Quit,
|
||||
}
|
||||
@@ -236,6 +244,33 @@ pub static KEYBINDINGS: &[KeyBinding] = &[
|
||||
symbol: ".",
|
||||
description: "Zoom out",
|
||||
},
|
||||
KeyBinding {
|
||||
action: Action::Mute,
|
||||
code: KeyCode::Char('m'),
|
||||
kind: KeyEventKind::Press,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
group: Group::Music,
|
||||
symbol: "m",
|
||||
description: "Mute music",
|
||||
},
|
||||
KeyBinding {
|
||||
action: Action::VolumeUp,
|
||||
code: KeyCode::Char('b'),
|
||||
kind: KeyEventKind::Press,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
group: Group::Music,
|
||||
symbol: "b",
|
||||
description: "Increase music volume",
|
||||
},
|
||||
KeyBinding {
|
||||
action: Action::VolumeDown,
|
||||
code: KeyCode::Char('n'),
|
||||
kind: KeyEventKind::Press,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
group: Group::Music,
|
||||
symbol: "n",
|
||||
description: "Decrease music volume",
|
||||
},
|
||||
KeyBinding {
|
||||
action: Action::WildCard('_'),
|
||||
code: KeyCode::Char('_'),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source, source::Amplify};
|
||||
use std::{fs::File, io::BufReader, sync::mpsc::Receiver};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AudioCmd {
|
||||
Mute,
|
||||
VolumeUp,
|
||||
VolumeDown,
|
||||
}
|
||||
|
||||
pub enum SoundrackParts {
|
||||
Calm,
|
||||
Buildup,
|
||||
Assult,
|
||||
Outro,
|
||||
}
|
||||
|
||||
fn load_audio(part: SoundrackParts) -> Amplify<Decoder<BufReader<File>>> {
|
||||
let file: File = match part {
|
||||
SoundrackParts::Calm => File::open("soundtrack/default/test.ogg").expect("open audio file"),
|
||||
SoundrackParts::Buildup => {
|
||||
File::open("soundtrack/default/test.ogg").expect("open audio file")
|
||||
}
|
||||
SoundrackParts::Assult => {
|
||||
File::open("soundtrack/default/test.ogg").expect("open audio file")
|
||||
}
|
||||
SoundrackParts::Outro => {
|
||||
File::open("soundtrack/default/test.ogg").expect("open audio file")
|
||||
}
|
||||
};
|
||||
|
||||
Decoder::try_from(file)
|
||||
.expect("decoder issue")
|
||||
.amplify(0.20)
|
||||
}
|
||||
|
||||
pub fn handle_audio(rx: Receiver<AudioCmd>, mute: bool) {
|
||||
let mut handle: MixerDeviceSink =
|
||||
DeviceSinkBuilder::open_default_sink().expect("open default audio stream");
|
||||
|
||||
handle.log_on_drop(false);
|
||||
|
||||
let player: Player = Player::connect_new(&handle.mixer());
|
||||
|
||||
let mut volume: f32 = player.volume();
|
||||
|
||||
if mute {
|
||||
player.set_volume(0.0);
|
||||
} else {
|
||||
player.set_volume(volume);
|
||||
}
|
||||
|
||||
player.append(load_audio(SoundrackParts::Calm));
|
||||
|
||||
loop {
|
||||
if player.empty() {
|
||||
player.append(load_audio(SoundrackParts::Calm));
|
||||
}
|
||||
|
||||
for cmd in rx.try_iter() {
|
||||
match cmd {
|
||||
AudioCmd::Mute => {
|
||||
if player.volume() == 0.0 {
|
||||
player.set_volume(volume);
|
||||
} else {
|
||||
player.set_volume(0.0);
|
||||
}
|
||||
}
|
||||
AudioCmd::VolumeUp => {
|
||||
volume = (volume + 0.1).min(1.0);
|
||||
player.set_volume(volume);
|
||||
}
|
||||
AudioCmd::VolumeDown => {
|
||||
volume = (volume - 0.1).max(0.0);
|
||||
player.set_volume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod audio;
|
||||
pub mod events;
|
||||
pub mod handle_events;
|
||||
|
||||
pub use audio::{AudioCmd, SoundrackParts, handle_audio};
|
||||
pub use events::AppEvent;
|
||||
pub use handle_events::handle_events;
|
||||
|
||||
@@ -17,6 +17,9 @@ const ACTIONS: &[Action] = &[
|
||||
Action::Up,
|
||||
Action::Down,
|
||||
Action::Space,
|
||||
Action::VolumeUp,
|
||||
Action::VolumeDown,
|
||||
Action::Mute,
|
||||
Action::Quit,
|
||||
Action::Quit2,
|
||||
];
|
||||
|
||||
@@ -23,6 +23,9 @@ const ACTIONS: &[Action] = &[
|
||||
Action::ScrollRight,
|
||||
Action::ZoomIn,
|
||||
Action::ZoomOut,
|
||||
Action::VolumeUp,
|
||||
Action::VolumeDown,
|
||||
Action::Mute,
|
||||
Action::Quit,
|
||||
Action::Quit2,
|
||||
Action::Esc,
|
||||
|
||||
@@ -140,6 +140,14 @@ pub struct Cli {
|
||||
default_value_t = false
|
||||
)]
|
||||
pub log: bool,
|
||||
|
||||
/// Mute soundtrack (default: disabled).
|
||||
#[arg(
|
||||
long,
|
||||
help = "Mute soundtrack (default: disabled)",
|
||||
default_value_t = false
|
||||
)]
|
||||
pub mute: bool,
|
||||
}
|
||||
|
||||
/// Parses the command line arguments and returns a fully populated `Cli`
|
||||
|
||||
+26
-7
@@ -2,12 +2,15 @@ use ratatui::{Terminal, prelude::CrosstermBackend};
|
||||
use std::{
|
||||
io::{Result, Stdout},
|
||||
sync::mpsc::channel,
|
||||
thread::{self, JoinHandle},
|
||||
thread::{
|
||||
self,
|
||||
// JoinHandle
|
||||
},
|
||||
};
|
||||
use war_in_tunnels::{
|
||||
app::{
|
||||
App,
|
||||
threads::{AppEvent, handle_events},
|
||||
threads::{AppEvent, AudioCmd, handle_audio, handle_events},
|
||||
},
|
||||
cli::{Cli, get_args},
|
||||
logs::init_logger,
|
||||
@@ -24,19 +27,35 @@ fn main() -> Result<()> {
|
||||
init_logger();
|
||||
}
|
||||
|
||||
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
|
||||
let mut app: App = App::new(args);
|
||||
|
||||
let (app_event_tx, app_event_rx) = channel::<AppEvent>();
|
||||
|
||||
let app_event_thread: JoinHandle<()> = thread::spawn(move || {
|
||||
// let app_event_thread: JoinHandle<()> = thread::spawn(move || {
|
||||
// handle_events(app_event_tx);
|
||||
// });
|
||||
|
||||
thread::spawn(move || {
|
||||
handle_events(app_event_tx);
|
||||
});
|
||||
|
||||
let (audio_tx, audio_rx) = channel::<AudioCmd>();
|
||||
|
||||
// let audio_event_thread: JoinHandle<()> = thread::spawn(move || {
|
||||
// handle_audio(audio_rx);
|
||||
// });
|
||||
|
||||
thread::spawn(move || {
|
||||
handle_audio(audio_rx, args.mute);
|
||||
});
|
||||
|
||||
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
|
||||
let mut app: App = App::new(args, audio_tx);
|
||||
|
||||
let app_result: Result<()> = app.run(&mut terminal, app_event_rx);
|
||||
|
||||
ratatui::restore();
|
||||
|
||||
let _ = app_event_thread.join();
|
||||
// let _ = app_event_thread.;
|
||||
// let _ = audio_event_thread.join();
|
||||
|
||||
app_result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user