From 013b06a3e2c1f326715b6eef82e412d86df15e1f Mon Sep 17 00:00:00 2001 From: Muaz Ahmad Date: Tue, 10 Dec 2024 14:09:28 +0500 Subject: [PATCH] - Volume control - Less aggressive chunk queuing and next signalling --- README.md | 2 +- src/audio/codec.rs | 13 +++++++++++-- src/audio/mod.rs | 3 +++ src/audio/sound_mgr.rs | 33 ++++++++++++++++++++++++++++----- src/config/mod.rs | 10 ++++------ src/player/player.rs | 20 ++++++++++++++------ src/ssonic/client.rs | 12 ++++++------ src/ssonic/mod.rs | 2 +- src/utils.rs | 10 +++++++++- 9 files changed, 77 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 256fb61..b2cb55a 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ next = 'n' # Skip current song ## Don't use this -I cannot stress enough how janky this is. Just don't use this. +I cannot stress enough how janky this is. Just don't use this. The audio controls and samples are sent on the same queue ffs. I might remake all of this with libmpv or mpv-ipc for a saner player at some point. diff --git a/src/audio/codec.rs b/src/audio/codec.rs index d6b1073..c9dcefb 100644 --- a/src/audio/codec.rs +++ b/src/audio/codec.rs @@ -14,6 +14,8 @@ pub struct DecoderContext { frames_out: Receiver>, sample_buf: VecDeque>, pub input_done: bool, + done_signalled: bool, + fetch_queued: bool, } pub fn init(stride: usize, sample_rate: u32) -> Result { @@ -51,6 +53,8 @@ pub fn init(stride: usize, sample_rate: u32) -> Result { frames_out, sample_buf: VecDeque::new(), input_done: false, + done_signalled: false, + fetch_queued: true, }) } @@ -63,6 +67,7 @@ impl Drop for DecoderContext { impl DecoderContext { pub fn append_chunk(&mut self, chunk: Vec) -> Result<(), Error> { self.encoded_in.send(chunk)?; + self.fetch_queued = false; Ok(()) } @@ -83,9 +88,13 @@ impl DecoderContext { self.fetch_samples(); if self.sample_buf.len() == curr_n_samples { if !self.input_done { - fetch_more_file = SampleReturnState::FetchMore; - } else if curr_n_samples < 50 { + if !self.fetch_queued { + fetch_more_file = SampleReturnState::FetchMore; + self.fetch_queued = true; + } + } else if curr_n_samples < 50 && !self.done_signalled { fetch_more_file = SampleReturnState::FileDone; + self.done_signalled = true; } } } diff --git a/src/audio/mod.rs b/src/audio/mod.rs index ef19e6f..bc0756b 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -17,6 +17,8 @@ pub enum AudioEvent { DecoderInit(u32, u32), FinalChunkRecvd, TogglePlaying, + VolumeUp, + VolumeDown, } pub struct SoundManager { @@ -28,6 +30,7 @@ pub struct SoundManager { pw_signal_in: pipewire::channel::Sender>, decoder_context: DecoderContext, playing: bool, + volume: f64, } pub fn init( diff --git a/src/audio/sound_mgr.rs b/src/audio/sound_mgr.rs index b7ec65f..d34aec3 100644 --- a/src/audio/sound_mgr.rs +++ b/src/audio/sound_mgr.rs @@ -10,9 +10,11 @@ use std::{ use crate::{ config::Settings, player::PlayerEvent, - utils::{new_pw_pod, Error}, + utils::{new_pw_pod, time_rem, Error}, }; +const VOLUME_CHANGE_INTERVAL: f64 = 0.05; + use super::{codec, errors::AudioError, pw, AudioEvent, SoundManager}; impl SoundManager { @@ -46,6 +48,7 @@ impl SoundManager { pw_signal_in, decoder_context, playing: false, + volume: 1.0, }) } pub fn begin(&mut self) -> Result<(), Error> { @@ -55,14 +58,32 @@ impl SoundManager { if self.playing { self.push_samples()?; } - let time_elapsed = SystemTime::now().duration_since(start)?; - let time_rem = Duration::from_millis(9).saturating_sub(time_elapsed); - thread::sleep(time_rem); + let time_left_cycle = time_rem(start, Duration::from_millis(9))?; + thread::sleep(time_left_cycle); + } + } + + fn adjust_volume(&self, frame: &mut [u8]) { + for i in 0..frame.len() / 2 { + let mut val_bytes = [0; 2]; + val_bytes.copy_from_slice(&frame[2 * i..2 * i + 2]); + let val = ((i16::from_le_bytes(val_bytes) as f64) * self.volume) as i16; + frame[2 * i..2 * i + 2].copy_from_slice(&i16::to_le_bytes(val)); + } + } + + fn set_volume(&mut self, vol_change: f64) { + self.volume += vol_change; + if self.volume < 0.0 { + self.volume = 0.0; + } else if self.volume > 100.0 { + self.volume = 100.0; } } fn push_samples(&mut self) -> Result<(), Error> { - let (frame, fetch_more) = self.decoder_context.next_sample(); + let (mut frame, fetch_more) = self.decoder_context.next_sample(); + self.adjust_volume(frame.as_mut_slice()); self.sample_in.send(frame)?; match fetch_more { codec::SampleReturnState::BufferPending => (), @@ -88,6 +109,8 @@ impl SoundManager { } AudioEvent::FinalChunkRecvd => self.decoder_context.input_done = true, AudioEvent::TogglePlaying => self.playing = !self.playing, + AudioEvent::VolumeUp => self.set_volume(VOLUME_CHANGE_INTERVAL), + AudioEvent::VolumeDown => self.set_volume(-VOLUME_CHANGE_INTERVAL), _ => unimplemented!(), } Ok(()) diff --git a/src/config/mod.rs b/src/config/mod.rs index 4f8725f..84f8fe2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -18,8 +18,6 @@ pub struct Subsonic { pub username: String, #[serde(default)] pub password: String, // Only optional in file, will otherwise pull from Env var - #[serde(default = "default_random_limit")] - pub random_limit: i32, } #[derive(Deserialize)] @@ -27,6 +25,8 @@ pub struct Controls { pub quit: char, pub play_state_toggle: char, pub next: char, + pub vol_up: char, + pub vol_down: char, } fn default_controls() -> Controls { @@ -34,13 +34,11 @@ fn default_controls() -> Controls { quit: 'q', play_state_toggle: ' ', next: 'n', + vol_up: '+', + vol_down: '-', } } -fn default_random_limit() -> i32 { - 10 -} - pub fn init() -> Result, Error> { let settings = parse_config()?; Ok(Arc::new(settings)) diff --git a/src/player/player.rs b/src/player/player.rs index 34c109b..5c9e136 100644 --- a/src/player/player.rs +++ b/src/player/player.rs @@ -10,7 +10,7 @@ use image::DynamicImage; use crate::{ audio::AudioEvent, ssonic::{response::Song, APIEvent, MAX_CHUNK_SIZE}, - utils::{default_cover, Error}, + utils::{default_cover, time_rem, Error}, }; use super::{errors::PlayerError, Player, PlayerEvent}; @@ -23,17 +23,19 @@ impl Player { } fn init_playlist(&self) -> Result<(), Error> { - self.api_chan - .send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?; + self.api_chan.send(APIEvent::FetchRandom)?; self.player_chan_in.send(PlayerEvent::PlayNext)?; Ok(()) } fn run(&mut self) -> Result<(), Error> { loop { + let st = SystemTime::now(); self.handle_events()?; self.handle_inputs()?; self.update()?; + let time_left_cycle = time_rem(st, Duration::from_millis(49))?; + thread::sleep(time_left_cycle); } } @@ -55,14 +57,13 @@ impl Player { } } if self.playlist.last_song() { - self.api_chan - .send(APIEvent::FetchRandom(self.settings.subsonic.random_limit))?; + self.api_chan.send(APIEvent::FetchRandom)?; } Ok(()) } fn handle_inputs(&mut self) -> Result<(), Error> { - if poll(Duration::from_millis(40))? { + if poll(Duration::from_millis(10))? { match read()? { Event::Key(e) => match e.code { x if x == KeyCode::Char(self.settings.controls.quit) => { @@ -74,6 +75,12 @@ impl Player { x if x == KeyCode::Char(self.settings.controls.next) => { self.player_chan_in.send(PlayerEvent::PlayNext)? } + x if x == KeyCode::Char(self.settings.controls.vol_up) => { + self.audio_chan.send(AudioEvent::VolumeUp)? + } + x if x == KeyCode::Char(self.settings.controls.vol_down) => { + self.audio_chan.send(AudioEvent::VolumeDown)? + } _ => (), }, _ => (), @@ -83,6 +90,7 @@ impl Player { } fn play_next(&mut self) -> Result<(), Error> { + dbg!("playing next"); let song = match self.playlist.get_next() { None => { // no song exists, requeue the event diff --git a/src/ssonic/client.rs b/src/ssonic/client.rs index 7dcd5f2..336e85a 100644 --- a/src/ssonic/client.rs +++ b/src/ssonic/client.rs @@ -23,7 +23,7 @@ impl APIClient { self.ping()?; loop { let player_resp = match self.api_requests.recv()? { - super::APIEvent::FetchRandom(n) => self.get_random(n)?, + super::APIEvent::FetchRandom => self.get_random()?, super::APIEvent::FetchCoverArt(id) => self.get_cover_art(id)?, super::APIEvent::StreamSong(id, start, end) => self.stream_song(id, start, end)?, _ => unimplemented!(), @@ -82,8 +82,8 @@ impl APIClient { return Ok(PlayerEvent::UpdateCover(cover)); } - fn get_random(&mut self, n: i32) -> Result { - let response = self.request("/getRandomSongs", Some(&[("size", n.to_string())]), None)?; + fn get_random(&mut self) -> Result { + let response = self.request("/getRandomSongs", None, None)?; let song_list = self .validate(response, "/getRandomSongs")? .subsonic_response @@ -95,7 +95,7 @@ impl APIClient { } fn ping(&mut self) -> Result<(), Error> { - let response = self.request::<[(&str, String); 0]>("/ping.view", None, None)?; + let response = self.request("/ping.view", None, None)?; self.validate(response, "/ping.view")?; Ok(()) } @@ -123,10 +123,10 @@ impl APIClient { (format!("{:x}", hash), salt) } - fn request( + fn request( &mut self, uri: &str, - extra_params: Option<&T>, + extra_params: Option<&[(&str, String)]>, extra_headers: Option, ) -> Result { let (token, salt) = self.generate_random_token(); diff --git a/src/ssonic/mod.rs b/src/ssonic/mod.rs index 72c7627..021c5fc 100644 --- a/src/ssonic/mod.rs +++ b/src/ssonic/mod.rs @@ -13,7 +13,7 @@ use crate::{audio::AudioEvent, config::Settings, player::PlayerEvent, utils::Err pub const MAX_CHUNK_SIZE: u32 = 1_000_000; pub enum APIEvent { - FetchRandom(i32), + FetchRandom, FetchCoverArt(String), StreamSong(String, u32, u32), } diff --git a/src/utils.rs b/src/utils.rs index 20f99f3..25b5db2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,7 +8,10 @@ use pipewire::spa::{ }, }; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use std::io::Cursor; +use std::{ + io::Cursor, + time::{Duration, SystemTime}, +}; pub type Error = Box; @@ -67,3 +70,8 @@ pub fn new_pw_pod(sample_rate: u32, channels: u32) -> Vec { .0 .into_inner() } + +pub fn time_rem(start_time: SystemTime, target_dur: Duration) -> Result { + let time_since = SystemTime::now().duration_since(start_time)?; + Ok(target_dur.saturating_sub(time_since)) +}