mirror of
https://github.com/oSumAtrIX/free-librespot.git
synced 2025-12-21 02:44:23 +00:00
- Store and output samples as 32-bit floats instead of 16-bit integers. This provides 24-25 bits of transparency, allowing for 42-48 dB of headroom to do volume control and normalisation without throwing away bits or dropping dynamic range below 96 dB CD quality. - Perform volume control and normalisation in 64-bit arithmetic. - Add a dynamic limiter with configurable threshold, attack time, release or decay time, and steepness for the sigmoid transfer function. This mimics the native Spotify limiter, offering greater dynamic range than the old limiter, that just reduced overall gain to prevent clipping. - Make the configurable threshold also apply to the old limiter, which is still available. Resolves: librespot-org/librespot#608
84 lines
2.4 KiB
Rust
84 lines
2.4 KiB
Rust
use super::{Open, Sink};
|
|
use crate::audio::AudioPacket;
|
|
use jack::{
|
|
AsyncClient, AudioOut, Client, ClientOptions, Control, Port, ProcessHandler, ProcessScope,
|
|
};
|
|
use std::io;
|
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
|
|
|
pub struct JackSink {
|
|
send: SyncSender<f32>,
|
|
// We have to keep hold of this object, or the Sink can't play...
|
|
#[allow(dead_code)]
|
|
active_client: AsyncClient<(), JackData>,
|
|
}
|
|
|
|
pub struct JackData {
|
|
rec: Receiver<f32>,
|
|
port_l: Port<AudioOut>,
|
|
port_r: Port<AudioOut>,
|
|
}
|
|
|
|
impl ProcessHandler for JackData {
|
|
fn process(&mut self, _: &Client, ps: &ProcessScope) -> Control {
|
|
// get output port buffers
|
|
let mut out_r = self.port_r.as_mut_slice(ps);
|
|
let mut out_l = self.port_l.as_mut_slice(ps);
|
|
let buf_r: &mut [f32] = &mut out_r;
|
|
let buf_l: &mut [f32] = &mut out_l;
|
|
// get queue iterator
|
|
let mut queue_iter = self.rec.try_iter();
|
|
|
|
let buf_size = buf_r.len();
|
|
for i in 0..buf_size {
|
|
buf_r[i] = queue_iter.next().unwrap_or(0.0);
|
|
buf_l[i] = queue_iter.next().unwrap_or(0.0);
|
|
}
|
|
Control::Continue
|
|
}
|
|
}
|
|
|
|
impl Open for JackSink {
|
|
fn open(client_name: Option<String>) -> JackSink {
|
|
info!("Using jack sink!");
|
|
|
|
let client_name = client_name.unwrap_or("librespot".to_string());
|
|
let (client, _status) =
|
|
Client::new(&client_name[..], ClientOptions::NO_START_SERVER).unwrap();
|
|
let ch_r = client.register_port("out_0", AudioOut::default()).unwrap();
|
|
let ch_l = client.register_port("out_1", AudioOut::default()).unwrap();
|
|
// buffer for samples from librespot (~10ms)
|
|
let (tx, rx) = sync_channel(2 * 1024 * 4);
|
|
let jack_data = JackData {
|
|
rec: rx,
|
|
port_l: ch_l,
|
|
port_r: ch_r,
|
|
};
|
|
let active_client = AsyncClient::new(client, (), jack_data).unwrap();
|
|
|
|
JackSink {
|
|
send: tx,
|
|
active_client: active_client,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Sink for JackSink {
|
|
fn start(&mut self) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn stop(&mut self) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
|
for s in packet.samples().iter() {
|
|
let res = self.send.send(*s);
|
|
if res.is_err() {
|
|
error!("jackaudio: cannot write to channel");
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|