commit 8fec560bcfdf44d520ef2ac3e11b7010941783ba
Author: G2-Games <ke0bhogsg@gmail.com>
Date:   Fri Jan 19 15:37:19 2024 -0600

    Restructured repository as a workspace

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4a63b78
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "dango-core"
+version = "0.1.1"
+edition = "2021"
+license = "AGPL-3.0-only"
+description = "A music backend that manages storage, querying, and playback of remote and local songs."
+homepage = "https://dangoware.com/dango-music-player"
+documentation = "https://docs.rs/dango-core"
+readme = "README.md"
+repository = "https://github.com/DangoWare/dango-music-player"
+keywords = ["audio", "music"]
+categories = ["multimedia::audio"]
+
+[dependencies]
+file-format = { version = "0.17.3", features = ["reader", "serde"] }
+lofty = "0.14.0"
+rusqlite = { version = "0.29.0", features = ["bundled"] }
+serde = { version = "1.0.164", features = ["derive"] }
+time = "0.3.22"
+toml = "0.7.5"
+walkdir = "2.3.3"
+cpal = "0.15.2"
+heapless = "0.7.16"
+rb = "0.4.1"
+symphonia = { version = "0.5.3", features = ["all-codecs"] }
+serde_json = "1.0.104"
+cue = "2.0.0"
+async-std = "1.12.0"
+async-trait = "0.1.73"
+md-5 = "0.10.5"
+surf = "2.3.2"
+futures = "0.3.28"
+rubato = "0.12.0"
+arrayvec = "0.7.4"
diff --git a/src/bus_control.rs b/src/bus_control.rs
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..3a25b3d
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,24 @@
+pub mod music_tracker {
+    pub mod music_tracker;
+}
+
+pub mod music_storage {
+    pub mod music_db;
+    pub mod playlist;
+}
+
+pub mod music_processor {
+    pub mod music_processor;
+}
+
+pub mod music_player {
+    pub mod music_player;
+    pub mod music_output;
+    pub mod music_resampler;
+}
+
+pub mod music_controller {
+    pub mod music_controller;
+    pub mod config;
+    pub mod init;
+}
\ No newline at end of file
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
new file mode 100644
index 0000000..70ee273
--- /dev/null
+++ b/src/music_controller/config.rs
@@ -0,0 +1,54 @@
+use std::path::PathBuf;
+use std::fs::read_to_string;
+use std::fs;
+
+use serde::{Deserialize, Serialize};
+
+use crate::music_tracker::music_tracker::LastFM;
+
+#[derive(Serialize, Deserialize)]
+pub struct Config {
+    pub db_path: Box<PathBuf>,
+    pub lastfm: Option<LastFM>,
+}
+
+impl Config {
+    // Creates and saves a new config with default values
+    pub fn new(config_file: &PathBuf) -> std::io::Result<Config> {
+        let path = PathBuf::from("./music_database.db3");
+        
+        let config = Config {
+            db_path: Box::new(path),
+            lastfm: None,
+        };
+        config.save(config_file)?;
+        
+        Ok(config)
+    }
+
+    // Loads config from given file path
+    pub fn from(config_file: &PathBuf) -> std::result::Result<Config, toml::de::Error> {
+        return toml::from_str(&read_to_string(config_file)
+            .expect("Failed to initalize music config: File not found!"));
+    }
+    
+    // Saves config to given path
+    // Saves -> temp file, if successful, removes old config, and renames temp to given path
+    pub fn save(&self, config_file: &PathBuf) -> std::io::Result<()> {
+        let toml = toml::to_string_pretty(self).unwrap();
+        
+        let mut temp_file = config_file.clone();
+        temp_file.set_extension("tomltemp");
+        
+        fs::write(&temp_file, toml)?;
+
+        // If configuration file already exists, delete it
+        match fs::metadata(config_file) {
+            Ok(_) => fs::remove_file(config_file)?,
+            Err(_) => {},
+        }
+
+        fs::rename(temp_file, config_file)?;
+        Ok(())
+    }
+}
diff --git a/src/music_controller/init.rs b/src/music_controller/init.rs
new file mode 100644
index 0000000..80b91bf
--- /dev/null
+++ b/src/music_controller/init.rs
@@ -0,0 +1,19 @@
+use std::path::Path;
+use std::fs::File;
+
+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() {  
+
+}
+
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
new file mode 100644
index 0000000..bb1ab66
--- /dev/null
+++ b/src/music_controller/music_controller.rs
@@ -0,0 +1,68 @@
+use std::path::PathBuf;
+
+use rusqlite::Result;
+
+use crate::music_controller::config::Config;
+use crate::music_player::music_player::{MusicPlayer, PlayerStatus, PlayerMessage, DSPMessage};
+use crate::music_processor::music_processor::MusicProcessor;
+use crate::music_storage::music_db::URI;
+
+pub struct MusicController {
+    pub config: Config,
+    music_player: MusicPlayer,
+}
+
+impl MusicController {
+    /// Creates new MusicController with config at given path
+    pub fn new(config_path: &PathBuf) -> Result<MusicController, std::io::Error>{
+        let config = Config::new(config_path)?;
+        let music_player = MusicPlayer::new();
+        
+        let controller = MusicController {
+            config,
+            music_player,
+        };
+        
+        return Ok(controller)
+    }
+        
+    /// Creates new music controller from a config at given path
+    pub fn from(config_path: &PathBuf) -> std::result::Result<MusicController, toml::de::Error> {
+        let config = Config::from(config_path)?;
+        let music_player = MusicPlayer::new();
+        
+        let controller = MusicController {
+            config,
+            music_player,
+        };
+        
+        return Ok(controller)
+    }
+    
+    /// Opens and plays song at given URI
+    pub fn open_song(&mut self, uri: &URI) {
+        self.music_player.open_song(uri);
+    }
+    
+    /// Sends given message to music player
+    pub fn song_control(&mut self, message: PlayerMessage) {
+        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 audio playback volume
+    pub fn get_vol(&mut self) -> f32 {
+        return self.music_player.music_processor.audio_volume;
+    }
+    
+    /// Sets audio playback volume on a scale of 0.0 to 1.0
+    pub fn set_vol(&mut self, volume: f32) {
+        self.music_player.music_processor.audio_volume = volume;
+        self.song_control(PlayerMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone()))));
+    }
+    
+}
diff --git a/src/music_player/music_output.rs b/src/music_player/music_output.rs
new file mode 100644
index 0000000..b774b0f
--- /dev/null
+++ b/src/music_player/music_output.rs
@@ -0,0 +1,167 @@
+use std::{result, thread};
+
+use symphonia::core::audio::{AudioBufferRef, SignalSpec, RawSample, SampleBuffer};
+use symphonia::core::conv::{ConvertibleSample, IntoSample, FromSample};
+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 = ((200 * 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();
+    }
+}
\ No newline at end of file
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
new file mode 100644
index 0000000..1996240
--- /dev/null
+++ b/src/music_player/music_player.rs
@@ -0,0 +1,364 @@
+use std::sync::mpsc::{self, Sender, Receiver};
+use std::thread;
+use std::io::SeekFrom;
+
+use async_std::io::ReadExt;
+use async_std::task;
+
+use symphonia::core::codecs::{CODEC_TYPE_NULL, DecoderOptions, Decoder};
+use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
+use symphonia::core::io::{MediaSourceStream, MediaSource};
+use symphonia::core::meta::MetadataOptions;
+use symphonia::core::probe::Hint;
+use symphonia::core::errors::Error;
+use symphonia::core::units::Time;
+
+use futures::AsyncBufRead;
+
+use crate::music_player::music_output::AudioStream;
+use crate::music_processor::music_processor::MusicProcessor;
+use crate::music_storage::music_db::URI;
+
+// Struct that controls playback of music
+pub struct MusicPlayer {
+    pub music_processor: MusicProcessor,
+    player_status: PlayerStatus,
+    message_sender: Option<Sender<PlayerMessage>>,
+    status_receiver: Option<Receiver<PlayerStatus>>,
+}
+
+#[derive(Clone, Copy)]
+pub enum PlayerStatus {
+    Playing,
+    Paused,
+    Stopped,
+    Error,
+}
+
+pub enum PlayerMessage {
+    Play,
+    Pause,
+    Stop,
+    SeekTo(u64),
+    DSP(DSPMessage)
+}
+
+pub enum DSPMessage {
+    UpdateProcessor(Box<MusicProcessor>)
+}
+
+impl MusicPlayer {
+    pub fn new() -> Self {
+        MusicPlayer {
+            music_processor: MusicProcessor::new(),
+            player_status: PlayerStatus::Stopped,
+            message_sender: None,
+            status_receiver: None,
+        }
+    }
+    
+    // Opens and plays song with given path in separate thread
+    pub fn open_song(&mut self, uri: &URI) {
+        // Creates mpsc channels to communicate with thread
+        let (message_sender, message_receiver) = mpsc::channel();
+        let (status_sender, status_receiver) = mpsc::channel();
+        self.message_sender = Some(message_sender);
+        self.status_receiver = Some(status_receiver);
+        
+        let owned_uri = uri.clone();
+
+        // Creates thread that audio is decoded in
+        thread::spawn(move || {
+            let (mut reader, mut decoder) = MusicPlayer::get_reader_and_dec(&owned_uri);
+            
+            let mut seek_time: Option<u64> = None;
+            
+            let mut audio_output: Option<Box<dyn AudioStream>> = None;
+            
+            let mut music_processor = MusicProcessor::new();
+            
+            'main_decode: loop {    
+                // Handles message received from the MusicPlayer if there is one // TODO: Refactor
+                let received_message = message_receiver.try_recv();
+                match received_message {
+                    Ok(PlayerMessage::Pause) => { 
+                        status_sender.send(PlayerStatus::Paused).unwrap();
+                        // Loops on a blocking message receiver to wait for a play/stop message
+                        'inner_pause: loop {
+                            let message = message_receiver.try_recv();
+                            match message {
+                                Ok(PlayerMessage::Play) => {
+                                    status_sender.send(PlayerStatus::Playing).unwrap();
+                                    break 'inner_pause
+                                },
+                                Ok(PlayerMessage::Stop) => {
+                                    status_sender.send(PlayerStatus::Stopped).unwrap();
+                                    break 'main_decode
+                                },
+                                _ => {},
+                            }
+                        }
+                    },
+                    // Exits main decode loop and subsequently ends thread (?)
+                    Ok(PlayerMessage::Stop) => {
+                        status_sender.send(PlayerStatus::Stopped).unwrap();
+                        break 'main_decode
+                    },
+                    Ok(PlayerMessage::SeekTo(time)) => seek_time = Some(time),
+                    Ok(PlayerMessage::DSP(dsp_message)) => {
+                        match dsp_message {
+                            DSPMessage::UpdateProcessor(new_processor) => music_processor = *new_processor,
+                        }
+                    }
+                    _ => {},
+                } 
+                
+                match seek_time {
+                    Some(time) => {
+                        let seek_to = SeekTo::Time { time: Time::from(time), track_id: Some(0) };
+                        reader.seek(SeekMode::Accurate, seek_to).unwrap();
+                        seek_time = None;
+                    }
+                    None => {} //Nothing to do!
+                }
+                
+                let packet = match reader.next_packet() {
+                    Ok(packet) => packet,
+                    Err(Error::ResetRequired) => panic!(), //TODO,
+                    Err(err) => {
+                        //Unrecoverable?
+                        panic!("{}", err);
+                    }
+                };
+                
+                match 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());
+                        }
+                        
+                        // Handles audio normally provided there is an audio stream
+                        if let Some(ref mut audio_output) = audio_output {
+                            // Changes buffer of the MusicProcessor if the packet has a differing capacity or spec
+                            if music_processor.audio_buffer.capacity() != decoded.capacity() ||music_processor.audio_buffer.spec() != decoded.spec() {
+                                let spec = *decoded.spec();
+                                let duration = decoded.capacity() as u64;
+                                
+                                music_processor.set_buffer(duration, spec);
+                            }
+                            
+                            let transformed_audio = music_processor.process(&decoded);
+                            
+                            // Writes transformed packet to audio out
+                            audio_output.write(transformed_audio).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);
+                    }
+                }
+            }
+        });
+    }
+    
+    fn get_reader_and_dec(uri: &URI) -> (Box<dyn FormatReader>, Box<dyn Decoder>) {
+        // 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) => Box::new(std::fs::File::open(path).expect("Failed to open file")),
+            URI::Remote(_, location) => Box::new(RemoteSource::new(location.as_ref(), &config).unwrap()),
+        };
+        
+        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 mut hint = Hint::new();
+        
+        let probed = symphonia::default::get_probe().format(&hint, mss, &fmt_opts, &meta_opts).expect("Unsupported format");
+        
+        let mut reader  = probed.format;
+        
+        let track = reader.tracks()
+                    .iter()
+                    .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
+                    .expect("no supported audio tracks");
+                    
+        let dec_opts: DecoderOptions = Default::default();
+        
+        let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
+                                                    .expect("unsupported codec");
+        
+        return (reader, decoder);
+    }
+    
+    // Updates status by checking on messages from spawned thread
+    fn update_status(&mut self) {
+        let status = self.status_receiver.as_mut().unwrap().try_recv();
+        if status.is_ok() {
+            self.player_status = status.unwrap();
+            match status.unwrap() {
+                // Removes receiver and sender since spawned thread no longer exists
+                PlayerStatus::Stopped => {
+                    self.status_receiver = None;
+                    self.message_sender = None;
+                }
+                _ => {}
+            }
+        }
+    }
+    
+    // Sends message to spawned thread
+    pub fn send_message(&mut self, message: PlayerMessage) {
+        self.update_status();
+        // Checks that message sender exists before sending a message off
+        if self.message_sender.is_some() {
+            self.message_sender.as_mut().unwrap().send(message).unwrap();
+        }
+    }
+    
+    pub fn get_status(&mut self) -> PlayerStatus {
+        self.update_status();
+        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;
+    }
+}
\ No newline at end of file
diff --git a/src/music_player/music_resampler.rs b/src/music_player/music_resampler.rs
new file mode 100644
index 0000000..f654a17
--- /dev/null
+++ b/src/music_player/music_resampler.rs
@@ -0,0 +1,147 @@
+// 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()));
+    }
+}
\ No newline at end of file
diff --git a/src/music_processor/music_processor.rs b/src/music_processor/music_processor.rs
new file mode 100644
index 0000000..dd7effc
--- /dev/null
+++ b/src/music_processor/music_processor.rs
@@ -0,0 +1,35 @@
+use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, AsAudioBufferRef, SignalSpec};
+
+#[derive(Clone)]
+pub struct MusicProcessor {
+    pub audio_buffer: AudioBuffer<f32>,
+    pub audio_volume: f32,
+}
+
+impl MusicProcessor {
+    /// Returns new MusicProcessor with blank buffer and 100% volume
+    pub fn new() -> Self {
+        MusicProcessor {
+            audio_buffer: AudioBuffer::unused(),
+            audio_volume: 1.0,
+        }
+    }
+    
+    /// Processes audio samples
+    /// 
+    /// Currently only supports transformations of volume
+    pub fn process(&mut self, audio_buffer_ref: &AudioBufferRef) -> AudioBufferRef {
+        audio_buffer_ref.convert(&mut self.audio_buffer);
+        
+        let process = |sample| sample * self.audio_volume;
+        
+        self.audio_buffer.transform(process);
+        
+        return self.audio_buffer.as_audio_buffer_ref();
+    }
+    
+    /// Sets buffer of the MusicProcessor
+    pub fn set_buffer(&mut self, duration: u64, spec: SignalSpec) {
+        self.audio_buffer = AudioBuffer::new(duration, spec);
+    }
+}
\ No newline at end of file
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
new file mode 100644
index 0000000..72d3053
--- /dev/null
+++ b/src/music_storage/music_db.rs
@@ -0,0 +1,456 @@
+use file_format::{FileFormat, Kind};
+use serde::Deserialize;
+use lofty::{Accessor, AudioFile, Probe, TaggedFileExt, ItemKey, ItemValue, TagType};
+use rusqlite::{params, Connection};
+use cue::{cd_text::PTI, cd::CD};
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::time::Duration;
+use time::Date;
+use walkdir::WalkDir;
+
+use crate::music_controller::config::Config;
+
+#[derive(Debug)]
+pub struct Song {
+    pub path: Box<Path>,
+    pub title:  Option<String>,
+    pub album:  Option<String>,
+    tracknum: Option<usize>,
+    pub artist: Option<String>,
+    date:   Option<Date>,
+    genre:  Option<String>,
+    plays:  Option<usize>,
+    favorited: Option<bool>,
+    format: Option<FileFormat>, // TODO: Make this a proper FileFormat eventually
+    duration: Option<Duration>,
+    pub custom_tags: Option<Vec<Tag>>,
+}
+#[derive(Clone)]
+pub enum URI{
+    Local(String),
+    Remote(Service, String),
+}
+
+#[derive(Clone, Copy)]
+pub enum Service {
+    InternetRadio,
+    Spotify,
+    Youtube,
+}
+
+#[derive(Debug)]
+pub struct Playlist {
+    title: String,
+    cover_art: Box<Path>,
+}
+
+pub fn create_db() -> Result<(), rusqlite::Error> {
+    let path = "./music_database.db3";
+    let db_connection = Connection::open(path)?;
+
+    db_connection.pragma_update(None, "synchronous", "0")?;
+    db_connection.pragma_update(None, "journal_mode", "WAL")?;
+
+    // Create the important tables
+    db_connection.execute(
+        "CREATE TABLE music_collection (
+            song_path TEXT PRIMARY KEY,
+            title   TEXT,
+            album   TEXT,
+            tracknum INTEGER,
+            artist  TEXT,
+            date    INTEGER,
+            genre   TEXT,
+            plays   INTEGER,
+            favorited BLOB,
+            format  TEXT,
+            duration INTEGER
+        )",
+        (), // empty list of parameters.
+    )?;
+
+    db_connection.execute(
+        "CREATE TABLE playlists (
+            playlist_name TEXT NOT NULL,
+            song_path   TEXT NOT NULL,
+            FOREIGN KEY(song_path) REFERENCES music_collection(song_path)
+        )",
+        (), // empty list of parameters.
+    )?;
+
+    db_connection.execute(
+        "CREATE TABLE custom_tags (
+            song_path TEXT NOT NULL,
+            tag TEXT NOT NULL,
+            tag_value TEXT,
+            FOREIGN KEY(song_path) REFERENCES music_collection(song_path)
+        )",
+        (), // empty list of parameters.
+    )?;
+
+    Ok(())
+}
+
+fn path_in_db(query_path: &Path, connection: &Connection) -> bool {
+    let query_string = format!("SELECT EXISTS(SELECT 1 FROM music_collection WHERE song_path='{}')", query_path.to_string_lossy());
+
+    let mut query_statement = connection.prepare(&query_string).unwrap();
+    let mut rows = query_statement.query([]).unwrap();
+
+    match rows.next().unwrap() {
+        Some(value) => value.get::<usize, bool>(0).unwrap(),
+        None => false
+    }
+}
+
+/// Parse a cuesheet given a path and a directory it is located in,
+/// returning a Vec of Song objects
+fn parse_cuesheet(
+    cuesheet_path: &Path,
+    current_dir: &PathBuf
+) -> Result<Vec<Song>, Box<dyn std::error::Error>>{
+    let cuesheet = CD::parse_file(cuesheet_path.to_path_buf())?;
+
+    let album = cuesheet.get_cdtext().read(PTI::Title);
+
+    let mut song_list:Vec<Song> = vec![];
+
+    for (index, track) in cuesheet.tracks().iter().enumerate() {
+        let track_string_path = format!("{}/{}", current_dir.to_string_lossy(), track.get_filename());
+        let track_path = Path::new(&track_string_path);
+
+        if !track_path.exists() {continue};
+
+        // Get the format as a string
+        let short_format = match FileFormat::from_file(track_path) {
+            Ok(fmt) => Some(fmt),
+            Err(_) => None
+        };
+
+        let duration = Duration::from_secs(track.get_length().unwrap_or(-1) as u64);
+
+        let custom_index_start = Tag::Custom{
+            tag: String::from("dango_cue_index_start"),
+            tag_value: track.get_index(0).unwrap_or(-1).to_string()
+        };
+        let custom_index_end = Tag::Custom{
+            tag: String::from("dango_cue_index_end"),
+            tag_value: track.get_index(0).unwrap_or(-1).to_string()
+        };
+
+        let custom_tags: Vec<Tag> = vec![custom_index_start, custom_index_end];
+
+        let tags = track.get_cdtext();
+        let cue_song = Song {
+            path: track_path.into(),
+            title: tags.read(PTI::Title),
+            album: album.clone(),
+            tracknum: Some(index + 1),
+            artist: tags.read(PTI::Performer),
+            date: None,
+            genre: tags.read(PTI::Genre),
+            plays: Some(0),
+            favorited: Some(false),
+            format: short_format,
+            duration: Some(duration),
+            custom_tags: Some(custom_tags)
+        };
+
+        song_list.push(cue_song);
+    }
+
+    Ok(song_list)
+}
+
+pub fn find_all_music(
+    config: &Config,
+    target_path: &str,
+) -> Result<(), Box<dyn std::error::Error>> {
+    let db_connection = Connection::open(&*config.db_path)?;
+
+    db_connection.pragma_update(None, "synchronous", "0")?;
+    db_connection.pragma_update(None, "journal_mode", "WAL")?;
+
+    let mut current_dir = PathBuf::new();
+    for entry in WalkDir::new(target_path).follow_links(true).into_iter().filter_map(|e| e.ok()) {
+        let target_file = entry;
+        let is_file = fs::metadata(target_file.path())?.is_file();
+
+        // Ensure the target is a file and not a directory, if it isn't, skip this loop
+        if !is_file {
+            current_dir = target_file.into_path();
+            continue;
+        }
+
+        let format = FileFormat::from_file(target_file.path())?;
+        let extension = target_file
+            .path()
+            .extension()
+            .expect("Could not find file extension");
+
+        // If it's a normal file, add it to the database
+        // if it's a cuesheet, do a bunch of fancy stuff
+        if format.kind() == Kind::Audio {
+            add_file_to_db(target_file.path(), &db_connection)
+        } else if extension.to_ascii_lowercase() == "cue" {
+            // TODO: implement cuesheet support
+            parse_cuesheet(target_file.path(), &current_dir);
+        }
+    }
+
+    // create the indexes after all the data is inserted
+    db_connection.execute(
+        "CREATE INDEX path_index ON music_collection (song_path)", ()
+    )?;
+
+    db_connection.execute(
+        "CREATE INDEX custom_tags_index ON custom_tags (song_path)", ()
+    )?;
+
+    Ok(())
+}
+
+pub fn add_file_to_db(target_file: &Path, connection: &Connection) {
+    // TODO: Fix error handling here
+    let tagged_file = match lofty::read_from_path(target_file) {
+        Ok(tagged_file) => tagged_file,
+
+        Err(_) => match Probe::open(target_file)
+            .expect("ERROR: Bad path provided!")
+            .read() {
+                Ok(tagged_file) => tagged_file,
+
+                Err(_) => return
+            }
+    };
+
+    // Ensure the tags exist, if not, insert blank data
+    let blank_tag = &lofty::Tag::new(TagType::Id3v2);
+    let tag = match tagged_file.primary_tag() {
+        Some(primary_tag) => primary_tag,
+
+        None => match tagged_file.first_tag() {
+            Some(first_tag) => first_tag,
+            None => blank_tag
+        },
+    };
+
+    let mut custom_insert = String::new();
+    let mut loops = 0;
+    for item in tag.items() {
+        let mut custom_key = String::new();
+        match item.key() {
+            ItemKey::TrackArtist |
+            ItemKey::TrackTitle  |
+            ItemKey::AlbumTitle  |
+            ItemKey::Genre       |
+            ItemKey::TrackNumber |
+            ItemKey::Year        |
+            ItemKey::RecordingDate  => continue,
+            ItemKey::Unknown(unknown) => custom_key.push_str(&unknown),
+            custom => custom_key.push_str(&format!("{:?}", custom))
+            // TODO: This is kind of cursed, maybe fix?
+        };
+
+        let custom_value = match item.value() {
+            ItemValue::Text(value) => value,
+            ItemValue::Locator(value) => value,
+            ItemValue::Binary(_) => ""
+        };
+
+        if loops > 0 {
+            custom_insert.push_str(", ");
+        }
+
+        custom_insert.push_str(&format!(" (?1, '{}', '{}')", custom_key.replace("\'", "''"), custom_value.replace("\'", "''")));
+
+        loops += 1;
+    }
+    
+    // Get the format as a string
+    let short_format: Option<String> = match FileFormat::from_file(target_file) {
+        Ok(fmt) => Some(fmt.to_string()),
+        Err(_) => None
+    };
+
+    println!("{}", short_format.as_ref().unwrap());
+    
+    let duration = tagged_file.properties().duration().as_secs().to_string();
+    
+    // TODO: Fix error handling
+    let binding = fs::canonicalize(target_file).unwrap();
+    let abs_path = binding.to_str().unwrap();
+
+    // Add all the info into the music_collection table
+    connection.execute(
+        "INSERT INTO music_collection (
+            song_path,
+            title,
+            album,
+            tracknum,
+            artist,
+            date,
+            genre,
+            plays,
+            favorited,
+            format,
+            duration
+        ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
+        params![abs_path, tag.title(), tag.album(), tag.track(), tag.artist(), tag.year(), tag.genre(), 0, false, short_format, duration],
+    ).unwrap();
+
+    //TODO: Fix this, it's horrible
+    if custom_insert != "" {
+        connection.execute(
+            &format!("INSERT INTO custom_tags ('song_path', 'tag', 'tag_value') VALUES {}", &custom_insert),
+            params![
+                abs_path,
+            ]
+        ).unwrap();
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub enum Tag {
+    SongPath,
+    Title,
+    Album,
+    TrackNum,
+    Artist,
+    Date,
+    Genre,
+    Plays,
+    Favorited,
+    Format,
+    Duration,
+    Custom{tag: String, tag_value: String},
+}
+
+impl Tag {
+    fn as_str(&self) -> &str {
+        match self {
+            Tag::SongPath => "song_path",
+            Tag::Title  => "title",
+            Tag::Album  => "album",
+            Tag::TrackNum => "tracknum",
+            Tag::Artist => "artist",
+            Tag::Date   => "date",
+            Tag::Genre  => "genre",
+            Tag::Plays  => "plays",
+            Tag::Favorited => "favorited",
+            Tag::Format => "format",
+            Tag::Duration => "duration",
+            Tag::Custom{tag, ..} => tag,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum MusicObject {
+    Song(Song),
+    Album(Playlist),
+    Playlist(Playlist),
+}
+
+impl MusicObject {
+    pub fn as_song(&self) -> Option<&Song> {
+        match self {
+            MusicObject::Song(data) => Some(data),
+            _ => None
+        }
+    }
+}
+
+/// Query the database, returning a list of items
+pub fn query(
+    config: &Config,
+    text_input: &String,
+    queried_tags: &Vec<&Tag>,
+    order_by_tags: &Vec<&Tag>,
+) -> Option<Vec<MusicObject>> {
+    let db_connection = Connection::open(&*config.db_path).unwrap();
+
+    // Set up some database settings
+    db_connection.pragma_update(None, "synchronous", "0").unwrap();
+    db_connection.pragma_update(None, "journal_mode", "WAL").unwrap();
+
+    // Build the "WHERE" part of the SQLite query
+    let mut where_string = String::new();
+    let mut loops = 0;
+    for tag in queried_tags {
+        if loops > 0 {
+            where_string.push_str("OR ");
+        }
+
+        match tag {
+            Tag::Custom{tag, ..} => where_string.push_str(&format!("custom_tags.tag = '{tag}' AND custom_tags.tag_value LIKE '{text_input}' ")),
+            Tag::SongPath => where_string.push_str(&format!("music_collection.{} LIKE '{text_input}' ", tag.as_str())),
+            _ => where_string.push_str(&format!("{} LIKE '{text_input}' ", tag.as_str()))
+        }
+
+        loops += 1;
+    }
+
+    // Build the "ORDER BY" part of the SQLite query
+    let mut order_by_string = String::new();
+    let mut loops = 0;
+    for tag in order_by_tags {
+        match tag {
+            Tag::Custom{..} => continue,
+            _ => ()
+        }
+
+        if loops > 0 {
+            order_by_string.push_str(", ");
+        }
+
+        order_by_string.push_str(tag.as_str());
+
+        loops += 1;
+    }
+
+    // Build the final query string
+    let query_string = format!("
+        SELECT music_collection.*, JSON_GROUP_ARRAY(JSON_OBJECT('Custom',JSON_OBJECT('tag', custom_tags.tag, 'tag_value', custom_tags.tag_value))) AS custom_tags
+        FROM music_collection
+        LEFT JOIN custom_tags ON music_collection.song_path = custom_tags.song_path
+        WHERE {where_string}
+        GROUP BY music_collection.song_path
+        ORDER BY {order_by_string}
+    ");
+    
+    let mut query_statement = db_connection.prepare(&query_string).unwrap();
+    let mut rows = query_statement.query([]).unwrap();
+
+    let mut final_result:Vec<MusicObject> = vec![];
+
+    while let Some(row) = rows.next().unwrap() {
+        let custom_tags: Vec<Tag> = match row.get::<usize, String>(11) {
+            Ok(result) => serde_json::from_str(&result).unwrap_or(vec![]),
+            Err(_) => vec![]
+        };
+
+        let file_format: FileFormat = FileFormat::from(row.get::<usize, String>(9).unwrap().as_bytes());
+
+        let new_song = Song {
+            // TODO: Implement proper errors here
+            path:   Path::new(&row.get::<usize, String>(0).unwrap_or("".to_owned())).into(),
+            title:  row.get::<usize, String>(1).ok(),
+            album:  row.get::<usize, String>(2).ok(),
+            tracknum: row.get::<usize, usize>(3).ok(),
+            artist: row.get::<usize, String>(4).ok(),
+            date:   Date::from_calendar_date(row.get::<usize, i32>(5).unwrap_or(0), time::Month::January, 1).ok(), // TODO: Fix this to get the actual date
+            genre:  row.get::<usize, String>(6).ok(),
+            plays:  row.get::<usize, usize>(7).ok(),
+            favorited: row.get::<usize, bool>(8).ok(),
+            format: Some(file_format),
+            duration: Some(Duration::from_secs(row.get::<usize, u64>(10).unwrap_or(0))),
+            custom_tags: Some(custom_tags),
+        };
+
+        final_result.push(MusicObject::Song(new_song));
+    };
+
+    Some(final_result)
+}
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
new file mode 100644
index 0000000..f6fc9b7
--- /dev/null
+++ b/src/music_storage/playlist.rs
@@ -0,0 +1,27 @@
+use std::path::Path;
+use crate::music_controller::config::Config;
+use rusqlite::{params, Connection};
+
+pub fn playlist_add(
+    config: &Config,
+    playlist_name: &str,
+    song_paths: &Vec<&Path>
+) {
+    let db_connection = Connection::open(&*config.db_path).unwrap();
+
+    for song_path in song_paths {
+        db_connection.execute(
+            "INSERT INTO playlists (
+                playlist_name,
+                song_path
+            ) VALUES (
+                ?1,
+                ?2
+            )",
+            params![
+                playlist_name,
+                song_path.to_str().unwrap()
+            ],
+        ).unwrap();
+    }
+}
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
new file mode 100644
index 0000000..93fb5fb
--- /dev/null
+++ b/src/music_tracker/music_tracker.rs
@@ -0,0 +1,186 @@
+use std::time::{SystemTime, UNIX_EPOCH};
+use std::collections::BTreeMap;
+
+use async_trait::async_trait;
+use serde::{Serialize, Deserialize};
+use md5::{Md5, Digest};
+
+#[async_trait]
+pub trait MusicTracker {
+    /// Adds one listen to a song halfway through playback
+    async fn track_song(&self, song: &String) -> Result<(), surf::Error>;
+    
+    /// Adds a 'listening' status to the music tracker service of choice
+    async fn track_now(&self, song: &String) -> Result<(), surf::Error>;
+    
+    /// Reads config files, and attempts authentication with service
+    async fn test_tracker(&self) -> Result<(), surf::Error>;
+    
+    /// Returns plays for a given song according to tracker service
+    async fn get_times_tracked(&self, song: &String) -> Result<u32, surf::Error>;
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct LastFM {
+    dango_api_key: String,
+    auth_token: Option<String>,
+    shared_secret: Option<String>,
+    session_key: Option<String>,
+}
+
+#[async_trait]
+impl MusicTracker for LastFM {
+    async fn track_song(&self, song: &String) -> Result<(), surf::Error> {
+        let mut params: BTreeMap<&str, &str> = BTreeMap::new();
+        
+        // Sets timestamp of song beginning play time
+        let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30;
+        let string_timestamp = timestamp.to_string();
+        params.insert("method", "track.scrobble");
+        params.insert("artist", "Kikuo");
+        params.insert("track", "A Happy Death - Again");
+        params.insert("timestamp", &string_timestamp);
+        
+        self.api_request(params).await?;
+        Ok(())
+    }
+    
+    async fn track_now(&self, song: &String) -> Result<(), surf::Error> {
+        let mut params: BTreeMap<&str, &str> = BTreeMap::new();
+        params.insert("method", "track.updateNowPlaying");
+        params.insert("artist", "Kikuo");
+        params.insert("track", "A Happy Death - Again");
+        self.api_request(params).await?;
+        Ok(())
+    }
+    
+    async fn test_tracker(&self) -> Result<(), surf::Error> {
+        let mut params: BTreeMap<&str, &str> = BTreeMap::new();
+        params.insert("method", "chart.getTopArtists");
+        self.api_request(params).await?;
+        Ok(())
+    }
+    
+    async fn get_times_tracked(&self, song: &String) -> Result<u32, surf::Error> {
+        todo!();
+    }
+}
+
+#[derive(Deserialize, Serialize)]
+struct AuthToken {
+    token: String
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+struct SessionResponse {
+    name: String,
+    key: String,
+    subscriber: i32,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+struct Session {
+    session: SessionResponse
+}
+
+impl LastFM {
+    // Returns a url to be accessed by the user
+    pub async fn get_auth_url(&mut self) -> Result<String, surf::Error> {
+        let method = String::from("auth.gettoken");
+        let api_key = self.dango_api_key.clone();
+        let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&format=json");
+        
+        let auth_token: AuthToken = surf::get(api_request_url).await?.body_json().await?;
+        self.auth_token = Some(auth_token.token.clone());
+        
+        let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token);
+        
+        return Ok(auth_url);
+    }
+    
+    pub async fn set_session(&mut self) {
+        let method = String::from("auth.getSession");
+        let api_key = self.dango_api_key.clone();
+        let auth_token = self.auth_token.clone().unwrap();
+        let shared_secret = self.shared_secret.clone().unwrap();
+        
+        // Creates api_sig as defined in last.fm documentation
+        let api_sig = format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}");
+
+        // Creates insecure MD5 hash for last.fm api sig
+        let mut hasher = Md5::new();
+        hasher.update(api_sig);
+        let hash_result = hasher.finalize();
+        let hex_string_hash = format!("{:#02x}", hash_result);
+        
+        let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&token={auth_token}&api_sig={hex_string_hash}&format=json");
+        
+        let response = surf::get(api_request_url).recv_string().await.unwrap();
+        
+        // Sets session key from received response
+        let session_response: Session = serde_json::from_str(&response).unwrap();
+        self.session_key = Some(session_response.session.key.clone());
+    }
+    
+    // Creates a new LastFM struct
+    pub fn new() -> LastFM {
+        let last_fm = LastFM {
+            // Grab this from config in future
+            dango_api_key: String::from("29a071e3113ab8ed36f069a2d3e20593"),
+            auth_token: None,
+            // Also grab from config in future
+            shared_secret: Some(String::from("5400c554430de5c5002d5e4bcc295b3d")),
+            session_key: None,
+        };
+        return last_fm;
+    }
+    
+    // Creates an api request with the given parameters
+    pub async fn api_request(&self, mut params: BTreeMap<&str, &str>) -> Result<surf::Response, surf::Error> {
+        params.insert("api_key", &self.dango_api_key);
+        params.insert("sk", &self.session_key.as_ref().unwrap());
+        
+        // Creates and sets api call signature
+        let api_sig = LastFM::request_sig(&params, &self.shared_secret.as_ref().unwrap());
+        params.insert("api_sig", &api_sig);
+        let mut string_params = String::from("");
+        
+        // Creates method call string
+        for key in params.keys() {
+            let param_value = params.get(key).unwrap();
+            string_params.push_str(&format!("{key}={param_value}&"));
+        }
+        
+        string_params.pop();
+        
+        let url = "http://ws.audioscrobbler.com/2.0/";
+        
+        let response = surf::post(url).body_string(string_params).await;
+
+        return response;
+    }
+
+    // Returns an api signature as defined in the last.fm api documentation
+    fn request_sig(params: &BTreeMap<&str, &str>, shared_secret: &str) -> String {
+        let mut sig_string = String::new();
+        // Appends keys and values of parameters to the unhashed sig
+        for key in params.keys() {
+            let param_value = params.get(*key);
+            sig_string.push_str(&format!("{key}{}", param_value.unwrap()));
+        }
+        sig_string.push_str(shared_secret);
+        
+        // Hashes signature using **INSECURE** MD5 (Required by last.fm api)
+        let mut md5_hasher = Md5::new();
+        md5_hasher.update(sig_string);
+        let hash_result = md5_hasher.finalize();
+        let hashed_sig = format!("{:#02x}", hash_result);
+    
+        return hashed_sig;
+    }
+    
+    // Removes last.fm account from dango-music-player
+    pub fn reset_account() {
+        todo!();
+    }
+}
\ No newline at end of file