Removed music_player in preparation for GStreamer based backend

This commit is contained in:
G2-Games 2023-11-24 14:22:20 -06:00
parent 12aaab9174
commit ba4ed346ce
8 changed files with 6 additions and 940 deletions

View file

@ -8,14 +8,7 @@ pub mod music_storage {
mod utils; mod utils;
} }
pub mod music_player {
pub mod music_output;
pub mod music_player;
pub mod music_resampler;
}
pub mod music_controller { pub mod music_controller {
pub mod config; pub mod config;
pub mod init;
pub mod music_controller; pub mod music_controller;
} }

View file

@ -1,14 +0,0 @@
use std::fs::File;
use std::path::Path;
pub fn init() {}
fn init_config() {
let config_path = "./config.toml";
if !Path::new(config_path).try_exists().unwrap() {
File::create("./config.toml").unwrap();
}
}
fn init_db() {}

View file

@ -2,20 +2,17 @@ use std::path::PathBuf;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crate::music_controller::config::Config; use crate::music_controller::config::Config;
use crate::music_player::music_player::{DecoderMessage, MusicPlayer, PlayerStatus};
use crate::music_storage::music_db::{MusicLibrary, Song, Tag}; use crate::music_storage::music_db::{MusicLibrary, Song, Tag};
pub struct MusicController { pub struct MusicController {
pub config: Arc<RwLock<Config>>, pub config: Arc<RwLock<Config>>,
pub library: MusicLibrary, pub library: MusicLibrary,
music_player: MusicPlayer,
} }
impl MusicController { impl MusicController {
/// Creates new MusicController with config at given path /// Creates new MusicController with config at given path
pub fn new(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::Error>> { pub fn new(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::Error>> {
let config = Arc::new(RwLock::new(Config::new(config_path)?)); let config = Arc::new(RwLock::new(Config::new(config_path)?));
let music_player = MusicPlayer::new(config.clone());
let library = match MusicLibrary::init(config.clone()) { let library = match MusicLibrary::init(config.clone()) {
Ok(library) => library, Ok(library) => library,
Err(error) => return Err(error), Err(error) => return Err(error),
@ -24,7 +21,6 @@ impl MusicController {
let controller = MusicController { let controller = MusicController {
config, config,
library, library,
music_player,
}; };
return Ok(controller); return Ok(controller);
@ -33,7 +29,6 @@ impl MusicController {
/// Creates new music controller from a config at given path /// Creates new music controller from a config at given path
pub fn from(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::Error>> { pub fn from(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::Error>> {
let config = Arc::new(RwLock::new(Config::from(config_path)?)); let config = Arc::new(RwLock::new(Config::from(config_path)?));
let music_player = MusicPlayer::new(config.clone());
let library = match MusicLibrary::init(config.clone()) { let library = match MusicLibrary::init(config.clone()) {
Ok(library) => library, Ok(library) => library,
Err(error) => return Err(error), Err(error) => return Err(error),
@ -42,33 +37,17 @@ impl MusicController {
let controller = MusicController { let controller = MusicController {
config, config,
library, library,
music_player,
}; };
return Ok(controller); return Ok(controller);
} }
/// Sends given message to control music player
pub fn song_control(&mut self, message: DecoderMessage) {
self.music_player.send_message(message);
}
/// Gets status of the music player
pub fn player_status(&mut self) -> PlayerStatus {
return self.music_player.get_status();
}
/// Gets current song being controlled, if any
pub fn get_current_song(&self) -> Option<Song> {
return self.music_player.get_current_song();
}
/// Queries the [MusicLibrary], returning a `Vec<Song>` /// Queries the [MusicLibrary], returning a `Vec<Song>`
pub fn query_library( pub fn query_library(
&self, &self,
query_string: &String, query_string: &String,
target_tags: Vec<Tag>, target_tags: Vec<Tag>,
search_location: bool, _search_location: bool,
sort_by: Vec<Tag>, sort_by: Vec<Tag>,
) -> Option<Vec<&Song>> { ) -> Option<Vec<&Song>> {
self.library self.library

View file

@ -1,209 +0,0 @@
use std::{result, thread};
use symphonia::core::audio::{AudioBufferRef, RawSample, SampleBuffer, SignalSpec};
use symphonia::core::conv::{ConvertibleSample, FromSample, IntoSample};
use symphonia::core::units::Duration;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{self, SizedSample};
use rb::*;
use crate::music_player::music_resampler::Resampler;
pub trait AudioStream {
fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()>;
fn flush(&mut self);
}
#[derive(Debug)]
pub enum AudioOutputError {
OpenStreamError,
PlayStreamError,
StreamClosedError,
}
pub type Result<T> = result::Result<T, AudioOutputError>;
pub trait OutputSample:
SizedSample
+ FromSample<f32>
+ IntoSample<f32>
+ cpal::Sample
+ ConvertibleSample
+ RawSample
+ std::marker::Send
+ 'static
{
}
pub struct AudioOutput<T>
where
T: OutputSample,
{
ring_buf_producer: rb::Producer<T>,
sample_buf: SampleBuffer<T>,
stream: cpal::Stream,
resampler: Option<Resampler<T>>,
}
impl OutputSample for i8 {}
impl OutputSample for i16 {}
impl OutputSample for i32 {}
//impl OutputSample for i64 {}
impl OutputSample for u8 {}
impl OutputSample for u16 {}
impl OutputSample for u32 {}
//impl OutputSample for u64 {}
impl OutputSample for f32 {}
impl OutputSample for f64 {}
//create a new trait with functions, then impl that somehow
pub fn open_stream(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioStream>> {
let host = cpal::default_host();
// Uses default audio device
let device = match host.default_output_device() {
Some(device) => device,
_ => return Err(AudioOutputError::OpenStreamError),
};
let config = match device.default_output_config() {
Ok(config) => config,
Err(err) => return Err(AudioOutputError::OpenStreamError),
};
return match config.sample_format() {
cpal::SampleFormat::I8 => {
AudioOutput::<i8>::create_stream(spec, &device, &config.into(), duration)
}
cpal::SampleFormat::I16 => {
AudioOutput::<i16>::create_stream(spec, &device, &config.into(), duration)
}
cpal::SampleFormat::I32 => {
AudioOutput::<i32>::create_stream(spec, &device, &config.into(), duration)
}
//cpal::SampleFormat::I64 => AudioOutput::<i64>::create_stream(spec, &device, &config.into(), duration),
cpal::SampleFormat::U8 => {
AudioOutput::<u8>::create_stream(spec, &device, &config.into(), duration)
}
cpal::SampleFormat::U16 => {
AudioOutput::<u16>::create_stream(spec, &device, &config.into(), duration)
}
cpal::SampleFormat::U32 => {
AudioOutput::<u32>::create_stream(spec, &device, &config.into(), duration)
}
//cpal::SampleFormat::U64 => AudioOutput::<u64>::create_stream(spec, &device, &config.into(), duration),
cpal::SampleFormat::F32 => {
AudioOutput::<f32>::create_stream(spec, &device, &config.into(), duration)
}
cpal::SampleFormat::F64 => {
AudioOutput::<f64>::create_stream(spec, &device, &config.into(), duration)
}
_ => todo!(),
};
}
impl<T: OutputSample> AudioOutput<T> {
// Creates the stream (TODO: Merge w/open_stream?)
fn create_stream(
spec: SignalSpec,
device: &cpal::Device,
config: &cpal::StreamConfig,
duration: Duration,
) -> Result<Box<dyn AudioStream>> {
let num_channels = config.channels as usize;
// Ring buffer is created with 200ms audio capacity
let ring_len = ((50 * config.sample_rate.0 as usize) / 1000) * num_channels;
let ring_buf = rb::SpscRb::new(ring_len);
let ring_buf_producer = ring_buf.producer();
let ring_buf_consumer = ring_buf.consumer();
let stream_result = device.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
// Writes samples in the ring buffer to the audio output
let written = ring_buf_consumer.read(data).unwrap_or(0);
// Mutes non-written samples
data[written..]
.iter_mut()
.for_each(|sample| *sample = T::MID);
},
//TODO: Handle error here properly
move |err| println!("Yeah we erroring out here"),
None,
);
if let Err(err) = stream_result {
return Err(AudioOutputError::OpenStreamError);
}
let stream = stream_result.unwrap();
//Start output stream
if let Err(err) = stream.play() {
return Err(AudioOutputError::PlayStreamError);
}
let sample_buf = SampleBuffer::<T>::new(duration, spec);
let mut resampler = None;
if spec.rate != config.sample_rate.0 {
println!("Resampling enabled");
resampler = Some(Resampler::new(
spec,
config.sample_rate.0 as usize,
duration,
))
}
Ok(Box::new(AudioOutput {
ring_buf_producer,
sample_buf,
stream,
resampler,
}))
}
}
impl<T: OutputSample> AudioStream for AudioOutput<T> {
// Writes given samples to ring buffer
fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()> {
if decoded.frames() == 0 {
return Ok(());
}
let mut samples: &[T] = if let Some(resampler) = &mut self.resampler {
// Resamples if required
match resampler.resample(decoded) {
Some(resampled) => resampled,
None => return Ok(()),
}
} else {
self.sample_buf.copy_interleaved_ref(decoded);
self.sample_buf.samples()
};
// Write samples into ring buffer
while let Some(written) = self.ring_buf_producer.write_blocking(samples) {
samples = &samples[written..];
}
Ok(())
}
// Flushes resampler if needed
fn flush(&mut self) {
if let Some(resampler) = &mut self.resampler {
let mut stale_samples = resampler.flush().unwrap_or_default();
while let Some(written) = self.ring_buf_producer.write_blocking(stale_samples) {
stale_samples = &stale_samples[written..];
}
}
let _ = self.stream.pause();
}
}

View file

@ -1,529 +0,0 @@
use std::io::SeekFrom;
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, RwLock};
use std::thread;
use async_std::io::ReadExt;
use async_std::task;
use futures::future::join_all;
use symphonia::core::codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL};
use symphonia::core::errors::Error;
use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
use symphonia::core::io::{MediaSource, MediaSourceStream};
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
use symphonia::core::units::{Time, TimeBase};
use futures::AsyncBufRead;
use crate::music_controller::config::Config;
use crate::music_player::music_output::AudioStream;
use crate::music_storage::music_db::{Song, URI};
use crate::music_tracker::music_tracker::{
DiscordRPC, LastFM, ListenBrainz, MusicTracker, TrackerError,
};
// Struct that controls playback of music
pub struct MusicPlayer {
player_status: PlayerStatus,
music_trackers: Vec<Box<dyn MusicTracker + Send>>,
current_song: Arc<RwLock<Option<Song>>>,
message_sender: Sender<DecoderMessage>,
status_receiver: Receiver<PlayerStatus>,
config: Arc<RwLock<Config>>,
}
#[derive(Clone, Copy, Debug)]
pub enum PlayerStatus {
Playing(f64),
Paused,
Stopped,
Error,
}
#[derive(Debug, Clone)]
pub enum DecoderMessage {
OpenSong(Song),
Play,
Pause,
Stop,
SeekTo(u64),
}
#[derive(Clone)]
pub enum TrackerMessage {
Track(Song),
TrackNow(Song),
}
// Holds a song decoder reader, etc
struct SongHandler {
pub reader: Box<dyn FormatReader>,
pub decoder: Box<dyn Decoder>,
pub time_base: Option<TimeBase>,
pub duration: Option<u64>,
}
// TODO: actual error handling here
impl SongHandler {
pub fn new(uri: &URI) -> Result<Self, ()> {
// Opens remote/local source and creates MediaSource for symphonia
let config = RemoteOptions {
media_buffer_len: 10000,
forward_buffer_len: 10000,
};
let src: Box<dyn MediaSource> = match uri {
URI::Local(path) => match std::fs::File::open(path) {
Ok(file) => Box::new(file),
Err(_) => return Err(()),
},
URI::Remote(_, location) => {
match RemoteSource::new(location.to_str().unwrap(), &config) {
Ok(remote_source) => Box::new(remote_source),
Err(_) => return Err(()),
}
}
_ => todo!(),
};
let mss = MediaSourceStream::new(src, Default::default());
// Use default metadata and format options
let meta_opts: MetadataOptions = Default::default();
let fmt_opts: FormatOptions = Default::default();
let hint = Hint::new();
let probed = symphonia::default::get_probe()
.format(&hint, mss, &fmt_opts, &meta_opts)
.expect("Unsupported format");
let reader = probed.format;
let track = reader
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.expect("no supported audio tracks");
let time_base = track.codec_params.time_base;
let duration = track.codec_params.n_frames;
let dec_opts: DecoderOptions = Default::default();
let decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &dec_opts)
.expect("unsupported codec");
return Ok(SongHandler {
reader,
decoder,
time_base,
duration,
});
}
}
impl MusicPlayer {
pub fn new(config: Arc<RwLock<Config>>) -> Self {
// Creates mpsc channels to communicate with music player threads
let (message_sender, message_receiver) = mpsc::channel();
let (status_sender, status_receiver) = mpsc::channel();
let current_song = Arc::new(RwLock::new(None));
MusicPlayer::start_player(
message_receiver,
status_sender,
config.clone(),
current_song.clone(),
);
MusicPlayer {
music_trackers: Vec::new(),
player_status: PlayerStatus::Stopped,
current_song,
message_sender,
status_receiver,
config,
}
}
fn start_tracker(
status_sender: Sender<Result<(), TrackerError>>,
tracker_receiver: Receiver<TrackerMessage>,
config: Arc<RwLock<Config>>,
) {
thread::spawn(move || {
let global_config = &*config.read().unwrap();
// Sets local config for trackers to detect changes
let local_config = global_config.clone();
let mut trackers: Vec<Box<dyn MusicTracker>> = Vec::new();
// Updates local trackers to the music controller config // TODO: refactor
let update_trackers = |trackers: &mut Vec<Box<dyn MusicTracker>>| {
if let Some(lastfm_config) = global_config.lastfm.clone() {
if lastfm_config.enabled {
trackers.push(Box::new(LastFM::new(&lastfm_config)));
}
}
if let Some(discord_config) = global_config.discord.clone() {
if discord_config.enabled {
trackers.push(Box::new(DiscordRPC::new(&discord_config)));
}
}
if let Some(listenbz_config) = global_config.listenbrainz.clone() {
if listenbz_config.enabled {
trackers.push(Box::new(ListenBrainz::new(&listenbz_config)));
}
}
};
update_trackers(&mut trackers);
loop {
if let message = tracker_receiver.recv() {
if local_config != global_config {
update_trackers(&mut trackers);
}
let mut results = Vec::new();
task::block_on(async {
let mut futures = Vec::new();
for tracker in trackers.iter_mut() {
match message.clone() {
Ok(TrackerMessage::Track(song)) => {
futures.push(tracker.track_song(song))
}
Ok(TrackerMessage::TrackNow(song)) => {
futures.push(tracker.track_now(song))
}
Err(_) => {}
}
}
results = join_all(futures).await;
});
for result in results {
status_sender.send(result).unwrap_or_default()
}
}
}
});
}
// Opens and plays song with given path in separate thread
fn start_player(
message_receiver: Receiver<DecoderMessage>,
status_sender: Sender<PlayerStatus>,
config: Arc<RwLock<Config>>,
current_song: Arc<RwLock<Option<Song>>>,
) {
// Creates thread that audio is decoded in
thread::spawn(move || {
let current_song = current_song;
let mut song_handler = None;
let mut seek_time: Option<u64> = None;
let mut audio_output: Option<Box<dyn AudioStream>> = None;
let (tracker_sender, tracker_receiver): (
Sender<TrackerMessage>,
Receiver<TrackerMessage>,
) = mpsc::channel();
let (tracker_status_sender, tracker_status_receiver): (
Sender<Result<(), TrackerError>>,
Receiver<Result<(), TrackerError>>,
) = mpsc::channel();
MusicPlayer::start_tracker(tracker_status_sender, tracker_receiver, config);
let mut song_tracked = false;
let mut song_time = 0.0;
let mut paused = true;
'main_decode: loop {
'handle_message: loop {
let message = if paused {
// Pauses playback by blocking on waiting for new player messages
match message_receiver.recv() {
Ok(message) => Some(message),
Err(_) => None,
}
} else {
// Resumes playback by not blocking
match message_receiver.try_recv() {
Ok(message) => Some(message),
Err(_) => break 'handle_message,
}
};
// Handles message received from MusicPlayer struct
match message {
Some(DecoderMessage::OpenSong(song)) => {
let song_uri = song.location.clone();
match SongHandler::new(&song_uri) {
Ok(new_handler) => {
song_handler = Some(new_handler);
*current_song.write().unwrap() = Some(song);
paused = false;
song_tracked = false;
}
Err(_) => status_sender.send(PlayerStatus::Error).unwrap(),
}
}
Some(DecoderMessage::Play) => {
if song_handler.is_some() {
paused = false;
}
}
Some(DecoderMessage::Pause) => {
paused = true;
status_sender.send(PlayerStatus::Paused).unwrap();
}
Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time),
// Exits main decode loop and subsequently ends thread
Some(DecoderMessage::Stop) => {
status_sender.send(PlayerStatus::Stopped).unwrap();
break 'main_decode;
}
None => {}
}
status_sender.send(PlayerStatus::Error).unwrap();
}
// In theory this check should not need to occur?
if let (Some(song_handler), current_song) =
(&mut song_handler, &*current_song.read().unwrap())
{
match seek_time {
Some(time) => {
let seek_to = SeekTo::Time {
time: Time::from(time),
track_id: Some(0),
};
song_handler
.reader
.seek(SeekMode::Accurate, seek_to)
.unwrap();
seek_time = None;
}
None => {} //Nothing to do!
}
let packet = match song_handler.reader.next_packet() {
Ok(packet) => packet,
Err(Error::ResetRequired) => panic!(), //TODO,
Err(err) => {
// Unrecoverable?
panic!("{}", err);
}
};
if let (Some(time_base), Some(song)) = (song_handler.time_base, current_song) {
let time_units = time_base.calc_time(packet.ts);
song_time = time_units.seconds as f64 + time_units.frac;
// Tracks song now if song has just started
if song_time == 0.0 {
tracker_sender
.send(TrackerMessage::TrackNow(song.clone()))
.unwrap();
}
if let Some(duration) = song_handler.duration {
let song_duration = time_base.calc_time(duration);
let song_duration_secs =
song_duration.seconds as f64 + song_duration.frac;
// Tracks song if current time is past half of total song duration or past 4 minutes
if (song_duration_secs / 2.0 < song_time || song_time > 240.0)
&& !song_tracked
{
song_tracked = true;
tracker_sender
.send(TrackerMessage::Track(song.clone()))
.unwrap();
}
}
}
status_sender
.send(PlayerStatus::Playing(song_time))
.unwrap();
match song_handler.decoder.decode(&packet) {
Ok(decoded) => {
// Opens audio stream if there is not one
if audio_output.is_none() {
let spec = *decoded.spec();
let duration = decoded.capacity() as u64;
audio_output.replace(
crate::music_player::music_output::open_stream(spec, duration)
.unwrap(),
);
}
}
Err(Error::IoError(_)) => {
// rest in peace packet
continue;
}
Err(Error::DecodeError(_)) => {
// may you one day be decoded
continue;
}
Err(err) => {
// Unrecoverable, though shouldn't panic here
panic!("{}", err);
}
}
}
}
});
}
// Updates status by checking on messages from spawned thread
fn update_player(&mut self) {
for message in self.status_receiver.try_recv() {
self.player_status = message;
}
}
pub fn get_current_song(&self) -> Option<Song> {
match self.current_song.try_read() {
Ok(song) => return (*song).clone(),
Err(_) => return None,
}
}
// Sends message to spawned thread
pub fn send_message(&mut self, message: DecoderMessage) {
self.update_player();
// Checks that message sender exists before sending a message off
self.message_sender.send(message).unwrap();
}
pub fn get_status(&mut self) -> PlayerStatus {
self.update_player();
return self.player_status;
}
}
// TODO: Make the buffer length do anything
/// Options for remote sources
///
/// media_buffer_len is how many bytes are to be buffered in totala
///
/// forward_buffer is how many bytes can ahead of the seek position without the remote source being read from
pub struct RemoteOptions {
media_buffer_len: u64,
forward_buffer_len: u64,
}
impl Default for RemoteOptions {
fn default() -> Self {
RemoteOptions {
media_buffer_len: 100000,
forward_buffer_len: 1024,
}
}
}
/// A remote source of media
struct RemoteSource {
reader: Box<dyn AsyncBufRead + Send + Sync + Unpin>,
media_buffer: Vec<u8>,
forward_buffer_len: u64,
offset: u64,
}
impl RemoteSource {
/// Creates a new RemoteSource with given uri and configuration
pub fn new(uri: &str, config: &RemoteOptions) -> Result<Self, surf::Error> {
let mut response = task::block_on(async {
return surf::get(uri).await;
})?;
let reader = response.take_body().into_reader();
Ok(RemoteSource {
reader,
media_buffer: Vec::new(),
forward_buffer_len: config.forward_buffer_len,
offset: 0,
})
}
}
// TODO: refactor this + buffer into the buffer passed into the function, not a newly allocated one
impl std::io::Read for RemoteSource {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// Reads bytes into the media buffer if the offset is within the specified distance from the end of the buffer
if self.media_buffer.len() as u64 - self.offset < self.forward_buffer_len {
let mut buffer = [0; 1024];
let read_bytes = task::block_on(async {
match self.reader.read_exact(&mut buffer).await {
Ok(_) => {
self.media_buffer.extend_from_slice(&buffer);
return Ok(());
}
Err(err) => return Err(err),
}
});
match read_bytes {
Err(err) => return Err(err),
_ => {}
}
}
// Reads bytes from the media buffer into the buffer given by
let mut bytes_read = 0;
for location in 0..1024 {
if (location + self.offset as usize) < self.media_buffer.len() {
buf[location] = self.media_buffer[location + self.offset as usize];
bytes_read += 1;
}
}
self.offset += bytes_read;
return Ok(bytes_read as usize);
}
}
impl std::io::Seek for RemoteSource {
// Seeks to a given position
// Seeking past the internal buffer's length results in the seeking to the end of content
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
match pos {
// Offset is set to given position
SeekFrom::Start(pos) => {
if pos > self.media_buffer.len() as u64 {
self.offset = self.media_buffer.len() as u64;
} else {
self.offset = pos;
}
return Ok(self.offset);
}
// Offset is set to length of buffer + given position
SeekFrom::End(pos) => {
if self.media_buffer.len() as u64 + pos as u64 > self.media_buffer.len() as u64 {
self.offset = self.media_buffer.len() as u64;
} else {
self.offset = self.media_buffer.len() as u64 + pos as u64;
}
return Ok(self.offset);
}
// Offset is set to current offset + given position
SeekFrom::Current(pos) => {
if self.offset + pos as u64 > self.media_buffer.len() as u64 {
self.offset = self.media_buffer.len() as u64;
} else {
self.offset += pos as u64
}
return Ok(self.offset);
}
}
}
}
impl MediaSource for RemoteSource {
fn is_seekable(&self) -> bool {
return true;
}
fn byte_len(&self) -> Option<u64> {
return None;
}
}

View file

@ -1,154 +0,0 @@
// Symphonia
// Copyright (c) 2019-2022 The Project Symphonia Developers.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, SignalSpec};
use symphonia::core::conv::{FromSample, IntoSample};
use symphonia::core::sample::Sample;
pub struct Resampler<T> {
resampler: rubato::FftFixedIn<f32>,
input: Vec<Vec<f32>>,
output: Vec<Vec<f32>>,
interleaved: Vec<T>,
duration: usize,
}
impl<T> Resampler<T>
where
T: Sample + FromSample<f32> + IntoSample<f32>,
{
fn resample_inner(&mut self) -> &[T] {
{
//let mut input = heapless::Vec::<f32, 32>::new();
let mut input: arrayvec::ArrayVec<&[f32], 32> = Default::default();
for channel in self.input.iter() {
input.push(&channel[..self.duration]);
}
// Resample.
rubato::Resampler::process_into_buffer(
&mut self.resampler,
&input,
&mut self.output,
None,
)
.unwrap();
}
// Remove consumed samples from the input buffer.
for channel in self.input.iter_mut() {
channel.drain(0..self.duration);
}
// Interleave the planar samples from Rubato.
let num_channels = self.output.len();
self.interleaved
.resize(num_channels * self.output[0].len(), T::MID);
for (i, frame) in self.interleaved.chunks_exact_mut(num_channels).enumerate() {
for (ch, s) in frame.iter_mut().enumerate() {
*s = self.output[ch][i].into_sample();
}
}
&self.interleaved
}
}
impl<T> Resampler<T>
where
T: Sample + FromSample<f32> + IntoSample<f32>,
{
pub fn new(spec: SignalSpec, to_sample_rate: usize, duration: u64) -> Self {
let duration = duration as usize;
let num_channels = spec.channels.count();
let resampler = rubato::FftFixedIn::<f32>::new(
spec.rate as usize,
to_sample_rate,
duration,
2,
num_channels,
)
.unwrap();
let output = rubato::Resampler::output_buffer_allocate(&resampler);
let input = vec![Vec::with_capacity(duration); num_channels];
Self {
resampler,
input,
output,
duration,
interleaved: Default::default(),
}
}
/// Resamples a planar/non-interleaved input.
///
/// Returns the resampled samples in an interleaved format.
pub fn resample(&mut self, input: AudioBufferRef<'_>) -> Option<&[T]> {
// Copy and convert samples into input buffer.
convert_samples_any(&input, &mut self.input);
// Check if more samples are required.
if self.input[0].len() < self.duration {
return None;
}
Some(self.resample_inner())
}
/// Resample any remaining samples in the resample buffer.
pub fn flush(&mut self) -> Option<&[T]> {
let len = self.input[0].len();
if len == 0 {
return None;
}
let partial_len = len % self.duration;
if partial_len != 0 {
// Fill each input channel buffer with silence to the next multiple of the resampler
// duration.
for channel in self.input.iter_mut() {
channel.resize(len + (self.duration - partial_len), f32::MID);
}
}
Some(self.resample_inner())
}
}
fn convert_samples_any(input: &AudioBufferRef<'_>, output: &mut [Vec<f32>]) {
match input {
AudioBufferRef::U8(input) => convert_samples(input, output),
AudioBufferRef::U16(input) => convert_samples(input, output),
AudioBufferRef::U24(input) => convert_samples(input, output),
AudioBufferRef::U32(input) => convert_samples(input, output),
AudioBufferRef::S8(input) => convert_samples(input, output),
AudioBufferRef::S16(input) => convert_samples(input, output),
AudioBufferRef::S24(input) => convert_samples(input, output),
AudioBufferRef::S32(input) => convert_samples(input, output),
AudioBufferRef::F32(input) => convert_samples(input, output),
AudioBufferRef::F64(input) => convert_samples(input, output),
}
}
fn convert_samples<S>(input: &AudioBuffer<S>, output: &mut [Vec<f32>])
where
S: Sample + IntoSample<f32>,
{
for (c, dst) in output.iter_mut().enumerate() {
let src = input.chan(c);
dst.extend(src.iter().map(|&s| s.into_sample()));
}
}

View file

@ -1,6 +1,6 @@
use crate::music_controller::config::Config; use crate::music_controller::config::Config;
use std::path::Path; use std::path::Path;
pub fn playlist_add(config: &Config, playlist_name: &str, song_paths: &Vec<&Path>) { pub fn playlist_add(_config: &Config, _playlist_name: &str, _song_paths: &Vec<&Path>) {
unimplemented!() unimplemented!()
} }

View file

@ -123,7 +123,7 @@ impl MusicTracker for LastFM {
}; };
} }
async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> { async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
todo!(); todo!();
} }
} }
@ -319,7 +319,7 @@ impl MusicTracker for DiscordRPC {
} }
} }
async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> { async fn track_song(&mut self, _song: Song) -> Result<(), TrackerError> {
return Ok(()); return Ok(());
} }
@ -327,7 +327,7 @@ impl MusicTracker for DiscordRPC {
return Ok(()); return Ok(());
} }
async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> { async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
return Ok(0); return Ok(0);
} }
} }
@ -408,7 +408,7 @@ impl MusicTracker for ListenBrainz {
async fn test_tracker(&mut self) -> Result<(), TrackerError> { async fn test_tracker(&mut self) -> Result<(), TrackerError> {
todo!() todo!()
} }
async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> { async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
todo!() todo!()
} }
} }