diff --git a/flake.lock b/flake.lock index a7805f1..5cc0eb6 100644 --- a/flake.lock +++ b/flake.lock @@ -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": { diff --git a/flake.nix b/flake.nix index 19e014d..a1122fa 100644 --- a/flake.nix +++ b/flake.nix @@ -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 diff --git a/soundtrack/default/test2.ogg b/soundtrack/default/test2.ogg new file mode 100644 index 0000000..2f005e3 Binary files /dev/null and b/soundtrack/default/test2.ogg differ diff --git a/src/app/app.rs b/src/app/app.rs index b1188bf..a3dd325 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -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, + pub audio_tx: Sender, } impl App { - pub fn new(args: Cli) -> Self { + pub fn new(args: Cli, audio_tx: Sender) -> Self { Self { exit: false, view: args.view, window_area: Rect::default(), args: args, states: None, + audio_tx, } } diff --git a/src/app/keybindings/default.rs b/src/app/keybindings/default.rs index 6e2655e..15fe734 100644 --- a/src/app/keybindings/default.rs +++ b/src/app/keybindings/default.rs @@ -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) {}, _ => (), } } diff --git a/src/app/keybindings/keybindings.rs b/src/app/keybindings/keybindings.rs index d8851d9..8101699 100644 --- a/src/app/keybindings/keybindings.rs +++ b/src/app/keybindings/keybindings.rs @@ -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('_'), diff --git a/src/app/threads/audio.rs b/src/app/threads/audio.rs new file mode 100644 index 0000000..e80ba1b --- /dev/null +++ b/src/app/threads/audio.rs @@ -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>> { + 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, 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); + } + } + } + } +} diff --git a/src/app/threads/mod.rs b/src/app/threads/mod.rs index 93ef8cb..1f27c25 100644 --- a/src/app/threads/mod.rs +++ b/src/app/threads/mod.rs @@ -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; diff --git a/src/app/views/main_menu.rs b/src/app/views/main_menu.rs index 537c09a..deb9546 100644 --- a/src/app/views/main_menu.rs +++ b/src/app/views/main_menu.rs @@ -17,6 +17,9 @@ const ACTIONS: &[Action] = &[ Action::Up, Action::Down, Action::Space, + Action::VolumeUp, + Action::VolumeDown, + Action::Mute, Action::Quit, Action::Quit2, ]; diff --git a/src/app/views/skirmish.rs b/src/app/views/skirmish.rs index a3149bd..72e9d3d 100644 --- a/src/app/views/skirmish.rs +++ b/src/app/views/skirmish.rs @@ -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, diff --git a/src/cli.rs b/src/cli.rs index 7fb05a9..c3c27e1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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` diff --git a/src/main.rs b/src/main.rs index 190de3f..2bc6c03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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> = ratatui::init(); - let mut app: App = App::new(args); - let (app_event_tx, app_event_rx) = channel::(); - 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::(); + + // 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> = 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 }