implement software volume mixing

closes #115
This commit is contained in:
Henrik Friedrichsen
2020-01-11 18:11:41 +01:00
parent bde9db2c38
commit a5da4897de
5 changed files with 71 additions and 11 deletions

View File

@@ -81,6 +81,7 @@ have them configurable.
* `Shift-u` updates the library cache (tracks, artists, albums, playlists)
* `<` and `>` play the previous or next track
* `,` and `.` to rewind or skip forward
* `-` and `+` decrease or increase the volume
* `r` to toggle repeat mode
* `z` to toggle shuffle playback
* `q` quits ncspot

View File

@@ -57,6 +57,8 @@ pub enum Command {
Delete,
Focus(String),
Seek(SeekDirection),
VolumeUp,
VolumeDown,
Repeat(Option<RepeatSetting>),
Shuffle(Option<bool>),
Share(TargetMode),

View File

@@ -9,7 +9,7 @@ use cursive::views::ViewRef;
use cursive::Cursive;
use library::Library;
use queue::{Queue, RepeatSetting};
use spotify::Spotify;
use spotify::{Spotify, VOLUME_PERCENT};
use traits::ViewExt;
use ui::layout::Layout;
@@ -110,6 +110,17 @@ impl CommandManager {
}
Ok(None)
}
Command::VolumeUp => {
let volume = self.spotify.volume().saturating_add(VOLUME_PERCENT);
self.spotify.set_volume(volume);
Ok(None)
}
Command::VolumeDown => {
let volume = self.spotify.volume().saturating_sub(VOLUME_PERCENT);
debug!("vol {}", volume);
self.spotify.set_volume(volume);
Ok(None)
}
Command::Search(_)
| Command::Move(_, _)
| Command::Shift(_, _)
@@ -202,6 +213,8 @@ impl CommandManager {
kb.insert("/".into(), Command::Focus("search".into()));
kb.insert(".".into(), Command::Seek(SeekDirection::Relative(500)));
kb.insert(",".into(), Command::Seek(SeekDirection::Relative(-500)));
kb.insert("+".into(), Command::VolumeUp);
kb.insert("-".into(), Command::VolumeDown);
kb.insert("r".into(), Command::Repeat(None));
kb.insert("z".into(), Command::Shuffle(None));
kb.insert("x".into(), Command::Share(TargetMode::Current));

View File

@@ -10,6 +10,7 @@ use librespot_playback::config::PlayerConfig;
use librespot_playback::audio_backend;
use librespot_playback::config::Bitrate;
use librespot_playback::mixer::Mixer;
use librespot_playback::player::Player;
use rspotify::spotify::client::ApiError;
@@ -36,6 +37,7 @@ use tokio_core::reactor::Core;
use tokio_timer;
use url::Url;
use std::sync::atomic::{AtomicU16, Ordering};
use std::sync::RwLock;
use std::thread;
use std::time::{Duration, SystemTime};
@@ -45,12 +47,15 @@ use config;
use events::{Event, EventManager};
use track::Track;
pub const VOLUME_PERCENT: u16 = ((u16::max_value() as f64) * 1.0 / 100.0) as u16;
enum WorkerCommand {
Load(Box<Track>),
Play,
Pause,
Stop,
Seek(u32),
SetVolume(u16),
RequestToken(oneshot::Sender<Token>),
}
@@ -70,6 +75,7 @@ pub struct Spotify {
token_issued: RwLock<Option<SystemTime>>,
channel: mpsc::UnboundedSender<WorkerCommand>,
user: String,
volume: AtomicU16,
}
struct Worker {
@@ -81,6 +87,7 @@ struct Worker {
refresh_task: Box<dyn futures::Stream<Item = (), Error = tokio_timer::Error>>,
token_task: Box<dyn futures::Future<Item = (), Error = MercuryError>>,
active: bool,
mixer: Box<dyn Mixer>,
}
impl Worker {
@@ -89,6 +96,7 @@ impl Worker {
commands: mpsc::UnboundedReceiver<WorkerCommand>,
session: Session,
player: Player,
mixer: Box<dyn Mixer>,
) -> Worker {
Worker {
events,
@@ -99,6 +107,7 @@ impl Worker {
refresh_task: Box::new(futures::stream::empty()),
token_task: Box::new(futures::empty()),
active: false,
mixer,
}
}
}
@@ -154,6 +163,9 @@ impl futures::Future for Worker {
WorkerCommand::Seek(pos) => {
self.player.seek(pos);
}
WorkerCommand::SetVolume(volume) => {
self.mixer.set_volume(volume);
}
WorkerCommand::RequestToken(sender) => {
self.token_task = Spotify::get_token(&self.session, sender);
progress = true;
@@ -206,12 +218,13 @@ impl Spotify {
normalisation_pregain: 0.0,
};
let (user_tx, user_rx) = oneshot::channel();
let volume = 0xFFFF;
let (tx, rx) = mpsc::unbounded();
{
let events = events.clone();
thread::spawn(move || {
Self::worker(cfg, events, rx, player_config, credentials, user_tx)
Self::worker(cfg, events, rx, player_config, credentials, user_tx, volume)
});
}
@@ -223,6 +236,7 @@ impl Spotify {
token_issued: RwLock::new(None),
channel: tx,
user: user_rx.wait().expect("error retrieving userid from worker"),
volume: AtomicU16::new(volume),
};
// acquire token for web api usage
@@ -297,6 +311,7 @@ impl Spotify {
player_config: PlayerConfig,
credentials: Credentials,
user_tx: oneshot::Sender<String>,
volume: u16,
) {
let mut core = Core::new().unwrap();
@@ -305,13 +320,20 @@ impl Spotify {
.send(session.username())
.expect("could not pass username back to Spotify::new");
let backend = audio_backend::find(None).unwrap();
let (player, _eventchannel) =
Player::new(player_config, session.clone(), None, move || {
(backend)(None)
});
let create_mixer = librespot_playback::mixer::find(Some("softvol".to_owned()))
.expect("could not create softvol mixer");
let mixer = create_mixer(None);
mixer.set_volume(volume);
let worker = Worker::new(events, commands, session, player);
let backend = audio_backend::find(None).unwrap();
let (player, _eventchannel) = Player::new(
player_config,
session.clone(),
mixer.get_audio_filter(),
move || (backend)(None),
);
let worker = Worker::new(events, commands, session, player, mixer);
debug!("worker thread ready.");
core.run(worker).unwrap();
debug!("worker thread finished.");
@@ -701,6 +723,18 @@ impl Spotify {
let new = (progress.as_secs() * 1000) as i32 + progress.subsec_millis() as i32 + delta;
self.seek(std::cmp::max(0, new) as u32);
}
pub fn volume(&self) -> u16 {
self.volume.load(Ordering::Relaxed) as u16
}
pub fn set_volume(&self, volume: u16) {
info!("setting volume to {}", volume);
self.volume.store(volume, Ordering::Relaxed);
self.channel
.unbounded_send(WorkerCommand::SetVolume(volume))
.unwrap();
}
}
pub enum URIType {

View File

@@ -90,7 +90,11 @@ impl View for StatusBar {
});
let updating = if !*self.library.is_done.read().unwrap() {
if self.use_nerdfont { "\u{f9e5} " } else { "[U] " }
if self.use_nerdfont {
"\u{f9e5} "
} else {
"[U] "
}
} else {
""
};
@@ -119,6 +123,11 @@ impl View for StatusBar {
""
};
let volume = format!(
" [{}%]",
(self.spotify.volume() as f64 / 0xffff as f64 * 100.0) as u16
);
printer.with_color(style_bar_bg, |printer| {
printer.print((0, 0), &"".repeat(printer.size.x));
});
@@ -147,7 +156,8 @@ impl View for StatusBar {
+ repeat
+ shuffle
+ saved
+ &format!("{} / {} ", formatted_elapsed, t.duration_str());
+ &format!("{} / {}", formatted_elapsed, t.duration_str())
+ &volume;
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
printer.with_color(style, |printer| {
@@ -160,7 +170,7 @@ impl View for StatusBar {
printer.print((0, 0), &"".repeat(duration_width + 1));
});
} else {
let right = updating.to_string() + repeat + shuffle;
let right = updating.to_string() + repeat + shuffle + &volume;
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
printer.with_color(style, |printer| {