From fd90471264e67f47745f2ee585a76f351305cd70 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 14:02:38 +0930
Subject: [PATCH 01/40] completely reworked player

---
 gonk-player/Cargo.toml                        |   4 +-
 gonk-player/src/buffer.rs                     | 105 ------
 gonk-player/src/conversions/channels.rs       |  94 -----
 gonk-player/src/conversions/mod.rs            |  15 -
 gonk-player/src/conversions/sample.rs         | 155 --------
 gonk-player/src/decoder.rs                    | 262 -------------
 gonk-player/src/dynamic_mixer.rs              | 188 ----------
 gonk-player/src/index.rs                      |   3 -
 gonk-player/src/lib.rs                        | 352 +++++++++---------
 gonk-player/src/queue.rs                      | 239 ------------
 gonk-player/src/sample_processor.rs           | 108 ++++++
 .../src/{conversions => }/sample_rate.rs      | 158 ++------
 gonk-player/src/sink.rs                       | 193 ----------
 gonk-player/src/source/amplify.rs             | 105 ------
 gonk-player/src/source/done.rs                |  98 -----
 gonk-player/src/source/empty.rs               |  64 ----
 gonk-player/src/source/fadein.rs              | 116 ------
 gonk-player/src/source/mod.rs                 | 203 ----------
 gonk-player/src/source/pausable.rs            | 125 -------
 gonk-player/src/source/periodic.rs            | 127 -------
 gonk-player/src/source/samples_converter.rs   |  97 -----
 gonk-player/src/source/stoppable.rs           | 100 -----
 gonk-player/src/source/take.rs                | 186 ---------
 gonk-player/src/source/uniform.rs             | 203 ----------
 gonk-player/src/source/zero.rs                |  67 ----
 gonk-player/src/stream.rs                     | 256 -------------
 gonk/src/app.rs                               |  14 +-
 gonk/src/app/queue.rs                         |  48 ++-
 gonk/src/app/search.rs                        |   2 +-
 gonk/src/app/status_bar.rs                    |  10 +-
 30 files changed, 359 insertions(+), 3338 deletions(-)
 delete mode 100644 gonk-player/src/buffer.rs
 delete mode 100644 gonk-player/src/conversions/channels.rs
 delete mode 100644 gonk-player/src/conversions/mod.rs
 delete mode 100644 gonk-player/src/conversions/sample.rs
 delete mode 100644 gonk-player/src/decoder.rs
 delete mode 100644 gonk-player/src/dynamic_mixer.rs
 delete mode 100644 gonk-player/src/queue.rs
 create mode 100644 gonk-player/src/sample_processor.rs
 rename gonk-player/src/{conversions => }/sample_rate.rs (53%)
 delete mode 100644 gonk-player/src/sink.rs
 delete mode 100644 gonk-player/src/source/amplify.rs
 delete mode 100644 gonk-player/src/source/done.rs
 delete mode 100644 gonk-player/src/source/empty.rs
 delete mode 100644 gonk-player/src/source/fadein.rs
 delete mode 100644 gonk-player/src/source/mod.rs
 delete mode 100644 gonk-player/src/source/pausable.rs
 delete mode 100644 gonk-player/src/source/periodic.rs
 delete mode 100644 gonk-player/src/source/samples_converter.rs
 delete mode 100644 gonk-player/src/source/stoppable.rs
 delete mode 100644 gonk-player/src/source/take.rs
 delete mode 100644 gonk-player/src/source/uniform.rs
 delete mode 100644 gonk-player/src/source/zero.rs
 delete mode 100644 gonk-player/src/stream.rs

diff --git a/gonk-player/Cargo.toml b/gonk-player/Cargo.toml
index ca5fb661..6d494c7f 100644
--- a/gonk-player/Cargo.toml
+++ b/gonk-player/Cargo.toml
@@ -11,5 +11,5 @@ license = "MIT"
 
 [dependencies]
 cpal = "0.13.5"
-rand = "0.8.5"
-symphonia = { version = "0.5.0", features = ["aac", "mp3", "isomp4"] }
+crossbeam-channel = "0.5.4"
+symphonia = "0.5.0"
diff --git a/gonk-player/src/buffer.rs b/gonk-player/src/buffer.rs
deleted file mode 100644
index d1755d3a..00000000
--- a/gonk-player/src/buffer.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-use std::time::Duration;
-use std::vec::IntoIter as VecIntoIter;
-
-use crate::{conversions::Sample, source::Source};
-
-/// A buffer of samples treated as a source.
-pub struct SamplesBuffer<S> {
-    data: VecIntoIter<S>,
-    channels: u16,
-    sample_rate: u32,
-    duration: Duration,
-}
-
-impl<S> SamplesBuffer<S>
-where
-    S: Sample,
-{
-    /// Builds a new `SamplesBuffer`.
-    ///
-    /// # Panic
-    ///
-    /// - Panics if the number of channels is zero.
-    /// - Panics if the samples rate is zero.
-    /// - Panics if the length of the buffer is larger than approximately 16 billion elements.
-    ///   This is because the calculation of the duration would overflow.
-    ///
-    pub fn new<D>(channels: u16, sample_rate: u32, data: D) -> SamplesBuffer<S>
-    where
-        D: Into<Vec<S>>,
-    {
-        assert!(channels != 0);
-        assert!(sample_rate != 0);
-
-        let data = data.into();
-        let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap()
-            / sample_rate as u64
-            / channels as u64;
-        let duration = Duration::new(
-            duration_ns / 1_000_000_000,
-            (duration_ns % 1_000_000_000) as u32,
-        );
-
-        SamplesBuffer {
-            data: data.into_iter(),
-            channels,
-            sample_rate,
-            duration,
-        }
-    }
-}
-
-impl<S> Source for SamplesBuffer<S>
-where
-    S: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        None
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.channels
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.sample_rate
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        Some(self.duration)
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        Duration::from_secs(0)
-    }
-
-    fn seek(&mut self, seek_time: Duration) -> Option<Duration> {
-        let iters = (self.sample_rate as f32 / 1000. * seek_time.as_millis() as f32).round() as u32;
-        for i in 0..iters {
-            self.data.next().ok_or(i).unwrap();
-        }
-        Some(seek_time)
-    }
-}
-
-impl<S> Iterator for SamplesBuffer<S>
-where
-    S: Sample,
-{
-    type Item = S;
-
-    #[inline]
-    fn next(&mut self) -> Option<S> {
-        self.data.next()
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.data.size_hint()
-    }
-}
diff --git a/gonk-player/src/conversions/channels.rs b/gonk-player/src/conversions/channels.rs
deleted file mode 100644
index a9d94d3d..00000000
--- a/gonk-player/src/conversions/channels.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-/// Iterator that converts from a certain channel count to another.
-#[derive(Clone, Debug)]
-pub struct ChannelCountConverter<I>
-where
-    I: Iterator,
-{
-    input: I,
-    from: cpal::ChannelCount,
-    to: cpal::ChannelCount,
-    sample_repeat: Option<I::Item>,
-    next_output_sample_pos: cpal::ChannelCount,
-}
-
-impl<I> ChannelCountConverter<I>
-where
-    I: Iterator,
-{
-    /// Initializes the iterator.
-    ///
-    /// # Panic
-    ///
-    /// Panicks if `from` or `to` are equal to 0.
-    ///
-    #[inline]
-    pub fn new(
-        input: I,
-        from: cpal::ChannelCount,
-        to: cpal::ChannelCount,
-    ) -> ChannelCountConverter<I> {
-        assert!(from >= 1);
-        assert!(to >= 1);
-
-        ChannelCountConverter {
-            input,
-            from,
-            to,
-            sample_repeat: None,
-            next_output_sample_pos: 0,
-        }
-    }
-
-    /// Destroys this iterator and returns the underlying iterator.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I> Iterator for ChannelCountConverter<I>
-where
-    I: Iterator,
-    I::Item: Clone,
-{
-    type Item = I::Item;
-
-    fn next(&mut self) -> Option<I::Item> {
-        let result = if self.next_output_sample_pos == self.from - 1 {
-            let value = self.input.next();
-            self.sample_repeat = value.clone();
-            value
-        } else if self.next_output_sample_pos < self.from {
-            self.input.next()
-        } else {
-            self.sample_repeat.clone()
-        };
-
-        self.next_output_sample_pos += 1;
-
-        if self.next_output_sample_pos == self.to {
-            self.next_output_sample_pos -= self.to;
-
-            if self.from > self.to {
-                for _ in self.to..self.from {
-                    self.input.next(); // discarding extra input
-                }
-            }
-        }
-
-        result
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        let (min, max) = self.input.size_hint();
-
-        let min =
-            (min / self.from as usize) * self.to as usize + self.next_output_sample_pos as usize;
-        let max = max.map(|max| {
-            (max / self.from as usize) * self.to as usize + self.next_output_sample_pos as usize
-        });
-
-        (min, max)
-    }
-}
diff --git a/gonk-player/src/conversions/mod.rs b/gonk-player/src/conversions/mod.rs
deleted file mode 100644
index a842ccd0..00000000
--- a/gonk-player/src/conversions/mod.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-/*!
-This module contains function that will convert from one PCM format to another.
-
-This includes conversion between sample formats, channels or sample rates.
-
-*/
-
-pub use self::channels::ChannelCountConverter;
-pub use self::sample::DataConverter;
-pub use self::sample::Sample;
-pub use self::sample_rate::SampleRateConverter;
-
-mod channels;
-mod sample;
-mod sample_rate;
diff --git a/gonk-player/src/conversions/sample.rs b/gonk-player/src/conversions/sample.rs
deleted file mode 100644
index b242a19d..00000000
--- a/gonk-player/src/conversions/sample.rs
+++ /dev/null
@@ -1,155 +0,0 @@
-use cpal::Sample as CpalSample;
-use std::marker::PhantomData;
-
-/// Converts the samples data type to `O`.
-#[derive(Clone, Debug)]
-pub struct DataConverter<I, O> {
-    input: I,
-    marker: PhantomData<O>,
-}
-
-impl<I, O> DataConverter<I, O> {
-    /// Builds a new converter.
-    #[inline]
-    pub fn new(input: I) -> DataConverter<I, O> {
-        DataConverter {
-            input,
-            marker: PhantomData,
-        }
-    }
-
-    /// Destroys this iterator and returns the underlying iterator.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I, O> Iterator for DataConverter<I, O>
-where
-    I: Iterator,
-    I::Item: Sample,
-    O: Sample,
-{
-    type Item = O;
-
-    #[inline]
-    fn next(&mut self) -> Option<O> {
-        self.input.next().map(|s| CpalSample::from(&s))
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.input.size_hint()
-    }
-}
-
-impl<I, O> ExactSizeIterator for DataConverter<I, O>
-where
-    I: ExactSizeIterator,
-    I::Item: Sample,
-    O: Sample,
-{
-}
-
-/// Represents a value of a single sample.
-///
-/// This trait is implemented by default on three types: `i16`, `u16` and `f32`.
-///
-/// - For `i16`, silence corresponds to the value `0`. The minimum and maximum amplitudes are
-///   represented by `i16::min_value()` and `i16::max_value()` respectively.
-/// - For `u16`, silence corresponds to the value `u16::max_value() / 2`. The minimum and maximum
-///   amplitudes are represented by `0` and `u16::max_value()` respectively.
-/// - For `f32`, silence corresponds to the value `0.0`. The minimum and maximum amplitudes are
-///  represented by `-1.0` and `1.0` respectively.
-///
-/// You can implement this trait on your own type as well if you wish so.
-///
-pub trait Sample: CpalSample {
-    /// Linear interpolation between two samples.
-    ///
-    /// The result should be equal to
-    /// `first * numerator / denominator + second * (1 - numerator / denominator)`.
-    fn lerp(first: Self, second: Self, numerator: u32, denominator: u32) -> Self;
-    /// Multiplies the value of this sample by the given amount.
-    #[must_use]
-    fn amplify(self, value: f32) -> Self;
-
-    /// Calls `saturating_add` on the sample.
-    #[must_use]
-    fn saturating_add(self, other: Self) -> Self;
-
-    /// Returns the value corresponding to the absence of sound.
-    fn zero_value() -> Self;
-}
-
-impl Sample for u16 {
-    #[inline]
-    fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 {
-        let a = first as i32;
-        let b = second as i32;
-        let n = numerator as i32;
-        let d = denominator as i32;
-        (a + (b - a) * n / d) as u16
-    }
-
-    #[inline]
-    fn amplify(self, value: f32) -> u16 {
-        self.to_i16().amplify(value).to_u16()
-    }
-
-    #[inline]
-    fn saturating_add(self, other: u16) -> u16 {
-        self.saturating_add(other)
-    }
-
-    #[inline]
-    fn zero_value() -> u16 {
-        32768
-    }
-}
-
-impl Sample for i16 {
-    #[inline]
-    fn lerp(first: i16, second: i16, numerator: u32, denominator: u32) -> i16 {
-        (first as i32 + (second as i32 - first as i32) * numerator as i32 / denominator as i32)
-            as i16
-    }
-
-    #[inline]
-    fn amplify(self, value: f32) -> i16 {
-        ((self as f32) * value) as i16
-    }
-
-    #[inline]
-    fn saturating_add(self, other: i16) -> i16 {
-        self.saturating_add(other)
-    }
-
-    #[inline]
-    fn zero_value() -> i16 {
-        0
-    }
-}
-
-impl Sample for f32 {
-    #[inline]
-    fn lerp(first: f32, second: f32, numerator: u32, denominator: u32) -> f32 {
-        first + (second - first) * numerator as f32 / denominator as f32
-    }
-
-    #[inline]
-    fn amplify(self, value: f32) -> f32 {
-        self * value
-    }
-
-    #[inline]
-    fn saturating_add(self, other: f32) -> f32 {
-        self + other
-    }
-
-    #[inline]
-    fn zero_value() -> f32 {
-        0.0
-    }
-}
diff --git a/gonk-player/src/decoder.rs b/gonk-player/src/decoder.rs
deleted file mode 100644
index 26e2fc34..00000000
--- a/gonk-player/src/decoder.rs
+++ /dev/null
@@ -1,262 +0,0 @@
-use crate::source::Source;
-use std::{fmt, fs::File, time::Duration};
-use symphonia::{
-    core::{
-        audio::{AudioBufferRef, SampleBuffer, SignalSpec},
-        codecs::{self, CodecParameters},
-        errors::Error,
-        formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
-        io::MediaSourceStream,
-        meta::MetadataOptions,
-        probe::Hint,
-        units::{Time, TimeBase},
-    },
-    default::get_probe,
-};
-// Decoder errors are not considered fatal.
-// The correct action is to just get a new packet and try again.
-// But a decode error in more than 3 consecutive packets is fatal.
-const MAX_DECODE_ERRORS: usize = 3;
-
-pub struct Decoder {
-    decoder: Box<dyn codecs::Decoder>,
-    current_frame_offset: usize,
-    format: Box<dyn FormatReader>,
-    buffer: SampleBuffer<i16>,
-    spec: SignalSpec,
-    duration: Duration,
-    elapsed: Duration,
-}
-
-impl Decoder {
-    pub fn new(file: File) -> Result<Self, DecoderError> {
-        let source = Box::new(file);
-
-        let mss = MediaSourceStream::new(source, Default::default());
-        match Decoder::init(mss) {
-            Err(e) => match e {
-                Error::IoError(e) => Err(DecoderError::IoError(e.to_string())),
-                Error::DecodeError(e) => Err(DecoderError::DecodeError(e)),
-                Error::SeekError(_) => {
-                    unreachable!("Seek errors should not occur during initialization")
-                }
-                Error::Unsupported(_) => Err(DecoderError::UnrecognizedFormat),
-                Error::LimitError(e) => Err(DecoderError::LimitError(e)),
-                Error::ResetRequired => Err(DecoderError::ResetRequired),
-            },
-            Ok(Some(decoder)) => Ok(decoder),
-            Ok(None) => Err(DecoderError::NoStreams),
-        }
-    }
-    fn init(mss: MediaSourceStream) -> symphonia::core::errors::Result<Option<Decoder>> {
-        let mut probed = get_probe().format(
-            &Hint::default(),
-            mss,
-            &FormatOptions {
-                prebuild_seek_index: true,
-                seek_index_fill_rate: 10,
-                enable_gapless: false,
-            },
-            &MetadataOptions::default(),
-        )?;
-
-        let track = match probed.format.default_track() {
-            Some(stream) => stream,
-            None => return Ok(None),
-        };
-
-        let mut decoder = symphonia::default::get_codecs().make(
-            &track.codec_params,
-            &codecs::DecoderOptions { verify: true },
-        )?;
-
-        let duration = Decoder::get_duration(&track.codec_params);
-
-        let mut decode_errors: usize = 0;
-        let decoded = loop {
-            let current_frame = probed.format.next_packet()?;
-            match decoder.decode(&current_frame) {
-                Ok(decoded) => break decoded,
-                Err(e) => match e {
-                    Error::DecodeError(_) => {
-                        decode_errors += 1;
-                        if decode_errors > MAX_DECODE_ERRORS {
-                            return Err(e);
-                        } else {
-                            continue;
-                        }
-                    }
-                    _ => return Err(e),
-                },
-            }
-        };
-        let spec = decoded.spec().to_owned();
-        let buffer = Decoder::get_buffer(decoded, &spec);
-
-        Ok(Some(Decoder {
-            decoder,
-            current_frame_offset: 0,
-            format: probed.format,
-            buffer,
-            spec,
-            duration,
-            elapsed: Duration::from_secs(0),
-        }))
-    }
-
-    fn get_duration(params: &CodecParameters) -> Duration {
-        if let Some(n_frames) = params.n_frames {
-            if let Some(tb) = params.time_base {
-                let time = tb.calc_time(n_frames);
-                Duration::from_secs(time.seconds) + Duration::from_secs_f64(time.frac)
-            } else {
-                panic!("no time base?");
-            }
-        } else {
-            panic!("no n_frames");
-        }
-    }
-
-    #[inline]
-    fn get_buffer(decoded: AudioBufferRef, spec: &SignalSpec) -> SampleBuffer<i16> {
-        let duration = decoded.capacity() as u64;
-        let mut buffer = SampleBuffer::<i16>::new(duration, *spec);
-        buffer.copy_interleaved_ref(decoded);
-        buffer
-    }
-}
-
-impl Source for Decoder {
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        Some(self.buffer.samples().len())
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.spec.channels.count() as u16
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.spec.rate
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        Some(self.duration)
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.elapsed
-    }
-
-    #[inline]
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        let nanos_per_sec = 1_000_000_000.0;
-        match self.format.seek(
-            SeekMode::Coarse,
-            SeekTo::Time {
-                time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / nanos_per_sec),
-                track_id: None,
-            },
-        ) {
-            Ok(seeked_to) => {
-                let base = TimeBase::new(1, self.sample_rate());
-                let time = base.calc_time(seeked_to.actual_ts);
-
-                Some(Duration::from_millis(
-                    time.seconds * 1000 + ((time.frac * 60. * 1000.).round() as u64),
-                ))
-            }
-            Err(_) => None,
-        }
-    }
-}
-
-impl Iterator for Decoder {
-    type Item = i16;
-
-    #[inline]
-    fn next(&mut self) -> Option<i16> {
-        if self.current_frame_offset == self.buffer.len() {
-            let mut decode_errors: usize = 0;
-            let decoded = loop {
-                match self.format.next_packet() {
-                    Ok(packet) => match self.decoder.decode(&packet) {
-                        Ok(decoded) => {
-                            let ts = packet.ts();
-                            if let Some(track) = self.format.default_track() {
-                                if let Some(tb) = track.codec_params.time_base {
-                                    let t = tb.calc_time(ts);
-                                    self.elapsed = Duration::from_secs(t.seconds)
-                                        + Duration::from_secs_f64(t.frac);
-                                }
-                            }
-                            break decoded;
-                        }
-                        Err(e) => match e {
-                            Error::DecodeError(_) => {
-                                decode_errors += 1;
-                                if decode_errors > MAX_DECODE_ERRORS {
-                                    return None;
-                                } else {
-                                    continue;
-                                }
-                            }
-                            _ => return None,
-                        },
-                    },
-                    Err(_) => return None,
-                }
-            };
-            self.spec = decoded.spec().to_owned();
-            self.buffer = Decoder::get_buffer(decoded, &self.spec);
-            self.current_frame_offset = 0;
-        }
-
-        let sample = self.buffer.samples()[self.current_frame_offset];
-        self.current_frame_offset += 1;
-
-        Some(sample)
-    }
-}
-
-/// Error that can happen when creating a decoder.
-#[derive(Debug, Clone)]
-pub enum DecoderError {
-    /// The format of the data has not been recognized.
-    UnrecognizedFormat,
-
-    /// An IO error occured while reading, writing, or seeking the stream.
-    IoError(String),
-
-    /// The stream contained malformed data and could not be decoded or demuxed.
-    DecodeError(&'static str),
-
-    /// A default or user-defined limit was reached while decoding or demuxing the stream. Limits
-    /// are used to prevent denial-of-service attacks from malicious streams.
-    LimitError(&'static str),
-
-    /// The demuxer or decoder needs to be reset before continuing.
-    ResetRequired,
-
-    /// No streams were found by the decoder
-    NoStreams,
-}
-
-impl fmt::Display for DecoderError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let text = match self {
-            DecoderError::UnrecognizedFormat => "Unrecognized format",
-            DecoderError::IoError(msg) => &msg[..],
-            DecoderError::DecodeError(msg) => msg,
-            DecoderError::LimitError(msg) => msg,
-            DecoderError::ResetRequired => "Reset required",
-            DecoderError::NoStreams => "No streams",
-        };
-        write!(f, "{}", text)
-    }
-}
-impl std::error::Error for DecoderError {}
diff --git a/gonk-player/src/dynamic_mixer.rs b/gonk-player/src/dynamic_mixer.rs
deleted file mode 100644
index dd99a79d..00000000
--- a/gonk-player/src/dynamic_mixer.rs
+++ /dev/null
@@ -1,188 +0,0 @@
-//! Mixer that plays multiple sounds at the same time.
-
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::{Arc, Mutex};
-use std::time::Duration;
-
-use crate::conversions::Sample;
-use crate::source::{Source, UniformSourceIterator};
-
-/// Builds a new mixer.
-///
-/// You can choose the characteristics of the output thanks to this constructor. All the sounds
-/// added to the mixer will be converted to these values.
-///
-/// After creating a mixer, you can add new sounds with the controller.
-pub fn mixer<S>(
-    channels: u16,
-    sample_rate: u32,
-) -> (Arc<DynamicMixerController<S>>, DynamicMixer<S>)
-where
-    S: Sample + Send + 'static,
-{
-    let input = Arc::new(DynamicMixerController {
-        has_pending: AtomicBool::new(false),
-        pending_sources: Mutex::new(Vec::new()),
-        channels,
-        sample_rate,
-    });
-
-    let output = DynamicMixer {
-        current_sources: Vec::with_capacity(16),
-        input: input.clone(),
-        sample_count: 0,
-        still_pending: vec![],
-        still_current: vec![],
-    };
-
-    (input, output)
-}
-
-/// The input of the mixer.
-pub struct DynamicMixerController<S> {
-    has_pending: AtomicBool,
-    pending_sources: Mutex<Vec<Box<dyn Source<Item = S> + Send>>>,
-    channels: u16,
-    sample_rate: u32,
-}
-
-impl<S> DynamicMixerController<S>
-where
-    S: Sample + Send + 'static,
-{
-    /// Adds a new source to mix to the existing ones.
-    #[inline]
-    pub fn add<T>(&self, source: T)
-    where
-        T: Source<Item = S> + Send + 'static,
-    {
-        let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate);
-        self.pending_sources
-            .lock()
-            .unwrap()
-            .push(Box::new(uniform_source) as Box<_>);
-        self.has_pending.store(true, Ordering::SeqCst); // TODO: can we relax this ordering?
-    }
-}
-
-/// The output of the mixer. Implements `Source`.
-pub struct DynamicMixer<S> {
-    // The current iterator that produces samples.
-    current_sources: Vec<Box<dyn Source<Item = S> + Send>>,
-
-    // The pending sounds.
-    input: Arc<DynamicMixerController<S>>,
-
-    // The number of samples produced so far.
-    sample_count: usize,
-
-    // A temporary vec used in start_pending_sources.
-    still_pending: Vec<Box<dyn Source<Item = S> + Send>>,
-
-    // A temporary vec used in sum_current_sources.
-    still_current: Vec<Box<dyn Source<Item = S> + Send>>,
-}
-
-impl<S> Source for DynamicMixer<S>
-where
-    S: Sample + Send + 'static,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        None
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        None
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        Duration::from_secs(0)
-    }
-
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.current_sources[0].seek(time)
-    }
-}
-
-impl<S> Iterator for DynamicMixer<S>
-where
-    S: Sample + Send + 'static,
-{
-    type Item = S;
-
-    #[inline]
-    fn next(&mut self) -> Option<S> {
-        if self.input.has_pending.load(Ordering::SeqCst) {
-            self.start_pending_sources();
-        }
-
-        self.sample_count += 1;
-
-        let sum = self.sum_current_sources();
-
-        if self.current_sources.is_empty() {
-            None
-        } else {
-            Some(sum)
-        }
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        (0, None)
-    }
-}
-
-impl<S> DynamicMixer<S>
-where
-    S: Sample + Send + 'static,
-{
-    // Samples from the #next() function are interlaced for each of the channels.
-    // We need to ensure we start playing sources so that their samples are
-    // in-step with the modulo of the samples produced so far. Otherwise, the
-    // sound will play on the wrong channels, e.g. left / right will be reversed.
-    fn start_pending_sources(&mut self) {
-        let mut pending = self.input.pending_sources.lock().unwrap(); // TODO: relax ordering?
-
-        for source in pending.drain(..) {
-            let in_step = self.sample_count % source.channels() as usize == 0;
-
-            if in_step {
-                self.current_sources.push(source);
-            } else {
-                self.still_pending.push(source);
-            }
-        }
-        std::mem::swap(&mut self.still_pending, &mut pending);
-
-        let has_pending = !pending.is_empty();
-        self.input.has_pending.store(has_pending, Ordering::SeqCst); // TODO: relax ordering?
-    }
-
-    fn sum_current_sources(&mut self) -> S {
-        let mut sum = S::zero_value();
-
-        for mut source in self.current_sources.drain(..) {
-            if let Some(value) = source.next() {
-                sum = sum.saturating_add(value);
-                self.still_current.push(source);
-            }
-        }
-        std::mem::swap(&mut self.still_current, &mut self.current_sources);
-
-        sum
-    }
-}
diff --git a/gonk-player/src/index.rs b/gonk-player/src/index.rs
index 595a5244..e779c135 100644
--- a/gonk-player/src/index.rs
+++ b/gonk-player/src/index.rs
@@ -69,9 +69,6 @@ impl<T> Index<T> {
     pub fn select(&mut self, i: Option<usize>) {
         self.index = i;
     }
-    pub fn is_none(&self) -> bool {
-        self.index.is_none()
-    }
     pub fn is_empty(&self) -> bool {
         self.data.is_empty()
     }
diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 87fd968a..a24cb50a 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -1,150 +1,90 @@
-#![allow(dead_code)]
-use cpal::traits::HostTrait;
-pub use cpal::{
-    self, traits::DeviceTrait, Device, Devices, DevicesError, InputDevices, OutputDevices,
-    SupportedStreamConfig,
+use cpal::{
+    traits::{HostTrait, StreamTrait},
+    StreamError,
+};
+use crossbeam_channel::{unbounded, Receiver, Sender};
+use sample_processor::SampleProcessor;
+use std::{
+    path::PathBuf,
+    sync::{Arc, RwLock},
+    thread,
 };
-use decoder::Decoder;
-use rand::prelude::SliceRandom;
-use rand::thread_rng;
-use sink::Sink;
-use source::Source;
-use std::fs::File;
-use std::time::Duration;
-use stream::{OutputStream, OutputStreamHandle};
-
-mod buffer;
-mod conversions;
-mod decoder;
-mod dynamic_mixer;
-mod queue;
-mod sink;
-mod source;
-mod stream;
 
-pub mod index;
-pub mod song;
+mod index;
+mod sample_processor;
+mod sample_rate;
+mod song;
 
+pub use cpal::traits::DeviceTrait;
+pub use cpal::Device;
 pub use index::Index;
 pub use song::Song;
 
-const VOLUME_STEP: u16 = 5;
-const VOLUME_REDUCTION: f32 = 600.0;
+#[derive(Debug)]
+pub enum Event {
+    Play,
+    Pause,
+    Stop,
+    SeekBy(f32),
+    SeekTo(f32),
+}
 
 pub struct Player {
-    stream: OutputStream,
-    handle: OutputStreamHandle,
-    sink: Sink,
-    pub duration: f64,
-    pub volume: u16,
-    pub songs: Index<Song>,
+    s: Sender<Event>,
+    r: Receiver<Event>,
+    playing: bool,
+    volume: u16,
+    songs: Index<Song>,
 }
 
 impl Player {
     pub fn new(volume: u16) -> Self {
-        let (stream, handle) =
-            OutputStream::try_default().expect("Could not create output stream.");
-        let sink = Sink::try_new(&handle).unwrap();
-        sink.set_volume(volume as f32 / VOLUME_REDUCTION);
-
+        let (s, r) = unbounded();
         Self {
-            stream,
-            handle,
-            sink,
-            duration: 0.0,
+            s,
+            r,
+            playing: true,
             volume,
             songs: Index::default(),
         }
     }
+    pub fn update(&mut self) {
+        // if self.elapsed() > self.duration {
+        //     self.next_song();
+        // }
+    }
+    pub fn duration(&self) -> f32 {
+        0.0
+    }
     pub fn is_empty(&self) -> bool {
         self.songs.is_empty()
     }
-    pub fn add_songs(&mut self, song: &[Song]) {
-        self.songs.data.extend(song.to_vec());
-        if self.songs.is_none() && !self.songs.is_empty() {
+    pub fn add_songs(&mut self, songs: &[Song]) {
+        self.songs.data.extend(songs.to_vec());
+        if self.songs.selected().is_none() {
             self.songs.select(Some(0));
             self.play_selected();
         }
     }
-    pub fn play_song(&mut self, i: usize) {
-        if self.songs.data.get(i).is_some() {
-            self.songs.select(Some(i));
-            self.play_selected();
-        };
+    pub fn get_volume(&self) -> u16 {
+        self.volume
     }
-    pub fn clear(&mut self) {
-        self.songs = Index::default();
-        self.stop();
-    }
-    //TODO: might remove this?
-    pub fn clear_except_playing(&mut self) {
-        let selected = self.songs.selected().cloned();
-        let mut i = 0;
-        while i < self.songs.len() {
-            if Some(&self.songs.data[i]) != selected.as_ref() {
-                self.songs.data.remove(i);
-            } else {
-                i += 1;
+    pub fn play_selected(&mut self) {
+        if let Some(song) = self.songs.selected() {
+            if self.playing {
+                self.stop();
             }
+            self.playing = true;
+            let r2 = self.r.clone();
+            Player::run(r2, song.path.clone());
         }
-        self.songs.select(Some(0));
-    }
-    pub fn prev_song(&mut self) {
-        self.songs.up();
-        self.play_selected();
     }
-    pub fn next_song(&mut self) {
-        self.songs.down();
+    pub fn play_index(&mut self, i: usize) {
+        self.songs.select(Some(i));
         self.play_selected();
     }
-    pub fn volume_up(&mut self) {
-        self.volume += VOLUME_STEP;
-
-        if self.volume > 100 {
-            self.volume = 100;
-        }
-
-        self.update_volume();
-    }
-    pub fn volume_down(&mut self) {
-        if self.volume != 0 {
-            self.volume -= VOLUME_STEP;
-        }
-
-        self.update_volume();
-    }
-    fn update_volume(&self) {
-        if let Some(song) = self.songs.selected() {
-            let volume = self.volume as f32 / VOLUME_REDUCTION;
-
-            //Calculate the volume with gain
-            let volume = if song.track_gain == 0.0 {
-                //Reduce the volume a little to match
-                //songs with replay gain information.
-                volume * 0.75
-            } else {
-                volume * song.track_gain as f32
-            };
-
-            self.sink.set_volume(volume);
-        } else {
-            self.sink.set_volume(self.volume as f32 / VOLUME_REDUCTION);
-        }
-    }
-    pub fn play_selected(&mut self) {
-        if let Some(song) = self.songs.selected().cloned() {
-            self.stop();
-            let file = File::open(&song.path).expect("Could not open song.");
-            let decoder = Decoder::new(file).unwrap();
-
-            //FIXME: The duration is slightly off for some reason.
-            self.duration = decoder.total_duration().unwrap().as_secs_f64() - 0.29;
-            self.sink.append(decoder);
-            self.update_volume();
-        }
-    }
-    pub fn delete_song(&mut self, selected: usize) {
-        self.songs.data.remove(selected);
+    pub fn delete_index(&mut self, i: usize) {
+        self.songs.data.remove(i);
 
         if let Some(playing) = self.songs.index() {
             let len = self.songs.len();
@@ -153,63 +93,120 @@ impl Player {
                 return self.clear();
             }
 
-            if selected == playing && selected == 0 {
-                if selected == 0 {
+            if i == playing && i == 0 {
+                if i == 0 {
                     self.songs.select(Some(0));
                 }
                 self.play_selected();
-            } else if selected == playing && selected == len {
+            } else if i == playing && i == len {
                 self.songs.select(Some(len - 1));
-            } else if selected < playing {
+            } else if i < playing {
                 self.songs.select(Some(playing - 1));
             }
         };
     }
-    pub fn randomize(&mut self) {
-        if let Some(song) = &self.songs.selected().cloned() {
-            self.songs.data.shuffle(&mut thread_rng());
-
-            let mut index = 0;
-            for (i, s) in self.songs.data.iter().enumerate() {
-                if s == song {
-                    index = i;
-                }
-            }
-            self.songs.select(Some(index));
-        }
-    }
-    pub fn stop(&mut self) {
-        self.sink = Sink::try_new(&self.handle).expect("Could not create new sink.");
-        self.update_volume();
-    }
-    pub fn elapsed(&self) -> f64 {
-        self.sink.elapsed().as_secs_f64()
-    }
-    pub fn toggle_playback(&self) {
-        self.sink.toggle_playback();
-    }
-    pub fn is_paused(&self) -> bool {
-        self.sink.is_paused()
+    pub fn clear(&mut self) {
+        self.songs = Index::default();
+        self.stop();
     }
-    pub fn seek_by(&mut self, amount: f64) {
-        let mut seek = self.elapsed() + amount;
-        if seek > self.duration {
-            return self.next_song();
-        } else if seek < 0.0 {
-            seek = 0.0;
+    pub fn clear_except_playing(&mut self) {
+        let selected = self.songs.selected().cloned();
+        let mut i = 0;
+        while i < self.songs.len() {
+            if Some(&self.songs.data[i]) != selected.as_ref() {
+                self.songs.data.remove(i);
+            } else {
+                i += 1;
+            }
         }
-        self.sink.seek(Duration::from_secs_f64(seek));
+        self.songs.select(Some(0));
     }
-    pub fn seek_to(&self, time: f64) {
-        self.sink.seek(Duration::from_secs_f64(time));
-        if self.is_paused() {
-            self.toggle_playback();
+    pub fn randomize(&self) {}
+    pub fn toggle_playback(&mut self) {
+        if self.playing {
+            self.pause();
+        } else {
+            self.play();
         }
     }
-    pub fn update(&mut self) {
-        if self.elapsed() > self.duration {
-            self.next_song();
-        }
+    pub fn previous(&self) {}
+    pub fn next(&self) {}
+    pub fn volume_up(&self) {}
+    pub fn volume_down(&self) {}
+    pub fn is_playing(&self) -> bool {
+        self.playing
+    }
+    pub fn total_songs(&self) -> usize {
+        self.songs.len()
+    }
+    fn play(&mut self) {
+        self.s.send(Event::Play).unwrap();
+        self.playing = true;
+    }
+    pub fn elapsed(&self) -> f32 {
+        0.0
+    }
+    pub fn get_index(&self) -> &Index<Song> {
+        &self.songs
+    }
+    fn pause(&mut self) {
+        self.s.send(Event::Pause).unwrap();
+        self.playing = false;
+    }
+    pub fn selected_song(&self) -> Option<&Song> {
+        self.songs.selected()
+    }
+    pub fn seek_by(&self, duration: f32) {
+        self.s.send(Event::SeekBy(duration)).unwrap();
+    }
+    pub fn seek_to(&self, duration: f32) {
+        self.s.send(Event::SeekTo(duration)).unwrap();
+    }
+    pub fn stop(&self) {
+        self.s.send(Event::Stop).unwrap();
+    }
+    fn run(r: Receiver<Event>, path: PathBuf) {
+        thread::spawn(move || {
+            let device = cpal::default_host().default_output_device().unwrap();
+            let config = device.default_output_config().unwrap();
+
+            let processor = Arc::new(RwLock::new(SampleProcessor::new(
+                Some(config.sample_rate().0),
+                path,
+            )));
+
+            let p = processor.clone();
+
+            let stream = device
+                .build_output_stream(
+                    &config.config(),
+                    move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
+                        for frame in data.chunks_mut(2) {
+                            for sample in frame.iter_mut() {
+                                *sample = p.write().unwrap().next_sample();
+                            }
+                        }
+                    },
+                    |err| panic!("{}", err),
+                )
+                .unwrap();
+
+            stream.play().unwrap();
+
+            loop {
+                if let Ok(event) = r.recv() {
+                    dbg!(&event);
+                    match event {
+                        Event::Play => stream.play().unwrap(),
+                        Event::Pause => stream.pause().unwrap(),
+                        Event::SeekBy(_) => (),
+                        Event::SeekTo(_) => (),
+                        // Event::Seek(duration) => processor.write().unwrap().seek_to(duration),
+                        Event::Stop => break,
+                    }
+                }
+            }
+        });
     }
     pub fn audio_devices() -> Vec<Device> {
         let host_id = cpal::default_host().id();
@@ -222,21 +219,22 @@ impl Player {
     pub fn default_device() -> Device {
         cpal::default_host().default_output_device().unwrap()
     }
-    pub fn change_output_device(&mut self, device: &Device) -> Result<(), stream::StreamError> {
-        match OutputStream::try_from_device(device) {
-            Ok((stream, handle)) => {
-                let pos = self.elapsed();
-                self.stop();
-                self.stream = stream;
-                self.handle = handle;
-                self.play_selected();
-                self.seek_to(pos);
-                Ok(())
-            }
-            Err(e) => match e {
-                stream::StreamError::DefaultStreamConfigError(_) => Ok(()),
-                _ => Err(e),
-            },
-        }
+    pub fn change_output_device(&mut self, _device: &Device) -> Result<(), StreamError> {
+        Ok(())
+        // match OutputStream::try_from_device(device) {
+        //     Ok((stream, handle)) => {
+        //         let pos = self.elapsed();
+        //         self.stop();
+        //         self.stream = stream;
+        //         self.handle = handle;
+        //         self.play_selected();
+        //         self.seek_to(pos);
+        //         Ok(())
+        //     }
+        //     Err(e) => match e {
+        //         stream::StreamError::DefaultStreamConfigError(_) => Ok(()),
+        //         _ => Err(e),
+        //     },
+        // }
     }
 }
diff --git a/gonk-player/src/queue.rs b/gonk-player/src/queue.rs
deleted file mode 100644
index 11383c27..00000000
--- a/gonk-player/src/queue.rs
+++ /dev/null
@@ -1,239 +0,0 @@
-//! Queue that plays sounds one after the other.
-
-use std::sync::mpsc::{Receiver, Sender};
-use std::sync::{mpsc, Arc, Mutex};
-use std::time::Duration;
-use std::{
-    collections::VecDeque,
-    sync::atomic::{AtomicBool, Ordering},
-};
-
-use crate::conversions::Sample;
-use crate::source::{Empty, Source, Zero};
-
-/// Builds a new queue. It consists of an input and an output.
-///
-/// The input can be used to add sounds to the end of the queue, while the output implements
-/// `Source` and plays the sounds.
-///
-/// The parameter indicates how the queue should behave if the queue becomes empty:
-///
-/// - If you pass `true`, then the queue is infinite and will play a silence instead until you add
-///   a new sound.
-/// - If you pass `false`, then the queue will report that it has finished playing.
-///
-pub fn queue<S>(keep_alive_if_empty: bool) -> (Arc<SourcesQueueInput<S>>, SourcesQueueOutput<S>)
-where
-    S: Sample + Send + 'static,
-{
-    let input = Arc::new(SourcesQueueInput {
-        next_sounds: Mutex::new(Vec::new()),
-        keep_alive_if_empty: AtomicBool::new(keep_alive_if_empty),
-    });
-
-    let output = SourcesQueueOutput {
-        current: Box::new(Empty::<S>::new()) as Box<_>,
-        signal_after_end: None,
-        input: input.clone(),
-        sample_cache: VecDeque::new(),
-    };
-
-    (input, output)
-}
-
-type BoxSource<S> = Box<dyn Source<Item = S> + Send>;
-type OptionSender = Option<Sender<()>>;
-
-// TODO: consider reimplementing this with `from_factory`
-/// The input of the queue.
-pub struct SourcesQueueInput<S> {
-    next_sounds: Mutex<Vec<(BoxSource<S>, OptionSender)>>,
-
-    // See constructor.
-    keep_alive_if_empty: AtomicBool,
-}
-
-impl<S> SourcesQueueInput<S>
-where
-    S: Sample + Send + 'static,
-{
-    /// Adds a new source to the end of the queue.
-    #[inline]
-    pub fn append<T>(&self, source: T)
-    where
-        T: Source<Item = S> + Send + 'static,
-    {
-        self.next_sounds
-            .lock()
-            .unwrap()
-            .push((Box::new(source) as Box<_>, None));
-    }
-
-    /// Adds a new source to the end of the queue.
-    ///
-    /// The `Receiver` will be signalled when the sound has finished playing.
-    #[inline]
-    pub fn append_with_signal<T>(&self, source: T) -> Receiver<()>
-    where
-        T: Source<Item = S> + Send + 'static,
-    {
-        let (tx, rx) = mpsc::channel();
-        self.next_sounds
-            .lock()
-            .unwrap()
-            .push((Box::new(source) as Box<_>, Some(tx)));
-        rx
-    }
-
-    /// Sets whether the queue stays alive if there's no more sound to play.
-    ///
-    /// See also the constructor.
-    pub fn set_keep_alive_if_empty(&self, keep_alive_if_empty: bool) {
-        self.keep_alive_if_empty
-            .store(keep_alive_if_empty, Ordering::Release);
-    }
-}
-
-/// The output of the queue. Implements `Source`.
-pub struct SourcesQueueOutput<S> {
-    // The current iterator that produces samples.
-    current: Box<dyn Source<Item = S> + Send>,
-
-    // Signal this sender before picking from `next`.
-    signal_after_end: Option<Sender<()>>,
-
-    // The next sounds.
-    input: Arc<SourcesQueueInput<S>>,
-    sample_cache: VecDeque<Option<S>>,
-}
-
-impl<S> Source for SourcesQueueOutput<S>
-where
-    S: Sample + Send + 'static,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        // This function is non-trivial because the boundary between two sounds in the queue should
-        // be a frame boundary as well.
-        //
-        // The current sound is free to return `None` for `current_frame_len()`, in which case
-        // we *should* return the number of samples remaining the current sound.
-        // This can be estimated with `size_hint()`.
-        //
-        // If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this
-        // situation we force a frame to have a maximum number of samples indicate by this
-        // constant.
-        const THRESHOLD: usize = 512;
-
-        // Try the current `current_frame_len`.
-        if let Some(val) = self.current.current_frame_len() {
-            if val != 0 {
-                return Some(val);
-            }
-        }
-
-        // Try the size hint.
-        let (lower_bound, _) = self.current.size_hint();
-        // The iterator default implementation just returns 0.
-        // That's a problematic value, so skip it.
-        if lower_bound > 0 {
-            return Some(lower_bound);
-        }
-
-        // Otherwise we use the constant value.
-        Some(THRESHOLD)
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.current.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.current.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        None
-    }
-
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.current.seek(time)
-    }
-
-    fn elapsed(&mut self) -> Duration {
-        Duration::from_secs(0)
-    }
-}
-
-impl<S> Iterator for SourcesQueueOutput<S>
-where
-    S: Sample + Send + 'static,
-{
-    type Item = S;
-
-    #[inline]
-    fn next(&mut self) -> Option<S> {
-        loop {
-            if !self.sample_cache.is_empty() {
-                return self.sample_cache.pop_front().unwrap();
-            }
-            // Basic situation that will happen most of the time.
-            if let Some(sample) = self.current.next() {
-                return Some(sample);
-            }
-
-            // Since `self.current` has finished, we need to pick the next sound.
-            // In order to avoid inlining this expensive operation, the code is in another function.
-            if self.go_next().is_err() {
-                return None;
-            };
-        }
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        (self.current.size_hint().0, None)
-    }
-}
-
-impl<S> SourcesQueueOutput<S>
-where
-    S: Sample + Send + 'static,
-{
-    // Called when `current` is empty and we must jump to the next element.
-    // Returns `Ok` if the sound should continue playing, or an error if it should stop.
-    //
-    // This method is separate so that it is not inlined.
-    fn go_next(&mut self) -> Result<(), ()> {
-        if let Some(signal_after_end) = self.signal_after_end.take() {
-            let _ = signal_after_end.send(());
-        }
-
-        let (next, signal_after_end) = {
-            let mut next = self.input.next_sounds.lock().unwrap();
-
-            if next.len() == 0 {
-                if self.input.keep_alive_if_empty.load(Ordering::Acquire) {
-                    // Play a short silence in order to avoid spinlocking.
-                    let silence = Zero::<S>::new(1, 44100); // TODO: meh
-                    (
-                        Box::new(silence.take_duration(Duration::from_millis(10))) as Box<_>,
-                        None,
-                    )
-                } else {
-                    return Err(());
-                }
-            } else {
-                next.remove(0)
-            }
-        };
-
-        self.current = next;
-
-        self.signal_after_end = signal_after_end;
-        Ok(())
-    }
-}
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
new file mode 100644
index 00000000..10049508
--- /dev/null
+++ b/gonk-player/src/sample_processor.rs
@@ -0,0 +1,108 @@
+use crate::sample_rate::SampleRateConverter;
+use std::{fs::File, path::Path, time::Duration};
+use symphonia::{
+    core::{
+        audio::{SampleBuffer, SignalSpec},
+        codecs::{Decoder, DecoderOptions},
+        formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
+        io::MediaSourceStream,
+        meta::MetadataOptions,
+        probe::Hint,
+        units::Time,
+    },
+    default::get_probe,
+};
+
+pub struct SampleProcessor {
+    pub decoder: Box<dyn Decoder>,
+    pub format: Box<dyn FormatReader>,
+    pub spec: SignalSpec,
+    pub duration: u64,
+    pub converter: SampleRateConverter,
+    pub finished: bool,
+    pub left: bool,
+}
+
+impl SampleProcessor {
+    pub fn next_sample(&mut self) -> f32 {
+        loop {
+            if let Some(sample) = self.converter.next() {
+                return sample * 0.1;
+            } else {
+                self.update();
+            }
+        }
+    }
+    pub fn update(&mut self) {
+        match self.format.next_packet() {
+            Ok(packet) => {
+                let decoded = self.decoder.decode(&packet).unwrap();
+                let mut buffer = SampleBuffer::<f32>::new(self.duration, self.spec);
+                buffer.copy_interleaved_ref(decoded);
+
+                self.converter.update(buffer.samples().to_vec().into_iter());
+            }
+            Err(e) => match e {
+                symphonia::core::errors::Error::IoError(_) => self.finished = true,
+                _ => panic!("{:?}", e),
+            },
+        }
+    }
+    pub fn seek_to(&mut self, time: Duration) {
+        let nanos_per_sec = 1_000_000_000.0;
+        self.format
+            .seek(
+                SeekMode::Coarse,
+                SeekTo::Time {
+                    time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / nanos_per_sec),
+                    track_id: None,
+                },
+            )
+            .unwrap();
+    }
+    pub fn new(sample_rate: Option<u32>, path: impl AsRef<Path>) -> Self {
+        let source = Box::new(File::open(path).unwrap());
+
+        let mss = MediaSourceStream::new(source, Default::default());
+
+        let mut probed = get_probe()
+            .format(
+                &Hint::default(),
+                mss,
+                &FormatOptions {
+                    prebuild_seek_index: true,
+                    ..Default::default()
+                },
+                &MetadataOptions::default(),
+            )
+            .unwrap();
+
+        let track = probed.format.default_track().unwrap();
+        let mut decoder = symphonia::default::get_codecs()
+            .make(&track.codec_params, &DecoderOptions::default())
+            .unwrap();
+
+        let current_frame = probed.format.next_packet().unwrap();
+        let decoded = decoder.decode(&current_frame).unwrap();
+
+        let spec = decoded.spec().to_owned();
+        let duration = decoded.capacity() as u64;
+
+        let mut sample_buffer = SampleBuffer::<f32>::new(duration, spec);
+        sample_buffer.copy_interleaved_ref(decoded);
+
+        Self {
+            format: probed.format,
+            decoder,
+            spec,
+            duration,
+            converter: SampleRateConverter::new(
+                sample_buffer.samples().to_vec().into_iter(),
+                spec.rate,
+                sample_rate.unwrap_or(44100),
+            ),
+            finished: false,
+            left: true,
+        }
+    }
+}
diff --git a/gonk-player/src/conversions/sample_rate.rs b/gonk-player/src/sample_rate.rs
similarity index 53%
rename from gonk-player/src/conversions/sample_rate.rs
rename to gonk-player/src/sample_rate.rs
index fe4ebea5..de907ff8 100644
--- a/gonk-player/src/conversions/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -1,113 +1,72 @@
-use crate::conversions::Sample;
-
-use std::mem;
+//https://github.com/RustAudio/rodio/blob/master/src/conversions/sample_rate.rs
+use std::{mem, vec::IntoIter};
+
+#[inline]
+const fn gcd(a: u32, b: u32) -> u32 {
+    if b == 0 {
+        a
+    } else {
+        gcd(b, a % b)
+    }
+}
 
 /// Iterator that converts from a certain sample rate to another.
-#[derive(Clone, Debug)]
-pub struct SampleRateConverter<I>
-where
-    I: Iterator,
-{
+pub struct SampleRateConverter {
     /// The iterator that gives us samples.
-    input: I,
+    input: IntoIter<f32>,
     /// We convert chunks of `from` samples into chunks of `to` samples.
     from: u32,
     /// We convert chunks of `from` samples into chunks of `to` samples.
     to: u32,
-    /// Number of channels in the stream
-    channels: cpal::ChannelCount,
     /// One sample per channel, extracted from `input`.
-    current_frame: Vec<I::Item>,
+    current_frame: Vec<f32>,
     /// Position of `current_sample` modulo `from`.
     current_frame_pos_in_chunk: u32,
     /// The samples right after `current_sample` (one per channel), extracted from `input`.
-    next_frame: Vec<I::Item>,
+    next_frame: Vec<f32>,
     /// The position of the next sample that the iterator should return, modulo `to`.
     /// This counter is incremented (modulo `to`) every time the iterator is called.
     next_output_frame_pos_in_chunk: u32,
     /// The buffer containing the samples waiting to be output.
-    output_buffer: Vec<I::Item>,
+    output_buffer: Vec<f32>,
 }
 
-impl<I> SampleRateConverter<I>
-where
-    I: Iterator,
-    I::Item: Sample,
-{
-    ///
-    ///
-    /// # Panic
-    ///
-    /// Panics if `from` or `to` are equal to 0.
-    ///
-    #[inline]
-    pub fn new(
-        mut input: I,
-        from: cpal::SampleRate,
-        to: cpal::SampleRate,
-        num_channels: cpal::ChannelCount,
-    ) -> SampleRateConverter<I> {
-        let from = from.0;
-        let to = to.0;
-
-        assert!(from >= 1);
-        assert!(to >= 1);
+impl SampleRateConverter {
+    pub fn new(mut input: IntoIter<f32>, from_rate: u32, to_rate: u32) -> SampleRateConverter {
+        assert!(from_rate >= 1);
+        assert!(to_rate >= 1);
 
         // finding greatest common divisor
-        let gcd = {
-            #[inline]
-            fn gcd(a: u32, b: u32) -> u32 {
-                if b == 0 {
-                    a
-                } else {
-                    gcd(b, a % b)
-                }
-            }
+        let gcd = gcd(from_rate, to_rate);
 
-            gcd(from, to)
-        };
-
-        let (first_samples, next_samples) = if from == to {
+        let (first_samples, next_samples) = if from_rate == to_rate {
             // if `from` == `to` == 1, then we just pass through
-            debug_assert_eq!(from, gcd);
+            debug_assert_eq!(from_rate, gcd);
             (Vec::new(), Vec::new())
         } else {
-            let first = input
-                .by_ref()
-                .take(num_channels as usize)
-                .collect::<Vec<_>>();
-            let next = input
-                .by_ref()
-                .take(num_channels as usize)
-                .collect::<Vec<_>>();
+            let first = vec![input.next().unwrap(), input.next().unwrap()];
+            let next = vec![input.next().unwrap(), input.next().unwrap()];
             (first, next)
         };
 
         SampleRateConverter {
             input,
-            from: from / gcd,
-            to: to / gcd,
-            channels: num_channels,
+            from: from_rate / gcd,
+            to: to_rate / gcd,
             current_frame_pos_in_chunk: 0,
             next_output_frame_pos_in_chunk: 0,
             current_frame: first_samples,
             next_frame: next_samples,
-            output_buffer: Vec::with_capacity(num_channels as usize - 1),
+            output_buffer: Vec::with_capacity(1),
         }
     }
 
-    /// Destroys this iterator and returns the underlying iterator.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-
     fn next_input_frame(&mut self) {
         self.current_frame_pos_in_chunk += 1;
 
         mem::swap(&mut self.current_frame, &mut self.next_frame);
         self.next_frame.clear();
-        for _ in 0..self.channels {
+        for _ in 0..2 {
             if let Some(i) = self.input.next() {
                 self.next_frame.push(i);
             } else {
@@ -115,19 +74,20 @@ where
             }
         }
     }
-}
 
-impl<I> Iterator for SampleRateConverter<I>
-where
-    I: Iterator,
-    I::Item: Sample + Clone,
-{
-    type Item = I::Item;
+    pub fn update(&mut self, mut input: IntoIter<f32>) {
+        let current_frame = vec![input.next().unwrap(), input.next().unwrap()];
+        let next_frame = vec![input.next().unwrap(), input.next().unwrap()];
+        self.input = input;
+        self.current_frame = current_frame;
+        self.next_frame = next_frame;
+        self.current_frame_pos_in_chunk = 0;
+        self.next_output_frame_pos_in_chunk = 0;
+    }
 
-    fn next(&mut self) -> Option<I::Item> {
+    pub fn next(&mut self) -> Option<f32> {
         // the algorithm below doesn't work if `self.from == self.to`
         if self.from == self.to {
-            debug_assert_eq!(self.from, 1);
             return self.input.next();
         }
 
@@ -173,7 +133,7 @@ where
             .zip(self.next_frame.iter())
             .enumerate()
         {
-            let sample = Sample::lerp(*cur, *next, numerator, self.to);
+            let sample = cur + (next - cur) * numerator as f32 / self.to as f32;
 
             if off == 0 {
                 result = Some(sample);
@@ -201,42 +161,4 @@ where
             }
         }
     }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        let apply = |samples: usize| {
-            // `samples_after_chunk` will contain the number of samples remaining after the chunk
-            // currently being processed
-            let samples_after_chunk = samples;
-            // adding the samples of the next chunk that may have already been read
-            let samples_after_chunk = if self.current_frame_pos_in_chunk == self.from - 1 {
-                samples_after_chunk + self.next_frame.len()
-            } else {
-                samples_after_chunk
-            };
-            // removing the samples of the current chunk that have not yet been read
-            let samples_after_chunk = samples_after_chunk.saturating_sub(
-                self.from
-                    .saturating_sub(self.current_frame_pos_in_chunk + 2) as usize
-                    * usize::from(self.channels),
-            );
-            // calculating the number of samples after the transformation
-            // TODO: this is wrong here \|/
-            let samples_after_chunk = samples_after_chunk * self.to as usize / self.from as usize;
-
-            // `samples_current_chunk` will contain the number of samples remaining to be output
-            // for the chunk currently being processed
-            let samples_current_chunk = (self.to - self.next_output_frame_pos_in_chunk) as usize
-                * usize::from(self.channels);
-
-            samples_current_chunk + samples_after_chunk + self.output_buffer.len()
-        };
-
-        if self.from == self.to {
-            self.input.size_hint()
-        } else {
-            let (min, max) = self.input.size_hint();
-            (apply(min), max.map(apply))
-        }
-    }
 }
diff --git a/gonk-player/src/sink.rs b/gonk-player/src/sink.rs
deleted file mode 100644
index 07b911e5..00000000
--- a/gonk-player/src/sink.rs
+++ /dev/null
@@ -1,193 +0,0 @@
-use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
-use std::sync::mpsc::Receiver;
-use std::sync::{Arc, Mutex, RwLock};
-use std::time::Duration;
-
-use crate::stream::{OutputStreamHandle, PlayError};
-use crate::{conversions::Sample, queue, source::Done, source::Source};
-
-/// Handle to an device that outputs sounds.
-///
-/// Dropping the `Sink` stops all sounds. You can use `detach` if you want the sounds to continue
-/// playing.
-pub struct Sink {
-    queue_tx: Arc<queue::SourcesQueueInput<f32>>,
-    sleep_until_end: Mutex<Option<Receiver<()>>>,
-
-    controls: Arc<Controls>,
-    sound_count: Arc<AtomicUsize>,
-
-    detached: bool,
-
-    elapsed: Arc<RwLock<Duration>>,
-}
-
-struct Controls {
-    pause: AtomicBool,
-    volume: Mutex<f32>,
-    seek: Mutex<Option<Duration>>,
-    stopped: AtomicBool,
-}
-
-impl Sink {
-    /// Builds a new `Sink`, beginning playback on a stream.
-    #[inline]
-    pub fn try_new(stream: &OutputStreamHandle) -> Result<Sink, PlayError> {
-        let (sink, queue_rx) = Sink::new_idle();
-        stream.play_raw(queue_rx)?;
-        Ok(sink)
-    }
-
-    /// Builds a new `Sink`.
-    #[inline]
-    pub fn new_idle() -> (Sink, queue::SourcesQueueOutput<f32>) {
-        let (queue_tx, queue_rx) = queue::queue(true);
-
-        let sink = Sink {
-            queue_tx,
-            sleep_until_end: Mutex::new(None),
-            controls: Arc::new(Controls {
-                pause: AtomicBool::new(false),
-                volume: Mutex::new(1.0),
-                stopped: AtomicBool::new(false),
-                seek: Mutex::new(None),
-            }),
-            sound_count: Arc::new(AtomicUsize::new(0)),
-            detached: false,
-            elapsed: Arc::new(RwLock::new(Duration::from_secs(0))),
-        };
-        (sink, queue_rx)
-    }
-
-    /// Appends a sound to the queue of sounds to play.
-    #[inline]
-    pub fn append<S>(&self, source: S)
-    where
-        S: Source + Send + 'static,
-        S::Item: Sample,
-        S::Item: Send,
-    {
-        let controls = self.controls.clone();
-
-        let elapsed = self.elapsed.clone();
-        let source = source
-            .pausable(false)
-            .amplify(1.0)
-            .stoppable()
-            .periodic_access(Duration::from_millis(5), move |src| {
-                if controls.stopped.load(Ordering::SeqCst) {
-                    src.stop();
-                } else {
-                    if let Some(seek_time) = controls.seek.lock().unwrap().take() {
-                        src.seek(seek_time).unwrap();
-                    }
-                    *elapsed.write().unwrap() = src.elapsed();
-                    src.inner_mut().set_factor(*controls.volume.lock().unwrap());
-                    src.inner_mut()
-                        .inner_mut()
-                        .set_paused(controls.pause.load(Ordering::SeqCst));
-                }
-            })
-            .convert_samples();
-        self.sound_count.fetch_add(1, Ordering::Relaxed);
-        let source = Done::new(source, self.sound_count.clone());
-        *self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source));
-    }
-
-    /// Gets the volume of the sound.
-    ///
-    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
-    /// multiply each sample by this value.
-    #[inline]
-    pub fn volume(&self) -> f32 {
-        *self.controls.volume.lock().unwrap()
-    }
-
-    /// Changes the volume of the sound.
-    ///
-    /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will
-    /// multiply each sample by this value.
-    #[inline]
-    pub fn set_volume(&self, value: f32) {
-        *self.controls.volume.lock().unwrap() = value;
-    }
-
-    /// Resumes playback of a paused sink.
-    ///
-    /// No effect if not paused.
-    #[inline]
-    pub fn play(&self) {
-        self.controls.pause.store(false, Ordering::SeqCst);
-    }
-
-    /// Pauses playback of this sink.
-    ///
-    /// No effect if already paused.
-    ///
-    /// A paused sink can be resumed with `play()`.
-    pub fn pause(&self) {
-        self.controls.pause.store(true, Ordering::SeqCst);
-    }
-
-    /// Toggles playback of the sink
-    pub fn toggle_playback(&self) {
-        if self.is_paused() {
-            self.play();
-        } else {
-            self.pause();
-        }
-    }
-
-    pub fn seek(&self, seek_time: Duration) {
-        *self.controls.seek.lock().unwrap() = Some(seek_time);
-    }
-
-    /// Gets if a sink is paused
-    ///
-    /// Sinks can be paused and resumed using `pause()` and `play()`. This returns `true` if the
-    /// sink is paused.
-    pub fn is_paused(&self) -> bool {
-        self.controls.pause.load(Ordering::SeqCst)
-    }
-
-    /// Destroys the sink without stopping the sounds that are still playing.
-    #[inline]
-    pub fn detach(mut self) {
-        self.detached = true;
-    }
-
-    /// Sleeps the current thread until the sound ends.
-    #[inline]
-    pub fn sleep_until_end(&self) {
-        if let Some(sleep_until_end) = self.sleep_until_end.lock().unwrap().take() {
-            let _ = sleep_until_end.recv();
-        }
-    }
-    /// Returns true if this sink has no more sounds to play.
-    #[inline]
-    pub fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-
-    /// Returns the number of sounds currently in the queue.
-    #[inline]
-    pub fn len(&self) -> usize {
-        self.sound_count.load(Ordering::Relaxed)
-    }
-
-    #[inline]
-    pub fn elapsed(&self) -> Duration {
-        *self.elapsed.read().unwrap()
-    }
-}
-
-impl Drop for Sink {
-    #[inline]
-    fn drop(&mut self) {
-        self.queue_tx.set_keep_alive_if_empty(false);
-
-        if !self.detached {
-            self.controls.stopped.store(true, Ordering::Relaxed);
-        }
-    }
-}
diff --git a/gonk-player/src/source/amplify.rs b/gonk-player/src/source/amplify.rs
deleted file mode 100644
index 48919ad9..00000000
--- a/gonk-player/src/source/amplify.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-
-/// Internal function that builds a `Amplify` object.
-pub fn amplify<I>(input: I, factor: f32) -> Amplify<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    Amplify { input, factor }
-}
-
-/// Filter that modifies each sample by a given value.
-#[derive(Clone, Debug)]
-pub struct Amplify<I> {
-    input: I,
-    factor: f32,
-}
-
-impl<I> Amplify<I> {
-    /// Modifies the amplification factor.
-    #[inline]
-    pub fn set_factor(&mut self, factor: f32) {
-        self.factor = factor;
-    }
-
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.input
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.input
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I> Iterator for Amplify<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    type Item = I::Item;
-
-    #[inline]
-    fn next(&mut self) -> Option<I::Item> {
-        self.input.next().map(|value| value.amplify(self.factor))
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.input.size_hint()
-    }
-}
-
-impl<I> ExactSizeIterator for Amplify<I>
-where
-    I: Source + ExactSizeIterator,
-    I::Item: Sample,
-{
-}
-
-impl<I> Source for Amplify<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        self.input.current_frame_len()
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.input.total_duration()
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.input.elapsed()
-    }
-
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.input.seek(time)
-    }
-}
diff --git a/gonk-player/src/source/done.rs b/gonk-player/src/source/done.rs
deleted file mode 100644
index aafcf638..00000000
--- a/gonk-player/src/source/done.rs
+++ /dev/null
@@ -1,98 +0,0 @@
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::Arc;
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-
-/// When the inner source is empty this decrements an `AtomicUsize`.
-#[derive(Debug, Clone)]
-pub struct Done<I> {
-    input: I,
-    signal: Arc<AtomicUsize>,
-    signal_sent: bool,
-}
-
-impl<I> Done<I> {
-    #[inline]
-    pub fn new(input: I, signal: Arc<AtomicUsize>) -> Done<I> {
-        Done {
-            input,
-            signal,
-            signal_sent: false,
-        }
-    }
-
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.input
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.input
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I: Source> Iterator for Done<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    type Item = I::Item;
-
-    #[inline]
-    fn next(&mut self) -> Option<I::Item> {
-        let next = self.input.next();
-        if !self.signal_sent && next.is_none() {
-            self.signal.fetch_sub(1, Ordering::Relaxed);
-            self.signal_sent = true;
-        }
-        next
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.input.size_hint()
-    }
-}
-
-impl<I> Source for Done<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        self.input.current_frame_len()
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.input.total_duration()
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.input.elapsed()
-    }
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.input.seek(time)
-    }
-}
diff --git a/gonk-player/src/source/empty.rs b/gonk-player/src/source/empty.rs
deleted file mode 100644
index 0dcdfbf5..00000000
--- a/gonk-player/src/source/empty.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use std::marker::PhantomData;
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-
-/// An empty source.
-#[derive(Debug, Copy, Clone)]
-pub struct Empty<S>(PhantomData<S>);
-
-impl<S> Default for Empty<S> {
-    #[inline]
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl<S> Empty<S> {
-    #[inline]
-    pub fn new() -> Empty<S> {
-        Empty(PhantomData)
-    }
-}
-
-impl<S> Iterator for Empty<S> {
-    type Item = S;
-
-    #[inline]
-    fn next(&mut self) -> Option<S> {
-        None
-    }
-}
-
-impl<S> Source for Empty<S>
-where
-    S: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        None
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        1
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        48000
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        Some(Duration::new(0, 0))
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        Duration::from_secs(0)
-    }
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        Some(time)
-    }
-}
diff --git a/gonk-player/src/source/fadein.rs b/gonk-player/src/source/fadein.rs
deleted file mode 100644
index b354b8e5..00000000
--- a/gonk-player/src/source/fadein.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-use crate::{conversions::Sample, source::Source};
-use std::time::Duration;
-
-/// Internal function that builds a `FadeIn` object.
-pub fn fadein<I>(input: I, duration: Duration) -> FadeIn<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    let duration = duration.as_secs() * 1000000000 + duration.subsec_nanos() as u64;
-
-    FadeIn {
-        input,
-        remaining_ns: duration as f32,
-        total_ns: duration as f32,
-    }
-}
-
-/// Filter that modifies raises the volume from silence over a time period.
-#[derive(Clone, Debug)]
-pub struct FadeIn<I> {
-    input: I,
-    remaining_ns: f32,
-    total_ns: f32,
-}
-
-impl<I> FadeIn<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.input
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.input
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I> Iterator for FadeIn<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    type Item = I::Item;
-
-    #[inline]
-    fn next(&mut self) -> Option<I::Item> {
-        if self.remaining_ns <= 0.0 {
-            return self.input.next();
-        }
-
-        let factor = 1.0 - self.remaining_ns / self.total_ns;
-        self.remaining_ns -=
-            1000000000.0 / (self.input.sample_rate() as f32 * self.channels() as f32);
-        self.input.next().map(|value| value.amplify(factor))
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.input.size_hint()
-    }
-}
-
-impl<I> ExactSizeIterator for FadeIn<I>
-where
-    I: Source + ExactSizeIterator,
-    I::Item: Sample,
-{
-}
-
-impl<I> Source for FadeIn<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        self.input.current_frame_len()
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.input.total_duration()
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.input.elapsed()
-    }
-
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.input.seek(time)
-    }
-}
diff --git a/gonk-player/src/source/mod.rs b/gonk-player/src/source/mod.rs
deleted file mode 100644
index 0ade1096..00000000
--- a/gonk-player/src/source/mod.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-//! Sources of sound and various filters.
-
-use std::time::Duration;
-
-use crate::conversions::Sample;
-
-pub use self::amplify::Amplify;
-pub use self::done::Done;
-pub use self::empty::Empty;
-pub use self::fadein::FadeIn;
-pub use self::pausable::Pausable;
-pub use self::periodic::PeriodicAccess;
-pub use self::samples_converter::SamplesConverter;
-pub use self::stoppable::Stoppable;
-pub use self::take::TakeDuration;
-pub use self::uniform::UniformSourceIterator;
-pub use self::zero::Zero;
-
-mod amplify;
-mod done;
-mod empty;
-mod fadein;
-mod pausable;
-mod periodic;
-mod samples_converter;
-mod stoppable;
-mod take;
-mod uniform;
-mod zero;
-
-/// A source of samples.
-///
-/// # A quick lesson about sounds
-///
-/// ## Sampling
-///
-/// A sound is a vibration that propagates through air and reaches your ears. This vibration can
-/// be represented as an analog signal.
-///
-/// In order to store this signal in the computer's memory or on the disk, we perform what is
-/// called *sampling*. The consists in choosing an interval of time (for example 20µs) and reading
-/// the amplitude of the signal at each interval (for example, if the interval is 20µs we read the
-/// amplitude every 20µs). By doing so we obtain a list of numerical values, each value being
-/// called a *sample*.
-///
-/// Therefore a sound can be represented in memory by a frequency and a list of samples. The
-/// frequency is expressed in hertz and corresponds to the number of samples that have been
-/// read per second. For example if we read one sample every 20µs, the frequency would be
-/// 50000 Hz. In reality, common values for the frequency are 44100, 48000 and 96000.
-///
-/// ## Channels
-///
-/// But a frequency and a list of values only represent one signal. When you listen to a sound,
-/// your left and right ears don't receive exactly the same signal. In order to handle this,
-/// we usually record not one but two different signals: one for the left ear and one for the right
-/// ear. We say that such a sound has two *channels*.
-///
-/// Sometimes sounds even have five or six channels, each corresponding to a location around the
-/// head of the listener.
-///
-/// The standard in audio manipulation is to *interleave* the multiple channels. In other words,
-/// in a sound with two channels the list of samples contains the first sample of the first
-/// channel, then the first sample of the second channel, then the second sample of the first
-/// channel, then the second sample of the second channel, and so on. The same applies if you have
-/// more than two channels. The rodio library only supports this schema.
-///
-/// Therefore in order to represent a sound in memory in fact we need three characteristics: the
-/// frequency, the number of channels, and the list of samples.
-///
-/// ## The `Source` trait
-///
-/// A Rust object that represents a sound should implement the `Source` trait.
-///
-/// The three characteristics that describe a sound are provided through this trait:
-///
-/// - The number of channels can be retrieved with `channels`.
-/// - The frequency can be retrieved with `sample_rate`.
-/// - The list of values can be retrieved by iterating on the source. The `Source` trait requires
-///   that the `Iterator` trait be implemented as well.
-///
-/// # Frames
-///
-/// The samples rate and number of channels of some sound sources can change by itself from time
-/// to time.
-///
-/// > **Note**: As a basic example, if you play two audio files one after the other and treat the
-/// > whole as a single source, then the channels and samples rate of that source may change at the
-/// > transition between the two files.
-///
-/// However, for optimization purposes rodio supposes that the number of channels and the frequency
-/// stay the same for long periods of time and avoids calling `channels()` and
-/// `sample_rate` too frequently.
-///
-/// In order to properly handle this situation, the `current_frame_len()` method should return
-/// the number of samples that remain in the iterator before the samples rate and number of
-/// channels can potentially change.
-///
-pub trait Source: Iterator
-where
-    Self::Item: Sample,
-{
-    /// Returns the number of samples before the current frame ends. `None` means "infinite" or
-    /// "until the sound ends".
-    /// Should never return 0 unless there's no more data.
-    ///
-    /// After the engine has finished reading the specified number of samples, it will check
-    /// whether the value of `channels()` and/or `sample_rate()` have changed.
-    fn current_frame_len(&self) -> Option<usize>;
-
-    /// Returns the number of channels. Channels are always interleaved.
-    fn channels(&self) -> u16;
-
-    /// Returns the rate at which the source should be played. In number of samples per second.
-    fn sample_rate(&self) -> u32;
-
-    /// Returns the total duration of this source, if known.
-    ///
-    /// `None` indicates at the same time "infinite" or "unknown".
-    fn total_duration(&self) -> Option<Duration>;
-
-    fn seek(&mut self, time: Duration) -> Option<Duration>;
-
-    fn elapsed(&mut self) -> Duration;
-
-    /// Takes a certain duration of this source and then stops.
-    #[inline]
-    fn take_duration(self, duration: Duration) -> TakeDuration<Self>
-    where
-        Self: Sized,
-    {
-        take::take_duration(self, duration)
-    }
-
-    /// Immediately skips a certain duration of this source.
-    ///
-    /// If the specified duration is longer than the source itself, `skip_duration` will skip to the end of the source.
-
-    /// Amplifies the sound by the given value.
-    #[inline]
-    fn amplify(self, value: f32) -> Amplify<Self>
-    where
-        Self: Sized,
-    {
-        amplify::amplify(self, value)
-    }
-
-    /// Fades in the sound.
-    #[inline]
-    fn fade_in(self, duration: Duration) -> FadeIn<Self>
-    where
-        Self: Sized,
-    {
-        fadein::fadein(self, duration)
-    }
-
-    /// Calls the `access` closure on `Self` the first time the source is iterated and every
-    /// time `period` elapses.
-    ///
-    /// Later changes in either `sample_rate()` or `channels_count()` won't be reflected in
-    /// the rate of access.
-    ///
-    /// The rate is based on playback speed, so both the following will call `access` when the
-    /// same samples are reached:
-    /// `periodic_access(Duration::from_secs(1), ...).speed(2.0)`
-    /// `speed(2.0).periodic_access(Duration::from_secs(2), ...)`
-    #[inline]
-    fn periodic_access<F>(self, period: Duration, access: F) -> PeriodicAccess<Self, F>
-    where
-        Self: Sized,
-        F: FnMut(&mut Self),
-    {
-        periodic::periodic(self, period, access)
-    }
-
-    /// Converts the samples of this source to another type.
-    #[inline]
-    fn convert_samples<D>(self) -> SamplesConverter<Self, D>
-    where
-        Self: Sized,
-        D: Sample,
-    {
-        SamplesConverter::new(self)
-    }
-
-    /// Makes the sound pausable.
-    // TODO: add example
-    #[inline]
-    fn pausable(self, initially_paused: bool) -> Pausable<Self>
-    where
-        Self: Sized,
-    {
-        pausable::pausable(self, initially_paused)
-    }
-
-    /// Makes the sound stoppable.
-    #[inline]
-    fn stoppable(self) -> Stoppable<Self>
-    where
-        Self: Sized,
-    {
-        stoppable::stoppable(self)
-    }
-}
diff --git a/gonk-player/src/source/pausable.rs b/gonk-player/src/source/pausable.rs
deleted file mode 100644
index 6b134408..00000000
--- a/gonk-player/src/source/pausable.rs
+++ /dev/null
@@ -1,125 +0,0 @@
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-
-/// Internal function that builds a `Pausable` object.
-pub fn pausable<I>(source: I, paused: bool) -> Pausable<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    let paused_channels = if paused {
-        Some(source.channels())
-    } else {
-        None
-    };
-    Pausable {
-        input: source,
-        paused_channels,
-        remaining_paused_samples: 0,
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct Pausable<I> {
-    input: I,
-    paused_channels: Option<u16>,
-    remaining_paused_samples: u16,
-}
-
-impl<I> Pausable<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    /// Sets whether the filter applies.
-    ///
-    /// If set to true, the inner sound stops playing and no samples are processed from it.
-    #[inline]
-    pub fn set_paused(&mut self, paused: bool) {
-        match (self.paused_channels, paused) {
-            (None, true) => self.paused_channels = Some(self.input.channels()),
-            (Some(_), false) => self.paused_channels = None,
-            _ => (),
-        }
-    }
-
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.input
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.input
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I> Iterator for Pausable<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    type Item = I::Item;
-
-    #[inline]
-    fn next(&mut self) -> Option<I::Item> {
-        if self.remaining_paused_samples > 0 {
-            self.remaining_paused_samples -= 1;
-            return Some(I::Item::zero_value());
-        }
-
-        if let Some(paused_channels) = self.paused_channels {
-            self.remaining_paused_samples = paused_channels - 1;
-            return Some(I::Item::zero_value());
-        }
-
-        self.input.next()
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.input.size_hint()
-    }
-}
-
-impl<I> Source for Pausable<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        self.input.current_frame_len()
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.input.total_duration()
-    }
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.input.elapsed()
-    }
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.input.seek(time)
-    }
-}
diff --git a/gonk-player/src/source/periodic.rs b/gonk-player/src/source/periodic.rs
deleted file mode 100644
index 41cbf269..00000000
--- a/gonk-player/src/source/periodic.rs
+++ /dev/null
@@ -1,127 +0,0 @@
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-
-/// Internal function that builds a `PeriodicAccess` object.
-pub fn periodic<I, F>(source: I, period: Duration, modifier: F) -> PeriodicAccess<I, F>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    // TODO: handle the fact that the samples rate can change
-    // TODO: generally, just wrong
-    let update_ms = period.as_secs() as u32 * 1_000 + period.subsec_millis();
-    let update_frequency = (update_ms * source.sample_rate()) / 1000 * source.channels() as u32;
-
-    PeriodicAccess {
-        input: source,
-        modifier,
-        // Can overflow when subtracting if this is 0
-        update_frequency: if update_frequency == 0 {
-            1
-        } else {
-            update_frequency
-        },
-        samples_until_update: 1,
-    }
-}
-
-/// Calls a function on a source every time a period elapsed.
-#[derive(Clone, Debug)]
-pub struct PeriodicAccess<I, F> {
-    // The inner source.
-    input: I,
-
-    // Closure that gets access to `inner`.
-    modifier: F,
-
-    // The frequency with which local_volume should be updated by remote_volume
-    update_frequency: u32,
-
-    // How many samples remain until it is time to update local_volume with remote_volume.
-    samples_until_update: u32,
-}
-
-impl<I, F> PeriodicAccess<I, F>
-where
-    I: Source,
-    I::Item: Sample,
-    F: FnMut(&mut I),
-{
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.input
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.input
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I, F> Iterator for PeriodicAccess<I, F>
-where
-    I: Source,
-    I::Item: Sample,
-    F: FnMut(&mut I),
-{
-    type Item = I::Item;
-
-    #[inline]
-    fn next(&mut self) -> Option<I::Item> {
-        self.samples_until_update -= 1;
-        if self.samples_until_update == 0 {
-            (self.modifier)(&mut self.input);
-            self.samples_until_update = self.update_frequency;
-        }
-
-        self.input.next()
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.input.size_hint()
-    }
-}
-
-impl<I, F> Source for PeriodicAccess<I, F>
-where
-    I: Source,
-    I::Item: Sample,
-    F: FnMut(&mut I),
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        self.input.current_frame_len()
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.input.total_duration()
-    }
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.input.elapsed()
-    }
-    fn seek(&mut self, seek_time: Duration) -> Option<Duration> {
-        self.input.seek(seek_time)
-    }
-}
diff --git a/gonk-player/src/source/samples_converter.rs b/gonk-player/src/source/samples_converter.rs
deleted file mode 100644
index 314e71e6..00000000
--- a/gonk-player/src/source/samples_converter.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-use std::marker::PhantomData;
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-use cpal::Sample as CpalSample;
-
-/// An iterator that reads from a `Source` and converts the samples to a specific rate and
-/// channels count.
-///
-/// It implements `Source` as well, but all the data is guaranteed to be in a single frame whose
-/// channels and samples rate have been passed to `new`.
-#[derive(Clone)]
-pub struct SamplesConverter<I, D> {
-    inner: I,
-    dest: PhantomData<D>,
-}
-
-impl<I, D> SamplesConverter<I, D> {
-    #[inline]
-    pub fn new(input: I) -> SamplesConverter<I, D> {
-        SamplesConverter {
-            inner: input,
-            dest: PhantomData,
-        }
-    }
-
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.inner
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.inner
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.inner
-    }
-}
-
-impl<I, D> Iterator for SamplesConverter<I, D>
-where
-    I: Source,
-    I::Item: Sample,
-    D: Sample,
-{
-    type Item = D;
-
-    #[inline]
-    fn next(&mut self) -> Option<D> {
-        self.inner.next().map(|s| CpalSample::from(&s))
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.inner.size_hint()
-    }
-}
-
-impl<I, D> Source for SamplesConverter<I, D>
-where
-    I: Source,
-    I::Item: Sample,
-    D: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        self.inner.current_frame_len()
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.inner.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.inner.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.inner.total_duration()
-    }
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        Duration::from_secs(0)
-    }
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.inner.seek(time)
-    }
-}
diff --git a/gonk-player/src/source/stoppable.rs b/gonk-player/src/source/stoppable.rs
deleted file mode 100644
index 8d9f75b8..00000000
--- a/gonk-player/src/source/stoppable.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use crate::{conversions::Sample, source::Source};
-use std::time::Duration;
-
-/// Internal function that builds a `Stoppable` object.
-pub fn stoppable<I>(source: I) -> Stoppable<I> {
-    Stoppable {
-        input: source,
-        stopped: false,
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct Stoppable<I> {
-    input: I,
-    stopped: bool,
-}
-
-impl<I> Stoppable<I> {
-    /// Stops the sound.
-    #[inline]
-    pub fn stop(&mut self) {
-        self.stopped = true;
-    }
-
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.input
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.input
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-}
-
-impl<I> Iterator for Stoppable<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    type Item = I::Item;
-
-    #[inline]
-    fn next(&mut self) -> Option<I::Item> {
-        if self.stopped {
-            None
-        } else {
-            self.input.next()
-        }
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.input.size_hint()
-    }
-}
-
-impl<I> Source for Stoppable<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        self.input.current_frame_len()
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.input.total_duration()
-    }
-
-    #[inline]
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.input.seek(time)
-    }
-
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.input.elapsed()
-    }
-}
diff --git a/gonk-player/src/source/take.rs b/gonk-player/src/source/take.rs
deleted file mode 100644
index 87a80b63..00000000
--- a/gonk-player/src/source/take.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-
-/// Internal function that builds a `TakeDuration` object.
-pub fn take_duration<I>(input: I, duration: Duration) -> TakeDuration<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    TakeDuration {
-        current_frame_len: input.current_frame_len(),
-        duration_per_sample: TakeDuration::get_duration_per_sample(&input),
-        input,
-        remaining_duration: duration,
-        requested_duration: duration,
-        filter: None,
-    }
-}
-
-/// A filter that can be applied to a `TakeDuration`.
-#[derive(Clone, Debug)]
-enum DurationFilter {
-    FadeOut,
-}
-impl DurationFilter {
-    fn apply<I: Iterator>(
-        &self,
-        sample: <I as Iterator>::Item,
-        parent: &TakeDuration<I>,
-    ) -> <I as Iterator>::Item
-    where
-        I::Item: Sample,
-    {
-        use self::DurationFilter::*;
-        match self {
-            FadeOut => {
-                let remaining = parent.remaining_duration.as_millis() as f32;
-                let total = parent.requested_duration.as_millis() as f32;
-                sample.amplify(remaining / total)
-            }
-        }
-    }
-}
-
-const NANOS_PER_SEC: u64 = 1_000_000_000;
-
-/// A source that truncates the given source to a certain duration.
-#[derive(Clone, Debug)]
-pub struct TakeDuration<I> {
-    input: I,
-    remaining_duration: Duration,
-    requested_duration: Duration,
-    filter: Option<DurationFilter>,
-    // Remaining samples in current frame.
-    current_frame_len: Option<usize>,
-    // Only updated when the current frame len is exhausted.
-    duration_per_sample: Duration,
-}
-
-impl<I> TakeDuration<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    /// Returns the duration elapsed for each sample extracted.
-    #[inline]
-    fn get_duration_per_sample(input: &I) -> Duration {
-        let ns = NANOS_PER_SEC / (input.sample_rate() as u64 * input.channels() as u64);
-        // \|/ the maximum value of `ns` is one billion, so this can't fail
-        Duration::new(0, ns as u32)
-    }
-
-    /// Returns a reference to the inner source.
-    #[inline]
-    pub fn inner(&self) -> &I {
-        &self.input
-    }
-
-    /// Returns a mutable reference to the inner source.
-    #[inline]
-    pub fn inner_mut(&mut self) -> &mut I {
-        &mut self.input
-    }
-
-    /// Returns the inner source.
-    #[inline]
-    pub fn into_inner(self) -> I {
-        self.input
-    }
-
-    pub fn set_filter_fadeout(&mut self) {
-        self.filter = Some(DurationFilter::FadeOut);
-    }
-
-    pub fn clear_filter(&mut self) {
-        self.filter = None;
-    }
-}
-
-impl<I> Iterator for TakeDuration<I>
-where
-    I: Source,
-    I::Item: Sample,
-{
-    type Item = <I as Iterator>::Item;
-
-    fn next(&mut self) -> Option<<I as Iterator>::Item> {
-        if let Some(frame_len) = self.current_frame_len.take() {
-            if frame_len > 0 {
-                self.current_frame_len = Some(frame_len - 1);
-            } else {
-                self.current_frame_len = self.input.current_frame_len();
-                // Sample rate might have changed
-                self.duration_per_sample = Self::get_duration_per_sample(&self.input);
-            }
-        }
-
-        if self.remaining_duration <= self.duration_per_sample {
-            None
-        } else if let Some(sample) = self.input.next() {
-            let sample = match &self.filter {
-                Some(filter) => filter.apply(sample, self),
-                None => sample,
-            };
-
-            self.remaining_duration -= self.duration_per_sample;
-
-            Some(sample)
-        } else {
-            None
-        }
-    }
-
-    // TODO: size_hint
-}
-
-impl<I> Source for TakeDuration<I>
-where
-    I: Iterator + Source,
-    I::Item: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        let remaining_nanos = self.remaining_duration.as_secs() * NANOS_PER_SEC
-            + self.remaining_duration.subsec_nanos() as u64;
-        let nanos_per_sample = self.duration_per_sample.as_secs() * NANOS_PER_SEC
-            + self.duration_per_sample.subsec_nanos() as u64;
-        let remaining_samples = (remaining_nanos / nanos_per_sample) as usize;
-
-        self.input
-            .current_frame_len()
-            .filter(|value| *value < remaining_samples)
-            .or(Some(remaining_samples))
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.input.channels()
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.input.sample_rate()
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        if let Some(duration) = self.input.total_duration() {
-            if duration < self.requested_duration {
-                Some(duration)
-            } else {
-                Some(self.requested_duration)
-            }
-        } else {
-            None
-        }
-    }
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        self.input.elapsed()
-    }
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        self.input.seek(time)
-    }
-}
diff --git a/gonk-player/src/source/uniform.rs b/gonk-player/src/source/uniform.rs
deleted file mode 100644
index 0e9edcd0..00000000
--- a/gonk-player/src/source/uniform.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-use std::cmp;
-use std::time::Duration;
-
-use crate::conversions::{ChannelCountConverter, DataConverter, SampleRateConverter};
-use crate::{conversions::Sample, source::Source};
-
-/// An iterator that reads from a `Source` and converts the samples to a specific rate and
-/// channels count.
-///
-/// It implements `Source` as well, but all the data is guaranteed to be in a single frame whose
-/// channels and samples rate have been passed to `new`.
-#[derive(Clone)]
-pub struct UniformSourceIterator<I, D>
-where
-    I: Source,
-    I::Item: Sample,
-    D: Sample,
-{
-    inner: Option<DataConverter<ChannelCountConverter<SampleRateConverter<Take<I>>>, D>>,
-    target_channels: u16,
-    target_sample_rate: u32,
-    total_duration: Option<Duration>,
-}
-
-impl<I, D> UniformSourceIterator<I, D>
-where
-    I: Source,
-    I::Item: Sample,
-    D: Sample,
-{
-    #[inline]
-    pub fn new(
-        input: I,
-        target_channels: u16,
-        target_sample_rate: u32,
-    ) -> UniformSourceIterator<I, D> {
-        let total_duration = input.total_duration();
-        let input = UniformSourceIterator::bootstrap(input, target_channels, target_sample_rate);
-
-        UniformSourceIterator {
-            inner: Some(input),
-            target_channels,
-            target_sample_rate,
-            total_duration,
-        }
-    }
-
-    #[inline]
-    fn bootstrap(
-        input: I,
-        target_channels: u16,
-        target_sample_rate: u32,
-    ) -> DataConverter<ChannelCountConverter<SampleRateConverter<Take<I>>>, D> {
-        // Limit the frame length to something reasonable
-        let frame_len = input.current_frame_len().map(|x| x.min(32768));
-
-        let from_channels = input.channels();
-        let from_sample_rate = input.sample_rate();
-
-        let input = Take {
-            iter: input,
-            n: frame_len,
-        };
-        let input = SampleRateConverter::new(
-            input,
-            cpal::SampleRate(from_sample_rate),
-            cpal::SampleRate(target_sample_rate),
-            from_channels,
-        );
-        let input = ChannelCountConverter::new(input, from_channels, target_channels);
-
-        DataConverter::new(input)
-    }
-}
-
-impl<I, D> Iterator for UniformSourceIterator<I, D>
-where
-    I: Source,
-    I::Item: Sample,
-    D: Sample,
-{
-    type Item = D;
-
-    #[inline]
-    fn next(&mut self) -> Option<D> {
-        if let Some(value) = self.inner.as_mut().unwrap().next() {
-            return Some(value);
-        }
-
-        let input = self
-            .inner
-            .take()
-            .unwrap()
-            .into_inner()
-            .into_inner()
-            .into_inner()
-            .iter;
-
-        let mut input =
-            UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate);
-
-        let value = input.next();
-        self.inner = Some(input);
-        value
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        (self.inner.as_ref().unwrap().size_hint().0, None)
-    }
-}
-
-impl<I, D> Source for UniformSourceIterator<I, D>
-where
-    I: Iterator + Source,
-    I::Item: Sample,
-    D: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        None
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.target_channels
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.target_sample_rate
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        self.total_duration
-    }
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        Duration::from_secs(0)
-    }
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        let mut input = self
-            .inner
-            .take()
-            .unwrap()
-            .into_inner()
-            .into_inner()
-            .into_inner()
-            .iter;
-        let ret = input.seek(time);
-        let input =
-            UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate);
-
-        self.inner = Some(input);
-        ret
-    }
-}
-
-#[derive(Clone, Debug)]
-struct Take<I> {
-    iter: I,
-    n: Option<usize>,
-}
-
-impl<I> Iterator for Take<I>
-where
-    I: Iterator,
-{
-    type Item = <I as Iterator>::Item;
-
-    #[inline]
-    fn next(&mut self) -> Option<<I as Iterator>::Item> {
-        if let Some(n) = &mut self.n {
-            if *n != 0 {
-                *n -= 1;
-                self.iter.next()
-            } else {
-                None
-            }
-        } else {
-            self.iter.next()
-        }
-    }
-
-    #[inline]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        if let Some(n) = self.n {
-            let (lower, upper) = self.iter.size_hint();
-
-            let lower = cmp::min(lower, n);
-
-            let upper = match upper {
-                Some(x) if x < n => Some(x),
-                _ => Some(n),
-            };
-
-            (lower, upper)
-        } else {
-            self.iter.size_hint()
-        }
-    }
-}
diff --git a/gonk-player/src/source/zero.rs b/gonk-player/src/source/zero.rs
deleted file mode 100644
index af4e61f0..00000000
--- a/gonk-player/src/source/zero.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use std::marker::PhantomData;
-use std::time::Duration;
-
-use crate::{conversions::Sample, source::Source};
-
-/// An infinite source that produces zero.
-#[derive(Clone, Debug)]
-pub struct Zero<S> {
-    channels: u16,
-    sample_rate: u32,
-    marker: PhantomData<S>,
-}
-
-impl<S> Zero<S> {
-    #[inline]
-    pub fn new(channels: u16, sample_rate: u32) -> Zero<S> {
-        Zero {
-            channels,
-            sample_rate,
-            marker: PhantomData,
-        }
-    }
-}
-
-impl<S> Iterator for Zero<S>
-where
-    S: Sample,
-{
-    type Item = S;
-
-    #[inline]
-    fn next(&mut self) -> Option<S> {
-        Some(S::zero_value())
-    }
-}
-
-impl<S> Source for Zero<S>
-where
-    S: Sample,
-{
-    #[inline]
-    fn current_frame_len(&self) -> Option<usize> {
-        None
-    }
-
-    #[inline]
-    fn channels(&self) -> u16 {
-        self.channels
-    }
-
-    #[inline]
-    fn sample_rate(&self) -> u32 {
-        self.sample_rate
-    }
-
-    #[inline]
-    fn total_duration(&self) -> Option<Duration> {
-        None
-    }
-    #[inline]
-    fn elapsed(&mut self) -> Duration {
-        Duration::from_secs(0)
-    }
-    fn seek(&mut self, time: Duration) -> Option<Duration> {
-        Some(time)
-    }
-}
diff --git a/gonk-player/src/stream.rs b/gonk-player/src/stream.rs
deleted file mode 100644
index fc54dabc..00000000
--- a/gonk-player/src/stream.rs
+++ /dev/null
@@ -1,256 +0,0 @@
-use std::sync::{Arc, Weak};
-use std::{error, fmt};
-
-use crate::decoder;
-use crate::dynamic_mixer::{self, DynamicMixerController};
-use crate::source::Source;
-use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
-use cpal::Sample;
-
-/// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`.
-///
-/// If this is dropped playback will end & attached `OutputStreamHandle`s will no longer work.
-pub struct OutputStream {
-    mixer: Arc<DynamicMixerController<f32>>,
-    _stream: cpal::Stream,
-}
-
-/// More flexible handle to a `OutputStream` that provides playback.
-#[derive(Clone)]
-pub struct OutputStreamHandle {
-    mixer: Weak<DynamicMixerController<f32>>,
-}
-
-impl OutputStream {
-    /// Returns a new stream & handle using the given output device.
-    pub fn try_from_device(
-        device: &cpal::Device,
-    ) -> Result<(Self, OutputStreamHandle), StreamError> {
-        let (mixer, _stream) = device.try_new_output_stream()?;
-        _stream.play()?;
-        let out = Self { mixer, _stream };
-        let handle = OutputStreamHandle {
-            mixer: Arc::downgrade(&out.mixer),
-        };
-        Ok((out, handle))
-    }
-
-    /// Return a new stream & handle using the default output device.
-    ///
-    /// On failure will fallback to trying any non-default output devices.
-    pub fn try_default() -> Result<(Self, OutputStreamHandle), StreamError> {
-        let default_device = cpal::default_host()
-            .default_output_device()
-            .ok_or(StreamError::NoDevice)?;
-
-        let default_stream = Self::try_from_device(&default_device);
-
-        default_stream.or_else(|original_err| {
-            // default device didn't work, try other ones
-            let mut devices = match cpal::default_host().output_devices() {
-                Ok(d) => d,
-                Err(_) => return Err(original_err),
-            };
-
-            devices
-                .find_map(|d| Self::try_from_device(&d).ok())
-                .ok_or(original_err)
-        })
-    }
-}
-
-impl OutputStreamHandle {
-    /// Plays a source with a device until it ends.
-    pub fn play_raw<S>(&self, source: S) -> Result<(), PlayError>
-    where
-        S: Source<Item = f32> + Send + 'static,
-    {
-        let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?;
-        mixer.add(source);
-        Ok(())
-    }
-}
-
-/// An error occurred while attemping to play a sound.
-#[derive(Debug)]
-pub enum PlayError {
-    /// Attempting to decode the audio failed.
-    DecoderError(decoder::DecoderError),
-    /// The output device was lost.
-    NoDevice,
-}
-
-impl From<decoder::DecoderError> for PlayError {
-    fn from(err: decoder::DecoderError) -> Self {
-        Self::DecoderError(err)
-    }
-}
-
-impl fmt::Display for PlayError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::DecoderError(e) => e.fmt(f),
-            Self::NoDevice => write!(f, "NoDevice"),
-        }
-    }
-}
-
-impl error::Error for PlayError {
-    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
-        match self {
-            Self::DecoderError(e) => Some(e),
-            Self::NoDevice => None,
-        }
-    }
-}
-
-#[derive(Debug)]
-pub enum StreamError {
-    PlayStreamError(cpal::PlayStreamError),
-    DefaultStreamConfigError(cpal::DefaultStreamConfigError),
-    BuildStreamError(cpal::BuildStreamError),
-    SupportedStreamConfigsError(cpal::SupportedStreamConfigsError),
-    NoDevice,
-}
-
-impl From<cpal::DefaultStreamConfigError> for StreamError {
-    fn from(err: cpal::DefaultStreamConfigError) -> Self {
-        Self::DefaultStreamConfigError(err)
-    }
-}
-
-impl From<cpal::SupportedStreamConfigsError> for StreamError {
-    fn from(err: cpal::SupportedStreamConfigsError) -> Self {
-        Self::SupportedStreamConfigsError(err)
-    }
-}
-
-impl From<cpal::BuildStreamError> for StreamError {
-    fn from(err: cpal::BuildStreamError) -> Self {
-        Self::BuildStreamError(err)
-    }
-}
-
-impl From<cpal::PlayStreamError> for StreamError {
-    fn from(err: cpal::PlayStreamError) -> Self {
-        Self::PlayStreamError(err)
-    }
-}
-
-impl fmt::Display for StreamError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Self::PlayStreamError(e) => e.fmt(f),
-            Self::BuildStreamError(e) => e.fmt(f),
-            Self::DefaultStreamConfigError(e) => e.fmt(f),
-            Self::SupportedStreamConfigsError(e) => e.fmt(f),
-            Self::NoDevice => write!(f, "NoDevice"),
-        }
-    }
-}
-
-impl error::Error for StreamError {
-    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
-        match self {
-            Self::PlayStreamError(e) => Some(e),
-            Self::BuildStreamError(e) => Some(e),
-            Self::DefaultStreamConfigError(e) => Some(e),
-            Self::SupportedStreamConfigsError(e) => Some(e),
-            Self::NoDevice => None,
-        }
-    }
-}
-
-/// Extensions to `cpal::Device`
-pub(crate) trait CpalDeviceExt {
-    fn new_output_stream_with_format(
-        &self,
-        format: cpal::SupportedStreamConfig,
-    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError>;
-
-    fn try_new_output_stream(
-        &self,
-    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError>;
-}
-
-impl CpalDeviceExt for cpal::Device {
-    fn new_output_stream_with_format(
-        &self,
-        format: cpal::SupportedStreamConfig,
-    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError> {
-        let (mixer_tx, mut mixer_rx) =
-            dynamic_mixer::mixer::<f32>(format.channels(), format.sample_rate().0);
-
-        let error_callback = |err| panic!("an error occurred on output stream: {}", err);
-
-        match format.sample_format() {
-            cpal::SampleFormat::F32 => self.build_output_stream::<f32, _, _>(
-                &format.config(),
-                move |data, _| {
-                    data.iter_mut()
-                        .for_each(|d| *d = mixer_rx.next().unwrap_or(0f32))
-                },
-                error_callback,
-            ),
-            cpal::SampleFormat::I16 => self.build_output_stream::<i16, _, _>(
-                &format.config(),
-                move |data, _| {
-                    data.iter_mut()
-                        .for_each(|d| *d = mixer_rx.next().map(|s| s.to_i16()).unwrap_or(0i16))
-                },
-                error_callback,
-            ),
-            cpal::SampleFormat::U16 => self.build_output_stream::<u16, _, _>(
-                &format.config(),
-                move |data, _| {
-                    data.iter_mut().for_each(|d| {
-                        *d = mixer_rx
-                            .next()
-                            .map(|s| s.to_u16())
-                            .unwrap_or(u16::max_value() / 2)
-                    })
-                },
-                error_callback,
-            ),
-        }
-        .map(|stream| (mixer_tx, stream))
-    }
-
-    fn try_new_output_stream(
-        &self,
-    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError> {
-        // Determine the format to use for the new stream.
-        let default_format = self.default_output_config()?;
-
-        self.new_output_stream_with_format(default_format)
-            .or_else(|err| {
-                // look through all supported formats to see if another works
-                supported_output_formats(self)?
-                    .filter_map(|format| self.new_output_stream_with_format(format).ok())
-                    .next()
-                    // return original error if nothing works
-                    .ok_or(StreamError::BuildStreamError(err))
-            })
-    }
-}
-
-/// All the supported output formats with sample rates
-fn supported_output_formats(
-    device: &cpal::Device,
-) -> Result<impl Iterator<Item = cpal::SupportedStreamConfig>, StreamError> {
-    const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100);
-
-    let mut supported: Vec<_> = device.supported_output_configs()?.collect();
-    supported.sort_by(|a, b| b.cmp_default_heuristics(a));
-
-    Ok(supported.into_iter().flat_map(|sf| {
-        let max_rate = sf.max_sample_rate();
-        let min_rate = sf.min_sample_rate();
-        let mut formats = vec![sf.clone().with_max_sample_rate()];
-        if HZ_44100 < max_rate && HZ_44100 > min_rate {
-            formats.push(sf.clone().with_sample_rate(HZ_44100))
-        }
-        formats.push(sf.with_sample_rate(min_rate));
-        formats
-    }))
-}
diff --git a/gonk/src/app.rs b/gonk/src/app.rs
index 7a75a783..c9005bd2 100644
--- a/gonk/src/app.rs
+++ b/gonk/src/app.rs
@@ -40,7 +40,7 @@ pub enum Mode {
 
 const TICK_RATE: Duration = Duration::from_millis(200);
 const POLL_RATE: Duration = Duration::from_millis(4);
-const SEEK_TIME: f64 = 10.0;
+const SEEK_TIME: f32 = 10.0;
 
 pub struct App {
     terminal: Terminal<CrosstermBackend<Stdout>>,
@@ -230,7 +230,7 @@ impl App {
                                     self.mode = Mode::Playlist;
                                 }
                                 Mode::Queue => {
-                                    if let Some(song) = self.queue.selected() {
+                                    if let Some(song) = self.queue.player.selected_song() {
                                         self.playlist.add_to_playlist(&[song.clone()]);
                                         self.mode = Mode::Playlist;
                                     }
@@ -244,7 +244,7 @@ impl App {
                                 }
                                 Mode::Queue => {
                                     if let Some(i) = self.queue.ui.index() {
-                                        self.queue.player.play_song(i);
+                                        self.queue.player.play_index(i);
                                     }
                                 }
                                 Mode::Search => self.search.on_enter(&mut self.queue.player),
@@ -294,18 +294,18 @@ impl App {
                                 self.queue.player.seek_by(SEEK_TIME)
                             }
                             _ if hotkey.previous == bind && self.mode != Mode::Search => {
-                                self.queue.player.prev_song()
+                                self.queue.player.previous()
                             }
                             _ if hotkey.next == bind && self.mode != Mode::Search => {
-                                self.queue.player.next_song()
+                                self.queue.player.next()
                             }
                             _ if hotkey.volume_up == bind => {
                                 self.queue.player.volume_up();
-                                self.toml.set_volume(self.queue.player.volume);
+                                self.toml.set_volume(self.queue.player.get_volume());
                             }
                             _ if hotkey.volume_down == bind => {
                                 self.queue.player.volume_down();
-                                self.toml.set_volume(self.queue.player.volume);
+                                self.toml.set_volume(self.queue.player.get_volume());
                             }
                             _ if hotkey.delete == bind => match self.mode {
                                 Mode::Queue => self.queue.delete(),
diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
index 3f18aba7..8e128c13 100644
--- a/gonk/src/app/queue.rs
+++ b/gonk/src/app/queue.rs
@@ -2,7 +2,7 @@ use crate::toml::Colors;
 use crate::widgets::{Cell, Gauge, Row, Table, TableState};
 use crate::Frame;
 use crossterm::event::KeyModifiers;
-use gonk_player::{Index, Player, Song};
+use gonk_player::{Index, Player};
 use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
 use tui::style::{Color, Modifier, Style};
 use tui::text::{Span, Spans};
@@ -28,7 +28,7 @@ impl Queue {
         }
     }
     pub fn update(&mut self) {
-        if self.ui.is_none() && !self.player.songs.is_empty() {
+        if self.ui.selected().is_none() && !self.player.is_empty() {
             self.ui.select(Some(0));
         }
         self.player.update();
@@ -51,10 +51,10 @@ impl Queue {
         );
     }
     pub fn up(&mut self) {
-        self.ui.up_with_len(self.player.songs.len());
+        self.ui.up_with_len(self.player.total_songs());
     }
     pub fn down(&mut self) {
-        self.ui.down_with_len(self.player.songs.len());
+        self.ui.down_with_len(self.player.total_songs());
     }
     pub fn clear(&mut self) {
         self.player.clear();
@@ -66,17 +66,14 @@ impl Queue {
     }
     pub fn delete(&mut self) {
         if let Some(i) = self.ui.index() {
-            self.player.delete_song(i);
+            self.player.delete_index(i);
             //make sure the ui index is in sync
-            let len = self.player.songs.len().saturating_sub(1);
+            let len = self.player.total_songs().saturating_sub(1);
             if i > len {
                 self.ui.select(Some(len));
             }
         }
     }
-    pub fn selected(&self) -> Option<&Song> {
-        self.player.songs.selected()
-    }
 }
 
 impl Queue {
@@ -102,8 +99,8 @@ impl Queue {
 
             //Mouse support for the seek bar.
             if (size.height - 2 == y || size.height - 1 == y) && size.height > 15 {
-                let ratio = f64::from(x) / f64::from(size.width);
-                let duration = self.player.duration;
+                let ratio = x as f32 / size.width as f32;
+                let duration = self.player.duration();
                 let new_time = duration * ratio;
                 self.player.seek_to(new_time);
                 self.clicked_pos = None;
@@ -117,7 +114,7 @@ impl Queue {
 
                     //Make sure you didn't click on the seek bar
                     //and that the song index exists.
-                    if index < self.player.songs.len()
+                    if index < self.player.total_songs()
                         && ((size.height < 15 && y < size.height.saturating_sub(1))
                             || y < size.height.saturating_sub(3))
                     {
@@ -138,9 +135,9 @@ impl Queue {
             area,
         );
 
-        let state = if self.player.songs.is_empty() {
+        let state = if self.player.is_empty() {
             String::from("╭─Stopped")
-        } else if !self.player.is_paused() {
+        } else if self.player.is_playing() {
             String::from("╭─Playing")
         } else {
             String::from("╭─Paused")
@@ -148,15 +145,15 @@ impl Queue {
 
         f.render_widget(Paragraph::new(state).alignment(Alignment::Left), area);
 
-        if !self.player.songs.is_empty() {
+        if !self.player.is_empty() {
             self.draw_title(f, area);
         }
 
-        let volume = Spans::from(format!("Vol: {}%─╮", self.player.volume));
+        let volume = Spans::from(format!("Vol: {}%─╮", self.player.get_volume()));
         f.render_widget(Paragraph::new(volume).alignment(Alignment::Right), area);
     }
     fn draw_title(&mut self, f: &mut Frame, area: Rect) {
-        let title = if let Some(song) = self.player.songs.selected() {
+        let title = if let Some(song) = self.player.selected_song() {
             let mut name = song.name.trim_end().to_string();
             let mut album = song.album.trim_end().to_string();
             let mut artist = song.artist.trim_end().to_string();
@@ -198,7 +195,7 @@ impl Queue {
         f.render_widget(Paragraph::new(title).alignment(Alignment::Center), area);
     }
     fn draw_body(&mut self, f: &mut Frame, area: Rect) -> Option<(usize, usize)> {
-        if self.player.songs.is_empty() {
+        if self.player.is_empty() {
             if self.clicked_pos.is_some() {
                 self.clicked_pos = None;
             }
@@ -212,11 +209,8 @@ impl Queue {
             return None;
         }
 
-        let (songs, player_index, ui_index) = (
-            &self.player.songs.data,
-            self.player.songs.index(),
-            self.ui.index(),
-        );
+        let songs = self.player.get_index();
+        let (songs, player_index, ui_index) = (&songs.data, songs.index(), self.ui.index());
 
         let mut items: Vec<Row> = songs
             .iter()
@@ -329,7 +323,7 @@ impl Queue {
         Some(row_bounds)
     }
     fn draw_seeker(&mut self, f: &mut Frame, area: Rect) {
-        if self.player.songs.is_empty() {
+        if self.player.is_empty() {
             return f.render_widget(
                 Block::default()
                     .border_type(BorderType::Rounded)
@@ -339,7 +333,7 @@ impl Queue {
         }
 
         let elapsed = self.player.elapsed();
-        let duration = self.player.duration;
+        let duration = self.player.duration();
 
         let seeker = format!(
             "{:02}:{:02}/{:02}:{:02}",
@@ -349,7 +343,7 @@ impl Queue {
             duration.trunc() as u32 % 60,
         );
 
-        let ratio = self.player.elapsed() / self.player.duration;
+        let ratio = self.player.elapsed() / self.player.duration();
         let ratio = if ratio.is_nan() {
             0.0
         } else {
@@ -364,7 +358,7 @@ impl Queue {
                         .border_type(BorderType::Rounded),
                 )
                 .gauge_style(Style::default().fg(self.colors.seeker))
-                .ratio(ratio)
+                .ratio(ratio as f64)
                 .label(seeker),
             area,
         );
diff --git a/gonk/src/app/search.rs b/gonk/src/app/search.rs
index e2c573b1..3003a743 100644
--- a/gonk/src/app/search.rs
+++ b/gonk/src/app/search.rs
@@ -465,7 +465,7 @@ impl Search {
         let area = f.size();
         //Move the cursor position when typing
         if let Mode::Search = self.mode {
-            if self.results.is_none() && self.query.is_empty() {
+            if self.results.selected().is_none() && self.query.is_empty() {
                 f.set_cursor(1, 1);
             } else {
                 let len = self.query.len() as u16;
diff --git a/gonk/src/app/status_bar.rs b/gonk/src/app/status_bar.rs
index 6cdc5cc5..00f94cd1 100644
--- a/gonk/src/app/status_bar.rs
+++ b/gonk/src/app/status_bar.rs
@@ -1,6 +1,6 @@
 use super::queue::Queue;
 use crate::Frame;
-use crate::{toml::Colors, sqlite};
+use crate::{sqlite, toml::Colors};
 use std::time::{Duration, Instant};
 use tui::{
     layout::{Alignment, Constraint, Direction, Layout, Rect},
@@ -106,7 +106,7 @@ impl StatusBar {
         } else if self.wait_timer.is_some() {
             Spans::from(self.scan_message.as_str())
         } else {
-            if let Some(song) = queue.selected() {
+            if let Some(song) = queue.player.selected_song() {
                 Spans::from(vec![
                     Span::raw(" "),
                     Span::styled(
@@ -144,10 +144,10 @@ impl StatusBar {
 
         //TODO: Draw mini progress bar here.
 
-        let text = if queue.player.is_paused() {
-            String::from("Paused ")
+        let text = if queue.player.is_playing() {
+            format!("Vol: {}% ", queue.player.get_volume())
         } else {
-            format!("Vol: {}% ", queue.player.volume)
+            String::from("Paused ")
         };
 
         f.render_widget(

From eaf3e43aee10534f89531c2dd5b2346e8165f7f5 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 14:47:47 +0930
Subject: [PATCH 02/40] duration

---
 gonk-player/src/lib.rs              | 17 ++++--
 gonk-player/src/sample_processor.rs | 88 ++++++++++++++++-------------
 gonk/src/app/queue.rs               |  6 +-
 3 files changed, 64 insertions(+), 47 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index a24cb50a..5539149a 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -8,6 +8,7 @@ use std::{
     path::PathBuf,
     sync::{Arc, RwLock},
     thread,
+    time::Duration,
 };
 
 mod index;
@@ -35,6 +36,7 @@ pub struct Player {
     playing: bool,
     volume: u16,
     songs: Index<Song>,
+    duration: Arc<RwLock<Duration>>,
 }
 
 impl Player {
@@ -45,6 +47,7 @@ impl Player {
             r,
             playing: true,
             volume,
+            duration: Arc::new(RwLock::new(Duration::default())),
             songs: Index::default(),
         }
     }
@@ -53,8 +56,8 @@ impl Player {
         //     self.next_song();
         // }
     }
-    pub fn duration(&self) -> f32 {
-        0.0
+    pub fn duration(&self) -> Duration {
+        *self.duration.read().unwrap()
     }
     pub fn is_empty(&self) -> bool {
         self.songs.is_empty()
@@ -75,8 +78,7 @@ impl Player {
                 self.stop();
             }
             self.playing = true;
-            let r2 = self.r.clone();
-            Player::run(r2, song.path.clone());
+            self.run(song.path.clone());
         }
     }
     pub fn play_index(&mut self, i: usize) {
@@ -165,7 +167,9 @@ impl Player {
     pub fn stop(&self) {
         self.s.send(Event::Stop).unwrap();
     }
-    fn run(r: Receiver<Event>, path: PathBuf) {
+    fn run(&self, path: PathBuf) {
+        let r = self.r.clone();
+        let duration = self.duration.clone();
         thread::spawn(move || {
             let device = cpal::default_host().default_output_device().unwrap();
             let config = device.default_output_config().unwrap();
@@ -175,6 +179,9 @@ impl Player {
                 path,
             )));
 
+            //Update the duration;
+            *duration.write().unwrap() = processor.read().unwrap().duration;
+
             let p = processor.clone();
 
             let stream = device
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 10049508..34feb508 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -17,49 +17,14 @@ pub struct SampleProcessor {
     pub decoder: Box<dyn Decoder>,
     pub format: Box<dyn FormatReader>,
     pub spec: SignalSpec,
-    pub duration: u64,
+    pub capacity: u64,
+    pub duration: Duration,
     pub converter: SampleRateConverter,
     pub finished: bool,
     pub left: bool,
 }
 
 impl SampleProcessor {
-    pub fn next_sample(&mut self) -> f32 {
-        loop {
-            if let Some(sample) = self.converter.next() {
-                return sample * 0.1;
-            } else {
-                self.update();
-            }
-        }
-    }
-    pub fn update(&mut self) {
-        match self.format.next_packet() {
-            Ok(packet) => {
-                let decoded = self.decoder.decode(&packet).unwrap();
-                let mut buffer = SampleBuffer::<f32>::new(self.duration, self.spec);
-                buffer.copy_interleaved_ref(decoded);
-
-                self.converter.update(buffer.samples().to_vec().into_iter());
-            }
-            Err(e) => match e {
-                symphonia::core::errors::Error::IoError(_) => self.finished = true,
-                _ => panic!("{:?}", e),
-            },
-        }
-    }
-    pub fn seek_to(&mut self, time: Duration) {
-        let nanos_per_sec = 1_000_000_000.0;
-        self.format
-            .seek(
-                SeekMode::Coarse,
-                SeekTo::Time {
-                    time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / nanos_per_sec),
-                    track_id: None,
-                },
-            )
-            .unwrap();
-    }
     pub fn new(sample_rate: Option<u32>, path: impl AsRef<Path>) -> Self {
         let source = Box::new(File::open(path).unwrap());
 
@@ -78,6 +43,14 @@ impl SampleProcessor {
             .unwrap();
 
         let track = probed.format.default_track().unwrap();
+
+        let duration = if let Some(tb) = track.codec_params.time_base {
+            let n_frames = track.codec_params.n_frames.unwrap();
+            let time = tb.calc_time(n_frames);
+            Duration::from_secs(time.seconds) + Duration::from_secs_f64(time.frac)
+        } else {
+            panic!("Could not decode track duration.");
+        };
         let mut decoder = symphonia::default::get_codecs()
             .make(&track.codec_params, &DecoderOptions::default())
             .unwrap();
@@ -86,15 +59,16 @@ impl SampleProcessor {
         let decoded = decoder.decode(&current_frame).unwrap();
 
         let spec = decoded.spec().to_owned();
-        let duration = decoded.capacity() as u64;
+        let capacity = decoded.capacity() as u64;
 
-        let mut sample_buffer = SampleBuffer::<f32>::new(duration, spec);
+        let mut sample_buffer = SampleBuffer::<f32>::new(capacity, spec);
         sample_buffer.copy_interleaved_ref(decoded);
 
         Self {
             format: probed.format,
             decoder,
             spec,
+            capacity,
             duration,
             converter: SampleRateConverter::new(
                 sample_buffer.samples().to_vec().into_iter(),
@@ -105,4 +79,40 @@ impl SampleProcessor {
             left: true,
         }
     }
+    pub fn next_sample(&mut self) -> f32 {
+        loop {
+            if let Some(sample) = self.converter.next() {
+                return sample * 0.1;
+            } else {
+                self.update();
+            }
+        }
+    }
+    pub fn update(&mut self) {
+        match self.format.next_packet() {
+            Ok(packet) => {
+                let decoded = self.decoder.decode(&packet).unwrap();
+                let mut buffer = SampleBuffer::<f32>::new(self.capacity, self.spec);
+                buffer.copy_interleaved_ref(decoded);
+
+                self.converter.update(buffer.samples().to_vec().into_iter());
+            }
+            Err(e) => match e {
+                symphonia::core::errors::Error::IoError(_) => self.finished = true,
+                _ => panic!("{:?}", e),
+            },
+        }
+    }
+    pub fn seek_to(&mut self, time: Duration) {
+        let nanos_per_sec = 1_000_000_000.0;
+        self.format
+            .seek(
+                SeekMode::Coarse,
+                SeekTo::Time {
+                    time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / nanos_per_sec),
+                    track_id: None,
+                },
+            )
+            .unwrap();
+    }
 }
diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
index 8e128c13..4dde6bad 100644
--- a/gonk/src/app/queue.rs
+++ b/gonk/src/app/queue.rs
@@ -100,7 +100,7 @@ impl Queue {
             //Mouse support for the seek bar.
             if (size.height - 2 == y || size.height - 1 == y) && size.height > 15 {
                 let ratio = x as f32 / size.width as f32;
-                let duration = self.player.duration();
+                let duration = self.player.duration().as_secs_f32();
                 let new_time = duration * ratio;
                 self.player.seek_to(new_time);
                 self.clicked_pos = None;
@@ -333,7 +333,7 @@ impl Queue {
         }
 
         let elapsed = self.player.elapsed();
-        let duration = self.player.duration();
+        let duration = self.player.duration().as_secs_f32();
 
         let seeker = format!(
             "{:02}:{:02}/{:02}:{:02}",
@@ -343,7 +343,7 @@ impl Queue {
             duration.trunc() as u32 % 60,
         );
 
-        let ratio = self.player.elapsed() / self.player.duration();
+        let ratio = elapsed / duration;
         let ratio = if ratio.is_nan() {
             0.0
         } else {

From 5da02abce21f75e1d8440f1230eea6063908b7ea Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 14:58:17 +0930
Subject: [PATCH 03/40] elapsed

---
 gonk-player/src/lib.rs              | 17 ++++++++++++-----
 gonk-player/src/sample_processor.rs |  9 +++++++++
 gonk/src/app/queue.rs               |  2 +-
 3 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 5539149a..c3035e8f 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -37,6 +37,7 @@ pub struct Player {
     volume: u16,
     songs: Index<Song>,
     duration: Arc<RwLock<Duration>>,
+    elapsed: Arc<RwLock<Duration>>,
 }
 
 impl Player {
@@ -45,9 +46,10 @@ impl Player {
         Self {
             s,
             r,
-            playing: true,
+            playing: false,
             volume,
             duration: Arc::new(RwLock::new(Duration::default())),
+            elapsed: Arc::new(RwLock::new(Duration::default())),
             songs: Index::default(),
         }
     }
@@ -59,6 +61,9 @@ impl Player {
     pub fn duration(&self) -> Duration {
         *self.duration.read().unwrap()
     }
+    pub fn elapsed(&self) -> Duration {
+        *self.elapsed.read().unwrap()
+    }
     pub fn is_empty(&self) -> bool {
         self.songs.is_empty()
     }
@@ -145,9 +150,7 @@ impl Player {
         self.s.send(Event::Play).unwrap();
         self.playing = true;
     }
-    pub fn elapsed(&self) -> f32 {
-        0.0
-    }
+
     pub fn get_index(&self) -> &Index<Song> {
         &self.songs
     }
@@ -170,6 +173,8 @@ impl Player {
     fn run(&self, path: PathBuf) {
         let r = self.r.clone();
         let duration = self.duration.clone();
+        let elapsed = self.elapsed.clone();
+
         thread::spawn(move || {
             let device = cpal::default_host().default_output_device().unwrap();
             let config = device.default_output_config().unwrap();
@@ -201,7 +206,9 @@ impl Player {
             stream.play().unwrap();
 
             loop {
-                if let Ok(event) = r.recv() {
+                *elapsed.write().unwrap() = processor.read().unwrap().elapsed;
+
+                if let Ok(event) = r.recv_timeout(Duration::from_millis(16)) {
                     dbg!(&event);
                     match event {
                         Event::Play => stream.play().unwrap(),
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 34feb508..499a41aa 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -19,6 +19,7 @@ pub struct SampleProcessor {
     pub spec: SignalSpec,
     pub capacity: u64,
     pub duration: Duration,
+    pub elapsed: Duration,
     pub converter: SampleRateConverter,
     pub finished: bool,
     pub left: bool,
@@ -70,6 +71,7 @@ impl SampleProcessor {
             spec,
             capacity,
             duration,
+            elapsed: Duration::default(),
             converter: SampleRateConverter::new(
                 sample_buffer.samples().to_vec().into_iter(),
                 spec.rate,
@@ -96,6 +98,13 @@ impl SampleProcessor {
                 buffer.copy_interleaved_ref(decoded);
 
                 self.converter.update(buffer.samples().to_vec().into_iter());
+
+                //Update elapsed
+                let ts = packet.ts();
+                let track = self.format.default_track().unwrap();
+                let tb = track.codec_params.time_base.unwrap();
+                let t = tb.calc_time(ts);
+                self.elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
             }
             Err(e) => match e {
                 symphonia::core::errors::Error::IoError(_) => self.finished = true,
diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
index 4dde6bad..dbe4312f 100644
--- a/gonk/src/app/queue.rs
+++ b/gonk/src/app/queue.rs
@@ -332,7 +332,7 @@ impl Queue {
             );
         }
 
-        let elapsed = self.player.elapsed();
+        let elapsed = self.player.elapsed().as_secs_f32();
         let duration = self.player.duration().as_secs_f32();
 
         let seeker = format!(

From cd05abbeb059cfd172645a89a27892fdda9c46ab Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 15:08:27 +0930
Subject: [PATCH 04/40] prev + next

---
 gonk-player/src/lib.rs              | 16 ++++++++++------
 gonk-player/src/sample_processor.rs | 10 +++++++---
 gonk/src/app/queue.rs               |  2 +-
 gonk/src/app/search.rs              |  2 +-
 4 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index c3035e8f..ecd752a5 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -136,8 +136,14 @@ impl Player {
             self.play();
         }
     }
-    pub fn previous(&self) {}
-    pub fn next(&self) {}
+    pub fn previous(&mut self) {
+        self.songs.up();
+        self.play_selected();
+    }
+    pub fn next(&mut self) {
+        self.songs.down();
+        self.play_selected();
+    }
     pub fn volume_up(&self) {}
     pub fn volume_down(&self) {}
     pub fn is_playing(&self) -> bool {
@@ -209,13 +215,11 @@ impl Player {
                 *elapsed.write().unwrap() = processor.read().unwrap().elapsed;
 
                 if let Ok(event) = r.recv_timeout(Duration::from_millis(16)) {
-                    dbg!(&event);
                     match event {
                         Event::Play => stream.play().unwrap(),
                         Event::Pause => stream.pause().unwrap(),
-                        Event::SeekBy(_) => (),
-                        Event::SeekTo(_) => (),
-                        // Event::Seek(duration) => processor.write().unwrap().seek_to(duration),
+                        Event::SeekBy(duration) => processor.write().unwrap().seek_by(duration),
+                        Event::SeekTo(duration) => processor.write().unwrap().seek_to(duration),
                         Event::Stop => break,
                     }
                 }
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 499a41aa..0972afcf 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -112,13 +112,17 @@ impl SampleProcessor {
             },
         }
     }
-    pub fn seek_to(&mut self, time: Duration) {
-        let nanos_per_sec = 1_000_000_000.0;
+    pub fn seek_by(&mut self, time: f32) {
+        let time = self.elapsed.as_secs_f32() + time;
+        self.seek_to(time);
+    }
+    pub fn seek_to(&mut self, time: f32) {
+        let time = Duration::from_secs_f32(time);
         self.format
             .seek(
                 SeekMode::Coarse,
                 SeekTo::Time {
-                    time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / nanos_per_sec),
+                    time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / 1_000_000_000.0),
                     track_id: None,
                 },
             )
diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
index dbe4312f..aa29cab4 100644
--- a/gonk/src/app/queue.rs
+++ b/gonk/src/app/queue.rs
@@ -28,7 +28,7 @@ impl Queue {
         }
     }
     pub fn update(&mut self) {
-        if self.ui.selected().is_none() && !self.player.is_empty() {
+        if self.ui.index().is_none() && !self.player.is_empty() {
             self.ui.select(Some(0));
         }
         self.player.update();
diff --git a/gonk/src/app/search.rs b/gonk/src/app/search.rs
index 3003a743..5299e846 100644
--- a/gonk/src/app/search.rs
+++ b/gonk/src/app/search.rs
@@ -465,7 +465,7 @@ impl Search {
         let area = f.size();
         //Move the cursor position when typing
         if let Mode::Search = self.mode {
-            if self.results.selected().is_none() && self.query.is_empty() {
+            if self.query.is_empty() {
                 f.set_cursor(1, 1);
             } else {
                 let len = self.query.len() as u16;

From cd02fefd4c47a944119324449a7eb882b9c84228 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 15:16:08 +0930
Subject: [PATCH 05/40] volume

---
 gonk-player/src/lib.rs              | 43 +++++++++++++++++++++++++++--
 gonk-player/src/sample_processor.rs |  6 ++--
 2 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index ecd752a5..3df0f51b 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -21,6 +21,9 @@ pub use cpal::Device;
 pub use index::Index;
 pub use song::Song;
 
+const VOLUME_STEP: u16 = 5;
+const VOLUME_REDUCTION: f32 = 600.0;
+
 #[derive(Debug)]
 pub enum Event {
     Play,
@@ -28,6 +31,7 @@ pub enum Event {
     Stop,
     SeekBy(f32),
     SeekTo(f32),
+    Volume(f32),
 }
 
 pub struct Player {
@@ -144,8 +148,40 @@ impl Player {
         self.songs.down();
         self.play_selected();
     }
-    pub fn volume_up(&self) {}
-    pub fn volume_down(&self) {}
+    pub fn volume_up(&mut self) {
+        self.volume += VOLUME_STEP;
+
+        if self.volume > 100 {
+            self.volume = 100;
+        }
+
+        self.update_volume();
+    }
+    pub fn volume_down(&mut self) {
+        if self.volume != 0 {
+            self.volume -= VOLUME_STEP;
+        }
+
+        self.update_volume();
+    }
+    fn update_volume(&self) {
+        self.s.send(Event::Volume(self.real_volume())).unwrap();
+    }
+    fn real_volume(&self) -> f32 {
+        if let Some(song) = self.songs.selected() {
+            let volume = self.volume as f32 / VOLUME_REDUCTION;
+            //Calculate the volume with gain
+            if song.track_gain == 0.0 {
+                //Reduce the volume a little to match
+                //songs with replay gain information.
+                volume * 0.75
+            } else {
+                volume * song.track_gain as f32
+            }
+        } else {
+            self.volume as f32 / VOLUME_REDUCTION
+        }
+    }
     pub fn is_playing(&self) -> bool {
         self.playing
     }
@@ -180,6 +216,7 @@ impl Player {
         let r = self.r.clone();
         let duration = self.duration.clone();
         let elapsed = self.elapsed.clone();
+        let volume = self.real_volume();
 
         thread::spawn(move || {
             let device = cpal::default_host().default_output_device().unwrap();
@@ -188,6 +225,7 @@ impl Player {
             let processor = Arc::new(RwLock::new(SampleProcessor::new(
                 Some(config.sample_rate().0),
                 path,
+                volume,
             )));
 
             //Update the duration;
@@ -220,6 +258,7 @@ impl Player {
                         Event::Pause => stream.pause().unwrap(),
                         Event::SeekBy(duration) => processor.write().unwrap().seek_by(duration),
                         Event::SeekTo(duration) => processor.write().unwrap().seek_to(duration),
+                        Event::Volume(volume) => processor.write().unwrap().volume = volume,
                         Event::Stop => break,
                     }
                 }
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 0972afcf..8b85a92c 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -23,10 +23,11 @@ pub struct SampleProcessor {
     pub converter: SampleRateConverter,
     pub finished: bool,
     pub left: bool,
+    pub volume: f32,
 }
 
 impl SampleProcessor {
-    pub fn new(sample_rate: Option<u32>, path: impl AsRef<Path>) -> Self {
+    pub fn new(sample_rate: Option<u32>, path: impl AsRef<Path>, volume: f32) -> Self {
         let source = Box::new(File::open(path).unwrap());
 
         let mss = MediaSourceStream::new(source, Default::default());
@@ -79,12 +80,13 @@ impl SampleProcessor {
             ),
             finished: false,
             left: true,
+            volume,
         }
     }
     pub fn next_sample(&mut self) -> f32 {
         loop {
             if let Some(sample) = self.converter.next() {
-                return sample * 0.1;
+                return sample * self.volume;
             } else {
                 self.update();
             }

From 990d7b5d94fabb4ae186d843643f1401ff77e919 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 15:21:23 +0930
Subject: [PATCH 06/40] handle decode errors

---
 gonk-player/src/sample_processor.rs | 44 ++++++++++++++++++-----------
 1 file changed, 27 insertions(+), 17 deletions(-)

diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 8b85a92c..cf43ef49 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -93,25 +93,35 @@ impl SampleProcessor {
         }
     }
     pub fn update(&mut self) {
-        match self.format.next_packet() {
-            Ok(packet) => {
-                let decoded = self.decoder.decode(&packet).unwrap();
-                let mut buffer = SampleBuffer::<f32>::new(self.capacity, self.spec);
-                buffer.copy_interleaved_ref(decoded);
+        let mut decode_errors: usize = 0;
+        const MAX_DECODE_ERRORS: usize = 3;
+        loop {
+            match self.format.next_packet() {
+                Ok(packet) => {
+                    let decoded = self.decoder.decode(&packet).unwrap();
+                    let mut buffer = SampleBuffer::<f32>::new(self.capacity, self.spec);
+                    buffer.copy_interleaved_ref(decoded);
 
-                self.converter.update(buffer.samples().to_vec().into_iter());
+                    self.converter.update(buffer.samples().to_vec().into_iter());
 
-                //Update elapsed
-                let ts = packet.ts();
-                let track = self.format.default_track().unwrap();
-                let tb = track.codec_params.time_base.unwrap();
-                let t = tb.calc_time(ts);
-                self.elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
-            }
-            Err(e) => match e {
-                symphonia::core::errors::Error::IoError(_) => self.finished = true,
-                _ => panic!("{:?}", e),
-            },
+                    //Update elapsed
+                    let ts = packet.ts();
+                    let track = self.format.default_track().unwrap();
+                    let tb = track.codec_params.time_base.unwrap();
+                    let t = tb.calc_time(ts);
+                    self.elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
+                    break;
+                }
+                Err(e) => match e {
+                    symphonia::core::errors::Error::DecodeError(_) => {
+                        decode_errors += 1;
+                        if decode_errors > MAX_DECODE_ERRORS {
+                            panic!("{:?}", e);
+                        }
+                    }
+                    _ => (),
+                },
+            };
         }
     }
     pub fn seek_by(&mut self, time: f32) {

From 2d23d70517d1a4dbc80dd7d124ec70603cf29104 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 16:38:47 +0930
Subject: [PATCH 07/40] error cleanup

---
 gonk-player/src/lib.rs              |  5 ++---
 gonk-player/src/sample_processor.rs | 10 +++++++---
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 3df0f51b..6c534a59 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -58,10 +58,9 @@ impl Player {
         }
     }
     pub fn update(&mut self) {
-        // if self.elapsed() > self.duration {
-        //     self.next_song();
-        // }
+        //TODO: Check if the song is finished playing and skip to next one.
     }
+
     pub fn duration(&self) -> Duration {
         *self.duration.read().unwrap()
     }
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index cf43ef49..137a1494 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -1,9 +1,10 @@
 use crate::sample_rate::SampleRateConverter;
-use std::{fs::File, path::Path, time::Duration};
+use std::{fs::File, io::ErrorKind, path::Path, time::Duration};
 use symphonia::{
     core::{
         audio::{SampleBuffer, SignalSpec},
         codecs::{Decoder, DecoderOptions},
+        errors::Error,
         formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
         io::MediaSourceStream,
         meta::MetadataOptions,
@@ -113,13 +114,16 @@ impl SampleProcessor {
                     break;
                 }
                 Err(e) => match e {
-                    symphonia::core::errors::Error::DecodeError(_) => {
+                    Error::DecodeError(e) => {
                         decode_errors += 1;
                         if decode_errors > MAX_DECODE_ERRORS {
                             panic!("{:?}", e);
                         }
                     }
-                    _ => (),
+                    Error::IoError(e) if e.kind() == ErrorKind::UnexpectedEof => {
+                        self.finished = true;
+                    }
+                    _ => panic!("{:?}", e),
                 },
             };
         }

From aeafa5ce89e340359dfc23f2cdee9d86c9186bf1 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 14 Jun 2022 21:53:35 +0930
Subject: [PATCH 08/40] fixed mp3 and alac not playing

---
 gonk-player/Cargo.toml              | 2 +-
 gonk-player/src/sample_processor.rs | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/gonk-player/Cargo.toml b/gonk-player/Cargo.toml
index 6d494c7f..82536013 100644
--- a/gonk-player/Cargo.toml
+++ b/gonk-player/Cargo.toml
@@ -12,4 +12,4 @@ license = "MIT"
 [dependencies]
 cpal = "0.13.5"
 crossbeam-channel = "0.5.4"
-symphonia = "0.5.0"
+symphonia = { version = "0.5.0", features = ["mp3", "isomp4", "alac", "aac"] }
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 137a1494..75ec1f0a 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -39,6 +39,7 @@ impl SampleProcessor {
                 mss,
                 &FormatOptions {
                     prebuild_seek_index: true,
+                    seek_index_fill_rate: 1,
                     ..Default::default()
                 },
                 &MetadataOptions::default(),

From 6aa3f258b410e022775ceb3f59f78c69d51a41ca Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 15 Jun 2022 14:21:44 +0930
Subject: [PATCH 09/40] various fixes

---
 gonk-player/src/lib.rs              | 155 +++++++++++++---------------
 gonk-player/src/sample_processor.rs | 123 +++++++++++++++++++---
 2 files changed, 179 insertions(+), 99 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 6c534a59..937d6eb2 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -2,10 +2,9 @@ use cpal::{
     traits::{HostTrait, StreamTrait},
     StreamError,
 };
-use crossbeam_channel::{unbounded, Receiver, Sender};
-use sample_processor::SampleProcessor;
+use crossbeam_channel::{unbounded, Sender};
+use sample_processor::Generator;
 use std::{
-    path::PathBuf,
     sync::{Arc, RwLock},
     thread,
     time::Duration,
@@ -28,7 +27,6 @@ const VOLUME_REDUCTION: f32 = 600.0;
 pub enum Event {
     Play,
     Pause,
-    Stop,
     SeekBy(f32),
     SeekTo(f32),
     Volume(f32),
@@ -36,33 +34,81 @@ pub enum Event {
 
 pub struct Player {
     s: Sender<Event>,
-    r: Receiver<Event>,
     playing: bool,
     volume: u16,
     songs: Index<Song>,
-    duration: Arc<RwLock<Duration>>,
     elapsed: Arc<RwLock<Duration>>,
+    generator: Arc<RwLock<Generator>>,
+    duration: Duration,
 }
 
 impl Player {
     pub fn new(volume: u16) -> Self {
         let (s, r) = unbounded();
+
+        let device = cpal::default_host().default_output_device().unwrap();
+        let config = device.default_output_config().unwrap();
+        let rate = config.sample_rate().0;
+
+        let generator = Arc::new(RwLock::new(Generator::new(
+            rate,
+            volume as f32 / VOLUME_REDUCTION,
+        )));
+        let gen = generator.clone();
+        let g = generator.clone();
+
+        let elapsed = Arc::new(RwLock::new(Duration::default()));
+        let e = elapsed.clone();
+
+        thread::spawn(move || {
+            let stream = device
+                .build_output_stream(
+                    &config.config(),
+                    move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
+                        for frame in data.chunks_mut(2) {
+                            for sample in frame.iter_mut() {
+                                *sample = g.write().unwrap().next();
+                            }
+                        }
+                    },
+                    |err| panic!("{}", err),
+                )
+                .unwrap();
+
+            stream.play().unwrap();
+
+            loop {
+                *e.write().unwrap() = gen.read().unwrap().elapsed();
+
+                if let Ok(event) = r.recv_timeout(Duration::from_millis(8)) {
+                    match event {
+                        Event::Play => stream.play().unwrap(),
+                        Event::Pause => stream.pause().unwrap(),
+                        Event::SeekBy(duration) => gen.write().unwrap().seek_by(duration).unwrap(),
+                        Event::SeekTo(duration) => gen.write().unwrap().seek_to(duration).unwrap(),
+                        Event::Volume(volume) => gen.write().unwrap().set_volume(volume),
+                    }
+                }
+            }
+        });
+
         Self {
             s,
-            r,
             playing: false,
             volume,
-            duration: Arc::new(RwLock::new(Duration::default())),
-            elapsed: Arc::new(RwLock::new(Duration::default())),
+            elapsed,
+            duration: Duration::default(),
             songs: Index::default(),
+            generator,
         }
     }
     pub fn update(&mut self) {
-        //TODO: Check if the song is finished playing and skip to next one.
+        if self.generator.read().unwrap().is_done() {
+            self.next();
+        }
     }
-
     pub fn duration(&self) -> Duration {
-        *self.duration.read().unwrap()
+        self.duration
     }
     pub fn elapsed(&self) -> Duration {
         *self.elapsed.read().unwrap()
@@ -82,11 +128,9 @@ impl Player {
     }
     pub fn play_selected(&mut self) {
         if let Some(song) = self.songs.selected() {
-            if self.playing {
-                self.stop();
-            }
             self.playing = true;
-            self.run(song.path.clone());
+            self.generator.write().unwrap().update(&song.path.clone());
+            self.duration = self.generator.read().unwrap().duration();
         }
     }
     pub fn play_index(&mut self, i: usize) {
@@ -117,7 +161,7 @@ impl Player {
     }
     pub fn clear(&mut self) {
         self.songs = Index::default();
-        self.stop();
+        self.generator.write().unwrap().stop();
     }
     pub fn clear_except_playing(&mut self) {
         let selected = self.songs.selected().cloned();
@@ -131,7 +175,6 @@ impl Player {
         }
         self.songs.select(Some(0));
     }
-    pub fn randomize(&self) {}
     pub fn toggle_playback(&mut self) {
         if self.playing {
             self.pause();
@@ -139,6 +182,14 @@ impl Player {
             self.play();
         }
     }
+    fn play(&mut self) {
+        self.s.send(Event::Play).unwrap();
+        self.playing = true;
+    }
+    fn pause(&mut self) {
+        self.s.send(Event::Pause).unwrap();
+        self.playing = false;
+    }
     pub fn previous(&mut self) {
         self.songs.up();
         self.play_selected();
@@ -163,6 +214,7 @@ impl Player {
 
         self.update_volume();
     }
+    pub fn randomize(&self) {}
     fn update_volume(&self) {
         self.s.send(Event::Volume(self.real_volume())).unwrap();
     }
@@ -187,18 +239,10 @@ impl Player {
     pub fn total_songs(&self) -> usize {
         self.songs.len()
     }
-    fn play(&mut self) {
-        self.s.send(Event::Play).unwrap();
-        self.playing = true;
-    }
-
     pub fn get_index(&self) -> &Index<Song> {
         &self.songs
     }
-    fn pause(&mut self) {
-        self.s.send(Event::Pause).unwrap();
-        self.playing = false;
-    }
+
     pub fn selected_song(&self) -> Option<&Song> {
         self.songs.selected()
     }
@@ -208,62 +252,6 @@ impl Player {
     pub fn seek_to(&self, duration: f32) {
         self.s.send(Event::SeekTo(duration)).unwrap();
     }
-    pub fn stop(&self) {
-        self.s.send(Event::Stop).unwrap();
-    }
-    fn run(&self, path: PathBuf) {
-        let r = self.r.clone();
-        let duration = self.duration.clone();
-        let elapsed = self.elapsed.clone();
-        let volume = self.real_volume();
-
-        thread::spawn(move || {
-            let device = cpal::default_host().default_output_device().unwrap();
-            let config = device.default_output_config().unwrap();
-
-            let processor = Arc::new(RwLock::new(SampleProcessor::new(
-                Some(config.sample_rate().0),
-                path,
-                volume,
-            )));
-
-            //Update the duration;
-            *duration.write().unwrap() = processor.read().unwrap().duration;
-
-            let p = processor.clone();
-
-            let stream = device
-                .build_output_stream(
-                    &config.config(),
-                    move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
-                        for frame in data.chunks_mut(2) {
-                            for sample in frame.iter_mut() {
-                                *sample = p.write().unwrap().next_sample();
-                            }
-                        }
-                    },
-                    |err| panic!("{}", err),
-                )
-                .unwrap();
-
-            stream.play().unwrap();
-
-            loop {
-                *elapsed.write().unwrap() = processor.read().unwrap().elapsed;
-
-                if let Ok(event) = r.recv_timeout(Duration::from_millis(16)) {
-                    match event {
-                        Event::Play => stream.play().unwrap(),
-                        Event::Pause => stream.pause().unwrap(),
-                        Event::SeekBy(duration) => processor.write().unwrap().seek_by(duration),
-                        Event::SeekTo(duration) => processor.write().unwrap().seek_to(duration),
-                        Event::Volume(volume) => processor.write().unwrap().volume = volume,
-                        Event::Stop => break,
-                    }
-                }
-            }
-        });
-    }
     pub fn audio_devices() -> Vec<Device> {
         let host_id = cpal::default_host().id();
         let host = cpal::host_from_id(host_id).unwrap();
@@ -276,6 +264,7 @@ impl Player {
         cpal::default_host().default_output_device().unwrap()
     }
     pub fn change_output_device(&mut self, _device: &Device) -> Result<(), StreamError> {
+        //TODO
         Ok(())
         // match OutputStream::try_from_device(device) {
         //     Ok((stream, handle)) => {
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 75ec1f0a..1db1b7d4 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -4,7 +4,7 @@ use symphonia::{
     core::{
         audio::{SampleBuffer, SignalSpec},
         codecs::{Decoder, DecoderOptions},
-        errors::Error,
+        errors::{Error, SeekErrorKind},
         formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
         io::MediaSourceStream,
         meta::MetadataOptions,
@@ -14,21 +14,97 @@ use symphonia::{
     default::get_probe,
 };
 
-pub struct SampleProcessor {
+pub struct Generator {
+    processor: Option<Processor>,
+    sample_rate: u32,
+    volume: f32,
+}
+
+impl Generator {
+    pub fn new(sample_rate: u32, volume: f32) -> Self {
+        Self {
+            processor: None,
+            sample_rate,
+            volume,
+        }
+    }
+    pub fn next(&mut self) -> f32 {
+        if let Some(processor) = &mut self.processor {
+            if processor.finished {
+                0.0
+            } else {
+                processor.next_sample()
+            }
+        } else {
+            0.0
+        }
+    }
+    pub fn seek_to(&mut self, time: f32) -> Result<(), ()> {
+        if let Some(processor) = &mut self.processor {
+            processor.seek_to(time);
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+    pub fn elapsed(&self) -> Duration {
+        if let Some(processor) = &self.processor {
+            processor.elapsed
+        } else {
+            Duration::default()
+        }
+    }
+    pub fn duration(&self) -> Duration {
+        if let Some(processor) = &self.processor {
+            processor.duration
+        } else {
+            Duration::default()
+        }
+    }
+    pub fn seek_by(&mut self, time: f32) -> Result<(), ()> {
+        if let Some(processor) = &mut self.processor {
+            processor.seek_by(time);
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+    pub fn set_volume(&mut self, volume: f32) {
+        self.volume = volume;
+        if let Some(processor) = &mut self.processor {
+            processor.volume = volume;
+        }
+    }
+    pub fn update(&mut self, path: &Path) {
+        self.processor = Some(Processor::new(self.sample_rate, path, self.volume));
+    }
+    pub fn is_done(&self) -> bool {
+        if let Some(processor) = &self.processor {
+            processor.finished
+        } else {
+            false
+        }
+    }
+    pub fn stop(&mut self) {
+        self.processor = None;
+    }
+}
+
+pub struct Processor {
     pub decoder: Box<dyn Decoder>,
     pub format: Box<dyn FormatReader>,
     pub spec: SignalSpec,
     pub capacity: u64,
-    pub duration: Duration,
-    pub elapsed: Duration,
     pub converter: SampleRateConverter,
     pub finished: bool,
     pub left: bool,
+    pub duration: Duration,
+    pub elapsed: Duration,
     pub volume: f32,
 }
 
-impl SampleProcessor {
-    pub fn new(sample_rate: Option<u32>, path: impl AsRef<Path>, volume: f32) -> Self {
+impl Processor {
+    pub fn new(sample_rate: u32, path: &Path, volume: f32) -> Self {
         let source = Box::new(File::open(path).unwrap());
 
         let mss = MediaSourceStream::new(source, Default::default());
@@ -55,6 +131,7 @@ impl SampleProcessor {
         } else {
             panic!("Could not decode track duration.");
         };
+
         let mut decoder = symphonia::default::get_codecs()
             .make(&track.codec_params, &DecoderOptions::default())
             .unwrap();
@@ -78,7 +155,7 @@ impl SampleProcessor {
             converter: SampleRateConverter::new(
                 sample_buffer.samples().to_vec().into_iter(),
                 spec.rate,
-                sample_rate.unwrap_or(44100),
+                sample_rate,
             ),
             finished: false,
             left: true,
@@ -87,7 +164,9 @@ impl SampleProcessor {
     }
     pub fn next_sample(&mut self) -> f32 {
         loop {
-            if let Some(sample) = self.converter.next() {
+            if self.finished {
+                return 0.0;
+            } else if let Some(sample) = self.converter.next() {
                 return sample * self.volume;
             } else {
                 self.update();
@@ -95,6 +174,10 @@ impl SampleProcessor {
         }
     }
     pub fn update(&mut self) {
+        if self.finished {
+            return;
+        }
+
         let mut decode_errors: usize = 0;
         const MAX_DECODE_ERRORS: usize = 3;
         loop {
@@ -123,6 +206,7 @@ impl SampleProcessor {
                     }
                     Error::IoError(e) if e.kind() == ErrorKind::UnexpectedEof => {
                         self.finished = true;
+                        return;
                     }
                     _ => panic!("{:?}", e),
                 },
@@ -135,14 +219,21 @@ impl SampleProcessor {
     }
     pub fn seek_to(&mut self, time: f32) {
         let time = Duration::from_secs_f32(time);
-        self.format
-            .seek(
-                SeekMode::Coarse,
-                SeekTo::Time {
-                    time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / 1_000_000_000.0),
-                    track_id: None,
+        match self.format.seek(
+            SeekMode::Coarse,
+            SeekTo::Time {
+                time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / 1_000_000_000.0),
+                track_id: None,
+            },
+        ) {
+            Ok(_) => (),
+            Err(e) => match e {
+                Error::SeekError(e) => match e {
+                    SeekErrorKind::OutOfRange => self.finished = true,
+                    _ => panic!("{:?}", e),
                 },
-            )
-            .unwrap();
+                _ => panic!("{}", e),
+            },
+        }
     }
 }

From bc13b1f172efc9b2450599459faef2b8e3ed3977 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 15 Jun 2022 14:33:05 +0930
Subject: [PATCH 10/40] fixed broken volume when playing new songs

---
 gonk-player/src/lib.rs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 937d6eb2..b657bb89 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -129,8 +129,10 @@ impl Player {
     pub fn play_selected(&mut self) {
         if let Some(song) = self.songs.selected() {
             self.playing = true;
-            self.generator.write().unwrap().update(&song.path.clone());
-            self.duration = self.generator.read().unwrap().duration();
+            let mut gen = self.generator.write().unwrap();
+            gen.update(&song.path.clone());
+            gen.set_volume(self.real_volume());
+            self.duration = gen.duration();
         }
     }
     pub fn play_index(&mut self, i: usize) {

From 20534431936ad7e381c0d4b403929fd73b7ecd83 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 15 Jun 2022 14:57:06 +0930
Subject: [PATCH 11/40] cleanup

---
 gonk-player/src/lib.rs  | 17 +++++++++++++++--
 gonk/src/app.rs         |  6 +++++-
 gonk/src/app/options.rs | 26 ++++++++++++++------------
 gonk/src/app/queue.rs   |  4 ++--
 4 files changed, 36 insertions(+), 17 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index b657bb89..0b5cf9b9 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -43,10 +43,20 @@ pub struct Player {
 }
 
 impl Player {
-    pub fn new(volume: u16) -> Self {
-        let (s, r) = unbounded();
+    //TODO: get device from toml file
+    pub fn new(_device: String, volume: u16) -> Self {
+        // let host_id = cpal::default_host().id();
+        // let host = cpal::host_from_id(host_id).unwrap();
+        // let mut devices: Vec<Device> = host.devices().unwrap().collect();
+        // devices.retain(|host| host.name().unwrap() == device);
 
+        // let device = if devices.is_empty() {
+        //     cpal::default_host().default_output_device().unwrap()
+        // } else {
+        //     devices.remove(0)
+        // };
         let device = cpal::default_host().default_output_device().unwrap();
+
         let config = device.default_output_config().unwrap();
         let rate = config.sample_rate().0;
 
@@ -60,6 +70,8 @@ impl Player {
         let elapsed = Arc::new(RwLock::new(Duration::default()));
         let e = elapsed.clone();
 
+        let (s, r) = unbounded();
+
         thread::spawn(move || {
             let stream = device
                 .build_output_stream(
@@ -254,6 +266,7 @@ impl Player {
     pub fn seek_to(&self, duration: f32) {
         self.s.send(Event::SeekTo(duration)).unwrap();
     }
+    //TODO: Remove?
     pub fn audio_devices() -> Vec<Device> {
         let host_id = cpal::default_host().id();
         let host = cpal::host_from_id(host_id).unwrap();
diff --git a/gonk/src/app.rs b/gonk/src/app.rs
index c9005bd2..03fd0aee 100644
--- a/gonk/src/app.rs
+++ b/gonk/src/app.rs
@@ -118,7 +118,11 @@ impl App {
                 Ok(Self {
                     terminal,
                     mode: Mode::Browser,
-                    queue: Queue::new(toml.config.volume, toml.colors),
+                    queue: Queue::new(
+                        toml.config.volume,
+                        toml.colors,
+                        toml.config.output_device.clone(),
+                    ),
                     browser: Browser::new(),
                     options: Options::new(&mut toml),
                     search: Search::new(toml.colors).init(),
diff --git a/gonk/src/app/options.rs b/gonk/src/app/options.rs
index de8b36f1..855a427f 100644
--- a/gonk/src/app/options.rs
+++ b/gonk/src/app/options.rs
@@ -38,19 +38,21 @@ impl Options {
     pub fn down(&mut self) {
         self.devices.down();
     }
+    #[allow(unused)]
     pub fn on_enter(&mut self, player: &mut Player, toml: &mut Toml) {
-        if let Some(device) = self.devices.selected() {
-            //don't update the device if there is an error
-            match player.change_output_device(device) {
-                Ok(_) => {
-                    let name = device.name().unwrap();
-                    self.current_device = name.clone();
-                    toml.set_output_device(name);
-                }
-                //TODO: Print error in status bar
-                Err(e) => panic!("{:?}", e),
-            }
-        }
+        //TODO: Update playback device.
+        // if let Some(device) = self.devices.selected() {
+        //     //don't update the device if there is an error
+        //     match player.change_output_device(device) {
+        //         Ok(_) => {
+        //             let name = device.name().unwrap();
+        //             self.current_device = name.clone();
+        //             toml.set_output_device(name);
+        //         }
+        //         //TODO: Print error in status bar
+        //         Err(e) => panic!("{:?}", e),
+        //     }
+        // }
     }
 }
 
diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
index aa29cab4..163333d0 100644
--- a/gonk/src/app/queue.rs
+++ b/gonk/src/app/queue.rs
@@ -18,12 +18,12 @@ pub struct Queue {
 }
 
 impl Queue {
-    pub fn new(vol: u16, colors: Colors) -> Self {
+    pub fn new(vol: u16, colors: Colors, device: String) -> Self {
         Self {
             ui: Index::default(),
             constraint: [8, 42, 24, 26],
             clicked_pos: None,
-            player: Player::new(vol),
+            player: Player::new(device, vol),
             colors,
         }
     }

From 842b024e178af09da51c7f3d72ec86fec17bbebb Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Sat, 18 Jun 2022 14:06:46 +0930
Subject: [PATCH 12/40] ci: build action

---
 .github/workflows/build.yml         | 22 ++++++++++++++++++++++
 gonk-player/src/sample_processor.rs |  2 +-
 2 files changed, 23 insertions(+), 1 deletion(-)
 create mode 100644 .github/workflows/build.yml

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..45bae997
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,22 @@
+name: Build
+
+on:
+  push:
+    branches: [ main ]
+  pull_request:
+    branches: [ main ]
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Install libasound2-dev
+      run: sudo apt install -y libasound2-dev
+    - name: Build
+      run: cargo build --verbose
\ No newline at end of file
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 1db1b7d4..29ce38c7 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -195,7 +195,7 @@ impl Processor {
                     let tb = track.codec_params.time_base.unwrap();
                     let t = tb.calc_time(ts);
                     self.elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
-                    break;
+                    return;
                 }
                 Err(e) => match e {
                     Error::DecodeError(e) => {

From 02c55eacf80b23faaf20163e995ae12f881ca3ca Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 22 Jun 2022 13:57:41 +0930
Subject: [PATCH 13/40] Clean failed merge from 'main'

---
 gonk/src/app.rs            |  6 +-----
 gonk/src/app/queue.rs      | 11 ++++-------
 gonk/src/app/status_bar.rs |  3 ++-
 3 files changed, 7 insertions(+), 13 deletions(-)

diff --git a/gonk/src/app.rs b/gonk/src/app.rs
index b95afd66..d96bd2fb 100644
--- a/gonk/src/app.rs
+++ b/gonk/src/app.rs
@@ -118,11 +118,7 @@ impl App {
                 Ok(Self {
                     terminal,
                     mode: Mode::Browser,
-                    queue: Queue::new(
-                        toml.config.volume,
-                        toml.colors,
-                        toml.config.output_device.clone(),
-                    ),
+                    queue: Queue::new(toml.config.volume, toml.config.output_device.clone()),
                     browser: Browser::new(),
                     options: Options::new(&mut toml),
                     search: Search::new().init(),
diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
index 4fa308e7..9b20050f 100644
--- a/gonk/src/app/queue.rs
+++ b/gonk/src/app/queue.rs
@@ -1,6 +1,5 @@
-use crate::toml::Colors;
 use crate::widgets::{Cell, Gauge, Row, Table, TableState};
-use crate::Frame;
+use crate::{Frame, COLORS};
 use crossterm::event::{KeyModifiers, MouseEvent};
 use gonk_player::{Index, Player};
 use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
@@ -16,13 +15,11 @@ pub struct Queue {
 }
 
 impl Queue {
-    pub fn new(vol: u16, colors: Colors, device: String) -> Self {
+    pub fn new(vol: u16, device: String) -> Self {
         Self {
             ui: Index::default(),
             constraint: [8, 42, 24, 26],
-            clicked_pos: None,
             player: Player::new(device, vol),
-            colors,
         }
     }
     pub fn update(&mut self) {
@@ -197,7 +194,7 @@ impl Queue {
         f.render_widget(Paragraph::new(title).alignment(Alignment::Center), area);
     }
     fn draw_body(&mut self, f: &mut Frame, area: Rect) -> Option<(usize, usize)> {
-        if self.player.songs.is_empty() {
+        if self.player.is_empty() {
             f.render_widget(
                 Block::default()
                     .border_type(BorderType::Rounded)
@@ -353,7 +350,7 @@ impl Queue {
                         .borders(Borders::ALL)
                         .border_type(BorderType::Rounded),
                 )
-                .gauge_style(Style::default().fg(self.colors.seeker))
+                .gauge_style(Style::default().fg(COLORS.seeker))
                 .ratio(ratio as f64)
                 .label(seeker),
             area,
diff --git a/gonk/src/app/status_bar.rs b/gonk/src/app/status_bar.rs
index 245c1eb5..f24f0776 100644
--- a/gonk/src/app/status_bar.rs
+++ b/gonk/src/app/status_bar.rs
@@ -1,6 +1,7 @@
 use super::queue::Queue;
+use crate::sqlite;
 use crate::Frame;
-use crate::{sqlite, toml::Colors};
+use crate::COLORS;
 use std::time::{Duration, Instant};
 use tui::{
     layout::{Alignment, Constraint, Direction, Layout, Rect},

From 7a508eb7462eaff954fb312537fe4b62e1c9bc65 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 22 Jun 2022 14:06:27 +0930
Subject: [PATCH 14/40] Clean failed merge into 'main'

---
 gonk/src/app/queue.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
index fe0aabf0..275ed68c 100644
--- a/gonk/src/app/queue.rs
+++ b/gonk/src/app/queue.rs
@@ -104,8 +104,8 @@ impl Queue {
             if (size.height - 3 == y || size.height - 2 == y || size.height - 1 == y)
                 && size.height > 15
             {
-                let ratio = f64::from(x) / f64::from(size.width);
-                let duration = self.player.duration;
+                let ratio = x as f32 / size.width as f32;
+                let duration = self.player.duration().as_secs_f32();
                 let new_time = duration * ratio;
                 self.player.seek_to(new_time);
             }

From 2f52d16569e23ac2761e7e207d0f40d4cc69436c Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Sat, 25 Jun 2022 15:33:08 +0930
Subject: [PATCH 15/40] Merged 'main into 'new_player'

---
 .gitignore                    |   7 +-
 Cargo.toml                    |   2 +-
 gonk-database/Cargo.toml      |  13 +
 gonk-database/src/database.rs |  61 +++++
 gonk-database/src/lib.rs      | 277 +++++++++++++++++++
 gonk-database/src/playlist.rs |  90 +++++++
 gonk-database/src/query.rs    | 182 +++++++++++++
 gonk-player/src/lib.rs        |  22 +-
 gonk-player/src/song.rs       |  27 +-
 gonk/Cargo.toml               |  29 +-
 gonk/src/app.rs               | 384 ---------------------------
 gonk/src/app/browser.rs       | 214 ---------------
 gonk/src/app/options.rs       |  91 -------
 gonk/src/app/playlist.rs      | 386 ---------------------------
 gonk/src/app/queue.rs         | 361 -------------------------
 gonk/src/app/search.rs        | 478 ---------------------------------
 gonk/src/app/status_bar.rs    | 155 -----------
 gonk/src/browser.rs           | 220 +++++++++++++++
 gonk/src/main.rs              | 391 +++++++++++++++++++++++++--
 gonk/src/playlist.rs          | 371 ++++++++++++++++++++++++++
 gonk/src/queue.rs             | 360 +++++++++++++++++++++++++
 gonk/src/search.rs            | 485 ++++++++++++++++++++++++++++++++++
 gonk/src/settings.rs          |  98 +++++++
 gonk/src/sqlite.rs            | 307 ---------------------
 gonk/src/status_bar.rs        | 143 ++++++++++
 gonk/src/toml.rs              | 262 ------------------
 gonk/src/widgets/list.rs      |  21 +-
 gonk/src/widgets/table.rs     |   8 +-
 28 files changed, 2701 insertions(+), 2744 deletions(-)
 create mode 100644 gonk-database/Cargo.toml
 create mode 100644 gonk-database/src/database.rs
 create mode 100644 gonk-database/src/lib.rs
 create mode 100644 gonk-database/src/playlist.rs
 create mode 100644 gonk-database/src/query.rs
 delete mode 100644 gonk/src/app.rs
 delete mode 100644 gonk/src/app/browser.rs
 delete mode 100644 gonk/src/app/options.rs
 delete mode 100644 gonk/src/app/playlist.rs
 delete mode 100644 gonk/src/app/queue.rs
 delete mode 100644 gonk/src/app/search.rs
 delete mode 100644 gonk/src/app/status_bar.rs
 create mode 100644 gonk/src/browser.rs
 create mode 100644 gonk/src/playlist.rs
 create mode 100644 gonk/src/queue.rs
 create mode 100644 gonk/src/search.rs
 create mode 100644 gonk/src/settings.rs
 delete mode 100644 gonk/src/sqlite.rs
 create mode 100644 gonk/src/status_bar.rs
 delete mode 100644 gonk/src/toml.rs

diff --git a/.gitignore b/.gitignore
index b456e766..a9719ed7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 /target
-/gonk/target
-Cargo.lock
+/.vscode
+
 *.log
 *.opt
-/.vscode
\ No newline at end of file
+*.db
+*.lock
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index a4927824..d8cb9068 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,2 @@
 [workspace]
-members = ["gonk", "gonk-player"]
\ No newline at end of file
+members = ["gonk", "gonk-player", "gonk-database"]
\ No newline at end of file
diff --git a/gonk-database/Cargo.toml b/gonk-database/Cargo.toml
new file mode 100644
index 00000000..49f856a5
--- /dev/null
+++ b/gonk-database/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "gonk-database"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+jwalk = "0.6.0"
+rayon = "1.5.3"
+rusqlite = { version = "0.27.0", features = ["bundled"] }
+gonk-player = {path = "../gonk-player"}
+lazy_static = "1.4.0"
diff --git a/gonk-database/src/database.rs b/gonk-database/src/database.rs
new file mode 100644
index 00000000..f8234dec
--- /dev/null
+++ b/gonk-database/src/database.rs
@@ -0,0 +1,61 @@
+use crate::*;
+use std::thread::{self, JoinHandle};
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum State {
+    Busy,
+    Idle,
+    NeedsUpdate,
+}
+
+#[derive(Default)]
+pub struct Database {
+    handle: Option<JoinHandle<()>>,
+}
+
+impl Database {
+    pub fn add_path(&mut self, path: &str) {
+        if let Some(handle) = &self.handle {
+            if !handle.is_finished() {
+                return;
+            }
+        }
+
+        let path = path.to_string();
+        self.handle = Some(thread::spawn(move || {
+            add_folder(&path);
+        }));
+    }
+
+    pub fn refresh(&mut self) {
+        if let Some(handle) = &self.handle {
+            if !handle.is_finished() {
+                return;
+            }
+        }
+
+        self.handle = Some(thread::spawn(move || {
+            for path in query::paths() {
+                let songs = collect_songs(&path);
+                insert_parents(&songs);
+                let query = create_batch_query("song", &path, &songs);
+                conn().execute_batch(&query).unwrap();
+            }
+        }));
+    }
+
+    pub fn state(&mut self) -> State {
+        match self.handle {
+            Some(ref handle) => {
+                let finished = handle.is_finished();
+                if finished {
+                    self.handle = None;
+                    State::NeedsUpdate
+                } else {
+                    State::Busy
+                }
+            }
+            None => State::Idle,
+        }
+    }
+}
diff --git a/gonk-database/src/lib.rs b/gonk-database/src/lib.rs
new file mode 100644
index 00000000..b56c643a
--- /dev/null
+++ b/gonk-database/src/lib.rs
@@ -0,0 +1,277 @@
+#[macro_use]
+extern crate lazy_static;
+
+use gonk_player::Song;
+use jwalk::WalkDir;
+use rayon::{
+    iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator},
+    slice::ParallelSliceMut,
+};
+use rusqlite::*;
+use std::{
+    path::{Path, PathBuf},
+    sync::{Mutex, MutexGuard},
+};
+
+mod database;
+pub mod playlist;
+pub mod query;
+
+pub use crate::database::*;
+
+lazy_static! {
+    pub static ref GONK_DIR: PathBuf = {
+        let gonk = if cfg!(windows) {
+            PathBuf::from(&std::env::var("APPDATA").unwrap())
+        } else {
+            PathBuf::from(&std::env::var("HOME").unwrap()).join(".config")
+        }
+        .join("gonk");
+
+        if !gonk.exists() {
+            std::fs::create_dir_all(&gonk).unwrap();
+        }
+        gonk
+    };
+    pub static ref DB_DIR: PathBuf = GONK_DIR.join("gonk.db");
+    pub static ref CONN: Mutex<Connection> = {
+        let exists = PathBuf::from(DB_DIR.as_path()).exists();
+        let conn = Connection::open(DB_DIR.as_path()).unwrap();
+
+        if !exists {
+            conn.execute(
+                "CREATE TABLE settings (
+             volume INTEGER UNIQUE,
+             device TEXT UNIQUE)",
+                [],
+            )
+            .unwrap();
+
+            conn.execute("INSERT INTO settings (volume, device) VALUES (15, '')", [])
+                .unwrap();
+
+            conn.execute(
+                "CREATE TABLE folder (
+            path TEXT PRIMARY KEY)",
+                [],
+            )
+            .unwrap();
+
+            conn.execute(
+                "CREATE TABLE artist (
+            name TEXT PRIMARY KEY)",
+                [],
+            )
+            .unwrap();
+
+            conn.execute("CREATE TABLE persist(song_id INTEGER)", [])
+                .unwrap();
+            conn.execute(
+                "CREATE TABLE album (
+            name TEXT PRIMARY KEY,
+            artist_id TEXT NOT NULL,
+            FOREIGN KEY (artist_id) REFERENCES artist (name) )",
+                [],
+            )
+            .unwrap();
+
+            conn.execute(
+                "CREATE TABLE song (
+            name TEXT NOT NULL,
+            disc INTEGER NOT NULL,
+            number INTEGER NOT NULL,
+            path TEXT NOT NULL,
+            gain DOUBLE NOT NULL,
+            album_id TEXT NOT NULL,
+            artist_id TEXT NOT NULL,
+            folder_id TEXT NOT NULL,
+            FOREIGN KEY (album_id) REFERENCES album (name),
+            FOREIGN KEY (artist_id) REFERENCES artist (name),
+            FOREIGN KEY (folder_id) REFERENCES folder (path),
+            UNIQUE(name, disc, number, path, album_id, artist_id, folder_id) ON CONFLICT REPLACE)",
+                [],
+            )
+            .unwrap();
+
+            conn.execute(
+                "CREATE TABLE playlist (
+            name TEXT PRIMARY KEY)",
+                [],
+            )
+            .unwrap();
+
+            //Used for intersects
+            //https://www.sqlitetutorial.net/sqlite-intersect/
+            conn.execute(
+                "CREATE TABLE temp_song (
+                name TEXT NOT NULL,
+                disc INTEGER NOT NULL,
+                number INTEGER NOT NULL,
+                path TEXT NOT NULL,
+                gain DOUBLE NOT NULL,
+                album_id TEXT NOT NULL,
+                artist_id TEXT NOT NULL,
+                folder_id TEXT NOT NULL,
+                FOREIGN KEY (album_id) REFERENCES album (name),
+                FOREIGN KEY (artist_id) REFERENCES artist (name),
+                FOREIGN KEY (folder_id) REFERENCES folder (path) 
+            )",
+                [],
+            )
+            .unwrap();
+
+            conn.execute(
+                "CREATE TABLE playlist_item (
+            path TEXT NOT NULL,
+            name TEXT NOT NULL,
+            album_id TEXT NOT NULL,
+            artist_id TEXT NOT NULL,
+            playlist_id TEXT NOT NULL,
+            FOREIGN KEY (album_id) REFERENCES album (name),
+            FOREIGN KEY (artist_id) REFERENCES artist (name),
+            FOREIGN KEY (playlist_id) REFERENCES playlist (name))",
+                [],
+            )
+            .unwrap();
+        }
+
+        Mutex::new(conn)
+    };
+}
+
+pub fn reset() -> Result<(), &'static str> {
+    *CONN.lock().unwrap() = Connection::open_in_memory().unwrap();
+
+    if std::fs::remove_file(DB_DIR.as_path()).is_err() {
+        Err("Could not remove database while it's in use.")
+    } else {
+        Ok(())
+    }
+}
+
+pub fn conn() -> MutexGuard<'static, Connection> {
+    CONN.lock().unwrap()
+}
+
+pub fn collect_songs(path: impl AsRef<Path>) -> Vec<Song> {
+    WalkDir::new(path)
+        .into_iter()
+        .flatten()
+        .map(|dir| dir.path())
+        .filter(|path| match path.extension() {
+            Some(ex) => {
+                matches!(ex.to_str(), Some("flac" | "mp3" | "ogg" | "wav" | "m4a"))
+            }
+            None => false,
+        })
+        .par_bridge()
+        .flat_map(|path| Song::from(&path))
+        .collect()
+}
+
+pub fn insert_parents(songs: &[Song]) {
+    let mut albums: Vec<(&str, &str)> = songs
+        .par_iter()
+        .map(|song| (song.album.as_str(), song.artist.as_str()))
+        .collect();
+
+    albums.par_sort();
+    albums.dedup();
+
+    let mut artists: Vec<&str> = songs.par_iter().map(|song| song.artist.as_str()).collect();
+
+    artists.par_sort();
+    artists.dedup();
+
+    let query: String = artists
+        .par_iter()
+        .map(|artist| {
+            let artist = artist.replace('\'', r"''");
+            format!("INSERT OR IGNORE INTO artist (name) VALUES ('{}');", artist)
+        })
+        .collect::<Vec<String>>()
+        .join("\n");
+
+    let query = format!("BEGIN;\n{}\nCOMMIT;", query);
+    conn().execute_batch(&query).unwrap();
+
+    let query: Vec<String> = albums
+        .par_iter()
+        .map(|(album, artist)| {
+            let artist = artist.replace('\'', r"''");
+            let album = album.replace('\'', r"''");
+            format!(
+                "INSERT OR IGNORE INTO album (name, artist_id) VALUES ('{}', '{}');",
+                album, artist
+            )
+        })
+        .collect();
+
+    let query = format!("BEGIN;\n{}\nCOMMIT;", query.join("\n"));
+    conn().execute_batch(&query).unwrap();
+}
+
+pub fn create_batch_query(table: &str, folder: &str, songs: &[Song]) -> String {
+    let queries: Vec<String> = songs
+        .iter()
+        .map(|song| {
+            let name = song.name.replace('\'', r"''");
+            let artist = song.artist.replace('\'', r"''");
+            let album = song.album.replace('\'', r"''");
+            let path = song.path.to_string_lossy().replace('\'', r"''");
+            let folder = folder.replace('\'', r"''");
+
+            format!(
+                "INSERT OR REPLACE INTO {} (name, disc, number, path, gain, album_id, artist_id, folder_id)
+                VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');",
+                table, name, song.disc, song.number, path, song.gain, album, artist, folder,
+            )
+        })
+        .collect();
+
+    format!("BEGIN;\n{}\nCOMMIT;", queries.join("\n"))
+}
+
+pub fn rescan_folder(folder: &str) {
+    //Make sure folder exists.
+    if conn()
+        .execute("INSERT INTO folder (path) VALUES (?1)", [folder])
+        .is_err()
+    {
+        //Collect the songs.
+        let songs = collect_songs(folder);
+        insert_parents(&songs);
+
+        //Create query.
+        let query = create_batch_query("temp_song", folder, &songs);
+
+        let conn = conn();
+
+        //Clean the temp table and add songs.
+        conn.execute("DELETE FROM temp_song", []).unwrap();
+        conn.execute_batch(&query).unwrap();
+
+        //Insert songs into default table.
+        let query = create_batch_query("song", folder, &songs);
+        conn.execute_batch(&query).unwrap();
+
+        //Drop the difference.
+        conn.execute(
+            "DELETE FROM song WHERE rowid IN (SELECT rowid FROM song EXCEPT SELECT rowid FROM temp_song)",
+            [],
+        ).unwrap();
+    }
+}
+
+pub fn add_folder(folder: &str) {
+    if conn()
+        .execute("INSERT INTO folder (path) VALUES (?1)", [folder])
+        .is_ok()
+    {
+        let songs = collect_songs(folder);
+        insert_parents(&songs);
+
+        let query = create_batch_query("song", folder, &songs);
+        conn().execute_batch(&query).unwrap();
+    }
+}
diff --git a/gonk-database/src/playlist.rs b/gonk-database/src/playlist.rs
new file mode 100644
index 00000000..b8167e96
--- /dev/null
+++ b/gonk-database/src/playlist.rs
@@ -0,0 +1,90 @@
+#[allow(unused)]
+use crate::query::*;
+use crate::*;
+
+#[derive(Debug)]
+pub struct PlaylistSong {
+    pub path: PathBuf,
+    pub name: String,
+    pub album: String,
+    pub artist: String,
+    pub id: usize,
+}
+
+pub fn add(playlist: &str, ids: &[usize]) {
+    let songs = songs_from_ids(ids);
+
+    if songs.is_empty() {
+        panic!("Failed to add song ids: {:?}", ids);
+    }
+
+    let conn = conn();
+    conn.execute(
+        "INSERT OR IGNORE INTO playlist (name) VALUES (?1)",
+        [playlist],
+    )
+    .unwrap();
+
+    let query: Vec<String> = songs.iter().map(|song|{
+            let name = song.name.replace('\'', r"''");
+            let artist = song.artist.replace('\'', r"''");
+            let album = song.album.replace('\'', r"''");
+            let path = song.path.to_string_lossy().replace('\'', r"''");
+            let playlist = playlist.replace('\'', r"''");
+            format!("INSERT OR IGNORE INTO playlist_item (path, name, album_id, artist_id, playlist_id) VALUES ('{}', '{}', '{}', '{}', '{}');",
+            path, name, album, artist, playlist)
+    }).collect();
+
+    let query = format!("BEGIN;\n{}\nCOMMIT;", query.join("\n"));
+    conn.execute_batch(&query).unwrap();
+}
+
+//Only select playlists with songs in them
+pub fn playlists() -> Vec<String> {
+    let conn = conn();
+    let mut stmt = conn
+        .prepare("SELECT DISTINCT playlist_id FROM playlist_item")
+        .unwrap();
+
+    stmt.query_map([], |row| row.get(0))
+        .unwrap()
+        .flatten()
+        .collect()
+}
+
+pub fn get(playlist_name: &str) -> Vec<PlaylistSong> {
+    let conn = conn();
+    let mut stmt = conn
+        .prepare("SELECT path, name, album_id, artist_id, rowid FROM playlist_item WHERE playlist_id = ?")
+        .unwrap();
+
+    stmt.query_map([playlist_name], |row| {
+        let path: String = row.get(0).unwrap();
+
+        Ok(PlaylistSong {
+            path: PathBuf::from(path),
+            name: row.get(1).unwrap(),
+            album: row.get(2).unwrap(),
+            artist: row.get(3).unwrap(),
+            id: row.get(4).unwrap(),
+        })
+    })
+    .unwrap()
+    .flatten()
+    .collect()
+}
+
+pub fn remove_id(id: usize) {
+    conn()
+        .execute("DELETE FROM playlist_item WHERE rowid = ?", [id])
+        .unwrap();
+}
+
+pub fn remove(name: &str) {
+    let conn = conn();
+    conn.execute("DELETE FROM playlist_item WHERE playlist_id = ?", [name])
+        .unwrap();
+
+    conn.execute("DELETE FROM playlist WHERE name = ?", [name])
+        .unwrap();
+}
diff --git a/gonk-database/src/query.rs b/gonk-database/src/query.rs
new file mode 100644
index 00000000..82c2d893
--- /dev/null
+++ b/gonk-database/src/query.rs
@@ -0,0 +1,182 @@
+use crate::conn;
+use gonk_player::Song;
+use rusqlite::*;
+use std::path::PathBuf;
+
+pub fn cache(ids: &[usize]) {
+    let conn = conn();
+
+    conn.execute("DELETE FROM persist", []).unwrap();
+
+    for id in ids {
+        conn.execute("INSERT INTO persist (song_id) VALUES (?)", [id])
+            .unwrap();
+    }
+}
+
+pub fn get_cache() -> Vec<Song> {
+    let ids: Vec<usize> = {
+        let conn = conn();
+        let mut stmt = conn.prepare("SELECT song_id FROM persist").unwrap();
+
+        stmt.query_map([], |row| row.get(0))
+            .unwrap()
+            .flatten()
+            .collect()
+    };
+
+    songs_from_ids(&ids)
+}
+
+pub fn volume() -> u16 {
+    let conn = conn();
+    let mut stmt = conn.prepare("SELECT volume FROM settings").unwrap();
+    stmt.query_row([], |row| row.get(0)).unwrap()
+}
+
+pub fn set_volume(vol: u16) {
+    conn()
+        .execute("UPDATE settings SET volume = ?", [vol])
+        .unwrap();
+}
+
+pub fn paths() -> Vec<String> {
+    let conn = conn();
+    let mut stmt = conn.prepare("SELECT path FROM folder").unwrap();
+
+    stmt.query_map([], |row| row.get(0))
+        .unwrap()
+        .flatten()
+        .collect()
+}
+
+pub fn remove_path(path: &str) -> Result<(), &str> {
+    let conn = conn();
+    let result = conn
+        .execute("DELETE FROM folder WHERE path = ?", [path])
+        .unwrap();
+    if result == 0 {
+        Err("Invalid path.")
+    } else {
+        Ok(())
+    }
+}
+
+pub fn total_songs() -> usize {
+    let conn = conn();
+    let mut stmt = conn.prepare("SELECT COUNT(*) FROM song").unwrap();
+    stmt.query_row([], |row| row.get(0)).unwrap()
+}
+
+pub fn songs() -> Vec<Song> {
+    collect_songs("SELECT *, rowid FROM song", params![])
+}
+
+pub fn artists() -> Vec<String> {
+    let conn = conn();
+    let mut stmt = conn
+        .prepare("SELECT name FROM artist ORDER BY name COLLATE NOCASE")
+        .unwrap();
+
+    stmt.query_map([], |row| {
+        let artist: String = row.get(0).unwrap();
+        Ok(artist)
+    })
+    .unwrap()
+    .flatten()
+    .collect()
+}
+
+pub fn albums() -> Vec<(String, String)> {
+    let conn = conn();
+    let mut stmt = conn
+        .prepare("SELECT name, artist_id FROM album ORDER BY artist_id COLLATE NOCASE")
+        .unwrap();
+
+    stmt.query_map([], |row| {
+        let album: String = row.get(0).unwrap();
+        let artist: String = row.get(1).unwrap();
+        Ok((album, artist))
+    })
+    .unwrap()
+    .flatten()
+    .collect()
+}
+
+pub fn albums_by_artist(artist: &str) -> Vec<String> {
+    let conn = conn();
+    let mut stmt = conn
+        .prepare("SELECT name FROM album WHERE artist_id = ? ORDER BY name COLLATE NOCASE")
+        .unwrap();
+
+    stmt.query_map([artist], |row| row.get(0))
+        .unwrap()
+        .flatten()
+        .collect()
+}
+
+pub fn songs_from_album(album: &str, artist: &str) -> Vec<Song> {
+    collect_songs(
+        "SELECT *, rowid FROM song WHERE artist_id = (?1) AND album_id = (?2) ORDER BY disc, number",
+        params![artist, album],
+    )
+}
+
+pub fn songs_by_artist(artist: &str) -> Vec<Song> {
+    collect_songs(
+        "SELECT *, rowid FROM song WHERE artist_id = ? ORDER BY album_id, disc, number",
+        params![artist],
+    )
+}
+
+pub fn songs_from_ids(ids: &[usize]) -> Vec<Song> {
+    let conn = conn();
+    let mut stmt = conn
+        .prepare("SELECT *, rowid FROM song WHERE rowid = ?")
+        .unwrap();
+
+    //TODO: Maybe batch this?
+    ids.iter()
+        .flat_map(|id| stmt.query_row([id], |row| Ok(song(row))))
+        .collect()
+}
+
+fn collect_songs<P>(query: &str, params: P) -> Vec<Song>
+where
+    P: Params,
+{
+    let conn = conn();
+    let mut stmt = conn.prepare(query).expect(query);
+
+    stmt.query_map(params, |row| Ok(song(row)))
+        .unwrap()
+        .flatten()
+        .collect()
+}
+
+fn song(row: &Row) -> Song {
+    let path: String = row.get(3).unwrap();
+    Song {
+        name: row.get(0).unwrap(),
+        disc: row.get(1).unwrap(),
+        number: row.get(2).unwrap(),
+        path: PathBuf::from(path),
+        gain: row.get(4).unwrap(),
+        album: row.get(5).unwrap(),
+        artist: row.get(6).unwrap(),
+        // folder: row.get(7).unwrap(),
+        id: row.get(8).unwrap(),
+    }
+}
+
+pub fn playback_device() -> String {
+    let conn = conn();
+    let mut stmt = conn.prepare("SELECT device FROM settings").unwrap();
+    stmt.query_row([], |row| row.get(0)).unwrap()
+}
+
+pub fn set_playback_device(name: &str) {
+    conn()
+        .execute("UPDATE settings SET device = ? ", [name])
+        .unwrap();
+}
diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 0b5cf9b9..17ce6c91 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -33,18 +33,18 @@ pub enum Event {
 }
 
 pub struct Player {
-    s: Sender<Event>,
-    playing: bool,
-    volume: u16,
-    songs: Index<Song>,
-    elapsed: Arc<RwLock<Duration>>,
-    generator: Arc<RwLock<Generator>>,
-    duration: Duration,
+    pub s: Sender<Event>,
+    pub playing: bool,
+    pub volume: u16,
+    pub songs: Index<Song>,
+    pub elapsed: Arc<RwLock<Duration>>,
+    pub generator: Arc<RwLock<Generator>>,
+    pub duration: Duration,
 }
 
 impl Player {
     //TODO: get device from toml file
-    pub fn new(_device: String, volume: u16) -> Self {
+    pub fn new(_device: String, volume: u16, _songs: &[Song]) -> Self {
         // let host_id = cpal::default_host().id();
         // let host = cpal::host_from_id(host_id).unwrap();
         // let mut devices: Vec<Device> = host.devices().unwrap().collect();
@@ -236,12 +236,12 @@ impl Player {
         if let Some(song) = self.songs.selected() {
             let volume = self.volume as f32 / VOLUME_REDUCTION;
             //Calculate the volume with gain
-            if song.track_gain == 0.0 {
+            if song.gain == 0.0 {
                 //Reduce the volume a little to match
                 //songs with replay gain information.
                 volume * 0.75
             } else {
-                volume * song.track_gain as f32
+                volume * song.gain as f32
             }
         } else {
             self.volume as f32 / VOLUME_REDUCTION
@@ -298,3 +298,5 @@ impl Player {
         // }
     }
 }
+
+unsafe impl Sync for Player {}
diff --git a/gonk-player/src/song.rs b/gonk-player/src/song.rs
index 5ea3084e..45659ba6 100644
--- a/gonk-player/src/song.rs
+++ b/gonk-player/src/song.rs
@@ -1,7 +1,6 @@
 use std::{
     fs::File,
     path::{Path, PathBuf},
-    time::Duration,
 };
 use symphonia::{
     core::{
@@ -25,8 +24,7 @@ pub struct Song {
     pub album: String,
     pub artist: String,
     pub path: PathBuf,
-    pub duration: Duration,
-    pub track_gain: f64,
+    pub gain: f64,
     pub id: Option<usize>,
 }
 
@@ -89,7 +87,7 @@ impl Song {
                                 .unwrap()
                                 .parse()
                                 .unwrap_or(0.0);
-                            song.track_gain = db_to_amplitude(db);
+                            song.gain = db_to_amplitude(db);
                         }
                         _ => (),
                     }
@@ -114,27 +112,6 @@ impl Song {
             song.album = String::from("Unknown Album");
         }
 
-        //Calculate duration
-        let track = probe.format.default_track().unwrap();
-        if let Some(tb) = track.codec_params.time_base {
-            let ts = track.codec_params.start_ts;
-
-            let dur = track
-                .codec_params
-                .n_frames
-                .map(|frames| track.codec_params.start_ts + frames);
-
-            if let Some(dur) = dur {
-                let d = tb.calc_time(dur.saturating_sub(ts));
-                let duration = Duration::from_secs(d.seconds) + Duration::from_secs_f64(d.frac);
-                song.duration = duration;
-            } else {
-                song.duration = Duration::from_secs(0);
-            }
-        } else {
-            song.duration = Duration::from_secs(0);
-        }
-
         Some(song)
     }
 }
diff --git a/gonk/Cargo.toml b/gonk/Cargo.toml
index 76712105..77bec6df 100644
--- a/gonk/Cargo.toml
+++ b/gonk/Cargo.toml
@@ -2,31 +2,16 @@
 name = "gonk"
 version = "0.0.10"
 edition = "2021"
-
-authors = ["Bay"]
-description = "A terminal music player"
-repository = "https://github.com/zX3no/gonk"
-readme = "../README.md"
-license = "MIT"
+default-run = "gonk"
 
 [dependencies]
-# Music
-gonk-player = {version = "0.0.10", path = "../gonk-player"}
-
-# UI
 crossterm = "0.23.2"
-tui = { version = "0.18.0", features = ["serde"] }
-unicode-width = "0.1.9"
+tui = "0.18.0"
 
-# Config
-static_init = "1.0.2"
-serde = { version = "1.0.137", features = ["derive"] }
-toml = { version = "0.5.9", features = ["preserve_order"] }
-
-# Database
-rusqlite = { version = "0.27.0", features = ["bundled"] }
-jwalk = "0.6.0"
 rayon = "1.5.3"
 
-# Search
-strsim = "0.10.0"
\ No newline at end of file
+unicode-width = "0.1.9"
+strsim = "0.10.0"
+
+gonk-player = {path = "../gonk-player"}
+gonk-database = {path = "../gonk-database"}
diff --git a/gonk/src/app.rs b/gonk/src/app.rs
deleted file mode 100644
index d96bd2fb..00000000
--- a/gonk/src/app.rs
+++ /dev/null
@@ -1,384 +0,0 @@
-use crate::sqlite::{Database, State};
-use crate::{sqlite, toml::*};
-use crossterm::{
-    event::{
-        self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers, MouseEventKind,
-    },
-    execute,
-    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
-};
-use std::time::Duration;
-use std::time::Instant;
-use std::{
-    io::{stdout, Stdout},
-    path::Path,
-};
-use tui::Terminal;
-use tui::{
-    backend::CrosstermBackend,
-    layout::{Constraint, Direction, Layout},
-};
-
-use self::status_bar::StatusBar;
-use {browser::Browser, options::Options, playlist::Playlist, queue::Queue, search::Search};
-
-mod browser;
-mod options;
-mod playlist;
-mod queue;
-mod search;
-mod status_bar;
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub enum Mode {
-    Browser,
-    Queue,
-    Search,
-    Options,
-    Playlist,
-}
-
-const TICK_RATE: Duration = Duration::from_millis(200);
-const POLL_RATE: Duration = Duration::from_millis(4);
-const SEEK_TIME: f32 = 10.0;
-
-pub struct App {
-    terminal: Terminal<CrosstermBackend<Stdout>>,
-    pub mode: Mode,
-    queue: Queue,
-    browser: Browser,
-    options: Options,
-    search: Search,
-    playlist: Playlist,
-    status_bar: StatusBar,
-    toml: Toml,
-    db: Database,
-    busy: bool,
-}
-
-impl App {
-    pub fn new() -> Result<Self, String> {
-        match Toml::new().check_paths() {
-            Ok(mut toml) => {
-                let args: Vec<String> = std::env::args().skip(1).collect();
-                let mut db = Database::default();
-
-                if let Some(first) = args.first() {
-                    match first as &str {
-                        "add" => {
-                            if let Some(dir) = args.get(1..) {
-                                let dir = dir.join(" ");
-                                let path = Path::new(&dir);
-                                if path.exists() {
-                                    toml.add_path(dir.clone());
-                                    db.add_paths(&[dir]);
-                                } else {
-                                    return Err(format!("{} is not a valid path.", dir));
-                                }
-                            }
-                        }
-                        "reset" => {
-                            sqlite::reset();
-                            toml.reset();
-                            return Err(String::from("Files reset!"));
-                        }
-                        "help" | "--help" => {
-                            println!("Usage");
-                            println!("   gonk [<command> <args>]");
-                            println!();
-                            println!("Options");
-                            println!("   add   <path>  Add music to the library");
-                            println!("   reset         Reset the database");
-                            return Err(String::new());
-                        }
-                        _ => return Err(String::from("Invalid command.")),
-                    }
-                }
-
-                //make sure the terminal recovers after a panic
-                let orig_hook = std::panic::take_hook();
-                std::panic::set_hook(Box::new(move |panic_info| {
-                    disable_raw_mode().unwrap();
-                    execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
-                    orig_hook(panic_info);
-                    std::process::exit(1);
-                }));
-
-                //Initialize the terminal and clear the screen
-                let mut terminal = Terminal::new(CrosstermBackend::new(stdout())).unwrap();
-                execute!(
-                    terminal.backend_mut(),
-                    EnterAlternateScreen,
-                    EnableMouseCapture,
-                )
-                .unwrap();
-                enable_raw_mode().unwrap();
-                terminal.clear().unwrap();
-
-                Ok(Self {
-                    terminal,
-                    mode: Mode::Browser,
-                    queue: Queue::new(toml.config.volume, toml.config.output_device.clone()),
-                    browser: Browser::new(),
-                    options: Options::new(&mut toml),
-                    search: Search::new().init(),
-                    playlist: Playlist::new(),
-                    status_bar: StatusBar::new(),
-                    busy: false,
-                    db,
-                    toml,
-                })
-            }
-            Err(err) => Err(err),
-        }
-    }
-    pub fn run(&mut self) -> std::io::Result<()> {
-        let mut last_tick = Instant::now();
-
-        loop {
-            if last_tick.elapsed() >= TICK_RATE {
-                //Update the status_bar at a constant rate.
-                self.status_bar.update(self.busy, &self.queue);
-                last_tick = Instant::now();
-            }
-
-            match self.db.state() {
-                State::Busy => self.busy = true,
-                State::Idle => self.busy = false,
-                State::NeedsUpdate => {
-                    self.browser.refresh();
-                    self.search.update();
-                }
-            }
-
-            self.queue.update();
-
-            self.terminal.draw(|f| {
-                let area = Layout::default()
-                    .direction(Direction::Vertical)
-                    .constraints([Constraint::Min(2), Constraint::Length(3)])
-                    .split(f.size());
-
-                let top = if self.status_bar.is_hidden() {
-                    f.size()
-                } else {
-                    area[0]
-                };
-
-                match self.mode {
-                    Mode::Browser => self.browser.draw(top, f),
-                    Mode::Queue => self.queue.draw(f, None),
-                    Mode::Options => self.options.draw(top, f),
-                    Mode::Search => self.search.draw(top, f),
-                    Mode::Playlist => self.playlist.draw(top, f),
-                };
-
-                if self.mode != Mode::Queue {
-                    self.status_bar.draw(area[1], f, self.busy, &self.queue);
-                }
-            })?;
-
-            if crossterm::event::poll(POLL_RATE)? {
-                match event::read()? {
-                    Event::Key(event) => {
-                        let hotkey = &self.toml.hotkey;
-                        let shift = event.modifiers == KeyModifiers::SHIFT;
-                        let bind = Bind {
-                            key: Key::from(event.code),
-                            modifiers: Modifier::from_bitflags(event.modifiers),
-                        };
-
-                        //Check if the user wants to exit.
-                        if event.code == KeyCode::Char('C') && shift {
-                            break;
-                        } else if hotkey.quit == bind {
-                            break;
-                        };
-
-                        match event.code {
-                            KeyCode::Char(c)
-                                if self.search.input_mode() && self.mode == Mode::Search =>
-                            {
-                                self.search.on_key(c)
-                            }
-                            KeyCode::Char(c)
-                                if self.playlist.input_mode() && self.mode == Mode::Playlist =>
-                            {
-                                self.playlist.on_key(c)
-                            }
-                            KeyCode::Up => self.up(),
-                            KeyCode::Down => self.down(),
-                            KeyCode::Left => self.left(),
-                            KeyCode::Right => self.right(),
-                            KeyCode::Tab => {
-                                self.mode = match self.mode {
-                                    Mode::Browser | Mode::Options => Mode::Queue,
-                                    Mode::Queue => Mode::Browser,
-                                    Mode::Search => Mode::Queue,
-                                    Mode::Playlist => Mode::Browser,
-                                };
-                            }
-                            KeyCode::Backspace => match self.mode {
-                                Mode::Search => self.search.on_backspace(event.modifiers),
-                                Mode::Playlist => self.playlist.on_backspace(event.modifiers),
-                                _ => (),
-                            },
-                            KeyCode::Enter if shift => match self.mode {
-                                Mode::Browser => {
-                                    let songs = self.browser.on_enter();
-                                    self.playlist.add_to_playlist(&songs);
-                                    self.mode = Mode::Playlist;
-                                }
-                                Mode::Queue => {
-                                    if let Some(song) = self.queue.player.selected_song() {
-                                        self.playlist.add_to_playlist(&[song.clone()]);
-                                        self.mode = Mode::Playlist;
-                                    }
-                                }
-                                _ => (),
-                            },
-                            KeyCode::Enter => match self.mode {
-                                Mode::Browser => {
-                                    let songs = self.browser.on_enter();
-                                    self.queue.player.add_songs(&songs);
-                                }
-                                Mode::Queue => {
-                                    if let Some(i) = self.queue.ui.index() {
-                                        self.queue.player.play_index(i);
-                                    }
-                                }
-                                Mode::Search => self.search.on_enter(&mut self.queue.player),
-                                Mode::Options => self
-                                    .options
-                                    .on_enter(&mut self.queue.player, &mut self.toml),
-                                Mode::Playlist => self.playlist.on_enter(&mut self.queue.player),
-                            },
-                            KeyCode::Esc => match self.mode {
-                                Mode::Search => self.search.on_escape(&mut self.mode),
-                                Mode::Options => self.mode = Mode::Queue,
-                                Mode::Playlist => self.playlist.on_escape(&mut self.mode),
-                                _ => (),
-                            },
-                            //TODO: Rework mode changing buttons
-                            KeyCode::Char('`') => {
-                                self.status_bar.toggle_hidden();
-                            }
-                            KeyCode::Char(',') => self.mode = Mode::Playlist,
-                            KeyCode::Char('.') => self.mode = Mode::Options,
-                            KeyCode::Char('/') => self.mode = Mode::Search,
-                            KeyCode::Char('1' | '!') => {
-                                self.queue.move_constraint(0, event.modifiers);
-                            }
-                            KeyCode::Char('2' | '@') => {
-                                self.queue.move_constraint(1, event.modifiers);
-                            }
-                            KeyCode::Char('3' | '#') => {
-                                self.queue.move_constraint(2, event.modifiers);
-                            }
-                            _ if hotkey.up == bind => self.up(),
-                            _ if hotkey.down == bind => self.down(),
-                            _ if hotkey.left == bind => self.left(),
-                            _ if hotkey.right == bind => self.right(),
-                            _ if hotkey.play_pause == bind => self.queue.player.toggle_playback(),
-                            _ if hotkey.clear == bind => self.queue.clear(),
-                            _ if hotkey.clear_except_playing == bind => {
-                                self.queue.clear_except_playing();
-                            }
-                            _ if hotkey.refresh_database == bind && self.mode == Mode::Browser => {
-                                self.db.add_paths(&self.toml.config.paths);
-                            }
-                            _ if hotkey.seek_backward == bind && self.mode != Mode::Search => {
-                                self.queue.player.seek_by(-SEEK_TIME)
-                            }
-                            _ if hotkey.seek_forward == bind && self.mode != Mode::Search => {
-                                self.queue.player.seek_by(SEEK_TIME)
-                            }
-                            _ if hotkey.previous == bind && self.mode != Mode::Search => {
-                                self.queue.player.previous()
-                            }
-                            _ if hotkey.next == bind && self.mode != Mode::Search => {
-                                self.queue.player.next()
-                            }
-                            _ if hotkey.volume_up == bind => {
-                                self.queue.player.volume_up();
-                                self.toml.set_volume(self.queue.player.get_volume());
-                            }
-                            _ if hotkey.volume_down == bind => {
-                                self.queue.player.volume_down();
-                                self.toml.set_volume(self.queue.player.get_volume());
-                            }
-                            _ if hotkey.delete == bind => match self.mode {
-                                Mode::Queue => self.queue.delete(),
-                                Mode::Playlist => self.playlist.delete(),
-                                _ => (),
-                            },
-                            _ if hotkey.random == bind => self.queue.player.randomize(),
-                            _ => (),
-                        }
-                    }
-                    Event::Mouse(event) => match event.kind {
-                        MouseEventKind::ScrollUp => self.up(),
-                        MouseEventKind::ScrollDown => self.down(),
-                        MouseEventKind::Down(_) => {
-                            if let Mode::Queue = self.mode {
-                                self.terminal.draw(|f| self.queue.draw(f, Some(event)))?;
-                            }
-                        }
-                        _ => (),
-                    },
-                    _ => (),
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    fn left(&mut self) {
-        match self.mode {
-            Mode::Browser => self.browser.left(),
-            Mode::Playlist => self.playlist.left(),
-            _ => (),
-        }
-    }
-
-    fn right(&mut self) {
-        match self.mode {
-            Mode::Browser => self.browser.right(),
-            Mode::Playlist => self.playlist.right(),
-            _ => (),
-        }
-    }
-
-    fn up(&mut self) {
-        match self.mode {
-            Mode::Browser => self.browser.up(),
-            Mode::Queue => self.queue.up(),
-            Mode::Search => self.search.up(),
-            Mode::Options => self.options.up(),
-            Mode::Playlist => self.playlist.up(),
-        }
-    }
-
-    fn down(&mut self) {
-        match self.mode {
-            Mode::Browser => self.browser.down(),
-            Mode::Queue => self.queue.down(),
-            Mode::Search => self.search.down(),
-            Mode::Options => self.options.down(),
-            Mode::Playlist => self.playlist.down(),
-        }
-    }
-}
-
-impl Drop for App {
-    fn drop(&mut self) {
-        disable_raw_mode().unwrap();
-        execute!(
-            self.terminal.backend_mut(),
-            LeaveAlternateScreen,
-            DisableMouseCapture
-        )
-        .unwrap();
-    }
-}
diff --git a/gonk/src/app/browser.rs b/gonk/src/app/browser.rs
deleted file mode 100644
index 54879c25..00000000
--- a/gonk/src/app/browser.rs
+++ /dev/null
@@ -1,214 +0,0 @@
-use crate::widgets::{List, ListItem, ListState};
-use crate::{sqlite, Frame};
-use gonk_player::{Index, Song};
-use tui::{
-    layout::{Constraint, Direction, Layout, Rect},
-    style::{Color, Style},
-    widgets::{Block, BorderType, Borders},
-};
-
-#[derive(PartialEq, Eq)]
-pub enum Mode {
-    Artist,
-    Album,
-    Song,
-}
-
-impl Mode {
-    pub fn right(&mut self) {
-        match self {
-            Mode::Artist => *self = Mode::Album,
-            Mode::Album => *self = Mode::Song,
-            Mode::Song => (),
-        }
-    }
-    pub fn left(&mut self) {
-        match self {
-            Mode::Artist => (),
-            Mode::Album => *self = Mode::Artist,
-            Mode::Song => *self = Mode::Album,
-        }
-    }
-}
-
-pub struct BrowserSong {
-    name: String,
-    id: usize,
-}
-
-pub struct Browser {
-    artists: Index<String>,
-    albums: Index<String>,
-    songs: Index<BrowserSong>,
-    pub mode: Mode,
-}
-
-impl Browser {
-    pub fn new() -> Self {
-        let artists = Index::new(sqlite::get_all_artists(), Some(0));
-
-        let (albums, songs) = if let Some(first_artist) = artists.selected() {
-            let albums = Index::new(sqlite::get_all_albums_by_artist(first_artist), Some(0));
-
-            if let Some(first_album) = albums.selected() {
-                let songs = sqlite::get_all_songs_from_album(first_album, first_artist)
-                    .into_iter()
-                    .map(|song| BrowserSong {
-                        name: format!("{}. {}", song.number, song.name),
-                        id: song.id.unwrap(),
-                    })
-                    .collect();
-                (albums, Index::new(songs, Some(0)))
-            } else {
-                (albums, Index::default())
-            }
-        } else {
-            (Index::default(), Index::default())
-        };
-
-        Self {
-            artists,
-            albums,
-            songs,
-            mode: Mode::Artist,
-        }
-    }
-    pub fn up(&mut self) {
-        match self.mode {
-            Mode::Artist => self.artists.up(),
-            Mode::Album => self.albums.up(),
-            Mode::Song => self.songs.up(),
-        }
-        self.update_browser();
-    }
-    pub fn down(&mut self) {
-        match self.mode {
-            Mode::Artist => self.artists.down(),
-            Mode::Album => self.albums.down(),
-            Mode::Song => self.songs.down(),
-        }
-        self.update_browser();
-    }
-    pub fn update_browser(&mut self) {
-        match self.mode {
-            Mode::Artist => self.update_albums(),
-            Mode::Album => self.update_songs(),
-            Mode::Song => (),
-        }
-    }
-    pub fn update_albums(&mut self) {
-        //Update the album based on artist selection
-        if let Some(artist) = self.artists.selected() {
-            self.albums = Index::new(sqlite::get_all_albums_by_artist(artist), Some(0));
-            self.update_songs();
-        }
-    }
-    pub fn update_songs(&mut self) {
-        if let Some(artist) = self.artists.selected() {
-            if let Some(album) = self.albums.selected() {
-                let songs = sqlite::get_all_songs_from_album(album, artist)
-                    .into_iter()
-                    .map(|song| BrowserSong {
-                        name: format!("{}. {}", song.number, song.name),
-                        id: song.id.unwrap(),
-                    })
-                    .collect();
-                self.songs = Index::new(songs, Some(0));
-            }
-        }
-    }
-    pub fn right(&mut self) {
-        self.mode.right();
-    }
-    pub fn left(&mut self) {
-        self.mode.left();
-    }
-    pub fn on_enter(&self) -> Vec<Song> {
-        if let Some(artist) = self.artists.selected() {
-            if let Some(album) = self.albums.selected() {
-                if let Some(song) = self.songs.selected() {
-                    return match self.mode {
-                        Mode::Artist => sqlite::get_songs_by_artist(artist),
-                        Mode::Album => sqlite::get_all_songs_from_album(album, artist),
-                        Mode::Song => sqlite::get_songs(&[song.id]),
-                    };
-                }
-            }
-        }
-        Vec::new()
-    }
-    pub fn refresh(&mut self) {
-        self.mode = Mode::Artist;
-
-        self.artists = Index::new(sqlite::get_all_artists(), Some(0));
-        self.albums = Index::default();
-        self.songs = Index::default();
-
-        self.update_albums();
-    }
-}
-
-impl Browser {
-    fn list<'a>(title: &'static str, content: &'a [ListItem], use_symbol: bool) -> List<'a> {
-        let list = List::new(content.to_vec())
-            .block(
-                Block::default()
-                    .title(title)
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded),
-            )
-            .style(Style::default().fg(Color::White));
-
-        if use_symbol {
-            list.highlight_symbol(">")
-        } else {
-            list.highlight_symbol("")
-        }
-    }
-    pub fn draw(&self, area: Rect, f: &mut Frame) {
-        let size = area.width / 3;
-        let rem = area.width % 3;
-
-        let chunks = Layout::default()
-            .direction(Direction::Horizontal)
-            .constraints([
-                Constraint::Length(size),
-                Constraint::Length(size),
-                Constraint::Length(size + rem),
-            ])
-            .split(area);
-
-        let a: Vec<ListItem> = self
-            .artists
-            .data
-            .iter()
-            .map(|name| ListItem::new(name.as_str()))
-            .collect();
-
-        let b: Vec<ListItem> = self
-            .albums
-            .data
-            .iter()
-            .map(|name| ListItem::new(name.as_str()))
-            .collect();
-
-        let c: Vec<ListItem> = self
-            .songs
-            .data
-            .iter()
-            .map(|song| ListItem::new(song.name.as_str()))
-            .collect();
-
-        let artists = Browser::list("─Aritst", &a, self.mode == Mode::Artist);
-        let albums = Browser::list("─Album", &b, self.mode == Mode::Album);
-        let songs = Browser::list("─Song", &c, self.mode == Mode::Song);
-
-        f.render_stateful_widget(
-            artists,
-            chunks[0],
-            &mut ListState::new(self.artists.index()),
-        );
-        f.render_stateful_widget(albums, chunks[1], &mut ListState::new(self.albums.index()));
-        f.render_stateful_widget(songs, chunks[2], &mut ListState::new(self.songs.index()));
-    }
-}
diff --git a/gonk/src/app/options.rs b/gonk/src/app/options.rs
deleted file mode 100644
index 855a427f..00000000
--- a/gonk/src/app/options.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-use crate::toml::Toml;
-use crate::widgets::{List, ListItem, ListState};
-use crate::Frame;
-use gonk_player::{Device, DeviceTrait, Index, Player};
-use tui::{
-    layout::Rect,
-    style::{Color, Modifier, Style},
-    widgets::{Block, BorderType, Borders},
-};
-
-pub struct Options {
-    pub devices: Index<Device>,
-    current_device: String,
-}
-
-impl Options {
-    pub fn new(toml: &mut Toml) -> Self {
-        let default_device = Player::default_device();
-        let mut config_device = toml.config.output_device.clone();
-
-        let devices = Player::audio_devices();
-        let device_names: Vec<String> = devices.iter().flat_map(DeviceTrait::name).collect();
-
-        if !device_names.contains(&config_device) {
-            let name = default_device.name().unwrap();
-            config_device = name.clone();
-            toml.set_output_device(name);
-        }
-
-        Self {
-            devices: Index::new(devices, Some(0)),
-            current_device: config_device,
-        }
-    }
-    pub fn up(&mut self) {
-        self.devices.up();
-    }
-    pub fn down(&mut self) {
-        self.devices.down();
-    }
-    #[allow(unused)]
-    pub fn on_enter(&mut self, player: &mut Player, toml: &mut Toml) {
-        //TODO: Update playback device.
-        // if let Some(device) = self.devices.selected() {
-        //     //don't update the device if there is an error
-        //     match player.change_output_device(device) {
-        //         Ok(_) => {
-        //             let name = device.name().unwrap();
-        //             self.current_device = name.clone();
-        //             toml.set_output_device(name);
-        //         }
-        //         //TODO: Print error in status bar
-        //         Err(e) => panic!("{:?}", e),
-        //     }
-        // }
-    }
-}
-
-impl Options {
-    pub fn draw(&self, area: Rect, f: &mut Frame) {
-        let items: Vec<_> = self
-            .devices
-            .data
-            .iter()
-            .map(|device| {
-                let name = device.name().unwrap();
-                if name == self.current_device {
-                    ListItem::new(name)
-                } else {
-                    ListItem::new(name).style(Style::default().add_modifier(Modifier::DIM))
-                }
-            })
-            .collect();
-
-        let list = List::new(items)
-            .block(
-                Block::default()
-                    .title("─Output Device")
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded),
-            )
-            .style(Style::default().fg(Color::White))
-            .highlight_style(Style::default())
-            .highlight_symbol("> ");
-
-        let mut state = ListState::default();
-        state.select(self.devices.index());
-
-        f.render_stateful_widget(list, area, &mut state);
-    }
-}
diff --git a/gonk/src/app/playlist.rs b/gonk/src/app/playlist.rs
deleted file mode 100644
index 3c83ca9f..00000000
--- a/gonk/src/app/playlist.rs
+++ /dev/null
@@ -1,386 +0,0 @@
-use crate::widgets::*;
-use crate::*;
-use crossterm::event::KeyModifiers;
-use gonk_player::{Index, Player, Song};
-use tui::style::Style;
-use tui::text::Span;
-use tui::{
-    layout::{Constraint, Direction, Layout, Margin, Rect},
-    widgets::{Block, BorderType, Borders, Clear, Paragraph},
-};
-
-#[derive(PartialEq, Eq)]
-enum Mode {
-    Playlist,
-    Song,
-    Popup,
-}
-
-pub struct Item {
-    row: usize,
-    song: Song,
-}
-
-pub struct Playlist {
-    mode: Mode,
-    playlist: Index<String>,
-    songs: Index<Item>,
-    songs_to_add: Vec<Song>,
-    search: String,
-    search_result: String,
-    changed: bool,
-}
-
-impl Playlist {
-    pub fn new() -> Self {
-        let playlists = sqlite::playlist::get_names();
-        let songs = Playlist::get_songs(playlists.first());
-
-        Self {
-            mode: Mode::Playlist,
-            playlist: Index::new(playlists, Some(0)),
-            songs: Index::new(songs, Some(0)),
-            songs_to_add: Vec::new(),
-            changed: false,
-            search: String::new(),
-            search_result: String::from("Enter a playlist name..."),
-        }
-    }
-    fn get_songs(playlist: Option<&String>) -> Vec<Item> {
-        if let Some(playlist) = playlist {
-            let (row_ids, song_ids) = sqlite::playlist::get(playlist);
-            let songs = sqlite::get_songs(&song_ids);
-            songs
-                .into_iter()
-                .zip(row_ids)
-                .map(|(song, row)| Item { row, song })
-                .collect()
-        } else {
-            Vec::new()
-        }
-    }
-    pub fn up(&mut self) {
-        match self.mode {
-            Mode::Playlist => {
-                self.playlist.up();
-                self.update_songs();
-            }
-            Mode::Song => self.songs.up(),
-            Mode::Popup => (),
-        }
-    }
-    pub fn down(&mut self) {
-        match self.mode {
-            Mode::Playlist => {
-                self.playlist.down();
-                self.update_songs();
-            }
-            Mode::Song => self.songs.down(),
-            Mode::Popup => (),
-        }
-    }
-    fn update_songs(&mut self) {
-        //Update the list of songs.
-        let songs = Playlist::get_songs(self.playlist.selected());
-        self.songs = if !songs.is_empty() {
-            Index::new(songs, self.songs.index())
-        } else {
-            self.mode = Mode::Playlist;
-            Index::default()
-        };
-    }
-    fn update_playlists(&mut self) {
-        self.playlist = Index::new(sqlite::playlist::get_names(), self.playlist.index());
-    }
-    pub fn on_enter(&mut self, player: &mut Player) {
-        match self.mode {
-            Mode::Playlist => {
-                let songs: Vec<Song> = self
-                    .songs
-                    .data
-                    .iter()
-                    .map(|item| item.song.clone())
-                    .collect();
-
-                player.add_songs(&songs);
-            }
-            Mode::Song => {
-                if let Some(item) = self.songs.selected() {
-                    player.add_songs(&[item.song.clone()]);
-                }
-            }
-            Mode::Popup if !self.songs_to_add.is_empty() => {
-                //Select an existing playlist or create a new one.
-                let name = self.search.trim().to_string();
-
-                let ids: Vec<usize> = self
-                    .songs_to_add
-                    .iter()
-                    .map(|song| song.id.unwrap())
-                    .collect();
-
-                sqlite::add_playlist(&name, &ids);
-
-                self.update_playlists();
-
-                let mut i = Some(0);
-                for (j, playlist) in self.playlist.data.iter().enumerate() {
-                    if playlist == &name {
-                        i = Some(j);
-                        break;
-                    }
-                }
-                //Select the playlist was just modified and update the songs.
-                self.playlist.select(i);
-                self.update_songs();
-
-                //Reset everything.
-                self.search = String::new();
-                self.mode = Mode::Song;
-            }
-            _ => (),
-        }
-    }
-    pub fn on_backspace(&mut self, modifiers: KeyModifiers) {
-        match self.mode {
-            Mode::Popup => {
-                self.changed = true;
-                if modifiers == KeyModifiers::CONTROL {
-                    self.search.clear();
-                } else {
-                    self.search.pop();
-                }
-            }
-            _ => self.left(),
-        }
-    }
-    pub fn left(&mut self) {
-        match self.mode {
-            Mode::Song => {
-                self.mode = Mode::Playlist;
-            }
-            Mode::Popup => (),
-            _ => (),
-        }
-    }
-    pub fn right(&mut self) {
-        match self.mode {
-            Mode::Playlist if !self.songs.is_empty() => {
-                self.mode = Mode::Song;
-            }
-            Mode::Popup => (),
-            _ => (),
-        }
-    }
-    pub fn add_to_playlist(&mut self, songs: &[Song]) {
-        self.songs_to_add = songs.to_vec();
-        self.mode = Mode::Popup;
-    }
-    pub fn delete(&mut self) {
-        match self.mode {
-            Mode::Playlist => {
-                if let Some(playlist) = self.playlist.selected() {
-                    //TODO: Prompt the user with yes or no.
-                    sqlite::playlist::remove(&playlist);
-
-                    let index = self.playlist.index().unwrap();
-                    self.playlist.remove(index);
-                    self.update_songs();
-                }
-            }
-            Mode::Song => {
-                if let Some(song) = self.songs.selected() {
-                    sqlite::playlist::remove_id(song.row);
-                    let index = self.songs.index().unwrap();
-                    self.songs.remove(index);
-                    if self.songs.is_empty() {
-                        self.update_playlists();
-                    }
-                }
-            }
-            Mode::Popup => return,
-        }
-    }
-    pub fn on_key(&mut self, c: char) {
-        self.changed = true;
-        self.search.push(c);
-    }
-    pub fn input_mode(&self) -> bool {
-        self.mode == Mode::Popup
-    }
-    pub fn on_escape(&mut self, mode: &mut super::Mode) {
-        match self.mode {
-            Mode::Popup => {
-                self.mode = Mode::Playlist;
-                self.search = String::new();
-                self.changed = true;
-            }
-            _ => *mode = super::Mode::Browser,
-        };
-    }
-}
-
-impl Playlist {
-    //TODO: I think I want a different popup.
-    //It should be a small side bar in the browser.
-    //There should be a list of existing playlists.
-    //The first playlist will be the one you just added to
-    //so it's fast to keep adding things
-    //The last item will be add a new playlist.
-    //If there are no playlists it will prompt you to create on.
-    //This should be similar to foobar on android.
-
-    //TODO: Renaming
-    //Move items around in lists
-    //There should be a hotkey to add to most recent playlist
-    //And a message should show up in the bottom bar saying
-    //"[name] has been has been added to [playlist name]"
-    //or
-    //"25 songs have been added to [playlist name]"
-
-    //TODO: Add keybindings to readme
-    pub fn draw_popup(&mut self, f: &mut Frame) {
-        if let Some(area) = centered_rect(45, 6, f.size()) {
-            let v = Layout::default()
-                .direction(Direction::Vertical)
-                .constraints([Constraint::Length(3), Constraint::Percentage(50)])
-                .margin(1)
-                .split(area);
-
-            f.render_widget(Clear, area);
-            f.render_widget(
-                Block::default()
-                    .title("─Add to playlist")
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded),
-                area,
-            );
-
-            //Scroll the playlist name.
-            let len = self.search.len() as u16;
-            let width = v[0].width.saturating_sub(1);
-            let offset_x = if len < width { 0 } else { len - width + 1 };
-
-            f.render_widget(
-                Paragraph::new(self.search.as_str())
-                    .block(
-                        Block::default()
-                            .borders(Borders::ALL)
-                            .border_type(BorderType::Rounded),
-                    )
-                    .scroll((0, offset_x)),
-                v[0],
-            );
-
-            if self.changed {
-                self.changed = false;
-                let eq = self.playlist.data.iter().any(|e| e == &self.search);
-                self.search_result = if eq {
-                    format!("Add to existing playlist: {}", self.search)
-                } else if self.search.is_empty() {
-                    String::from("Enter a playlist name...")
-                } else {
-                    format!("Add to new playlist: {}", self.search)
-                }
-            }
-
-            f.render_widget(
-                Paragraph::new(self.search_result.as_str()),
-                v[1].inner(&Margin {
-                    horizontal: 1,
-                    vertical: 0,
-                }),
-            );
-
-            //Draw the cursor.
-            let (x, y) = (v[0].x + 1, v[0].y + 1);
-            if self.search.is_empty() {
-                f.set_cursor(x, y);
-            } else {
-                let width = v[0].width.saturating_sub(3);
-                if len < width {
-                    f.set_cursor(x + len, y)
-                } else {
-                    f.set_cursor(x + width, y)
-                }
-            }
-        }
-    }
-    pub fn draw(&mut self, area: Rect, f: &mut Frame) {
-        let horizontal = Layout::default()
-            .direction(Direction::Horizontal)
-            .constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
-            .split(area);
-
-        let items: Vec<ListItem> = self
-            .playlist
-            .clone()
-            .into_iter()
-            .map(|str| ListItem::new(str))
-            .collect();
-
-        let list = List::new(items)
-            .block(
-                Block::default()
-                    .title("─Playlist")
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded),
-            )
-            .highlight_symbol(">");
-
-        let list = if let Mode::Playlist = self.mode {
-            list.highlight_symbol(">")
-        } else {
-            list.highlight_symbol("")
-        };
-
-        f.render_stateful_widget(
-            list,
-            horizontal[0],
-            &mut ListState::new(self.playlist.index()),
-        );
-
-        let content = self
-            .songs
-            .data
-            .iter()
-            .map(|item| {
-                let song = item.song.clone();
-                Row::new(vec![
-                    Span::styled(song.name, Style::default().fg(COLORS.name)),
-                    Span::styled(song.album, Style::default().fg(COLORS.album)),
-                    Span::styled(song.artist, Style::default().fg(COLORS.artist)),
-                ])
-            })
-            .collect();
-
-        let table = Table::new(content)
-            .widths(&[
-                Constraint::Percentage(42),
-                Constraint::Percentage(30),
-                Constraint::Percentage(28),
-            ])
-            .block(
-                Block::default()
-                    .title("─Songs")
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded),
-            );
-
-        let table = if let Mode::Song = self.mode {
-            table.highlight_symbol(">")
-        } else {
-            table.highlight_symbol("")
-        };
-
-        f.render_stateful_widget(
-            table,
-            horizontal[1],
-            &mut TableState::new(self.songs.index()),
-        );
-
-        if let Mode::Popup = self.mode {
-            self.draw_popup(f);
-        }
-    }
-}
diff --git a/gonk/src/app/queue.rs b/gonk/src/app/queue.rs
deleted file mode 100644
index 275ed68c..00000000
--- a/gonk/src/app/queue.rs
+++ /dev/null
@@ -1,361 +0,0 @@
-use crate::widgets::{Cell, Gauge, Row, Table, TableState};
-use crate::{Frame, COLORS};
-use crossterm::event::{KeyModifiers, MouseEvent};
-use gonk_player::{Index, Player};
-use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
-use tui::style::{Color, Modifier, Style};
-use tui::text::{Span, Spans};
-use tui::widgets::{Block, BorderType, Borders, Paragraph};
-use unicode_width::UnicodeWidthStr;
-
-pub struct Queue {
-    pub ui: Index<()>,
-    pub constraint: [u16; 4],
-    pub player: Player,
-}
-
-impl Queue {
-    pub fn new(vol: u16, device: String) -> Self {
-        Self {
-            ui: Index::default(),
-            constraint: [8, 42, 24, 26],
-            player: Player::new(device, vol),
-        }
-    }
-    pub fn update(&mut self) {
-        if self.ui.index().is_none() && !self.player.is_empty() {
-            self.ui.select(Some(0));
-        }
-        self.player.update();
-    }
-    pub fn move_constraint(&mut self, row: usize, modifier: KeyModifiers) {
-        if modifier == KeyModifiers::SHIFT && self.constraint[row] != 0 {
-            //Move row back.
-            self.constraint[row + 1] += 1;
-            self.constraint[row] = self.constraint[row].saturating_sub(1);
-        } else if self.constraint[row + 1] != 0 {
-            //Move row forward.
-            self.constraint[row] += 1;
-            self.constraint[row + 1] = self.constraint[row + 1].saturating_sub(1);
-        }
-
-        debug_assert!(
-            self.constraint.iter().sum::<u16>() == 100,
-            "Constraint went out of bounds: {:?}",
-            self.constraint
-        );
-    }
-    pub fn up(&mut self) {
-        self.ui.up_with_len(self.player.total_songs());
-    }
-    pub fn down(&mut self) {
-        self.ui.down_with_len(self.player.total_songs());
-    }
-    pub fn clear(&mut self) {
-        self.player.clear();
-        self.ui.select(Some(0));
-    }
-    pub fn clear_except_playing(&mut self) {
-        self.player.clear_except_playing();
-        self.ui.select(Some(0));
-    }
-    pub fn delete(&mut self) {
-        if let Some(i) = self.ui.index() {
-            self.player.delete_index(i);
-            //make sure the ui index is in sync
-            let len = self.player.total_songs().saturating_sub(1);
-            if i > len {
-                self.ui.select(Some(len));
-            }
-        }
-    }
-}
-
-impl Queue {
-    pub fn draw(&mut self, f: &mut Frame, event: Option<MouseEvent>) {
-        let chunks = Layout::default()
-            .direction(Direction::Vertical)
-            .constraints([
-                Constraint::Length(3),
-                Constraint::Min(10),
-                Constraint::Length(3),
-            ])
-            .split(f.size());
-
-        self.draw_header(f, chunks[0]);
-
-        let row_bounds = self.draw_body(f, chunks[1]);
-
-        self.draw_seeker(f, chunks[2]);
-
-        //Don't handle mouse input when the queue is empty.
-        if self.player.is_empty() {
-            return;
-        }
-
-        //Handle mouse input.
-        if let Some(event) = event {
-            let (x, y) = (event.column, event.row);
-            const HEADER_HEIGHT: u16 = 5;
-
-            let size = f.size();
-
-            //Mouse support for the seek bar.
-            if (size.height - 3 == y || size.height - 2 == y || size.height - 1 == y)
-                && size.height > 15
-            {
-                let ratio = x as f32 / size.width as f32;
-                let duration = self.player.duration().as_secs_f32();
-                let new_time = duration * ratio;
-                self.player.seek_to(new_time);
-            }
-
-            //Mouse support for the queue.
-            if let Some((start, _)) = row_bounds {
-                //Check if you clicked on the header.
-                if y >= HEADER_HEIGHT {
-                    let index = (y - HEADER_HEIGHT) as usize + start;
-
-                    //Make sure you didn't click on the seek bar
-                    //and that the song index exists.
-                    if index < self.player.total_songs()
-                        && ((size.height < 15 && y < size.height.saturating_sub(1))
-                            || y < size.height.saturating_sub(3))
-                    {
-                        self.ui.select(Some(index));
-                    }
-                }
-            }
-        }
-    }
-    fn draw_header(&mut self, f: &mut Frame, area: Rect) {
-        f.render_widget(
-            Block::default()
-                .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
-                .border_type(BorderType::Rounded),
-            area,
-        );
-
-        let state = if self.player.is_empty() {
-            String::from("╭─Stopped")
-        } else if self.player.is_playing() {
-            String::from("╭─Playing")
-        } else {
-            String::from("╭─Paused")
-        };
-
-        f.render_widget(Paragraph::new(state).alignment(Alignment::Left), area);
-
-        if !self.player.is_empty() {
-            self.draw_title(f, area);
-        }
-
-        let volume = Spans::from(format!("Vol: {}%─╮", self.player.get_volume()));
-        f.render_widget(Paragraph::new(volume).alignment(Alignment::Right), area);
-    }
-    fn draw_title(&mut self, f: &mut Frame, area: Rect) {
-        let title = if let Some(song) = self.player.selected_song() {
-            let mut name = song.name.trim_end().to_string();
-            let mut album = song.album.trim_end().to_string();
-            let mut artist = song.artist.trim_end().to_string();
-            let max_width = area.width.saturating_sub(30) as usize;
-
-            while artist.width() + name.width() + "-| - |-".width() > max_width {
-                if artist.width() > name.width() {
-                    artist.pop();
-                } else {
-                    name.pop();
-                }
-            }
-
-            while album.width() > max_width {
-                album.pop();
-            }
-
-            let n = album
-                .width()
-                .saturating_sub(artist.width() + name.width() + 3);
-            let rem = n % 2;
-            let pad_front = " ".repeat(n / 2);
-            let pad_back = " ".repeat(n / 2 + rem);
-
-            vec![
-                Spans::from(vec![
-                    Span::raw(format!("─│ {}", pad_front)),
-                    Span::styled(artist, Style::default().fg(COLORS.artist)),
-                    Span::raw(" ─ "),
-                    Span::styled(name, Style::default().fg(COLORS.name)),
-                    Span::raw(format!("{} │─", pad_back)),
-                ]),
-                Spans::from(Span::styled(album, Style::default().fg(COLORS.album))),
-            ]
-        } else {
-            Vec::new()
-        };
-
-        f.render_widget(Paragraph::new(title).alignment(Alignment::Center), area);
-    }
-    fn draw_body(&mut self, f: &mut Frame, area: Rect) -> Option<(usize, usize)> {
-        if self.player.is_empty() {
-            f.render_widget(
-                Block::default()
-                    .border_type(BorderType::Rounded)
-                    .borders(Borders::LEFT | Borders::RIGHT),
-                area,
-            );
-            return None;
-        }
-
-        let songs = self.player.get_index();
-        let (songs, player_index, ui_index) = (&songs.data, songs.index(), self.ui.index());
-
-        let mut items: Vec<Row> = songs
-            .iter()
-            .map(|song| {
-                Row::new(vec![
-                    Cell::from(""),
-                    Cell::from(song.number.to_string()).style(Style::default().fg(COLORS.number)),
-                    Cell::from(song.name.as_str()).style(Style::default().fg(COLORS.name)),
-                    Cell::from(song.album.as_str()).style(Style::default().fg(COLORS.album)),
-                    Cell::from(song.artist.as_str()).style(Style::default().fg(COLORS.artist)),
-                ])
-            })
-            .collect();
-
-        if let Some(player_index) = player_index {
-            if let Some(song) = songs.get(player_index) {
-                if let Some(ui_index) = ui_index {
-                    //Currently playing song
-                    let row = if ui_index == player_index {
-                        Row::new(vec![
-                            Cell::from(">>").style(
-                                Style::default()
-                                    .fg(Color::White)
-                                    .add_modifier(Modifier::DIM | Modifier::BOLD),
-                            ),
-                            Cell::from(song.number.to_string())
-                                .style(Style::default().bg(COLORS.number).fg(Color::Black)),
-                            Cell::from(song.name.as_str())
-                                .style(Style::default().bg(COLORS.name).fg(Color::Black)),
-                            Cell::from(song.album.as_str())
-                                .style(Style::default().bg(COLORS.album).fg(Color::Black)),
-                            Cell::from(song.artist.as_str())
-                                .style(Style::default().bg(COLORS.artist).fg(Color::Black)),
-                        ])
-                    } else {
-                        Row::new(vec![
-                            Cell::from(">>").style(
-                                Style::default()
-                                    .fg(Color::White)
-                                    .add_modifier(Modifier::DIM | Modifier::BOLD),
-                            ),
-                            Cell::from(song.number.to_string())
-                                .style(Style::default().fg(COLORS.number)),
-                            Cell::from(song.name.as_str()).style(Style::default().fg(COLORS.name)),
-                            Cell::from(song.album.as_str())
-                                .style(Style::default().fg(COLORS.album)),
-                            Cell::from(song.artist.as_str())
-                                .style(Style::default().fg(COLORS.artist)),
-                        ])
-                    };
-
-                    items.remove(player_index);
-                    items.insert(player_index, row);
-
-                    //Current selection
-                    if ui_index != player_index {
-                        if let Some(song) = songs.get(ui_index) {
-                            let row = Row::new(vec![
-                                Cell::default(),
-                                Cell::from(song.number.to_string())
-                                    .style(Style::default().bg(COLORS.number)),
-                                Cell::from(song.name.as_str())
-                                    .style(Style::default().bg(COLORS.name)),
-                                Cell::from(song.album.as_str())
-                                    .style(Style::default().bg(COLORS.album)),
-                                Cell::from(song.artist.as_str())
-                                    .style(Style::default().bg(COLORS.artist)),
-                            ])
-                            .style(Style::default().fg(Color::Black));
-                            items.remove(ui_index);
-                            items.insert(ui_index, row);
-                        }
-                    }
-                }
-            }
-        }
-
-        let con = [
-            Constraint::Length(2),
-            Constraint::Percentage(self.constraint[0]),
-            Constraint::Percentage(self.constraint[1]),
-            Constraint::Percentage(self.constraint[2]),
-            Constraint::Percentage(self.constraint[3]),
-        ];
-
-        let t = Table::new(items)
-            .header(
-                Row::new(["", "Track", "Title", "Album", "Artist"])
-                    .style(
-                        Style::default()
-                            .fg(Color::White)
-                            .add_modifier(Modifier::BOLD),
-                    )
-                    .bottom_margin(1),
-            )
-            .block(
-                Block::default()
-                    .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
-                    .border_type(BorderType::Rounded),
-            )
-            // .separator()
-            .widths(&con);
-
-        let row_bounds = t.get_row_bounds(ui_index, t.get_row_height(area));
-
-        f.render_stateful_widget(t, area, &mut TableState::new(ui_index));
-
-        Some(row_bounds)
-    }
-    fn draw_seeker(&mut self, f: &mut Frame, area: Rect) {
-        if self.player.is_empty() {
-            return f.render_widget(
-                Block::default()
-                    .border_type(BorderType::Rounded)
-                    .borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT),
-                area,
-            );
-        }
-
-        let elapsed = self.player.elapsed().as_secs_f32();
-        let duration = self.player.duration().as_secs_f32();
-
-        let seeker = format!(
-            "{:02}:{:02}/{:02}:{:02}",
-            (elapsed / 60.0).floor(),
-            elapsed.trunc() as u32 % 60,
-            (duration / 60.0).floor(),
-            duration.trunc() as u32 % 60,
-        );
-
-        let ratio = elapsed / duration;
-        let ratio = if ratio.is_nan() {
-            0.0
-        } else {
-            ratio.clamp(0.0, 1.0)
-        };
-
-        f.render_widget(
-            Gauge::default()
-                .block(
-                    Block::default()
-                        .borders(Borders::ALL)
-                        .border_type(BorderType::Rounded),
-                )
-                .gauge_style(Style::default().fg(COLORS.seeker))
-                .ratio(ratio as f64)
-                .label(seeker),
-            area,
-        );
-    }
-}
diff --git a/gonk/src/app/search.rs b/gonk/src/app/search.rs
deleted file mode 100644
index 81f4a88b..00000000
--- a/gonk/src/app/search.rs
+++ /dev/null
@@ -1,478 +0,0 @@
-use super::Mode as AppMode;
-use crate::widgets::*;
-use crate::*;
-use crossterm::event::KeyModifiers;
-use gonk_player::{Index, Player};
-use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
-use std::cmp::Ordering;
-use tui::{
-    layout::{Alignment, Constraint, Direction, Layout, Rect},
-    style::{Modifier, Style},
-    text::{Span, Spans},
-    widgets::{Block, BorderType, Borders, Paragraph},
-};
-
-#[derive(Clone)]
-pub enum Item {
-    Song(Song),
-    Album(Album),
-    Artist(Artist),
-}
-
-#[derive(Clone, Default)]
-pub struct Song {
-    pub id: usize,
-    pub name: String,
-    pub album: String,
-    pub artist: String,
-}
-
-#[derive(Clone, Default)]
-pub struct Album {
-    pub name: String,
-    pub artist: String,
-}
-
-#[derive(Clone, Default)]
-pub struct Artist {
-    pub name: String,
-}
-
-#[derive(PartialEq, Eq)]
-pub enum Mode {
-    Search,
-    Select,
-}
-
-pub struct Search {
-    query: String,
-    query_changed: bool,
-    mode: Mode,
-    results: Index<Item>,
-    cache: Vec<Item>,
-}
-
-impl Search {
-    pub fn new() -> Self {
-        Self {
-            cache: Vec::new(),
-            query: String::new(),
-            query_changed: false,
-            mode: Mode::Search,
-            results: Index::default(),
-        }
-    }
-    pub fn init(mut self) -> Self {
-        self.update();
-        self
-    }
-    pub fn update(&mut self) {
-        self.update_cache();
-        self.update_search();
-    }
-    fn update_cache(&mut self) {
-        self.cache = Vec::new();
-
-        for song in sqlite::get_all_songs() {
-            self.cache.push(Item::Song(Song {
-                name: song.name,
-                album: song.album,
-                artist: song.artist,
-                id: song.id.unwrap(),
-            }));
-        }
-
-        for (name, artist) in sqlite::get_all_albums() {
-            self.cache.push(Item::Album(Album { name, artist }));
-        }
-
-        for name in sqlite::get_all_artists() {
-            self.cache.push(Item::Artist(Artist { name }));
-        }
-    }
-    fn update_search(&mut self) {
-        let query = &self.query.to_lowercase();
-
-        let mut results: Vec<_> = if query.is_empty() {
-            //If there user has not asked to search anything
-            //populate the list with 40 results.
-            self.cache
-                .iter()
-                .take(40)
-                .rev()
-                .map(|item| {
-                    let acc = match item {
-                        Item::Song(song) => strsim::jaro_winkler(query, &song.name.to_lowercase()),
-                        Item::Album(album) => {
-                            strsim::jaro_winkler(query, &album.name.to_lowercase())
-                        }
-                        Item::Artist(artist) => {
-                            strsim::jaro_winkler(query, &artist.name.to_lowercase())
-                        }
-                    };
-
-                    (item, acc)
-                })
-                .collect()
-        } else {
-            self.cache
-                .par_iter()
-                .filter_map(|item| {
-                    //I don't know if 'to_lowercase' has any overhead.
-                    let acc = match item {
-                        Item::Song(song) => strsim::jaro_winkler(query, &song.name.to_lowercase()),
-                        Item::Album(album) => {
-                            strsim::jaro_winkler(query, &album.name.to_lowercase())
-                        }
-                        Item::Artist(artist) => {
-                            strsim::jaro_winkler(query, &artist.name.to_lowercase())
-                        }
-                    };
-
-                    //Filter out results that are poor matches. 0.75 is an arbitrary value.
-                    if acc > 0.75 {
-                        Some((item, acc))
-                    } else {
-                        None
-                    }
-                })
-                .collect()
-        };
-
-        //Sort results by score.
-        results.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap());
-
-        //Sort artists above self-titled albums.
-        results.sort_by(|(item, a), (_, b)| {
-            //If the score is the same
-            if a == b {
-                //And the item is an album
-                if let Item::Album(_) = item {
-                    //Move item lower in the list.
-                    Ordering::Greater
-                } else {
-                    //Move item higher in the list.
-                    Ordering::Less
-                }
-            } else {
-                //Keep the same order.
-                Ordering::Equal
-            }
-        });
-
-        self.results.data = results.into_iter().map(|(item, _)| item.clone()).collect();
-    }
-    pub fn on_key(&mut self, c: char) {
-        self.query_changed = true;
-        self.query.push(c);
-    }
-    pub fn up(&mut self) {
-        self.results.up();
-    }
-    pub fn down(&mut self) {
-        self.results.down();
-    }
-    pub fn on_backspace(&mut self, modifiers: KeyModifiers) {
-        match self.mode {
-            Mode::Search => {
-                if modifiers == KeyModifiers::CONTROL {
-                    self.query.clear();
-                } else {
-                    self.query.pop();
-                }
-            }
-            Mode::Select => {
-                self.results.select(None);
-                self.mode = Mode::Search;
-            }
-        }
-    }
-    pub fn on_escape(&mut self, mode: &mut AppMode) {
-        match self.mode {
-            Mode::Search => {
-                if let Mode::Search = self.mode {
-                    self.query.clear();
-                    *mode = AppMode::Queue;
-                }
-            }
-            Mode::Select => {
-                self.mode = Mode::Search;
-                self.results.select(None);
-            }
-        }
-    }
-    pub fn on_enter(&mut self, player: &mut Player) {
-        match self.mode {
-            Mode::Search => {
-                if !self.results.is_empty() {
-                    self.mode = Mode::Select;
-                    self.results.select(Some(0));
-                }
-            }
-            Mode::Select => {
-                if let Some(item) = self.results.selected() {
-                    let songs = match item {
-                        Item::Song(song) => sqlite::get_songs(&[song.id]),
-                        Item::Album(album) => {
-                            sqlite::get_all_songs_from_album(&album.name, &album.artist)
-                        }
-                        Item::Artist(artist) => sqlite::get_songs_by_artist(&artist.name),
-                    };
-
-                    player.add_songs(&songs);
-                }
-            }
-        }
-    }
-    pub fn input_mode(&self) -> bool {
-        self.mode == Mode::Search
-    }
-}
-
-impl Search {
-    pub fn draw(&mut self, area: Rect, f: &mut Frame) {
-        if self.query_changed {
-            self.update_search();
-        }
-
-        let v = Layout::default()
-            .direction(Direction::Vertical)
-            .constraints([
-                Constraint::Length(3),
-                Constraint::Percentage(30),
-                Constraint::Percentage(60),
-            ])
-            .split(area);
-
-        let h = Layout::default()
-            .direction(Direction::Horizontal)
-            .constraints([Constraint::Percentage(35), Constraint::Percentage(65)])
-            .split(v[1]);
-
-        self.draw_textbox(f, v[0]);
-
-        let item = if self.results.selected().is_some() {
-            self.results.selected()
-        } else {
-            self.results.data.first()
-        };
-
-        if let Some(item) = item {
-            match item {
-                Item::Song(song) => {
-                    Search::song(f, &song.name, &song.album, &song.artist, h[0]);
-                    self.album(f, &song.album, &song.artist, h[1]);
-                }
-                Item::Album(album) => {
-                    self.album(f, &album.name, &album.artist, h[0]);
-                    self.artist(f, &album.artist, h[1]);
-                }
-                Item::Artist(artist) => {
-                    let albums = sqlite::get_all_albums_by_artist(&artist.name);
-
-                    self.artist(f, &artist.name, h[0]);
-
-                    let h_split = Layout::default()
-                        .direction(Direction::Horizontal)
-                        .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
-                        .split(h[1]);
-
-                    //draw the first two albums
-                    for (i, area) in h_split.iter().enumerate() {
-                        if let Some(album) = albums.get(i) {
-                            self.album(f, album, &artist.name, *area);
-                        }
-                    }
-                }
-            }
-            self.draw_results(f, v[2]);
-        } else {
-            self.draw_results(f, v[1].union(v[2]));
-        }
-
-        self.update_cursor(f);
-    }
-    fn song(f: &mut Frame, name: &str, album: &str, artist: &str, area: Rect) {
-        let song_table = Table::new(vec![
-            Row::new(vec![Spans::from(Span::raw(album))]),
-            Row::new(vec![Spans::from(Span::raw(artist))]),
-        ])
-        .header(
-            Row::new(vec![Span::styled(
-                format!("{} ", name),
-                Style::default().add_modifier(Modifier::ITALIC),
-            )])
-            .bottom_margin(1),
-        )
-        .block(
-            Block::default()
-                .borders(Borders::ALL)
-                .border_type(BorderType::Rounded)
-                .title("Song"),
-        )
-        .widths(&[Constraint::Percentage(100)]);
-
-        f.render_widget(song_table, area);
-    }
-    fn album(&self, f: &mut Frame, album: &str, artist: &str, area: Rect) {
-        let cells: Vec<_> = sqlite::get_all_songs_from_album(album, artist)
-            .iter()
-            .map(|song| Row::new(vec![Cell::from(format!("{}. {}", song.number, song.name))]))
-            .collect();
-
-        let table = Table::new(cells)
-            .header(
-                Row::new(vec![Cell::from(Span::styled(
-                    format!("{} ", album),
-                    Style::default().add_modifier(Modifier::ITALIC),
-                ))])
-                .bottom_margin(1),
-            )
-            .block(
-                Block::default()
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded)
-                    .title("Album"),
-            )
-            .widths(&[Constraint::Percentage(100)]);
-
-        f.render_widget(table, area);
-    }
-    fn artist(&self, f: &mut Frame, artist: &str, area: Rect) {
-        let albums = sqlite::get_all_albums_by_artist(artist);
-        let cells: Vec<_> = albums
-            .iter()
-            .map(|album| Row::new(vec![Cell::from(Span::raw(album))]))
-            .collect();
-
-        let table = Table::new(cells)
-            .header(
-                Row::new(vec![Cell::from(Span::styled(
-                    format!("{} ", artist),
-                    Style::default().add_modifier(Modifier::ITALIC),
-                ))])
-                .bottom_margin(1),
-            )
-            .block(
-                Block::default()
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded)
-                    .title("Artist"),
-            )
-            .widths(&[Constraint::Percentage(100)]);
-
-        f.render_widget(table, area);
-    }
-    fn draw_results(&self, f: &mut Frame, area: Rect) {
-        let get_cell = |item: &Item, selected: bool| -> Row {
-            let selected_cell = if selected {
-                Cell::from(">")
-            } else {
-                Cell::default()
-            };
-
-            match item {
-                Item::Song(song) => {
-                    let song = sqlite::get_songs(&[song.id])[0].clone();
-                    Row::new(vec![
-                        selected_cell,
-                        Cell::from(song.name).style(Style::default().fg(COLORS.name)),
-                        Cell::from(song.album).style(Style::default().fg(COLORS.album)),
-                        Cell::from(song.artist).style(Style::default().fg(COLORS.artist)),
-                    ])
-                }
-                Item::Album(album) => Row::new(vec![
-                    selected_cell,
-                    Cell::from(format!("{} - Album", album.name))
-                        .style(Style::default().fg(COLORS.name)),
-                    Cell::from("").style(Style::default().fg(COLORS.album)),
-                    Cell::from(album.artist.clone()).style(Style::default().fg(COLORS.artist)),
-                ]),
-                Item::Artist(artist) => Row::new(vec![
-                    selected_cell,
-                    Cell::from(format!("{} - Artist", artist.name))
-                        .style(Style::default().fg(COLORS.name)),
-                    Cell::from("").style(Style::default().fg(COLORS.album)),
-                    Cell::from("").style(Style::default().fg(COLORS.artist)),
-                ]),
-            }
-        };
-
-        let rows: Vec<_> = self
-            .results
-            .data
-            .iter()
-            .enumerate()
-            .map(|(i, item)| {
-                if let Some(s) = self.results.index() {
-                    if s == i {
-                        return get_cell(item, true);
-                    }
-                } else if i == 0 {
-                    return get_cell(item, false);
-                }
-                get_cell(item, false)
-            })
-            .collect();
-
-        let italic = Style::default().add_modifier(Modifier::ITALIC);
-        let table = Table::new(rows)
-            .header(
-                Row::new(vec![
-                    Cell::default(),
-                    Cell::from("Name").style(italic),
-                    Cell::from("Album").style(italic),
-                    Cell::from("Artist").style(italic),
-                ])
-                .bottom_margin(1),
-            )
-            .block(
-                Block::default()
-                    .borders(Borders::ALL)
-                    .border_type(BorderType::Rounded),
-            )
-            .widths(&[
-                Constraint::Length(1),
-                Constraint::Percentage(40),
-                Constraint::Percentage(40),
-                Constraint::Percentage(20),
-            ]);
-
-        f.render_stateful_widget(table, area, &mut TableState::new(self.results.index()));
-    }
-    fn draw_textbox(&self, f: &mut Frame, area: Rect) {
-        let len = self.query.len() as u16;
-        //Search box is a little smaller than the max width
-        let width = area.width.saturating_sub(1);
-        let offset_x = if len < width { 0 } else { len - width + 1 };
-
-        f.render_widget(
-            Paragraph::new(self.query.as_str())
-                .block(
-                    Block::default()
-                        .borders(Borders::ALL)
-                        .border_type(BorderType::Rounded),
-                )
-                .alignment(Alignment::Left)
-                .scroll((0, offset_x)),
-            area,
-        );
-    }
-    fn update_cursor(&self, f: &mut Frame) {
-        let area = f.size();
-        //Move the cursor position when typing
-        if let Mode::Search = self.mode {
-            if self.query.is_empty() {
-                f.set_cursor(1, 1);
-            } else {
-                let len = self.query.len() as u16;
-                let max_width = area.width.saturating_sub(2);
-                if len >= max_width {
-                    f.set_cursor(max_width, 1);
-                } else {
-                    f.set_cursor(len + 1, 1);
-                }
-            }
-        }
-    }
-}
diff --git a/gonk/src/app/status_bar.rs b/gonk/src/app/status_bar.rs
deleted file mode 100644
index f24f0776..00000000
--- a/gonk/src/app/status_bar.rs
+++ /dev/null
@@ -1,155 +0,0 @@
-use super::queue::Queue;
-use crate::sqlite;
-use crate::Frame;
-use crate::COLORS;
-use std::time::{Duration, Instant};
-use tui::{
-    layout::{Alignment, Constraint, Direction, Layout, Rect},
-    style::Style,
-    text::{Span, Spans},
-    widgets::{Block, BorderType, Borders, Paragraph},
-};
-
-const WAIT_TIME: Duration = Duration::from_secs(2);
-
-pub struct StatusBar {
-    dots: usize,
-    busy: bool,
-    scan_message: String,
-    wait_timer: Option<Instant>,
-    scan_timer: Option<Instant>,
-    hidden: bool,
-}
-
-impl StatusBar {
-    pub fn new() -> Self {
-        Self {
-            dots: 1,
-            busy: false,
-            scan_message: String::new(),
-            wait_timer: None,
-            scan_timer: None,
-            hidden: true,
-        }
-    }
-
-    //Updates the dots in "Scanning for files .."
-    pub fn update(&mut self, db_busy: bool, queue: &Queue) {
-        if db_busy {
-            if self.dots < 3 {
-                self.dots += 1;
-            } else {
-                self.dots = 1;
-            }
-        } else {
-            self.dots = 1;
-        }
-
-        if let Some(timer) = self.wait_timer {
-            if timer.elapsed() >= WAIT_TIME {
-                self.wait_timer = None;
-                self.busy = false;
-
-                //FIXME: If the queue was not empty
-                //and the status bar was hidden
-                //before triggering an update
-                //the status bar will stay open
-                //without the users permission.
-                if queue.player.is_empty() {
-                    self.hidden = true;
-                }
-            }
-        }
-    }
-
-    pub fn toggle_hidden(&mut self) {
-        self.hidden = !self.hidden;
-    }
-
-    pub fn is_hidden(&self) -> bool {
-        self.hidden
-    }
-}
-impl StatusBar {
-    pub fn draw(&mut self, area: Rect, f: &mut Frame, busy: bool, queue: &Queue) {
-        if busy {
-            //If database is busy but status_bar is not
-            //set the status bar to busy
-            if !self.busy {
-                self.busy = true;
-                self.hidden = false;
-                self.scan_timer = Some(Instant::now());
-            }
-        } else if self.busy {
-            //If database is no-longer busy
-            //but status bar is. Print the duration
-            //and start the wait timer.
-            if let Some(scan_time) = self.scan_timer {
-                self.busy = false;
-                self.wait_timer = Some(Instant::now());
-                self.scan_timer = None;
-                self.scan_message = format!(
-                    "Finished adding {} files in {:.2} seconds.",
-                    sqlite::total_songs(),
-                    scan_time.elapsed().as_secs_f32(),
-                );
-            }
-        }
-
-        if self.hidden {
-            return;
-        }
-
-        let text = if busy {
-            Spans::from(format!("Scannig for files{}", ".".repeat(self.dots)))
-        } else if self.wait_timer.is_some() {
-            Spans::from(self.scan_message.as_str())
-        } else {
-            if let Some(song) = queue.player.selected_song() {
-                Spans::from(vec![
-                    Span::raw(" "),
-                    Span::styled(song.number.to_string(), Style::default().fg(COLORS.number)),
-                    Span::raw(" | "),
-                    Span::styled(song.name.as_str(), Style::default().fg(COLORS.name)),
-                    Span::raw(" | "),
-                    Span::styled(song.album.as_str(), Style::default().fg(COLORS.album)),
-                    Span::raw(" | "),
-                    Span::styled(song.artist.as_str(), Style::default().fg(COLORS.artist)),
-                ])
-            } else {
-                Spans::default()
-            }
-        };
-
-        let area = Layout::default()
-            .direction(Direction::Horizontal)
-            .constraints([Constraint::Percentage(85), Constraint::Percentage(15)])
-            .split(area);
-
-        f.render_widget(
-            Paragraph::new(text).alignment(Alignment::Left).block(
-                Block::default()
-                    .borders(Borders::TOP | Borders::LEFT | Borders::BOTTOM)
-                    .border_type(BorderType::Rounded),
-            ),
-            area[0],
-        );
-
-        //TODO: Draw mini progress bar here.
-
-        let text = if queue.player.is_playing() {
-            format!("Vol: {}% ", queue.player.get_volume())
-        } else {
-            String::from("Paused ")
-        };
-
-        f.render_widget(
-            Paragraph::new(text).alignment(Alignment::Right).block(
-                Block::default()
-                    .borders(Borders::TOP | Borders::RIGHT | Borders::BOTTOM)
-                    .border_type(BorderType::Rounded),
-            ),
-            area[1],
-        );
-    }
-}
diff --git a/gonk/src/browser.rs b/gonk/src/browser.rs
new file mode 100644
index 00000000..a3180245
--- /dev/null
+++ b/gonk/src/browser.rs
@@ -0,0 +1,220 @@
+use crate::widgets::{List, ListItem, ListState};
+use crate::{Frame, Input};
+use gonk_database::query;
+use gonk_player::{Index, Song};
+use tui::{
+    layout::{Constraint, Direction, Layout, Rect},
+    style::{Color, Style},
+    widgets::{Block, BorderType, Borders},
+};
+
+#[derive(PartialEq, Eq)]
+pub enum Mode {
+    Artist,
+    Album,
+    Song,
+}
+
+pub struct BrowserSong {
+    name: String,
+    id: usize,
+}
+
+pub struct Browser {
+    artists: Index<String>,
+    albums: Index<String>,
+    songs: Index<BrowserSong>,
+    pub mode: Mode,
+}
+
+impl Browser {
+    pub fn new() -> Self {
+        let artists = Index::new(query::artists(), Some(0));
+
+        let (albums, songs) = if let Some(first_artist) = artists.selected() {
+            let albums = Index::new(query::albums_by_artist(first_artist), Some(0));
+
+            if let Some(first_album) = albums.selected() {
+                let songs = query::songs_from_album(first_album, first_artist)
+                    .into_iter()
+                    .map(|song| BrowserSong {
+                        name: format!("{}. {}", song.number, song.name),
+                        id: song.id.unwrap(),
+                    })
+                    .collect();
+                (albums, Index::new(songs, Some(0)))
+            } else {
+                (albums, Index::default())
+            }
+        } else {
+            (Index::default(), Index::default())
+        };
+
+        Self {
+            artists,
+            albums,
+            songs,
+            mode: Mode::Artist,
+        }
+    }
+}
+
+impl Input for Browser {
+    fn up(&mut self) {
+        match self.mode {
+            Mode::Artist => self.artists.up(),
+            Mode::Album => self.albums.up(),
+            Mode::Song => self.songs.up(),
+        }
+        update_browser(self);
+    }
+
+    fn down(&mut self) {
+        match self.mode {
+            Mode::Artist => self.artists.down(),
+            Mode::Album => self.albums.down(),
+            Mode::Song => self.songs.down(),
+        }
+        update_browser(self);
+    }
+
+    fn left(&mut self) {
+        match self.mode {
+            Mode::Artist => (),
+            Mode::Album => self.mode = Mode::Artist,
+            Mode::Song => self.mode = Mode::Album,
+        }
+    }
+
+    fn right(&mut self) {
+        match self.mode {
+            Mode::Artist => self.mode = Mode::Album,
+            Mode::Album => self.mode = Mode::Song,
+            Mode::Song => (),
+        }
+    }
+}
+
+pub fn refresh(browser: &mut Browser) {
+    browser.mode = Mode::Artist;
+
+    browser.artists = Index::new(query::artists(), Some(0));
+    browser.albums = Index::default();
+    browser.songs = Index::default();
+
+    update_albums(browser);
+}
+
+pub fn update_browser(browser: &mut Browser) {
+    match browser.mode {
+        Mode::Artist => update_albums(browser),
+        Mode::Album => update_songs(browser),
+        Mode::Song => (),
+    }
+}
+
+pub fn update_albums(browser: &mut Browser) {
+    //Update the album based on artist selection
+    if let Some(artist) = browser.artists.selected() {
+        browser.albums = Index::new(query::albums_by_artist(artist), Some(0));
+        update_songs(browser);
+    }
+}
+
+pub fn update_songs(browser: &mut Browser) {
+    if let Some(artist) = browser.artists.selected() {
+        if let Some(album) = browser.albums.selected() {
+            let songs = query::songs_from_album(album, artist)
+                .into_iter()
+                .map(|song| BrowserSong {
+                    name: format!("{}. {}", song.number, song.name),
+                    id: song.id.unwrap(),
+                })
+                .collect();
+            browser.songs = Index::new(songs, Some(0));
+        }
+    }
+}
+
+pub fn get_selected(browser: &Browser) -> Vec<Song> {
+    if let Some(artist) = browser.artists.selected() {
+        if let Some(album) = browser.albums.selected() {
+            if let Some(song) = browser.songs.selected() {
+                return match browser.mode {
+                    Mode::Artist => query::songs_by_artist(artist),
+                    Mode::Album => query::songs_from_album(album, artist),
+                    Mode::Song => query::songs_from_ids(&[song.id]),
+                };
+            }
+        }
+    }
+    Vec::new()
+}
+
+pub fn draw(browser: &Browser, area: Rect, f: &mut Frame) {
+    let size = area.width / 3;
+    let rem = area.width % 3;
+
+    let chunks = Layout::default()
+        .direction(Direction::Horizontal)
+        .constraints([
+            Constraint::Length(size),
+            Constraint::Length(size),
+            Constraint::Length(size + rem),
+        ])
+        .split(area);
+
+    let a: Vec<ListItem> = browser
+        .artists
+        .data
+        .iter()
+        .map(|name| ListItem::new(name.as_str()))
+        .collect();
+
+    let b: Vec<ListItem> = browser
+        .albums
+        .data
+        .iter()
+        .map(|name| ListItem::new(name.as_str()))
+        .collect();
+
+    let c: Vec<ListItem> = browser
+        .songs
+        .data
+        .iter()
+        .map(|song| ListItem::new(song.name.as_str()))
+        .collect();
+
+    let artists = list("─Aritst", &a, browser.mode == Mode::Artist);
+    let albums = list("─Album", &b, browser.mode == Mode::Album);
+    let songs = list("─Song", &c, browser.mode == Mode::Song);
+
+    f.render_stateful_widget(
+        artists,
+        chunks[0],
+        &mut ListState::new(browser.artists.index()),
+    );
+    f.render_stateful_widget(
+        albums,
+        chunks[1],
+        &mut ListState::new(browser.albums.index()),
+    );
+    f.render_stateful_widget(songs, chunks[2], &mut ListState::new(browser.songs.index()));
+}
+
+fn list<'a>(title: &'static str, content: &'a [ListItem], use_symbol: bool) -> List<'a> {
+    let list = List::new(content)
+        .block(
+            Block::default()
+                .title(title)
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded),
+        )
+        .style(Style::default().fg(Color::White));
+
+    if use_symbol {
+        list.highlight_symbol(">")
+    } else {
+        list.highlight_symbol("")
+    }
+}
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index b540d637..fc5ebd6a 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -1,42 +1,375 @@
-use crate::toml::{Colors, Toml};
-use app::App;
-use static_init::dynamic;
+use browser::Browser;
+use crossterm::{event::*, terminal::*, *};
+use gonk_database::{query, Database, State};
+use gonk_player::Player;
+use playlist::{Mode as PlaylistMode, Playlist};
+use queue::Queue;
+use search::{Mode as SearchMode, Search};
+use settings::Settings;
+use status_bar::StatusBar;
 use std::{
-    io::{Result, Stdout},
-    path::PathBuf,
+    io::{stdout, Stdout},
+    path::Path,
+    sync::mpsc,
+    time::{Duration, Instant},
 };
-use tui::backend::CrosstermBackend;
+use tui::{backend::CrosstermBackend, layout::*, style::Color, Terminal};
 
-mod app;
-mod sqlite;
-mod toml;
+mod browser;
+mod playlist;
+mod queue;
+mod search;
+mod settings;
+mod status_bar;
 mod widgets;
 
-#[dynamic]
-static GONK_DIR: PathBuf = {
-    let gonk = if cfg!(windows) {
-        PathBuf::from(&std::env::var("APPDATA").unwrap())
-    } else {
-        PathBuf::from(&std::env::var("HOME").unwrap()).join(".config")
-    }
-    .join("gonk");
+type Frame<'a> = tui::Frame<'a, CrosstermBackend<Stdout>>;
 
-    if !gonk.exists() {
-        std::fs::create_dir_all(&gonk).unwrap();
-    }
-    gonk
+//TODO: Cleanup colors
+pub struct Colors {
+    pub number: Color,
+    pub name: Color,
+    pub album: Color,
+    pub artist: Color,
+    pub seeker: Color,
+}
+
+const COLORS: Colors = Colors {
+    number: Color::Green,
+    name: Color::Cyan,
+    album: Color::Magenta,
+    artist: Color::Blue,
+    seeker: Color::White,
 };
 
-#[dynamic]
-static COLORS: Colors = Toml::new().colors;
+#[derive(PartialEq, Eq)]
+pub enum Mode {
+    Browser,
+    Queue,
+    Search,
+    Playlist,
+    Settings,
+}
 
-type Frame<'a> = tui::Frame<'a, CrosstermBackend<Stdout>>;
+pub trait Input {
+    fn up(&mut self);
+    fn down(&mut self);
+    fn left(&mut self);
+    fn right(&mut self);
+}
+
+fn init() -> Terminal<CrosstermBackend<Stdout>> {
+    //Panic handler
+    let orig_hook = std::panic::take_hook();
+    std::panic::set_hook(Box::new(move |panic_info| {
+        disable_raw_mode().unwrap();
+        execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
+        orig_hook(panic_info);
+        std::process::exit(1);
+    }));
 
-fn main() -> Result<()> {
-    sqlite::initialize_database();
+    //Terminal
+    let mut terminal = Terminal::new(CrosstermBackend::new(stdout())).unwrap();
+    execute!(
+        terminal.backend_mut(),
+        EnterAlternateScreen,
+        EnableMouseCapture,
+    )
+    .unwrap();
+    enable_raw_mode().unwrap();
+    terminal.clear().unwrap();
 
-    match App::new() {
-        Ok(mut app) => app.run(),
-        Err(err) => Ok(println!("{}", err)),
+    terminal
+}
+
+fn main() {
+    let mut db = Database::default();
+    let args: Vec<String> = std::env::args().skip(1).collect();
+
+    if !args.is_empty() {
+        match args[0].as_str() {
+            "add" if args.len() > 1 => {
+                let path = args[1..].join(" ");
+                //TODO: This might silently scan a directory but not add anything.
+                //Might be confusing.
+                if Path::new(&path).exists() {
+                    db.add_path(&path);
+                } else {
+                    return println!("Invalid path.");
+                }
+            }
+            "rm" if args.len() > 1 => {
+                let path = args[1..].join(" ");
+                match query::remove_path(&path) {
+                    Ok(_) => return,
+                    Err(e) => return println!("{e}"),
+                };
+            }
+            "list" => {
+                return for path in query::paths() {
+                    println!("{path}");
+                };
+            }
+            "reset" => {
+                return match gonk_database::reset() {
+                    Ok(_) => println!("Files reset!"),
+                    Err(e) => println!("{}", e),
+                }
+            }
+            "help" | "--help" => {
+                println!("Usage");
+                println!("   gonk [<command> <args>]");
+                println!();
+                println!("Options");
+                println!("   add   <path>  Add music to the library");
+                println!("   reset         Reset the database");
+                return;
+            }
+            _ if !args.is_empty() => return println!("Invalid command."),
+            _ if args.len() > 1 => return println!("Invalid argument."),
+            _ => (),
+        }
     }
+
+    //Player takes a while so off-load it to another thread.
+    let (s, r) = mpsc::channel();
+
+    //TODO: figure out why database is crashing
+    let songs = query::get_cache();
+    let volume = query::volume();
+
+    std::thread::spawn(move || {
+        let player = Player::new(String::from("device"), volume, &songs);
+        s.send(player).unwrap();
+    });
+
+    let mut terminal = init();
+
+    //13ms
+    let mut search = Search::new();
+    let mut settings = Settings::new();
+    let mut browser = Browser::new();
+    let mut queue = Queue::new();
+    let mut status_bar = StatusBar::new();
+    let mut playlist = Playlist::new();
+
+    let mut mode = Mode::Browser;
+
+    let mut busy = false;
+    let mut last_tick = Instant::now();
+
+    let mut player = r.recv().unwrap();
+
+    loop {
+        if last_tick.elapsed() >= Duration::from_millis(200) {
+            //Update the status_bar at a constant rate.
+            status_bar::update(&mut status_bar, busy, &player);
+            last_tick = Instant::now();
+        }
+
+        queue.len = player.songs.len();
+        player.update();
+
+        match db.state() {
+            State::Busy => busy = true,
+            State::Idle => busy = false,
+            State::NeedsUpdate => {
+                browser::refresh(&mut browser);
+                search::refresh_cache(&mut search);
+                search::refresh_results(&mut search);
+            }
+        }
+
+        //Draw
+        terminal
+            .draw(|f| {
+                let area = Layout::default()
+                    .direction(Direction::Vertical)
+                    .constraints([Constraint::Min(2), Constraint::Length(3)])
+                    .split(f.size());
+
+                let (top, bottom) = if status_bar.hidden {
+                    (f.size(), area[1])
+                } else {
+                    (area[0], area[1])
+                };
+
+                match mode {
+                    Mode::Browser => browser::draw(&browser, top, f),
+                    Mode::Queue => queue::draw(&mut queue, &mut player, f, None),
+                    Mode::Search => search::draw(&mut search, top, f),
+                    Mode::Playlist => playlist::draw(&mut playlist, top, f),
+                    Mode::Settings => settings::draw(&mut settings, top, f),
+                };
+
+                if mode != Mode::Queue {
+                    status_bar::draw(&mut status_bar, bottom, f, busy, &player);
+                }
+            })
+            .unwrap();
+
+        let input_search = search.mode == SearchMode::Search && mode == Mode::Search;
+        let input_playlist = playlist.mode == PlaylistMode::Popup && mode == Mode::Playlist;
+
+        let input = match mode {
+            Mode::Browser => &mut browser as &mut dyn Input,
+            Mode::Queue => &mut queue as &mut dyn Input,
+            Mode::Search => &mut search as &mut dyn Input,
+            Mode::Playlist => &mut playlist as &mut dyn Input,
+            Mode::Settings => &mut settings as &mut dyn Input,
+        };
+
+        if event::poll(Duration::from_millis(2)).unwrap() {
+            match event::read().unwrap() {
+                Event::Key(event) => {
+                    let shift = event.modifiers == KeyModifiers::SHIFT;
+                    let control = event.modifiers == KeyModifiers::CONTROL;
+
+                    match event.code {
+                        KeyCode::Char('c') if control => break,
+                        KeyCode::Char(c) if input_search => {
+                            //Handle ^W as control backspace.
+                            if control && c == 'w' {
+                                search::on_backspace(&mut search, true);
+                            } else {
+                                search.query_changed = true;
+                                search.query.push(c);
+                            }
+                        }
+                        KeyCode::Char(c) if input_playlist => {
+                            if control && c == 'w' {
+                                playlist::on_backspace(&mut playlist, true);
+                            } else {
+                                playlist.changed = true;
+                                playlist.search.push(c);
+                            }
+                        }
+                        KeyCode::Char(' ') => player.toggle_playback(),
+                        KeyCode::Char('c') if shift => {
+                            player.clear_except_playing();
+                            queue.ui.select(Some(0));
+                        }
+                        KeyCode::Char('c') => {
+                            player.clear();
+                            queue.ui.select(Some(0));
+                        }
+                        KeyCode::Char('x') => match mode {
+                            Mode::Queue => queue::delete(&mut queue, &mut player),
+                            Mode::Playlist => playlist::delete(&mut playlist),
+                            _ => (),
+                        },
+                        KeyCode::Char('u') if mode == Mode::Browser => db.refresh(),
+                        KeyCode::Char('q') => player.seek_by(-10.0),
+                        KeyCode::Char('e') => player.seek_by(10.0),
+                        KeyCode::Char('a') => player.previous(),
+                        KeyCode::Char('d') => player.next(),
+                        KeyCode::Char('w') => player.volume_up(),
+                        KeyCode::Char('s') => player.volume_down(),
+                        KeyCode::Char('r') => player.randomize(),
+                        //TODO: Rework mode changing buttons
+                        KeyCode::Char('`') => {
+                            status_bar.hidden = !status_bar.hidden;
+                        }
+                        KeyCode::Char(',') => mode = Mode::Playlist,
+                        KeyCode::Char('.') => mode = Mode::Settings,
+                        KeyCode::Char('/') => mode = Mode::Search,
+                        KeyCode::Tab => {
+                            mode = match mode {
+                                Mode::Browser | Mode::Settings | Mode::Search => Mode::Queue,
+                                Mode::Queue | Mode::Playlist => Mode::Browser,
+                            };
+                        }
+                        KeyCode::Esc => match mode {
+                            Mode::Search => {
+                                search::on_escape(&mut search, &mut mode);
+                            }
+                            Mode::Settings => mode = Mode::Queue,
+                            Mode::Playlist => playlist::on_escape(&mut playlist, &mut mode),
+                            _ => (),
+                        },
+                        KeyCode::Enter if shift => match mode {
+                            Mode::Browser => {
+                                let songs = browser::get_selected(&browser);
+                                playlist::add_to_playlist(&mut playlist, &songs);
+                                mode = Mode::Playlist;
+                            }
+                            Mode::Queue => {
+                                if let Some(song) = player.songs.selected() {
+                                    playlist::add_to_playlist(&mut playlist, &[song.clone()]);
+                                    mode = Mode::Playlist;
+                                }
+                            }
+                            _ => (),
+                        },
+                        KeyCode::Enter => match mode {
+                            Mode::Browser => {
+                                let songs = browser::get_selected(&browser);
+                                player.add_songs(&songs);
+                            }
+                            Mode::Queue => {
+                                if let Some(i) = queue.ui.index() {
+                                    player.play_index(i);
+                                }
+                            }
+                            Mode::Search => search::on_enter(&mut search, &mut player),
+                            Mode::Settings => settings::on_enter(&mut settings, &mut player),
+                            Mode::Playlist => playlist::on_enter(&mut playlist, &mut player),
+                        },
+                        KeyCode::Backspace => match mode {
+                            Mode::Search => search::on_backspace(&mut search, control),
+                            Mode::Playlist => playlist::on_backspace(&mut playlist, control),
+                            _ => (),
+                        },
+                        KeyCode::Up => input.up(),
+                        KeyCode::Down => input.down(),
+                        KeyCode::Left => input.left(),
+                        KeyCode::Right => input.right(),
+                        KeyCode::Char('1' | '!') => {
+                            queue::constraint(&mut queue, 0, shift);
+                        }
+                        KeyCode::Char('2' | '@') => {
+                            queue::constraint(&mut queue, 1, shift);
+                        }
+                        KeyCode::Char('3' | '#') => {
+                            queue::constraint(&mut queue, 2, shift);
+                        }
+                        KeyCode::Char(c) => match c {
+                            'h' => input.left(),
+                            'j' => input.down(),
+                            'k' => input.up(),
+                            'l' => input.right(),
+                            _ => (),
+                        },
+                        _ => (),
+                    }
+                }
+                Event::Mouse(event) => match event.kind {
+                    MouseEventKind::ScrollUp => input.up(),
+                    MouseEventKind::ScrollDown => input.down(),
+                    MouseEventKind::Down(_) => {
+                        if let Mode::Queue = mode {
+                            terminal
+                                .draw(|f| queue::draw(&mut queue, &mut player, f, Some(event)))
+                                .unwrap();
+                        }
+                    }
+                    _ => (),
+                },
+                Event::Resize(..) => (),
+            }
+        }
+    }
+
+    query::set_volume(player.volume);
+
+    let ids: Vec<usize> = player.songs.data.iter().flat_map(|song| song.id).collect();
+    query::cache(&ids);
+
+    disable_raw_mode().unwrap();
+    execute!(
+        terminal.backend_mut(),
+        LeaveAlternateScreen,
+        DisableMouseCapture
+    )
+    .unwrap();
 }
diff --git a/gonk/src/playlist.rs b/gonk/src/playlist.rs
new file mode 100644
index 00000000..bbebcb07
--- /dev/null
+++ b/gonk/src/playlist.rs
@@ -0,0 +1,371 @@
+use crate::{widgets::*, Frame, Input, COLORS};
+use gonk_database::playlist::PlaylistSong;
+use gonk_database::{playlist, query};
+use gonk_player::{Index, Player, Song};
+use tui::style::Style;
+use tui::text::Span;
+use tui::{
+    layout::{Constraint, Direction, Layout, Margin, Rect},
+    widgets::{Block, BorderType, Borders, Clear, Paragraph},
+};
+
+#[derive(PartialEq, Eq)]
+pub enum Mode {
+    Playlist,
+    Song,
+    Popup,
+}
+
+pub struct Playlist {
+    pub mode: Mode,
+    pub playlists: Index<String>,
+    pub songs: Index<PlaylistSong>,
+    pub song_buffer: Vec<Song>,
+    pub search: String,
+    pub search_result: String,
+    pub changed: bool,
+}
+
+impl Playlist {
+    pub fn new() -> Self {
+        let playlists = playlist::playlists();
+
+        let songs = if let Some(playlist) = playlists.first() {
+            Index::new(playlist::get(playlist), Some(0))
+        } else {
+            Index::new(Vec::new(), Some(0))
+        };
+
+        Self {
+            mode: Mode::Playlist,
+            playlists: Index::new(playlists, Some(0)),
+            songs,
+            song_buffer: Vec::new(),
+            changed: false,
+            search: String::new(),
+            search_result: String::from("Enter a playlist name..."),
+        }
+    }
+}
+
+impl Input for Playlist {
+    fn up(&mut self) {
+        match self.mode {
+            Mode::Playlist => {
+                self.playlists.up();
+                let songs = playlist::get(&self.playlists.selected().unwrap());
+                self.songs = Index::new(songs, Some(0));
+            }
+            Mode::Song => self.songs.up(),
+            Mode::Popup => (),
+        }
+    }
+
+    fn down(&mut self) {
+        match self.mode {
+            Mode::Playlist => {
+                self.playlists.down();
+                let songs = playlist::get(&self.playlists.selected().unwrap());
+                self.songs = Index::new(songs, Some(0));
+            }
+            Mode::Song => self.songs.down(),
+            Mode::Popup => (),
+        }
+    }
+
+    fn left(&mut self) {
+        if self.mode == Mode::Song {
+            self.mode = Mode::Playlist;
+        }
+    }
+
+    fn right(&mut self) {
+        match self.mode {
+            Mode::Playlist if !self.songs.is_empty() => {
+                self.mode = Mode::Song;
+            }
+            _ => (),
+        }
+    }
+}
+
+pub fn on_enter(playlist: &mut Playlist, player: &mut Player) {
+    match playlist.mode {
+        Mode::Playlist => {
+            let ids: Vec<usize> = playlist.songs.data.iter().map(|song| song.id).collect();
+            let songs = query::songs_from_ids(&ids);
+            player.add_songs(&songs);
+        }
+        Mode::Song => {
+            if let Some(item) = playlist.songs.selected() {
+                let song = query::songs_from_ids(&[item.id]).remove(0);
+                player.add_songs(&[song]);
+            }
+        }
+        Mode::Popup if !playlist.song_buffer.is_empty() => {
+            //Select an existing playlist or create a new one.
+            let name = playlist.search.trim().to_string();
+
+            let ids: Vec<usize> = playlist
+                .song_buffer
+                .iter()
+                .map(|song| song.id.unwrap())
+                .collect();
+
+            playlist::add(&name, &ids);
+
+            playlist.playlists = Index::new(playlist::playlists(), playlist.playlists.index());
+
+            let mut i = Some(0);
+            for (j, playlist) in playlist.playlists.data.iter().enumerate() {
+                if playlist == &name {
+                    i = Some(j);
+                    break;
+                }
+            }
+
+            //Select the playlist that was just modified and update the songs.
+            playlist.playlists.select(i);
+            let songs = playlist::get(playlist.playlists.selected().unwrap());
+            playlist.songs = Index::new(songs, Some(0));
+
+            //Reset everything.
+            playlist.search = String::new();
+            playlist.mode = Mode::Playlist;
+        }
+        Mode::Popup => (),
+    }
+}
+
+pub fn on_backspace(playlist: &mut Playlist, control: bool) {
+    match playlist.mode {
+        Mode::Popup => {
+            playlist.changed = true;
+            if control {
+                playlist.search.clear();
+            } else {
+                playlist.search.pop();
+            }
+        }
+        _ => playlist.left(),
+    }
+}
+
+pub fn add_to_playlist(playlist: &mut Playlist, songs: &[Song]) {
+    playlist.song_buffer = songs.to_vec();
+    playlist.mode = Mode::Popup;
+}
+
+pub fn delete(playlist: &mut Playlist) {
+    match playlist.mode {
+        Mode::Playlist => {
+            if let Some(index) = playlist.playlists.index() {
+                //TODO: Prompt the user with yes or no.
+                playlist::remove(&playlist.playlists.data[index]);
+                playlist.playlists.remove(index);
+
+                if playlist.playlists.is_empty() {
+                    //No more playlists mean no more songs.
+                    playlist.songs = Index::default();
+                } else {
+                    //After removing a playlist the next songs will need to be loaded.
+                    let songs = playlist::get(playlist.playlists.selected().unwrap());
+                    playlist.songs = Index::new(songs, Some(0));
+                }
+            }
+        }
+        Mode::Song => {
+            if let Some(song) = playlist.songs.selected() {
+                playlist::remove_id(song.id);
+                let index = playlist.songs.index().unwrap();
+                playlist.songs.remove(index);
+
+                //If there are no songs left delete the playlist.
+                if playlist.songs.is_empty() {
+                    let index = playlist.playlists.index().unwrap();
+                    playlist.playlists.remove(index);
+                }
+            }
+        }
+        Mode::Popup => (),
+    }
+}
+
+pub fn on_escape(playlist: &mut Playlist, mode: &mut super::Mode) {
+    match playlist.mode {
+        Mode::Popup => {
+            playlist.mode = Mode::Playlist;
+            playlist.search = String::new();
+            playlist.changed = true;
+        }
+        _ => *mode = super::Mode::Browser,
+    };
+}
+
+//TODO: I think I want a different popup.
+//It should be a small side bar in the browser.
+//There should be a list of existing playlists.
+//The first playlist will be the one you just added to
+//so it's fast to keep adding things
+//The last item will be add a new playlist.
+//If there are no playlists it will prompt you to create on.
+//This should be similar to foobar on android.
+
+//TODO: Renaming
+//Move items around in lists
+//There should be a hotkey to add to most recent playlist
+//And a message should show up in the bottom bar saying
+//"[name] has been has been added to [playlist name]"
+//or
+//"25 songs have been added to [playlist name]"
+
+//TODO: Prompt the user with yes or no on deletes.
+//TODO: Clear playlist with confirmation.
+pub fn draw_popup(playlist: &mut Playlist, f: &mut Frame) {
+    if let Some(area) = centered_rect(45, 6, f.size()) {
+        let v = Layout::default()
+            .direction(Direction::Vertical)
+            .constraints([Constraint::Length(3), Constraint::Percentage(50)])
+            .margin(1)
+            .split(area);
+
+        f.render_widget(Clear, area);
+        f.render_widget(
+            Block::default()
+                .title("─Add to playlist")
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded),
+            area,
+        );
+
+        //Scroll the playlist name.
+        let len = playlist.search.len() as u16;
+        let width = v[0].width.saturating_sub(1);
+        let offset_x = if len < width { 0 } else { len - width + 1 };
+
+        f.render_widget(
+            Paragraph::new(playlist.search.as_str())
+                .block(
+                    Block::default()
+                        .borders(Borders::ALL)
+                        .border_type(BorderType::Rounded),
+                )
+                .scroll((0, offset_x)),
+            v[0],
+        );
+
+        if playlist.changed {
+            playlist.changed = false;
+            let eq = playlist
+                .playlists
+                .data
+                .iter()
+                .any(|e| e == &playlist.search);
+            playlist.search_result = if eq {
+                format!("Add to existing playlist: {}", playlist.search)
+            } else if playlist.search.is_empty() {
+                String::from("Enter a playlist name...")
+            } else {
+                format!("Add to new playlist: {}", playlist.search)
+            }
+        }
+
+        f.render_widget(
+            Paragraph::new(playlist.search_result.as_str()),
+            v[1].inner(&Margin {
+                horizontal: 1,
+                vertical: 0,
+            }),
+        );
+
+        //Draw the cursor.
+        let (x, y) = (v[0].x + 1, v[0].y + 1);
+        if playlist.search.is_empty() {
+            f.set_cursor(x, y);
+        } else {
+            let width = v[0].width.saturating_sub(3);
+            if len < width {
+                f.set_cursor(x + len, y)
+            } else {
+                f.set_cursor(x + width, y)
+            }
+        }
+    }
+}
+
+pub fn draw(playlist: &mut Playlist, area: Rect, f: &mut Frame) {
+    let horizontal = Layout::default()
+        .direction(Direction::Horizontal)
+        .constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
+        .split(area);
+
+    let items: Vec<ListItem> = playlist
+        .playlists
+        .clone()
+        .into_iter()
+        .map(ListItem::new)
+        .collect();
+
+    let list = List::new(&items)
+        .block(
+            Block::default()
+                .title("─Playlist")
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded),
+        )
+        .highlight_symbol(">");
+
+    let list = if let Mode::Playlist = playlist.mode {
+        list.highlight_symbol(">")
+    } else {
+        list.highlight_symbol("")
+    };
+
+    f.render_stateful_widget(
+        list,
+        horizontal[0],
+        &mut ListState::new(playlist.playlists.index()),
+    );
+
+    let content = playlist
+        .songs
+        .data
+        .iter()
+        .map(|song| {
+            Row::new(vec![
+                Span::styled(song.name.as_str(), Style::default().fg(COLORS.name)),
+                Span::styled(song.album.as_str(), Style::default().fg(COLORS.album)),
+                Span::styled(song.artist.as_str(), Style::default().fg(COLORS.artist)),
+            ])
+        })
+        .collect();
+
+    let table = Table::new(content)
+        .widths(&[
+            Constraint::Percentage(42),
+            Constraint::Percentage(30),
+            Constraint::Percentage(28),
+        ])
+        .block(
+            Block::default()
+                .title("─Songs")
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded),
+        );
+
+    let table = if let Mode::Song = playlist.mode {
+        table.highlight_symbol(">")
+    } else {
+        table.highlight_symbol("")
+    };
+
+    f.render_stateful_widget(
+        table,
+        horizontal[1],
+        &mut TableState::new(playlist.songs.index()),
+    );
+
+    if let Mode::Popup = playlist.mode {
+        draw_popup(playlist, f);
+    }
+}
diff --git a/gonk/src/queue.rs b/gonk/src/queue.rs
new file mode 100644
index 00000000..7b071147
--- /dev/null
+++ b/gonk/src/queue.rs
@@ -0,0 +1,360 @@
+use crate::widgets::*;
+use crate::*;
+use crossterm::event::MouseEvent;
+use gonk_player::{Index, Player};
+use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
+use tui::style::{Color, Modifier, Style};
+use tui::text::{Span, Spans};
+use tui::widgets::{Block, BorderType, Borders, Paragraph};
+use unicode_width::UnicodeWidthStr;
+
+pub struct Queue {
+    pub ui: Index<()>,
+    pub constraint: [u16; 4],
+    pub len: usize,
+}
+
+impl Queue {
+    pub fn new() -> Self {
+        Self {
+            ui: Index::new(Vec::new(), Some(0)),
+            constraint: [8, 42, 24, 26],
+            len: 0,
+        }
+    }
+}
+
+impl Input for Queue {
+    fn up(&mut self) {
+        self.ui.up_with_len(self.len);
+    }
+
+    fn down(&mut self) {
+        self.ui.down_with_len(self.len);
+    }
+
+    fn left(&mut self) {}
+
+    fn right(&mut self) {}
+}
+
+pub fn constraint(queue: &mut Queue, row: usize, shift: bool) {
+    if shift && queue.constraint[row] != 0 {
+        //Move row back.
+        queue.constraint[row + 1] += 1;
+        queue.constraint[row] = queue.constraint[row].saturating_sub(1);
+    } else if queue.constraint[row + 1] != 0 {
+        //Move row forward.
+        queue.constraint[row] += 1;
+        queue.constraint[row + 1] = queue.constraint[row + 1].saturating_sub(1);
+    }
+
+    debug_assert!(
+        queue.constraint.iter().sum::<u16>() == 100,
+        "Constraint went out of bounds: {:?}",
+        queue.constraint
+    );
+}
+
+pub fn delete(queue: &mut Queue, player: &mut Player) {
+    if let Some(i) = queue.ui.index() {
+        player.delete_index(i);
+        //make sure the ui index is in sync
+        let len = player.songs.len().saturating_sub(1);
+        if i > len {
+            queue.ui.select(Some(len));
+        }
+    }
+}
+
+pub fn draw(queue: &mut Queue, player: &mut Player, f: &mut Frame, event: Option<MouseEvent>) {
+    let chunks = Layout::default()
+        .direction(Direction::Vertical)
+        .constraints([
+            Constraint::Length(3),
+            Constraint::Min(10),
+            Constraint::Length(3),
+        ])
+        .split(f.size());
+
+    draw_header(player, f, chunks[0]);
+
+    let row_bounds = draw_body(queue, player, f, chunks[1]);
+
+    draw_seeker(player, f, chunks[2]);
+
+    //Don't handle mouse input when the queue is empty.
+    if player.is_empty() {
+        return;
+    }
+
+    //Handle mouse input.
+    if let Some(event) = event {
+        let (x, y) = (event.column, event.row);
+        const HEADER_HEIGHT: u16 = 5;
+
+        let size = f.size();
+
+        //Mouse support for the seek bar.
+        if (size.height - 3 == y || size.height - 2 == y || size.height - 1 == y)
+            && size.height > 15
+        {
+            let ratio = x as f32 / size.width as f32;
+            let duration = player.duration.as_secs_f32();
+            player.seek_to(duration * ratio);
+        }
+
+        //Mouse support for the queue.
+        if let Some((start, _)) = row_bounds {
+            //Check if you clicked on the header.
+            if y >= HEADER_HEIGHT {
+                let index = (y - HEADER_HEIGHT) as usize + start;
+
+                //Make sure you didn't click on the seek bar
+                //and that the song index exists.
+                if index < player.songs.len()
+                    && ((size.height < 15 && y < size.height.saturating_sub(1))
+                        || y < size.height.saturating_sub(3))
+                {
+                    queue.ui.select(Some(index));
+                }
+            }
+        }
+    }
+}
+
+fn draw_header(player: &mut Player, f: &mut Frame, area: Rect) {
+    f.render_widget(
+        Block::default()
+            .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
+            .border_type(BorderType::Rounded),
+        area,
+    );
+
+    let state = if player.songs.is_empty() {
+        String::from("╭─Stopped")
+    } else if player.is_playing() {
+        String::from("╭─Playing")
+    } else {
+        String::from("╭─Paused")
+    };
+
+    f.render_widget(Paragraph::new(state).alignment(Alignment::Left), area);
+
+    if !player.songs.is_empty() {
+        draw_title(player, f, area);
+    }
+
+    let volume = Spans::from(format!("Vol: {}%─╮", player.volume));
+    f.render_widget(Paragraph::new(volume).alignment(Alignment::Right), area);
+}
+
+fn draw_title(player: &mut Player, f: &mut Frame, area: Rect) {
+    let title = if let Some(song) = player.songs.selected() {
+        let mut name = song.name.trim_end().to_string();
+        let mut album = song.album.trim_end().to_string();
+        let mut artist = song.artist.trim_end().to_string();
+        let max_width = area.width.saturating_sub(30) as usize;
+
+        while artist.width() + name.width() + "-| - |-".width() > max_width {
+            if artist.width() > name.width() {
+                artist.pop();
+            } else {
+                name.pop();
+            }
+        }
+
+        while album.width() > max_width {
+            album.pop();
+        }
+
+        let n = album
+            .width()
+            .saturating_sub(artist.width() + name.width() + 3);
+        let rem = n % 2;
+        let pad_front = " ".repeat(n / 2);
+        let pad_back = " ".repeat(n / 2 + rem);
+
+        vec![
+            Spans::from(vec![
+                Span::raw(format!("─│ {}", pad_front)),
+                Span::styled(artist, Style::default().fg(COLORS.artist)),
+                Span::raw(" ─ "),
+                Span::styled(name, Style::default().fg(COLORS.name)),
+                Span::raw(format!("{} │─", pad_back)),
+            ]),
+            Spans::from(Span::styled(album, Style::default().fg(COLORS.album))),
+        ]
+    } else {
+        Vec::new()
+    };
+
+    f.render_widget(Paragraph::new(title).alignment(Alignment::Center), area);
+}
+
+fn draw_body(
+    queue: &mut Queue,
+    player: &mut Player,
+    f: &mut Frame,
+    area: Rect,
+) -> Option<(usize, usize)> {
+    if player.songs.is_empty() {
+        f.render_widget(
+            Block::default()
+                .border_type(BorderType::Rounded)
+                .borders(Borders::LEFT | Borders::RIGHT),
+            area,
+        );
+        return None;
+    }
+
+    let (songs, player_index, ui_index) =
+        (&player.songs.data, player.songs.index(), queue.ui.index());
+
+    let mut items: Vec<Row> = songs
+        .iter()
+        .map(|song| {
+            Row::new(vec![
+                Cell::from(""),
+                Cell::from(song.number.to_string()).style(Style::default().fg(COLORS.number)),
+                Cell::from(song.name.as_str()).style(Style::default().fg(COLORS.name)),
+                Cell::from(song.album.as_str()).style(Style::default().fg(COLORS.album)),
+                Cell::from(song.artist.as_str()).style(Style::default().fg(COLORS.artist)),
+            ])
+        })
+        .collect();
+
+    if let Some(player_index) = player_index {
+        if let Some(song) = songs.get(player_index) {
+            if let Some(ui_index) = ui_index {
+                //Currently playing song
+                let row = if ui_index == player_index {
+                    Row::new(vec![
+                        Cell::from(">>").style(
+                            Style::default()
+                                .fg(Color::White)
+                                .add_modifier(Modifier::DIM | Modifier::BOLD),
+                        ),
+                        Cell::from(song.number.to_string())
+                            .style(Style::default().bg(COLORS.number).fg(Color::Black)),
+                        Cell::from(song.name.as_str())
+                            .style(Style::default().bg(COLORS.name).fg(Color::Black)),
+                        Cell::from(song.album.as_str())
+                            .style(Style::default().bg(COLORS.album).fg(Color::Black)),
+                        Cell::from(song.artist.as_str())
+                            .style(Style::default().bg(COLORS.artist).fg(Color::Black)),
+                    ])
+                } else {
+                    Row::new(vec![
+                        Cell::from(">>").style(
+                            Style::default()
+                                .fg(Color::White)
+                                .add_modifier(Modifier::DIM | Modifier::BOLD),
+                        ),
+                        Cell::from(song.number.to_string())
+                            .style(Style::default().fg(COLORS.number)),
+                        Cell::from(song.name.as_str()).style(Style::default().fg(COLORS.name)),
+                        Cell::from(song.album.as_str()).style(Style::default().fg(COLORS.album)),
+                        Cell::from(song.artist.as_str()).style(Style::default().fg(COLORS.artist)),
+                    ])
+                };
+
+                items.remove(player_index);
+                items.insert(player_index, row);
+
+                //Current selection
+                if ui_index != player_index {
+                    if let Some(song) = songs.get(ui_index) {
+                        let row = Row::new(vec![
+                            Cell::default(),
+                            Cell::from(song.number.to_string())
+                                .style(Style::default().bg(COLORS.number)),
+                            Cell::from(song.name.as_str()).style(Style::default().bg(COLORS.name)),
+                            Cell::from(song.album.as_str())
+                                .style(Style::default().bg(COLORS.album)),
+                            Cell::from(song.artist.as_str())
+                                .style(Style::default().bg(COLORS.artist)),
+                        ])
+                        .style(Style::default().fg(Color::Black));
+                        items.remove(ui_index);
+                        items.insert(ui_index, row);
+                    }
+                }
+            }
+        }
+    }
+
+    let con = [
+        Constraint::Length(2),
+        Constraint::Percentage(queue.constraint[0]),
+        Constraint::Percentage(queue.constraint[1]),
+        Constraint::Percentage(queue.constraint[2]),
+        Constraint::Percentage(queue.constraint[3]),
+    ];
+
+    let t = Table::new(items)
+        .header(
+            Row::new(["", "Track", "Title", "Album", "Artist"])
+                .style(
+                    Style::default()
+                        .fg(Color::White)
+                        .add_modifier(Modifier::BOLD),
+                )
+                .bottom_margin(1),
+        )
+        .block(
+            Block::default()
+                .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
+                .border_type(BorderType::Rounded),
+        )
+        // .separator()
+        .widths(&con);
+
+    let row_bounds = t.get_row_bounds(ui_index, t.get_row_height(area));
+
+    f.render_stateful_widget(t, area, &mut TableState::new(ui_index));
+
+    Some(row_bounds)
+}
+
+fn draw_seeker(player: &mut Player, f: &mut Frame, area: Rect) {
+    if player.songs.is_empty() {
+        return f.render_widget(
+            Block::default()
+                .border_type(BorderType::Rounded)
+                .borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT),
+            area,
+        );
+    }
+
+    let elapsed = player.elapsed().as_secs_f64();
+    let duration = player.duration.as_secs_f64();
+
+    let seeker = format!(
+        "{:02}:{:02}/{:02}:{:02}",
+        (elapsed / 60.0).floor(),
+        elapsed.trunc() as u32 % 60,
+        (duration / 60.0).floor(),
+        duration.trunc() as u32 % 60,
+    );
+
+    let ratio = elapsed / duration;
+    let ratio = if ratio.is_nan() {
+        0.0
+    } else {
+        ratio.clamp(0.0, 1.0)
+    };
+
+    f.render_widget(
+        Gauge::default()
+            .block(
+                Block::default()
+                    .borders(Borders::ALL)
+                    .border_type(BorderType::Rounded),
+            )
+            .gauge_style(Style::default().fg(COLORS.seeker))
+            .ratio(ratio)
+            .label(seeker),
+        area,
+    );
+}
diff --git a/gonk/src/search.rs b/gonk/src/search.rs
new file mode 100644
index 00000000..9556053a
--- /dev/null
+++ b/gonk/src/search.rs
@@ -0,0 +1,485 @@
+use super::Mode as AppMode;
+use crate::widgets::*;
+use crate::*;
+use gonk_database::query;
+use gonk_player::{Index, Player};
+use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
+use std::cmp::Ordering;
+use tui::{
+    layout::{Alignment, Constraint, Direction, Layout, Rect},
+    style::{Modifier, Style},
+    text::{Span, Spans},
+    widgets::{Block, BorderType, Borders, Paragraph},
+};
+
+#[derive(Clone)]
+pub enum Item {
+    Song(Song),
+    Album(Album),
+    Artist(Artist),
+}
+
+#[derive(Clone, Default)]
+pub struct Song {
+    pub id: usize,
+    pub name: String,
+    pub album: String,
+    pub artist: String,
+}
+
+#[derive(Clone, Default)]
+pub struct Album {
+    pub name: String,
+    pub artist: String,
+}
+
+#[derive(Clone, Default)]
+pub struct Artist {
+    pub name: String,
+}
+
+#[derive(PartialEq, Eq)]
+pub enum Mode {
+    Search,
+    Select,
+}
+
+pub struct Search {
+    pub query: String,
+    pub query_changed: bool,
+    pub mode: Mode,
+    pub results: Index<Item>,
+    pub cache: Vec<Item>,
+}
+
+impl Search {
+    pub fn new() -> Self {
+        let mut search = Self {
+            cache: Vec::new(),
+            query: String::new(),
+            query_changed: false,
+            mode: Mode::Search,
+            results: Index::default(),
+        };
+        refresh_cache(&mut search);
+        refresh_results(&mut search);
+        search
+    }
+}
+
+impl Input for Search {
+    fn up(&mut self) {
+        self.results.up();
+    }
+
+    fn down(&mut self) {
+        self.results.down();
+    }
+
+    fn left(&mut self) {}
+
+    fn right(&mut self) {}
+}
+
+pub fn on_backspace(search: &mut Search, shift: bool) {
+    match search.mode {
+        Mode::Search => {
+            if shift {
+                search.query.clear();
+            } else {
+                search.query.pop();
+            }
+        }
+        Mode::Select => {
+            search.results.select(None);
+            search.mode = Mode::Search;
+        }
+    }
+}
+
+pub fn on_escape(search: &mut Search, mode: &mut AppMode) {
+    match search.mode {
+        Mode::Search => {
+            if let Mode::Search = search.mode {
+                search.query.clear();
+                *mode = AppMode::Queue;
+            }
+        }
+        Mode::Select => {
+            search.mode = Mode::Search;
+            search.results.select(None);
+        }
+    }
+}
+
+pub fn on_enter(search: &mut Search, player: &mut Player) {
+    match search.mode {
+        Mode::Search => {
+            if !search.results.is_empty() {
+                search.mode = Mode::Select;
+                search.results.select(Some(0));
+            }
+        }
+        Mode::Select => {
+            if let Some(item) = search.results.selected() {
+                let songs = match item {
+                    Item::Song(song) => query::songs_from_ids(&[song.id]),
+                    Item::Album(album) => query::songs_from_album(&album.name, &album.artist),
+                    Item::Artist(artist) => query::songs_by_artist(&artist.name),
+                };
+
+                player.add_songs(&songs);
+            }
+        }
+    }
+}
+
+pub fn refresh_cache(search: &mut Search) {
+    search.cache = Vec::new();
+
+    for song in query::songs() {
+        search.cache.push(Item::Song(Song {
+            name: song.name,
+            album: song.album,
+            artist: song.artist,
+            id: song.id.unwrap(),
+        }));
+    }
+
+    for (name, artist) in query::albums() {
+        search.cache.push(Item::Album(Album { name, artist }));
+    }
+
+    for name in query::artists() {
+        search.cache.push(Item::Artist(Artist { name }));
+    }
+}
+
+pub fn refresh_results(search: &mut Search) {
+    let query = &search.query.to_lowercase();
+
+    let get_accuary = |item: &Item| -> f64 {
+        match item {
+            Item::Song(song) => strsim::jaro_winkler(query, &song.name.to_lowercase()),
+            Item::Album(album) => strsim::jaro_winkler(query, &album.name.to_lowercase()),
+            Item::Artist(artist) => strsim::jaro_winkler(query, &artist.name.to_lowercase()),
+        }
+    };
+
+    let mut results: Vec<_> = if query.is_empty() {
+        //If there user has not asked to search anything
+        //populate the list with 40 results.
+        search
+            .cache
+            .iter()
+            .take(40)
+            .rev()
+            .map(|item| (item, get_accuary(item)))
+            .collect()
+    } else {
+        search
+            .cache
+            .par_iter()
+            .filter_map(|item| {
+                let acc = get_accuary(item);
+
+                //Filter out results that are poor matches. 0.75 is a magic number.
+                if acc > 0.75 {
+                    Some((item, acc))
+                } else {
+                    None
+                }
+            })
+            .collect()
+    };
+
+    //Sort results by score.
+    results.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap());
+
+    //Sort artists above search-titled albums.
+    results.sort_by(|(item, a), (_, b)| {
+        //This is just x == y but for floats.
+        if (a - b).abs() < f64::EPSILON {
+            //And the item is an album
+            if let Item::Album(_) = item {
+                //Move item lower in the list.
+                Ordering::Greater
+            } else {
+                //Move item higher in the list.
+                Ordering::Less
+            }
+        } else {
+            //Keep the same order.
+            Ordering::Equal
+        }
+    });
+
+    search.results.data = results.into_iter().map(|(item, _)| item.clone()).collect();
+}
+
+pub fn draw(search: &mut Search, area: Rect, f: &mut Frame) {
+    if search.query_changed {
+        refresh_results(search);
+    }
+
+    let v = Layout::default()
+        .direction(Direction::Vertical)
+        .constraints([
+            Constraint::Length(3),
+            Constraint::Percentage(30),
+            Constraint::Percentage(60),
+        ])
+        .split(area);
+
+    let h = Layout::default()
+        .direction(Direction::Horizontal)
+        .constraints([Constraint::Percentage(35), Constraint::Percentage(65)])
+        .split(v[1]);
+
+    draw_textbox(search, f, v[0]);
+
+    let item = if search.results.selected().is_some() {
+        search.results.selected()
+    } else {
+        search.results.data.first()
+    };
+
+    if let Some(item) = item {
+        match item {
+            Item::Song(song) => {
+                search::song(f, &song.name, &song.album, &song.artist, h[0]);
+                album(f, &song.album, &song.artist, h[1]);
+            }
+            Item::Album(album) => {
+                search::album(f, &album.name, &album.artist, h[0]);
+                artist(f, &album.artist, h[1]);
+            }
+            Item::Artist(artist) => {
+                let albums = query::albums_by_artist(&artist.name);
+
+                search::artist(f, &artist.name, h[0]);
+
+                let h_split = Layout::default()
+                    .direction(Direction::Horizontal)
+                    .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
+                    .split(h[1]);
+
+                //draw the first two albums
+                for (i, area) in h_split.iter().enumerate() {
+                    if let Some(album) = albums.get(i) {
+                        search::album(f, album, &artist.name, *area);
+                    }
+                }
+            }
+        }
+        draw_results(search, f, v[2]);
+    } else {
+        draw_results(search, f, v[1].union(v[2]));
+    }
+
+    //Move the cursor position when typing
+    if let Mode::Search = search.mode {
+        if search.results.index().is_none() && search.query.is_empty() {
+            f.set_cursor(1, 1);
+        } else {
+            let len = search.query.len() as u16;
+            let max_width = area.width.saturating_sub(2);
+            if len >= max_width {
+                f.set_cursor(max_width, 1);
+            } else {
+                f.set_cursor(len + 1, 1);
+            }
+        }
+    }
+}
+
+fn song(f: &mut Frame, name: &str, album: &str, artist: &str, area: Rect) {
+    let song_table = Table::new(vec![
+        Row::new(vec![Spans::from(Span::raw(album))]),
+        Row::new(vec![Spans::from(Span::raw(artist))]),
+    ])
+    .header(
+        Row::new(vec![Span::styled(
+            format!("{} ", name),
+            Style::default().add_modifier(Modifier::ITALIC),
+        )])
+        .bottom_margin(1),
+    )
+    .block(
+        Block::default()
+            .borders(Borders::ALL)
+            .border_type(BorderType::Rounded)
+            .title("Song"),
+    )
+    .widths(&[Constraint::Percentage(100)]);
+
+    f.render_widget(song_table, area);
+}
+
+fn album(f: &mut Frame, album: &str, artist: &str, area: Rect) {
+    let cells: Vec<_> = query::songs_from_album(album, artist)
+        .iter()
+        .map(|song| Row::new(vec![Cell::from(format!("{}. {}", song.number, song.name))]))
+        .collect();
+
+    let table = Table::new(cells)
+        .header(
+            Row::new(vec![Cell::from(Span::styled(
+                format!("{} ", album),
+                Style::default().add_modifier(Modifier::ITALIC),
+            ))])
+            .bottom_margin(1),
+        )
+        .block(
+            Block::default()
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded)
+                .title("Album"),
+        )
+        .widths(&[Constraint::Percentage(100)]);
+
+    f.render_widget(table, area);
+}
+
+fn artist(f: &mut Frame, artist: &str, area: Rect) {
+    let albums = query::albums_by_artist(artist);
+    let cells: Vec<_> = albums
+        .iter()
+        .map(|album| Row::new(vec![Cell::from(Span::raw(album))]))
+        .collect();
+
+    let table = Table::new(cells)
+        .header(
+            Row::new(vec![Cell::from(Span::styled(
+                format!("{} ", artist),
+                Style::default().add_modifier(Modifier::ITALIC),
+            ))])
+            .bottom_margin(1),
+        )
+        .block(
+            Block::default()
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded)
+                .title("Artist"),
+        )
+        .widths(&[Constraint::Percentage(100)]);
+
+    f.render_widget(table, area);
+}
+
+fn draw_results(search: &Search, f: &mut Frame, area: Rect) {
+    let get_cell = |item: &Item, selected: bool| -> Row {
+        let selected_cell = if selected {
+            Cell::from(">")
+        } else {
+            Cell::default()
+        };
+
+        match item {
+            Item::Song(song) => {
+                let song = query::songs_from_ids(&[song.id])[0].clone();
+                Row::new(vec![
+                    selected_cell,
+                    Cell::from(song.name).style(Style::default().fg(COLORS.name)),
+                    Cell::from(song.album).style(Style::default().fg(COLORS.album)),
+                    Cell::from(song.artist).style(Style::default().fg(COLORS.artist)),
+                ])
+            }
+            Item::Album(album) => Row::new(vec![
+                selected_cell,
+                Cell::from(Spans::from(vec![
+                    Span::styled(
+                        format!("{} - ", album.name),
+                        Style::default().fg(COLORS.name),
+                    ),
+                    Span::styled(
+                        "Album",
+                        Style::default()
+                            .fg(COLORS.name)
+                            .add_modifier(Modifier::ITALIC),
+                    ),
+                ])),
+                Cell::from("").style(Style::default().fg(COLORS.album)),
+                Cell::from(album.artist.clone()).style(Style::default().fg(COLORS.artist)),
+            ]),
+            Item::Artist(artist) => Row::new(vec![
+                selected_cell,
+                Cell::from(Spans::from(vec![
+                    Span::styled(
+                        format!("{} - ", artist.name),
+                        Style::default().fg(COLORS.name),
+                    ),
+                    Span::styled(
+                        "Artist",
+                        Style::default()
+                            .fg(COLORS.name)
+                            .add_modifier(Modifier::ITALIC),
+                    ),
+                ])),
+                Cell::from("").style(Style::default().fg(COLORS.album)),
+                Cell::from("").style(Style::default().fg(COLORS.artist)),
+            ]),
+        }
+    };
+
+    let rows: Vec<_> = search
+        .results
+        .data
+        .iter()
+        .enumerate()
+        .map(|(i, item)| {
+            if let Some(s) = search.results.index() {
+                if s == i {
+                    return get_cell(item, true);
+                }
+            } else if i == 0 {
+                return get_cell(item, false);
+            }
+            get_cell(item, false)
+        })
+        .collect();
+
+    let italic = Style::default().add_modifier(Modifier::ITALIC);
+    let table = Table::new(rows)
+        .header(
+            Row::new(vec![
+                Cell::default(),
+                Cell::from("Name").style(italic),
+                Cell::from("Album").style(italic),
+                Cell::from("Artist").style(italic),
+            ])
+            .bottom_margin(1),
+        )
+        .block(
+            Block::default()
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded),
+        )
+        .widths(&[
+            Constraint::Length(1),
+            Constraint::Percentage(40),
+            Constraint::Percentage(40),
+            Constraint::Percentage(20),
+        ]);
+
+    f.render_stateful_widget(table, area, &mut TableState::new(search.results.index()));
+}
+
+fn draw_textbox(search: &Search, f: &mut Frame, area: Rect) {
+    let len = search.query.len() as u16;
+    //Search box is a little smaller than the max width
+    let width = area.width.saturating_sub(1);
+    let offset_x = if len < width { 0 } else { len - width + 1 };
+
+    f.render_widget(
+        Paragraph::new(search.query.as_str())
+            .block(
+                Block::default()
+                    .borders(Borders::ALL)
+                    .border_type(BorderType::Rounded),
+            )
+            .alignment(Alignment::Left)
+            .scroll((0, offset_x)),
+        area,
+    );
+}
diff --git a/gonk/src/settings.rs b/gonk/src/settings.rs
new file mode 100644
index 00000000..2b943856
--- /dev/null
+++ b/gonk/src/settings.rs
@@ -0,0 +1,98 @@
+use crate::{widgets::*, Frame, Input};
+use gonk_database::query;
+use gonk_player::{Device, DeviceTrait, Index, Player};
+use tui::{
+    layout::Rect,
+    style::{Color, Modifier, Style},
+    widgets::{Block, BorderType, Borders},
+};
+
+pub struct Settings {
+    pub devices: Index<Device>,
+    pub current_device: String,
+}
+
+impl Settings {
+    pub fn new() -> Self {
+        let default_device = Player::default_device();
+        let wanted_device = query::playback_device();
+
+        let devices = Player::audio_devices();
+        let device_names: Vec<String> = devices.iter().flat_map(DeviceTrait::name).collect();
+
+        let current_device = if !device_names.contains(&wanted_device) {
+            let name = default_device.name().unwrap();
+            query::set_playback_device(&name);
+            name
+        } else {
+            wanted_device
+        };
+
+        Self {
+            devices: Index::new(devices, Some(0)),
+            current_device,
+        }
+    }
+}
+
+impl Input for Settings {
+    fn up(&mut self) {
+        self.devices.up();
+    }
+
+    fn down(&mut self) {
+        self.devices.down()
+    }
+
+    fn left(&mut self) {}
+
+    fn right(&mut self) {}
+}
+
+pub fn on_enter(settings: &mut Settings, player: &mut Player) {
+    if let Some(device) = settings.devices.selected() {
+        match player.change_output_device(device) {
+            Ok(_) => {
+                let name = device.name().unwrap();
+                dbg!(&name);
+                query::set_playback_device(&name);
+                settings.current_device = name;
+            }
+            //TODO: Print error in status bar
+            Err(e) => panic!("{:?}", e),
+        }
+    }
+}
+
+#[allow(unused)]
+pub fn draw(settings: &mut Settings, area: Rect, f: &mut Frame) {
+    let items: Vec<ListItem> = settings
+        .devices
+        .data
+        .iter()
+        .map(|device| {
+            let name = device.name().unwrap();
+            if name == settings.current_device {
+                ListItem::new(name)
+            } else {
+                ListItem::new(name).style(Style::default().add_modifier(Modifier::DIM))
+            }
+        })
+        .collect();
+
+    let list = List::new(&items)
+        .block(
+            Block::default()
+                .title("─Output Device")
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded),
+        )
+        .style(Style::default().fg(Color::White))
+        .highlight_style(Style::default())
+        .highlight_symbol("> ");
+
+    let mut state = ListState::default();
+    state.select(settings.devices.index());
+
+    f.render_stateful_widget(list, area, &mut state);
+}
diff --git a/gonk/src/sqlite.rs b/gonk/src/sqlite.rs
deleted file mode 100644
index 4474661d..00000000
--- a/gonk/src/sqlite.rs
+++ /dev/null
@@ -1,307 +0,0 @@
-use crate::GONK_DIR;
-use gonk_player::Song;
-use jwalk::WalkDir;
-use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
-use rusqlite::{params, Connection, Params, Row};
-use static_init::dynamic;
-use std::{
-    path::PathBuf,
-    sync::{Mutex, MutexGuard},
-    thread::{self, JoinHandle},
-    time::Duration,
-};
-
-#[dynamic]
-static DB_DIR: PathBuf = GONK_DIR.join("gonk.db");
-
-pub fn total_songs() -> usize {
-    let conn = conn();
-    let mut stmt = conn.prepare("SELECT COUNT(*) FROM song").unwrap();
-    stmt.query_row([], |row| row.get(0)).unwrap()
-}
-pub fn get_all_songs() -> Vec<Song> {
-    collect_songs("SELECT *, rowid FROM song", params![])
-}
-pub fn get_all_artists() -> Vec<String> {
-    let conn = conn();
-    let mut stmt = conn
-        .prepare("SELECT DISTINCT artist FROM song ORDER BY artist COLLATE NOCASE")
-        .unwrap();
-
-    stmt.query_map([], |row| {
-        let artist: String = row.get(0).unwrap();
-        Ok(artist)
-    })
-    .unwrap()
-    .flatten()
-    .collect()
-}
-pub fn get_all_albums() -> Vec<(String, String)> {
-    let conn = conn();
-    let mut stmt = conn
-        .prepare("SELECT DISTINCT album, artist FROM song ORDER BY artist COLLATE NOCASE")
-        .unwrap();
-
-    stmt.query_map([], |row| {
-        let album: String = row.get(0).unwrap();
-        let artist: String = row.get(1).unwrap();
-        Ok((album, artist))
-    })
-    .unwrap()
-    .flatten()
-    .collect()
-}
-pub fn get_all_albums_by_artist(artist: &str) -> Vec<String> {
-    let conn = conn();
-    let mut stmt = conn
-        .prepare("SELECT DISTINCT album FROM song WHERE artist = ? ORDER BY album COLLATE NOCASE")
-        .unwrap();
-
-    stmt.query_map([artist], |row| row.get(0))
-        .unwrap()
-        .flatten()
-        .collect()
-}
-pub fn get_all_songs_from_album(album: &str, artist: &str) -> Vec<Song> {
-    collect_songs(
-        "SELECT *, rowid FROM song WHERE artist=(?1) AND album=(?2) ORDER BY disc, number",
-        params![artist, album],
-    )
-}
-pub fn get_songs_by_artist(artist: &str) -> Vec<Song> {
-    collect_songs(
-        "SELECT *, rowid FROM song WHERE artist = ? ORDER BY album, disc, number",
-        params![artist],
-    )
-}
-pub fn get_songs(ids: &[usize]) -> Vec<Song> {
-    let conn = conn();
-    let mut stmt = conn
-        .prepare("SELECT *, rowid FROM song WHERE rowid = ?")
-        .unwrap();
-
-    ids.iter()
-        .map(|id| stmt.query_row([id], |row| Ok(song(row))))
-        .flatten()
-        .collect()
-}
-fn collect_songs<P>(query: &str, params: P) -> Vec<Song>
-where
-    P: Params,
-{
-    let conn = conn();
-    let mut stmt = conn.prepare(query).expect(query);
-
-    stmt.query_map(params, |row| Ok(song(row)))
-        .unwrap()
-        .flatten()
-        .collect()
-}
-fn song(row: &Row) -> Song {
-    let path: String = row.get(5).unwrap();
-    let dur: f64 = row.get(6).unwrap();
-    let _parent: String = row.get(8).unwrap();
-    Song {
-        number: row.get(0).unwrap(),
-        disc: row.get(1).unwrap(),
-        name: row.get(2).unwrap(),
-        album: row.get(3).unwrap(),
-        artist: row.get(4).unwrap(),
-        duration: Duration::from_secs_f64(dur),
-        path: PathBuf::from(path),
-        track_gain: row.get(7).unwrap(),
-        id: row.get(9).unwrap(),
-    }
-}
-
-pub static mut CONN: Option<Mutex<rusqlite::Connection>> = None;
-
-pub fn initialize_database() {
-    let exists = DB_DIR.exists();
-    if let Ok(conn) = Connection::open(DB_DIR.as_path()) {
-        if !exists {
-            conn.execute(
-                "CREATE TABLE song (
-                    number     INTEGER NOT NULL,
-                    disc       INTEGER NOT NULL,
-                    name       TEXT NOT NULL,
-                    album      TEXT NOT NULL,
-                    artist     TEXT NOT NULL,
-                    path       TEXT NOT NULL UNIQUE,
-                    duration   DOUBLE NOT NULL,
-                    track_gain DOUBLE NOT NULL,
-                    parent     TEXT NOT NULL
-                )",
-                [],
-            )
-            .unwrap();
-
-            conn.execute(
-                "CREATE TABLE playlist (
-                    song_id INTEGER NOT NULL,
-                    name TEXT NOT NULL
-                )",
-                [],
-            )
-            .unwrap();
-        }
-
-        unsafe {
-            CONN = Some(Mutex::new(conn));
-        }
-    } else {
-        panic!("Could not open database!")
-    }
-}
-
-pub fn reset() {
-    unsafe {
-        CONN = None;
-    }
-    let _ = std::fs::remove_file(DB_DIR.as_path());
-}
-
-pub fn conn() -> MutexGuard<'static, Connection> {
-    unsafe { CONN.as_ref().unwrap().lock().unwrap() }
-}
-
-pub fn add_playlist(name: &str, ids: &[usize]) {
-    let conn = conn();
-
-    //TODO: batch this
-    for id in ids {
-        conn.execute(
-            "INSERT INTO playlist (song_id, name) VALUES (?1, ?2)",
-            params![id, name],
-        )
-        .unwrap();
-    }
-}
-
-pub mod playlist {
-    use super::conn;
-
-    pub fn get_names() -> Vec<String> {
-        let conn = conn();
-        let mut stmt = conn.prepare("SELECT DISTINCT name FROM playlist").unwrap();
-
-        stmt.query_map([], |row| row.get(0))
-            .unwrap()
-            .flatten()
-            .collect()
-    }
-
-    pub fn get(playlist_name: &str) -> (Vec<usize>, Vec<usize>) {
-        let conn = conn();
-        let mut stmt = conn
-            .prepare("SELECT rowid, song_id FROM playlist WHERE name = ?")
-            .unwrap();
-
-        let ids: Vec<_> = stmt
-            .query_map([playlist_name], |row| {
-                Ok((row.get(0).unwrap(), row.get(1).unwrap()))
-            })
-            .unwrap()
-            .flatten()
-            .collect();
-
-        let row_ids: Vec<_> = ids.iter().map(|id| id.0).collect();
-        let song_ids: Vec<_> = ids.iter().map(|id| id.1).collect();
-        (row_ids, song_ids)
-    }
-
-    pub fn remove_id(id: usize) {
-        conn()
-            .execute("DELETE FROM playlist WHERE rowid = ?", [id])
-            .unwrap();
-    }
-    pub fn remove(name: &str) {
-        conn()
-            .execute("DELETE FROM playlist WHERE name = ?", [name])
-            .unwrap();
-    }
-}
-
-pub enum State {
-    Busy,
-    Idle,
-    NeedsUpdate,
-}
-
-#[derive(Default)]
-pub struct Database {
-    handle: Option<JoinHandle<()>>,
-}
-
-impl Database {
-    pub fn add_paths(&mut self, paths: &[String]) {
-        if let Some(handle) = &self.handle {
-            if !handle.is_finished() {
-                return;
-            }
-        }
-
-        let paths = paths.to_vec();
-
-        self.handle = Some(thread::spawn(move || {
-            let queries: Vec<String> = paths
-                .iter()
-                .map(|path| {
-                    let paths: Vec<PathBuf> = WalkDir::new(path)
-                        .into_iter()
-                        .flatten()
-                        .map(|dir| dir.path())
-                        .filter(|path| match path.extension() {
-                            Some(ex) => {
-                                matches!(ex.to_str(), Some("flac" | "mp3" | "ogg" | "wav" | "m4a"))
-                            }
-                            None => false,
-                        })
-                        .collect();
-
-                    let songs: Vec<Song> = paths
-                        .par_iter()
-                        .map(|dir| Song::from(dir))
-                        .flatten()
-                        .collect();
-
-                    if songs.is_empty() {
-                        String::new()
-                    } else {
-                        songs
-                            .iter()
-                            .map(|song| {
-                                let artist = song.artist.replace('\'', r"''");
-                                let album = song.album.replace('\'', r"''");
-                                let name = song.name.replace('\'', r"''");
-                                let song_path= song.path.to_string_lossy().replace('\'', r"''");
-                                let parent = path.replace('\'', r"''");
-
-                                format!("INSERT OR IGNORE INTO song (number, disc, name, album, artist, path, duration, track_gain, parent) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');",
-                                            song.number, song.disc, name, album, artist, song_path, song.duration.as_secs_f64(), song.track_gain, parent)
-                            })
-                            .collect::<Vec<String>>()
-                            .join("\n")
-                    }
-                })
-                .collect();
-
-            let stmt = format!("BEGIN;\nDELETE FROM song;\n{}COMMIT;\n", queries.join("\n"));
-            conn().execute_batch(&stmt).unwrap();
-        }));
-    }
-    pub fn state(&mut self) -> State {
-        match self.handle {
-            Some(ref handle) => {
-                let finished = handle.is_finished();
-                if finished {
-                    self.handle = None;
-                    State::NeedsUpdate
-                } else {
-                    State::Busy
-                }
-            }
-            None => State::Idle,
-        }
-    }
-}
diff --git a/gonk/src/status_bar.rs b/gonk/src/status_bar.rs
new file mode 100644
index 00000000..dd39291d
--- /dev/null
+++ b/gonk/src/status_bar.rs
@@ -0,0 +1,143 @@
+use crate::{Frame, COLORS};
+use gonk_database::query;
+use gonk_player::Player;
+use std::time::{Duration, Instant};
+use tui::{
+    layout::{Alignment, Constraint, Direction, Layout, Rect},
+    style::Style,
+    text::{Span, Spans},
+    widgets::{Block, BorderType, Borders, Paragraph},
+};
+
+const WAIT_TIME: Duration = Duration::from_secs(2);
+
+pub struct StatusBar {
+    pub dots: usize,
+    pub busy: bool,
+    pub scan_message: String,
+    pub wait_timer: Option<Instant>,
+    pub scan_timer: Option<Instant>,
+    pub hidden: bool,
+}
+
+impl StatusBar {
+    pub fn new() -> Self {
+        Self {
+            dots: 1,
+            busy: false,
+            scan_message: String::new(),
+            wait_timer: None,
+            scan_timer: None,
+            hidden: true,
+        }
+    }
+}
+
+//Updates the dots in "Scanning for files .."
+pub fn update(status_bar: &mut StatusBar, db_busy: bool, player: &Player) {
+    if db_busy {
+        if status_bar.dots < 3 {
+            status_bar.dots += 1;
+        } else {
+            status_bar.dots = 1;
+        }
+    } else {
+        status_bar.dots = 1;
+    }
+
+    if let Some(timer) = status_bar.wait_timer {
+        if timer.elapsed() >= WAIT_TIME {
+            status_bar.wait_timer = None;
+            status_bar.busy = false;
+
+            //FIXME: If the queue was not empty
+            //and the status bar was hidden
+            //before triggering an update
+            //the status bar will stay open
+            //without the users permission.
+            if player.is_empty() {
+                status_bar.hidden = true;
+            }
+        }
+    }
+}
+
+pub fn draw(status_bar: &mut StatusBar, area: Rect, f: &mut Frame, busy: bool, player: &Player) {
+    if busy {
+        //If database is busy but status_bar is not
+        //set the status bar to busy
+        if !status_bar.busy {
+            status_bar.busy = true;
+            status_bar.hidden = false;
+            status_bar.scan_timer = Some(Instant::now());
+        }
+    } else if status_bar.busy {
+        //If database is no-longer busy
+        //but status bar is. Print the duration
+        //and start the wait timer.
+        if let Some(scan_time) = status_bar.scan_timer {
+            status_bar.busy = false;
+            status_bar.wait_timer = Some(Instant::now());
+            status_bar.scan_timer = None;
+            status_bar.scan_message = format!(
+                "Finished adding {} files in {:.2} seconds.",
+                query::total_songs(),
+                scan_time.elapsed().as_secs_f32(),
+            );
+        }
+    }
+
+    if status_bar.hidden {
+        return;
+    }
+
+    let text = if busy {
+        Spans::from(format!("Scannig for files{}", ".".repeat(status_bar.dots)))
+    } else if status_bar.wait_timer.is_some() {
+        Spans::from(status_bar.scan_message.as_str())
+    } else if let Some(song) = player.songs.selected() {
+        Spans::from(vec![
+            Span::raw(" "),
+            Span::styled(song.number.to_string(), Style::default().fg(COLORS.number)),
+            Span::raw(" | "),
+            Span::styled(song.name.as_str(), Style::default().fg(COLORS.name)),
+            Span::raw(" | "),
+            Span::styled(song.album.as_str(), Style::default().fg(COLORS.album)),
+            Span::raw(" | "),
+            Span::styled(song.artist.as_str(), Style::default().fg(COLORS.artist)),
+        ])
+    } else {
+        Spans::default()
+    };
+
+    let area = Layout::default()
+        .direction(Direction::Horizontal)
+        .constraints([Constraint::Percentage(85), Constraint::Percentage(15)])
+        .split(area);
+
+    f.render_widget(
+        Paragraph::new(text).alignment(Alignment::Left).block(
+            Block::default()
+                .borders(Borders::TOP | Borders::LEFT | Borders::BOTTOM)
+                .border_type(BorderType::Rounded),
+        ),
+        area[0],
+    );
+
+    //TODO: Draw mini progress bar here.
+
+    let text = if !player.is_playing() {
+        String::from("Paused ")
+    } else {
+        format!("Vol: {}% ", player.volume)
+    };
+
+    f.render_widget(
+        Paragraph::new(text).alignment(Alignment::Right).block(
+            Block::default()
+                .borders(Borders::TOP | Borders::RIGHT | Borders::BOTTOM)
+                .border_type(BorderType::Rounded),
+        ),
+        area[1],
+    );
+}
diff --git a/gonk/src/toml.rs b/gonk/src/toml.rs
deleted file mode 100644
index 0c2be45e..00000000
--- a/gonk/src/toml.rs
+++ /dev/null
@@ -1,262 +0,0 @@
-use crate::GONK_DIR;
-use crossterm::event::{KeyCode, KeyModifiers};
-use serde::{Deserialize, Serialize};
-use static_init::dynamic;
-use std::{
-    fs,
-    path::{Path, PathBuf},
-};
-use tui::style::Color;
-
-#[dynamic]
-static TOML_DIR: PathBuf = GONK_DIR.join("gonk.toml");
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq)]
-pub enum Modifier {
-    Control,
-    Shift,
-    Alt,
-}
-
-impl Modifier {
-    pub fn from_bitflags(m: KeyModifiers) -> Option<Vec<Self>> {
-        match m.bits() {
-            0b0000_0001 => Some(vec![Modifier::Shift]),
-            0b0000_0100 => Some(vec![Modifier::Alt]),
-            0b0000_0010 => Some(vec![Modifier::Control]),
-            3 => Some(vec![Modifier::Control, Modifier::Shift]),
-            5 => Some(vec![Modifier::Alt, Modifier::Shift]),
-            6 => Some(vec![Modifier::Control, Modifier::Alt]),
-            7 => Some(vec![Modifier::Control, Modifier::Alt, Modifier::Shift]),
-            _ => None,
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq)]
-pub struct Key(pub String);
-
-impl From<&str> for Key {
-    fn from(key: &str) -> Self {
-        Self(key.to_string())
-    }
-}
-
-impl From<KeyCode> for Key {
-    fn from(item: KeyCode) -> Self {
-        match item {
-            KeyCode::Char(' ') => Key::from("SPACE"),
-            KeyCode::Char(c) => Key(c.to_string().to_ascii_uppercase()),
-            KeyCode::Backspace => Key::from("BACKSPACE"),
-            KeyCode::Enter => Key::from("ENTER"),
-            KeyCode::Left => Key::from("LEFT"),
-            KeyCode::Right => Key::from("RIGHT"),
-            KeyCode::Up => Key::from("UP"),
-            KeyCode::Down => Key::from("DOWN"),
-            KeyCode::Home => Key::from("HOME"),
-            KeyCode::End => Key::from("END"),
-            KeyCode::PageUp => Key::from("PAGEUP"),
-            KeyCode::PageDown => Key::from("PAGEDOWN"),
-            KeyCode::Tab => Key::from("TAB"),
-            KeyCode::BackTab => Key::from("BACKTAB"),
-            KeyCode::Delete => Key::from("DELETE"),
-            KeyCode::Insert => Key::from("INSERT"),
-            KeyCode::F(num) => Key(format!("F{num}")),
-            KeyCode::Null => Key::from("NULL"),
-            KeyCode::Esc => Key::from("ESCAPE"),
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq)]
-pub struct Bind {
-    pub key: Key,
-    pub modifiers: Option<Vec<Modifier>>,
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-pub struct Hotkey {
-    pub up: Bind,
-    pub down: Bind,
-    pub left: Bind,
-    pub right: Bind,
-    pub play_pause: Bind,
-    pub volume_up: Bind,
-    pub volume_down: Bind,
-    pub next: Bind,
-    pub previous: Bind,
-    pub seek_forward: Bind,
-    pub seek_backward: Bind,
-    pub clear: Bind,
-    pub clear_except_playing: Bind,
-    pub delete: Bind,
-    pub random: Bind,
-    pub refresh_database: Bind,
-    pub quit: Bind,
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-pub struct Config {
-    pub paths: Vec<String>,
-    pub output_device: String,
-    pub volume: u16,
-}
-
-#[derive(Serialize, Deserialize, Clone, Copy)]
-pub struct Colors {
-    pub number: Color,
-    pub name: Color,
-    pub album: Color,
-    pub artist: Color,
-    pub seeker: Color,
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-pub struct Toml {
-    pub config: Config,
-    pub colors: Colors,
-    pub hotkey: Hotkey,
-}
-
-impl Toml {
-    pub fn new() -> Self {
-        let file = if TOML_DIR.exists() {
-            fs::read_to_string(TOML_DIR.as_path()).unwrap()
-        } else {
-            let toml = Toml {
-                config: Config {
-                    paths: Vec::new(),
-                    output_device: String::new(),
-                    volume: 15,
-                },
-                colors: Colors {
-                    number: Color::Green,
-                    name: Color::Cyan,
-                    album: Color::Magenta,
-                    artist: Color::Blue,
-                    seeker: Color::White,
-                },
-                hotkey: Hotkey {
-                    up: Bind {
-                        key: Key::from("K"),
-                        modifiers: None,
-                    },
-                    down: Bind {
-                        key: Key::from("J"),
-                        modifiers: None,
-                    },
-                    left: Bind {
-                        key: Key::from("H"),
-                        modifiers: None,
-                    },
-                    right: Bind {
-                        key: Key::from("L"),
-                        modifiers: None,
-                    },
-                    play_pause: Bind {
-                        key: Key::from("SPACE"),
-                        modifiers: None,
-                    },
-                    volume_up: Bind {
-                        key: Key::from("W"),
-                        modifiers: None,
-                    },
-                    volume_down: Bind {
-                        key: Key::from("S"),
-                        modifiers: None,
-                    },
-                    seek_forward: Bind {
-                        key: Key::from("E"),
-                        modifiers: None,
-                    },
-                    seek_backward: Bind {
-                        key: Key::from("Q"),
-                        modifiers: None,
-                    },
-                    next: Bind {
-                        key: Key::from("D"),
-                        modifiers: None,
-                    },
-                    previous: Bind {
-                        key: Key::from("A"),
-                        modifiers: None,
-                    },
-                    clear: Bind {
-                        key: Key::from("C"),
-                        modifiers: None,
-                    },
-                    clear_except_playing: Bind {
-                        key: Key::from("C"),
-                        modifiers: Some(vec![Modifier::Shift]),
-                    },
-                    delete: Bind {
-                        key: Key::from("X"),
-                        modifiers: None,
-                    },
-                    random: Bind {
-                        key: Key::from("R"),
-                        modifiers: None,
-                    },
-                    refresh_database: Bind {
-                        key: Key::from("U"),
-                        modifiers: None,
-                    },
-                    quit: Bind {
-                        key: Key::from("C"),
-                        modifiers: Some(vec![Modifier::Control]),
-                    },
-                },
-            };
-
-            match toml::to_string_pretty(&toml) {
-                Ok(toml) => toml,
-                Err(err) => panic!("{}", &err),
-            }
-        };
-
-        match toml::from_str(&file) {
-            Ok(toml) => toml,
-            Err(err) => {
-                //TODO: parse and describe error to user?
-                panic!("{:#?}", &err);
-            }
-        }
-    }
-    pub fn check_paths(self) -> Result<Self, String> {
-        for path in &self.config.paths {
-            let path = Path::new(&path);
-            if !path.exists() {
-                return Err(format!("{} is not a valid path.", path.to_string_lossy()));
-            }
-        }
-        Ok(self)
-    }
-    pub fn add_path(&mut self, path: String) {
-        if !self.config.paths.contains(&path) {
-            self.config.paths.push(path);
-            self.write();
-        }
-    }
-    pub fn set_volume(&mut self, vol: u16) {
-        self.config.volume = vol;
-        self.write();
-    }
-    pub fn set_output_device(&mut self, device: String) {
-        self.config.output_device = device;
-        self.write();
-    }
-    pub fn write(&self) {
-        let toml = toml::to_string(&self).expect("Failed to write toml file.");
-        fs::write(TOML_DIR.as_path(), toml).expect("Could not write toml flie.");
-    }
-    pub fn reset(&mut self) {
-        *self = Self::default();
-        self.write();
-    }
-}
-
-impl Default for Toml {
-    fn default() -> Self {
-        Toml::new()
-    }
-}
diff --git a/gonk/src/widgets/list.rs b/gonk/src/widgets/list.rs
index 0407393f..b0ce3b93 100644
--- a/gonk/src/widgets/list.rs
+++ b/gonk/src/widgets/list.rs
@@ -79,7 +79,7 @@ pub struct List<'a> {
 }
 
 impl<'a> List<'a> {
-    pub fn new(items: Vec<ListItem<'a>>) -> List<'a> {
+    pub fn new(items: &[ListItem<'a>]) -> List<'a> {
         List {
             block: None,
             style: Style::default(),
@@ -114,7 +114,7 @@ impl<'a> List<'a> {
     fn get_items_bounds(&self, selection: usize, terminal_height: usize) -> (usize, usize) {
         let mut real_end = 0;
         let mut height = 0;
-        for item in self.items.iter() {
+        for item in &self.items {
             if height + item.height() > terminal_height {
                 break;
             }
@@ -180,16 +180,13 @@ impl<'a> StatefulWidget for List<'a> {
             .skip(start)
             .take(end - start)
         {
-            let (x, y) = match self.start_corner {
-                Corner::BottomLeft => {
-                    current_height += item.height() as u16;
-                    (list_area.left(), list_area.bottom() - current_height)
-                }
-                _ => {
-                    let pos = (list_area.left(), list_area.top() + current_height);
-                    current_height += item.height() as u16;
-                    pos
-                }
+            let (x, y) = if self.start_corner == Corner::BottomLeft {
+                current_height += item.height() as u16;
+                (list_area.left(), list_area.bottom() - current_height)
+            } else {
+                let pos = (list_area.left(), list_area.top() + current_height);
+                current_height += item.height() as u16;
+                pos
             };
 
             let area = Rect {
diff --git a/gonk/src/widgets/table.rs b/gonk/src/widgets/table.rs
index 77608112..9e081801 100644
--- a/gonk/src/widgets/table.rs
+++ b/gonk/src/widgets/table.rs
@@ -48,7 +48,7 @@ impl<'a> Row<'a> {
     {
         Self {
             height: 1,
-            cells: cells.into_iter().map(|c| c.into()).collect(),
+            cells: cells.into_iter().map(Into::into).collect(),
             style: Style::default(),
             bottom_margin: 0,
         }
@@ -143,7 +143,7 @@ impl<'a> Table<'a> {
     fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec<u16> {
         let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1);
         if has_selection {
-            let highlight_symbol_width = self.highlight_symbol.map(|s| s.len() as u16).unwrap_or(0);
+            let highlight_symbol_width = self.highlight_symbol.map_or(0, |s| s.len() as u16);
             constraints.push(Constraint::Length(highlight_symbol_width));
         }
         for constraint in self.widths {
@@ -174,7 +174,7 @@ impl<'a> Table<'a> {
         let len = self.rows.len();
         let selection = selected.unwrap_or(0).min(len.saturating_sub(1));
 
-        for item in self.rows.iter() {
+        for item in &self.rows {
             if height + item.height as usize > terminal_height as usize {
                 break;
             }
@@ -327,7 +327,7 @@ impl<'a> StatefulWidget for Table<'a> {
                 height: table_row.height,
             };
             buf.set_style(table_row_area, table_row.style);
-            let is_selected = state.selected.map(|s| s == i).unwrap_or(false);
+            let is_selected = state.selected.map_or(false, |s| s == i);
             let table_row_start_col = if has_selection {
                 let symbol = if is_selected {
                     highlight_symbol

From 7ddee68bee45d00a34def36591b79c47542ddcb8 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Sun, 3 Jul 2022 10:59:08 +0930
Subject: [PATCH 16/40] fix: update now works when from == to

---
 gonk-player/src/lib.rs         | 11 -----------
 gonk-player/src/sample_rate.rs | 11 ++++++++---
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 17ce6c91..cc924ccd 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -43,18 +43,7 @@ pub struct Player {
 }
 
 impl Player {
-    //TODO: get device from toml file
     pub fn new(_device: String, volume: u16, _songs: &[Song]) -> Self {
-        // let host_id = cpal::default_host().id();
-        // let host = cpal::host_from_id(host_id).unwrap();
-        // let mut devices: Vec<Device> = host.devices().unwrap().collect();
-        // devices.retain(|host| host.name().unwrap() == device);
-
-        // let device = if devices.is_empty() {
-        //     cpal::default_host().default_output_device().unwrap()
-        // } else {
-        //     devices.remove(0)
-        // };
         let device = cpal::default_host().default_output_device().unwrap();
 
         let config = device.default_output_config().unwrap();
diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index de907ff8..0fe1545f 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -76,11 +76,16 @@ impl SampleRateConverter {
     }
 
     pub fn update(&mut self, mut input: IntoIter<f32>) {
-        let current_frame = vec![input.next().unwrap(), input.next().unwrap()];
-        let next_frame = vec![input.next().unwrap(), input.next().unwrap()];
-        self.input = input;
+        let (current_frame, next_frame) = if self.from == self.to {
+            (Vec::new(), Vec::new())
+        } else {
+            let current = vec![input.next().unwrap(), input.next().unwrap()];
+            let next = vec![input.next().unwrap(), input.next().unwrap()];
+            (current, next)
+        };
         self.current_frame = current_frame;
         self.next_frame = next_frame;
+        self.input = input;
         self.current_frame_pos_in_chunk = 0;
         self.next_output_frame_pos_in_chunk = 0;
     }

From d72f7ea1885b3a470e3c828b925e728d8c2debdc Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Sun, 3 Jul 2022 14:56:16 +0930
Subject: [PATCH 17/40] cleanup

---
 gonk-player/src/lib.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index cc924ccd..f09cc77d 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -103,6 +103,7 @@ impl Player {
             generator,
         }
     }
+    //TODO: Run update loop in the player not in the client.
     pub fn update(&mut self) {
         if self.generator.read().unwrap().is_done() {
             self.next();

From 8aea6f806a38742ccd7565cf960041ee3b343e2a Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 6 Jul 2022 14:34:17 +0930
Subject: [PATCH 18/40] simplified resampler

---
 gonk-player/src/lib.rs         |  1 +
 gonk-player/src/sample_rate.rs | 61 ++++++++++++++--------------------
 2 files changed, 26 insertions(+), 36 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index f09cc77d..72b61c26 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -1,3 +1,4 @@
+#![feature(const_fn_floating_point_arithmetic)]
 use cpal::{
     traits::{HostTrait, StreamTrait},
     StreamError,
diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index 0fe1545f..305b5d8c 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -10,6 +10,11 @@ const fn gcd(a: u32, b: u32) -> u32 {
     }
 }
 
+#[inline]
+const fn lerp(a: f32, b: f32, t: f32) -> f32 {
+    return a + t * (b - a);
+}
+
 /// Iterator that converts from a certain sample rate to another.
 pub struct SampleRateConverter {
     /// The iterator that gives us samples.
@@ -28,7 +33,7 @@ pub struct SampleRateConverter {
     /// This counter is incremented (modulo `to`) every time the iterator is called.
     next_output_frame_pos_in_chunk: u32,
     /// The buffer containing the samples waiting to be output.
-    output_buffer: Vec<f32>,
+    output_buffer: Option<f32>,
 }
 
 impl SampleRateConverter {
@@ -57,7 +62,7 @@ impl SampleRateConverter {
             next_output_frame_pos_in_chunk: 0,
             current_frame: first_samples,
             next_frame: next_samples,
-            output_buffer: Vec::with_capacity(1),
+            output_buffer: None,
         }
     }
 
@@ -66,12 +71,11 @@ impl SampleRateConverter {
 
         mem::swap(&mut self.current_frame, &mut self.next_frame);
         self.next_frame.clear();
-        for _ in 0..2 {
-            if let Some(i) = self.input.next() {
-                self.next_frame.push(i);
-            } else {
-                break;
-            }
+        if let Some(i) = self.input.next() {
+            self.next_frame.push(i);
+        }
+        if let Some(i) = self.input.next() {
+            self.next_frame.push(i);
         }
     }
 
@@ -97,8 +101,8 @@ impl SampleRateConverter {
         }
 
         // Short circuit if there are some samples waiting.
-        if !self.output_buffer.is_empty() {
-            return Some(self.output_buffer.remove(0));
+        if let Some(output) = self.output_buffer.take() {
+            return Some(output);
         }
 
         // The frame we are going to return from this function will be a linear interpolation
@@ -130,40 +134,25 @@ impl SampleRateConverter {
         // Merging `self.current_frame` and `self.next_frame` into `self.output_buffer`.
         // Note that `self.output_buffer` can be truncated if there is not enough data in
         // `self.next_frame`.
-        let mut result = None;
         let numerator = (self.from * self.next_output_frame_pos_in_chunk) % self.to;
-        for (off, (cur, next)) in self
-            .current_frame
-            .iter()
-            .zip(self.next_frame.iter())
-            .enumerate()
-        {
-            let sample = cur + (next - cur) * numerator as f32 / self.to as f32;
-
-            if off == 0 {
-                result = Some(sample);
-            } else {
-                self.output_buffer.push(sample);
-            }
-        }
 
         // Incrementing the counter for the next iteration.
         self.next_output_frame_pos_in_chunk += 1;
 
-        if result.is_some() {
-            result
-        } else {
-            debug_assert!(self.next_frame.is_empty());
-
-            // draining `self.current_frame`
-            if !self.current_frame.is_empty() {
+        if self.next_frame.is_empty() {
+            if self.current_frame.is_empty() {
+                None
+            } else {
                 let r = Some(self.current_frame.remove(0));
-                mem::swap(&mut self.output_buffer, &mut self.current_frame);
-                self.current_frame.clear();
+                let current_frame = self.current_frame[0];
+                self.output_buffer = Some(current_frame);
                 r
-            } else {
-                None
             }
+        } else {
+            let ratio = numerator as f32 / self.to as f32;
+            let sample = lerp(self.current_frame[1], self.next_frame[1], ratio);
+            self.output_buffer = Some(sample);
+            Some(lerp(self.current_frame[0], self.next_frame[0], ratio))
         }
     }
 }

From 5dfddc0b8d27db7a465723c62c12ab87e08930f7 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 6 Jul 2022 14:56:19 +0930
Subject: [PATCH 19/40] cleanup

---
 gonk-player/src/sample_processor.rs |  2 +-
 gonk-player/src/sample_rate.rs      | 80 +++++++++++++++--------------
 2 files changed, 43 insertions(+), 39 deletions(-)

diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 29ce38c7..1694a08a 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -187,7 +187,7 @@ impl Processor {
                     let mut buffer = SampleBuffer::<f32>::new(self.capacity, self.spec);
                     buffer.copy_interleaved_ref(decoded);
 
-                    self.converter.update(buffer.samples().to_vec().into_iter());
+                    self.converter.update(buffer.samples().to_vec());
 
                     //Update elapsed
                     let ts = packet.ts();
diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index 305b5d8c..1bdac854 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -18,11 +18,11 @@ const fn lerp(a: f32, b: f32, t: f32) -> f32 {
 /// Iterator that converts from a certain sample rate to another.
 pub struct SampleRateConverter {
     /// The iterator that gives us samples.
-    input: IntoIter<f32>,
-    /// We convert chunks of `from` samples into chunks of `to` samples.
-    from: u32,
-    /// We convert chunks of `from` samples into chunks of `to` samples.
-    to: u32,
+    sample_buffer: IntoIter<f32>,
+    ///Input sample rate - interpolation factor
+    input: u32,
+    ///Output sample rate - decimation factor
+    output: u32,
     /// One sample per channel, extracted from `input`.
     current_frame: Vec<f32>,
     /// Position of `current_sample` modulo `from`.
@@ -55,9 +55,9 @@ impl SampleRateConverter {
         };
 
         SampleRateConverter {
-            input,
-            from: from_rate / gcd,
-            to: to_rate / gcd,
+            sample_buffer: input,
+            input: from_rate / gcd,
+            output: to_rate / gcd,
             current_frame_pos_in_chunk: 0,
             next_output_frame_pos_in_chunk: 0,
             current_frame: first_samples,
@@ -71,33 +71,39 @@ impl SampleRateConverter {
 
         mem::swap(&mut self.current_frame, &mut self.next_frame);
         self.next_frame.clear();
-        if let Some(i) = self.input.next() {
+        if let Some(i) = self.sample_buffer.next() {
             self.next_frame.push(i);
         }
-        if let Some(i) = self.input.next() {
+        if let Some(i) = self.sample_buffer.next() {
             self.next_frame.push(i);
         }
     }
 
-    pub fn update(&mut self, mut input: IntoIter<f32>) {
-        let (current_frame, next_frame) = if self.from == self.to {
-            (Vec::new(), Vec::new())
+    pub fn update(&mut self, input: Vec<f32>) {
+        self.sample_buffer = input.into_iter();
+
+        if self.input == self.output {
+            self.current_frame = Vec::new();
+            self.next_frame = Vec::new();
         } else {
-            let current = vec![input.next().unwrap(), input.next().unwrap()];
-            let next = vec![input.next().unwrap(), input.next().unwrap()];
-            (current, next)
+            self.current_frame = vec![
+                self.sample_buffer.next().unwrap(),
+                self.sample_buffer.next().unwrap(),
+            ];
+            self.next_frame = vec![
+                self.sample_buffer.next().unwrap(),
+                self.sample_buffer.next().unwrap(),
+            ];
         };
-        self.current_frame = current_frame;
-        self.next_frame = next_frame;
-        self.input = input;
+
         self.current_frame_pos_in_chunk = 0;
         self.next_output_frame_pos_in_chunk = 0;
     }
 
     pub fn next(&mut self) -> Option<f32> {
         // the algorithm below doesn't work if `self.from == self.to`
-        if self.from == self.to {
-            return self.input.next();
+        if self.input == self.output {
+            return self.sample_buffer.next();
         }
 
         // Short circuit if there are some samples waiting.
@@ -107,51 +113,49 @@ impl SampleRateConverter {
 
         // The frame we are going to return from this function will be a linear interpolation
         // between `self.current_frame` and `self.next_frame`.
-
-        if self.next_output_frame_pos_in_chunk == self.to {
+        if self.next_output_frame_pos_in_chunk == self.output {
             // If we jump to the next frame, we reset the whole state.
             self.next_output_frame_pos_in_chunk = 0;
 
             self.next_input_frame();
-            while self.current_frame_pos_in_chunk != self.from {
+            while self.current_frame_pos_in_chunk != self.input {
                 self.next_input_frame();
             }
             self.current_frame_pos_in_chunk = 0;
         } else {
             // Finding the position of the first sample of the linear interpolation.
             let req_left_sample =
-                (self.from * self.next_output_frame_pos_in_chunk / self.to) % self.from;
+                (self.input * self.next_output_frame_pos_in_chunk / self.output) % self.input;
 
             // Advancing `self.current_frame`, `self.next_frame` and
             // `self.current_frame_pos_in_chunk` until the latter variable
             // matches `req_left_sample`.
             while self.current_frame_pos_in_chunk != req_left_sample {
                 self.next_input_frame();
-                debug_assert!(self.current_frame_pos_in_chunk < self.from);
+                debug_assert!(self.current_frame_pos_in_chunk < self.input);
             }
         }
 
         // Merging `self.current_frame` and `self.next_frame` into `self.output_buffer`.
         // Note that `self.output_buffer` can be truncated if there is not enough data in
         // `self.next_frame`.
-        let numerator = (self.from * self.next_output_frame_pos_in_chunk) % self.to;
+        let numerator = (self.input * self.next_output_frame_pos_in_chunk) % self.output;
 
         // Incrementing the counter for the next iteration.
         self.next_output_frame_pos_in_chunk += 1;
 
+        if self.current_frame.is_empty() && self.next_frame.is_empty() {
+            return None;
+        }
+
         if self.next_frame.is_empty() {
-            if self.current_frame.is_empty() {
-                None
-            } else {
-                let r = Some(self.current_frame.remove(0));
-                let current_frame = self.current_frame[0];
-                self.output_buffer = Some(current_frame);
-                r
-            }
+            let r = self.current_frame.remove(0);
+            self.output_buffer = self.current_frame.get(0).cloned();
+            self.current_frame.clear();
+            Some(r)
         } else {
-            let ratio = numerator as f32 / self.to as f32;
-            let sample = lerp(self.current_frame[1], self.next_frame[1], ratio);
-            self.output_buffer = Some(sample);
+            let ratio = numerator as f32 / self.output as f32;
+            self.output_buffer = Some(lerp(self.current_frame[1], self.next_frame[1], ratio));
             Some(lerp(self.current_frame[0], self.next_frame[0], ratio))
         }
     }

From c680699feb2367b536ba9827190e45c8b6481cdc Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 6 Jul 2022 15:09:28 +0930
Subject: [PATCH 20/40] cleanup

---
 gonk-player/src/sample_rate.rs | 92 ++++++++++++++++------------------
 1 file changed, 43 insertions(+), 49 deletions(-)

diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index 1bdac854..bd0b7ce4 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -1,4 +1,3 @@
-//https://github.com/RustAudio/rodio/blob/master/src/conversions/sample_rate.rs
 use std::{mem, vec::IntoIter};
 
 #[inline]
@@ -15,49 +14,51 @@ const fn lerp(a: f32, b: f32, t: f32) -> f32 {
     return a + t * (b - a);
 }
 
-/// Iterator that converts from a certain sample rate to another.
 pub struct SampleRateConverter {
     /// The iterator that gives us samples.
-    sample_buffer: IntoIter<f32>,
-    ///Input sample rate - interpolation factor
+    buffer: IntoIter<f32>,
+
+    ///Input sample rate - interpolation factor.
     input: u32,
-    ///Output sample rate - decimation factor
+    ///Output sample rate - decimation factor.
     output: u32,
+
     /// One sample per channel, extracted from `input`.
     current_frame: Vec<f32>,
     /// Position of `current_sample` modulo `from`.
+    ///
+    /// `0..input / gcd`
     current_frame_pos_in_chunk: u32,
+
     /// The samples right after `current_sample` (one per channel), extracted from `input`.
     next_frame: Vec<f32>,
     /// The position of the next sample that the iterator should return, modulo `to`.
     /// This counter is incremented (modulo `to`) every time the iterator is called.
+    ///
+    /// `0..output / gcd`
     next_output_frame_pos_in_chunk: u32,
-    /// The buffer containing the samples waiting to be output.
+
     output_buffer: Option<f32>,
 }
 
 impl SampleRateConverter {
-    pub fn new(mut input: IntoIter<f32>, from_rate: u32, to_rate: u32) -> SampleRateConverter {
-        assert!(from_rate >= 1);
-        assert!(to_rate >= 1);
+    pub fn new(mut buffer: IntoIter<f32>, input: u32, output: u32) -> SampleRateConverter {
+        assert!(input >= 1);
+        assert!(output >= 1);
 
-        // finding greatest common divisor
-        let gcd = gcd(from_rate, to_rate);
-
-        let (first_samples, next_samples) = if from_rate == to_rate {
-            // if `from` == `to` == 1, then we just pass through
-            debug_assert_eq!(from_rate, gcd);
+        let gcd = gcd(input, output);
+        let (first_samples, next_samples) = if input == output {
             (Vec::new(), Vec::new())
         } else {
-            let first = vec![input.next().unwrap(), input.next().unwrap()];
-            let next = vec![input.next().unwrap(), input.next().unwrap()];
+            let first = vec![buffer.next().unwrap(), buffer.next().unwrap()];
+            let next = vec![buffer.next().unwrap(), buffer.next().unwrap()];
             (first, next)
         };
 
         SampleRateConverter {
-            sample_buffer: input,
-            input: from_rate / gcd,
-            output: to_rate / gcd,
+            buffer,
+            input: input / gcd,
+            output: output / gcd,
             current_frame_pos_in_chunk: 0,
             next_output_frame_pos_in_chunk: 0,
             current_frame: first_samples,
@@ -66,49 +67,42 @@ impl SampleRateConverter {
         }
     }
 
-    fn next_input_frame(&mut self) {
-        self.current_frame_pos_in_chunk += 1;
-
-        mem::swap(&mut self.current_frame, &mut self.next_frame);
-        self.next_frame.clear();
-        if let Some(i) = self.sample_buffer.next() {
-            self.next_frame.push(i);
-        }
-        if let Some(i) = self.sample_buffer.next() {
-            self.next_frame.push(i);
-        }
-    }
-
     pub fn update(&mut self, input: Vec<f32>) {
-        self.sample_buffer = input.into_iter();
+        self.buffer = input.into_iter();
 
         if self.input == self.output {
             self.current_frame = Vec::new();
             self.next_frame = Vec::new();
         } else {
-            self.current_frame = vec![
-                self.sample_buffer.next().unwrap(),
-                self.sample_buffer.next().unwrap(),
-            ];
-            self.next_frame = vec![
-                self.sample_buffer.next().unwrap(),
-                self.sample_buffer.next().unwrap(),
-            ];
+            self.current_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
+            self.next_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
         };
 
         self.current_frame_pos_in_chunk = 0;
         self.next_output_frame_pos_in_chunk = 0;
     }
 
-    pub fn next(&mut self) -> Option<f32> {
-        // the algorithm below doesn't work if `self.from == self.to`
-        if self.input == self.output {
-            return self.sample_buffer.next();
+    fn next_input_frame(&mut self) {
+        mem::swap(&mut self.current_frame, &mut self.next_frame);
+
+        self.next_frame.clear();
+
+        if let Some(sample) = self.buffer.next() {
+            self.next_frame.push(sample);
         }
 
-        // Short circuit if there are some samples waiting.
-        if let Some(output) = self.output_buffer.take() {
-            return Some(output);
+        if let Some(sample) = self.buffer.next() {
+            self.next_frame.push(sample);
+        }
+
+        self.current_frame_pos_in_chunk += 1;
+    }
+
+    pub fn next(&mut self) -> Option<f32> {
+        if self.input == self.output {
+            return self.buffer.next();
+        } else if let Some(sample) = self.output_buffer.take() {
+            return Some(sample);
         }
 
         // The frame we are going to return from this function will be a linear interpolation

From 8af6c70f65cf5fcf7f2b03e3ca1fb5853f93b9d8 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Wed, 6 Jul 2022 15:16:31 +0930
Subject: [PATCH 21/40] fix: left over error from merge

---
 gonk/src/main.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index 14744086..8187e489 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -137,7 +137,7 @@ fn main() {
     let volume = query::volume();
 
     //40ms
-    let player = thread::spawn(move || Player::new(volume, &cache, &Vec::new()));
+    let player = thread::spawn(move || Player::new(String::new(), volume, &cache));
 
     //3ms
     let mut browser = Browser::new();

From 6653b2d678b191688970c8b342cf0405c5e8632c Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Thu, 7 Jul 2022 10:51:15 +0930
Subject: [PATCH 22/40] removed update function

---
 gonk-player/src/sample_processor.rs | 15 +++++++++++---
 gonk-player/src/sample_rate.rs      | 32 ++++++++---------------------
 2 files changed, 21 insertions(+), 26 deletions(-)

diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 1694a08a..1e53135f 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -101,10 +101,13 @@ pub struct Processor {
     pub duration: Duration,
     pub elapsed: Duration,
     pub volume: f32,
+
+    pub input: u32,
+    pub output: u32,
 }
 
 impl Processor {
-    pub fn new(sample_rate: u32, path: &Path, volume: f32) -> Self {
+    pub fn new(output_rate: u32, path: &Path, volume: f32) -> Self {
         let source = Box::new(File::open(path).unwrap());
 
         let mss = MediaSourceStream::new(source, Default::default());
@@ -155,11 +158,13 @@ impl Processor {
             converter: SampleRateConverter::new(
                 sample_buffer.samples().to_vec().into_iter(),
                 spec.rate,
-                sample_rate,
+                output_rate,
             ),
             finished: false,
             left: true,
             volume,
+            input: spec.rate,
+            output: output_rate,
         }
     }
     pub fn next_sample(&mut self) -> f32 {
@@ -187,7 +192,11 @@ impl Processor {
                     let mut buffer = SampleBuffer::<f32>::new(self.capacity, self.spec);
                     buffer.copy_interleaved_ref(decoded);
 
-                    self.converter.update(buffer.samples().to_vec());
+                    self.converter = SampleRateConverter::new(
+                        buffer.samples().to_vec().into_iter(),
+                        self.input,
+                        self.output,
+                    );
 
                     //Update elapsed
                     let ts = packet.ts();
diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index bd0b7ce4..414cf5f7 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -43,16 +43,17 @@ pub struct SampleRateConverter {
 
 impl SampleRateConverter {
     pub fn new(mut buffer: IntoIter<f32>, input: u32, output: u32) -> SampleRateConverter {
-        assert!(input >= 1);
-        assert!(output >= 1);
+        debug_assert!(input >= 1);
+        debug_assert!(output >= 1);
 
         let gcd = gcd(input, output);
-        let (first_samples, next_samples) = if input == output {
+        let (current_frame, next_frame) = if input == output {
             (Vec::new(), Vec::new())
         } else {
-            let first = vec![buffer.next().unwrap(), buffer.next().unwrap()];
-            let next = vec![buffer.next().unwrap(), buffer.next().unwrap()];
-            (first, next)
+            (
+                vec![buffer.next().unwrap(), buffer.next().unwrap()],
+                vec![buffer.next().unwrap(), buffer.next().unwrap()],
+            )
         };
 
         SampleRateConverter {
@@ -61,27 +62,12 @@ impl SampleRateConverter {
             output: output / gcd,
             current_frame_pos_in_chunk: 0,
             next_output_frame_pos_in_chunk: 0,
-            current_frame: first_samples,
-            next_frame: next_samples,
+            current_frame,
+            next_frame,
             output_buffer: None,
         }
     }
 
-    pub fn update(&mut self, input: Vec<f32>) {
-        self.buffer = input.into_iter();
-
-        if self.input == self.output {
-            self.current_frame = Vec::new();
-            self.next_frame = Vec::new();
-        } else {
-            self.current_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
-            self.next_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
-        };
-
-        self.current_frame_pos_in_chunk = 0;
-        self.next_output_frame_pos_in_chunk = 0;
-    }
-
     fn next_input_frame(&mut self) {
         mem::swap(&mut self.current_frame, &mut self.next_frame);
 

From d36cb2081b82d9088e66f602ae11246eacf2fbc4 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Thu, 7 Jul 2022 10:58:45 +0930
Subject: [PATCH 23/40] changed mem::swap to mem::take

---
 gonk-player/src/sample_rate.rs | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index 414cf5f7..86dca38e 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -69,9 +69,7 @@ impl SampleRateConverter {
     }
 
     fn next_input_frame(&mut self) {
-        mem::swap(&mut self.current_frame, &mut self.next_frame);
-
-        self.next_frame.clear();
+        self.current_frame = mem::take(&mut self.next_frame);
 
         if let Some(sample) = self.buffer.next() {
             self.next_frame.push(sample);

From 521d38d4474a944a03a638d3fa75bffaf32ef277 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Thu, 7 Jul 2022 14:34:47 +0930
Subject: [PATCH 24/40] fix build failing because of nightly feature

---
 gonk-player/src/lib.rs         | 1 -
 gonk-player/src/sample_rate.rs | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 312e461c..ae6d6a33 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -1,4 +1,3 @@
-#![feature(const_fn_floating_point_arithmetic)]
 use cpal::{
     traits::{HostTrait, StreamTrait},
     StreamError,
diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index 86dca38e..6fc0a269 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -10,7 +10,7 @@ const fn gcd(a: u32, b: u32) -> u32 {
 }
 
 #[inline]
-const fn lerp(a: f32, b: f32, t: f32) -> f32 {
+fn lerp(a: f32, b: f32, t: f32) -> f32 {
     return a + t * (b - a);
 }
 

From 334718a49a46e342558f7facb5feb65459bac9d3 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Thu, 7 Jul 2022 14:54:25 +0930
Subject: [PATCH 25/40] clippy

---
 gonk-database/src/database.rs  |  2 +-
 gonk-database/src/lib.rs       |  7 ++-----
 gonk-database/src/query.rs     |  4 ++--
 gonk-player/src/sample_rate.rs |  4 ++--
 gonk-player/src/song.rs        |  2 +-
 gonk/src/main.rs               |  8 +++++++-
 gonk/src/playlist.rs           |  8 ++++----
 gonk/src/queue.rs              |  6 +++---
 gonk/src/search.rs             | 16 +++++-----------
 gonk/src/settings.rs           | 13 ++++++++-----
 gonk/src/status_bar.rs         |  7 +++----
 11 files changed, 38 insertions(+), 39 deletions(-)

diff --git a/gonk-database/src/database.rs b/gonk-database/src/database.rs
index 7c4f0bec..68997060 100644
--- a/gonk-database/src/database.rs
+++ b/gonk-database/src/database.rs
@@ -34,7 +34,7 @@ impl Database {
             }
         }
 
-        self.handle = Some(thread::spawn(|| rescan_folders()));
+        self.handle = Some(thread::spawn(rescan_folders));
     }
 
     pub fn state(&mut self) -> State {
diff --git a/gonk-database/src/lib.rs b/gonk-database/src/lib.rs
index 6b36739d..aaf91424 100644
--- a/gonk-database/src/lib.rs
+++ b/gonk-database/src/lib.rs
@@ -127,10 +127,7 @@ pub fn collect_songs(path: &str) -> Vec<Song> {
         })
         .collect();
 
-    paths
-        .par_iter()
-        .flat_map(|path| Song::from(&path))
-        .collect()
+    paths.par_iter().flat_map(|path| Song::from(path)).collect()
 }
 
 pub fn rescan_folders() {
@@ -145,7 +142,7 @@ pub fn rescan_folders() {
 }
 
 pub fn add_folder(folder: &str) {
-    let folder = folder.replace("\\", "/");
+    let folder = folder.replace('\\', "/");
 
     conn()
         .execute(
diff --git a/gonk-database/src/query.rs b/gonk-database/src/query.rs
index a6244361..249459aa 100644
--- a/gonk-database/src/query.rs
+++ b/gonk-database/src/query.rs
@@ -50,7 +50,7 @@ pub fn folders() -> Vec<String> {
 }
 
 pub fn remove_folder(path: &str) -> Result<(), &str> {
-    let path = path.replace("\\", "/");
+    let path = path.replace('\\', "/");
     let conn = conn();
 
     conn.execute("DELETE FROM song WHERE folder = ?", [&path])
@@ -146,7 +146,7 @@ pub fn songs_from_ids(ids: &[usize]) -> Vec<Song> {
     //Remove the last 'UNION ALL'
     let sql = &sql[..sql.len() - 10];
 
-    let mut stmt = conn.prepare(&sql).unwrap();
+    let mut stmt = conn.prepare(sql).unwrap();
 
     stmt.query_map([], |row| Ok(song(row)))
         .unwrap()
diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
index 6fc0a269..df7738c6 100644
--- a/gonk-player/src/sample_rate.rs
+++ b/gonk-player/src/sample_rate.rs
@@ -11,7 +11,7 @@ const fn gcd(a: u32, b: u32) -> u32 {
 
 #[inline]
 fn lerp(a: f32, b: f32, t: f32) -> f32 {
-    return a + t * (b - a);
+    a + t * (b - a)
 }
 
 pub struct SampleRateConverter {
@@ -128,7 +128,7 @@ impl SampleRateConverter {
 
         if self.next_frame.is_empty() {
             let r = self.current_frame.remove(0);
-            self.output_buffer = self.current_frame.get(0).cloned();
+            self.output_buffer = self.current_frame.first().cloned();
             self.current_frame.clear();
             Some(r)
         } else {
diff --git a/gonk-player/src/song.rs b/gonk-player/src/song.rs
index 4ce6ab0b..b4d1d764 100644
--- a/gonk-player/src/song.rs
+++ b/gonk-player/src/song.rs
@@ -110,7 +110,7 @@ impl Song {
             update_metadata(metadata);
         }
 
-        if song.artist == String::from("Unknown Artist") {
+        if song.artist == *"Unknown Artist" {
             song.artist = backup_artist;
         }
 
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index 8187e489..02674abd 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -383,7 +383,13 @@ fn main() {
 
     query::set_volume(player.volume);
 
-    let ids: Vec<usize> = player.songs.data.iter().flat_map(|song| song.id).collect();
+    let ids: Vec<usize> = player
+        .songs
+        .data
+        .iter()
+        .filter_map(|song| song.id)
+        .collect();
+
     query::cache(&ids);
 
     disable_raw_mode().unwrap();
diff --git a/gonk/src/playlist.rs b/gonk/src/playlist.rs
index 154414bc..3df81fc9 100644
--- a/gonk/src/playlist.rs
+++ b/gonk/src/playlist.rs
@@ -53,7 +53,7 @@ impl Input for Playlist {
         match self.mode {
             Mode::Playlist => {
                 self.playlists.up();
-                let songs = playlist::get(&self.playlists.selected().unwrap());
+                let songs = playlist::get(self.playlists.selected().unwrap());
                 self.songs = Index::new(songs, Some(0));
             }
             Mode::Song => self.songs.up(),
@@ -65,7 +65,7 @@ impl Input for Playlist {
         match self.mode {
             Mode::Playlist => {
                 self.playlists.down();
-                let songs = playlist::get(&self.playlists.selected().unwrap());
+                let songs = playlist::get(self.playlists.selected().unwrap());
                 self.songs = Index::new(songs, Some(0));
             }
             Mode::Song => self.songs.down(),
@@ -285,9 +285,9 @@ pub fn draw_popup(playlist: &mut Playlist, f: &mut Frame) {
         } else {
             let width = v[0].width.saturating_sub(3);
             if len < width {
-                f.set_cursor(x + len, y)
+                f.set_cursor(x + len, y);
             } else {
-                f.set_cursor(x + width, y)
+                f.set_cursor(x + width, y);
             }
         }
     }
diff --git a/gonk/src/queue.rs b/gonk/src/queue.rs
index 527fb5c4..4f9b9700 100644
--- a/gonk/src/queue.rs
+++ b/gonk/src/queue.rs
@@ -91,7 +91,7 @@ pub fn draw(queue: &mut Queue, player: &mut Player, f: &mut Frame, event: Option
     //Handle mouse input.
     if let Some(event) = event {
         let (x, y) = (event.column, event.row);
-        const HEADER_HEIGHT: u16 = 5;
+        let header_height = 5;
 
         let size = f.size();
 
@@ -107,8 +107,8 @@ pub fn draw(queue: &mut Queue, player: &mut Player, f: &mut Frame, event: Option
         //Mouse support for the queue.
         if let Some((start, _)) = row_bounds {
             //Check if you clicked on the header.
-            if y >= HEADER_HEIGHT {
-                let index = (y - HEADER_HEIGHT) as usize + start;
+            if y >= header_height {
+                let index = (y - header_height) as usize + start;
 
                 //Make sure you didn't click on the seek bar
                 //and that the song index exists.
diff --git a/gonk/src/search.rs b/gonk/src/search.rs
index 4531c0b1..6c55060c 100644
--- a/gonk/src/search.rs
+++ b/gonk/src/search.rs
@@ -121,17 +121,11 @@ pub fn on_enter(search: &mut Search) -> Option<Vec<Song>> {
             }
             None
         }
-        Mode::Select => {
-            if let Some(item) = search.results.selected() {
-                Some(match item {
-                    Item::Song(song) => query::songs_from_ids(&[song.id]),
-                    Item::Album(album) => query::songs_from_album(&album.name, &album.artist),
-                    Item::Artist(artist) => query::songs_by_artist(&artist.name),
-                })
-            } else {
-                None
-            }
-        }
+        Mode::Select => search.results.selected().map(|item| match item {
+            Item::Song(song) => query::songs_from_ids(&[song.id]),
+            Item::Album(album) => query::songs_from_album(&album.name, &album.artist),
+            Item::Artist(artist) => query::songs_by_artist(&artist.name),
+        }),
     }
 }
 
diff --git a/gonk/src/settings.rs b/gonk/src/settings.rs
index 57ee2f0d..b9c2f0a4 100644
--- a/gonk/src/settings.rs
+++ b/gonk/src/settings.rs
@@ -18,14 +18,17 @@ impl Settings {
         let wanted_device = query::playback_device();
 
         let devices = Player::audio_devices();
-        let device_names: Vec<String> = devices.iter().flat_map(DeviceTrait::name).collect();
 
-        let current_device = if !device_names.contains(&wanted_device) {
+        let current_device = if devices
+            .iter()
+            .flat_map(DeviceTrait::name)
+            .any(|x| x == wanted_device)
+        {
+            wanted_device
+        } else {
             let name = default_device.name().unwrap();
             query::set_playback_device(&name);
             name
-        } else {
-            wanted_device
         };
 
         Self {
@@ -41,7 +44,7 @@ impl Input for Settings {
     }
 
     fn down(&mut self) {
-        self.devices.down()
+        self.devices.down();
     }
 
     fn left(&mut self) {}
diff --git a/gonk/src/status_bar.rs b/gonk/src/status_bar.rs
index dd39291d..28bf08b8 100644
--- a/gonk/src/status_bar.rs
+++ b/gonk/src/status_bar.rs
@@ -125,11 +125,10 @@ pub fn draw(status_bar: &mut StatusBar, area: Rect, f: &mut Frame, busy: bool, p
     );
 
     //TODO: Draw mini progress bar here.
-
-    let text = if !player.is_playing() {
-        String::from("Paused ")
-    } else {
+    let text = if player.is_playing() {
         format!("Vol: {}% ", player.volume)
+    } else {
+        String::from("Paused ")
     };
 
     f.render_widget(

From b978fe9c43f23693f0a17418e15a9e664a95c8d9 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 12:57:28 +0930
Subject: [PATCH 26/40] cleanup

---
 gonk-player/src/lib.rs              | 18 +-----------------
 gonk-player/src/sample_processor.rs |  2 --
 2 files changed, 1 insertion(+), 19 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index ae6d6a33..1e04487f 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -268,23 +268,7 @@ impl Player {
         cpal::default_host().default_output_device().unwrap()
     }
     pub fn change_output_device(&mut self, _device: &Device) -> Result<(), StreamError> {
-        //TODO
-        Ok(())
-        // match OutputStream::try_from_device(device) {
-        //     Ok((stream, handle)) => {
-        //         let pos = self.elapsed();
-        //         self.stop();
-        //         self.stream = stream;
-        //         self.handle = handle;
-        //         self.play_selected();
-        //         self.seek_to(pos);
-        //         Ok(())
-        //     }
-        //     Err(e) => match e {
-        //         stream::StreamError::DefaultStreamConfigError(_) => Ok(()),
-        //         _ => Err(e),
-        //     },
-        // }
+        todo!()
     }
 }
 
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
index 1e53135f..9c5284c1 100644
--- a/gonk-player/src/sample_processor.rs
+++ b/gonk-player/src/sample_processor.rs
@@ -97,7 +97,6 @@ pub struct Processor {
     pub capacity: u64,
     pub converter: SampleRateConverter,
     pub finished: bool,
-    pub left: bool,
     pub duration: Duration,
     pub elapsed: Duration,
     pub volume: f32,
@@ -161,7 +160,6 @@ impl Processor {
                 output_rate,
             ),
             finished: false,
-            left: true,
             volume,
             input: spec.rate,
             output: output_rate,

From 05d7bdb291426899f43b37387eeafdd405f3652b Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 14:16:43 +0930
Subject: [PATCH 27/40] reworked player again

---
 gonk-player/Cargo.toml              |   1 -
 gonk-player/src/lib.rs              | 564 +++++++++++++++++++---------
 gonk-player/src/sample_processor.rs | 246 ------------
 gonk-player/src/sample_rate.rs      | 140 -------
 gonk-player/src/song.rs             |   6 +-
 gonk/src/main.rs                    |   2 +-
 gonk/src/queue.rs                   |  12 +-
 gonk/src/settings.rs                |  57 +--
 gonk/src/status_bar.rs              |   4 +-
 9 files changed, 428 insertions(+), 604 deletions(-)
 delete mode 100644 gonk-player/src/sample_processor.rs
 delete mode 100644 gonk-player/src/sample_rate.rs

diff --git a/gonk-player/Cargo.toml b/gonk-player/Cargo.toml
index 6c60104c..b8381996 100644
--- a/gonk-player/Cargo.toml
+++ b/gonk-player/Cargo.toml
@@ -11,5 +11,4 @@ license = "MIT"
 
 [dependencies]
 cpal = "0.13.5"
-crossbeam-channel = "0.5.4"
 symphonia = { version = "0.5.0", features = ["mp3", "isomp4", "alac", "aac"] }
diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 1e04487f..9892b424 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -1,123 +1,311 @@
+#![feature(const_fn_floating_point_arithmetic)]
 use cpal::{
     traits::{HostTrait, StreamTrait},
-    StreamError,
+    Stream,
 };
-use crossbeam_channel::{unbounded, Sender};
-use sample_processor::Generator;
-use std::{
-    sync::{Arc, RwLock},
-    thread,
-    time::Duration,
+use std::{fs::File, time::Duration, vec::IntoIter};
+use symphonia::{
+    core::{
+        audio::SampleBuffer,
+        codecs::{Decoder, DecoderOptions},
+        errors::{Error, SeekErrorKind},
+        formats::{FormatOptions, SeekMode, SeekTo},
+        io::MediaSourceStream,
+        meta::MetadataOptions,
+        probe::{Hint, ProbeResult},
+        units::{Time, TimeBase},
+    },
+    default::get_probe,
 };
 
+pub use cpal::{traits::DeviceTrait, Device};
+pub use index::Index;
+pub use song::Song;
+
 mod index;
-mod sample_processor;
-mod sample_rate;
 mod song;
 
-pub use cpal::traits::DeviceTrait;
-pub use cpal::Device;
-pub use index::Index;
-pub use song::Song;
+#[inline]
+const fn gcd(a: usize, b: usize) -> usize {
+    if b == 0 {
+        a
+    } else {
+        gcd(b, a % b)
+    }
+}
+
+#[inline]
+const fn lerp(a: f32, b: f32, t: f32) -> f32 {
+    a + t * (b - a)
+}
+
+static mut RESAMPLER: Option<Resampler> = None;
 
 const VOLUME_STEP: u16 = 5;
 const VOLUME_REDUCTION: f32 = 600.0;
+const MAX_VOLUME: f32 = 100.0 / VOLUME_REDUCTION;
+const MIN_VOLUME: f32 = 0.0 / VOLUME_REDUCTION;
 
-#[derive(Debug)]
-pub enum Event {
-    Play,
-    Pause,
-    SeekBy(f32),
-    SeekTo(f32),
-    Volume(f32),
-}
+pub struct Resampler {
+    probed: ProbeResult,
+    decoder: Box<dyn Decoder>,
 
-pub struct Player {
-    pub s: Sender<Event>,
-    pub playing: bool,
-    pub volume: u16,
-    pub songs: Index<Song>,
-    pub elapsed: Arc<RwLock<Duration>>,
-    pub generator: Arc<RwLock<Generator>>,
+    input: usize,
+    output: usize,
+
+    buffer: IntoIter<f32>,
+
+    current_frame: Vec<f32>,
+    current_frame_pos_in_chunk: usize,
+
+    next_frame: Vec<f32>,
+    next_output_frame_pos_in_chunk: usize,
+
+    output_buffer: Option<f32>,
+
+    time_base: TimeBase,
+
+    gain: f32,
+
+    pub volume: f32,
     pub duration: Duration,
+    pub finished: bool,
+    pub elapsed: Duration,
 }
 
-impl Player {
-    pub fn new(_device: String, volume: u16, _songs: &[Song]) -> Self {
-        let device = cpal::default_host().default_output_device().unwrap();
+impl Resampler {
+    pub fn new(output: usize, path: &str, gain: f32) -> Self {
+        let source = Box::new(File::open(path).unwrap());
+        let mss = MediaSourceStream::new(source, Default::default());
+
+        let mut probed = get_probe()
+            .format(
+                &Hint::default(),
+                mss,
+                &FormatOptions::default(),
+                &MetadataOptions::default(),
+            )
+            .unwrap();
+
+        let track = probed.format.default_track().unwrap();
+        let input = track.codec_params.sample_rate.unwrap() as usize;
+        let time_base = track.codec_params.time_base.unwrap();
+
+        let n_frames = track.codec_params.n_frames.unwrap();
+        let time = track.codec_params.time_base.unwrap().calc_time(n_frames);
+        let duration = Duration::from_secs(time.seconds) + Duration::from_secs_f64(time.frac);
+
+        let mut decoder = symphonia::default::get_codecs()
+            .make(&track.codec_params, &DecoderOptions::default())
+            .unwrap();
+
+        let next_packet = probed.format.next_packet().unwrap();
+        let decoded = decoder.decode(&next_packet).unwrap();
+        let mut buffer = SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
+        buffer.copy_interleaved_ref(decoded);
+        let mut buffer = buffer.samples().to_vec().into_iter();
+
+        let ts = next_packet.ts();
+        let t = time_base.calc_time(ts);
+        let elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
+
+        let gcd = gcd(input, output);
+
+        let (current_frame, next_frame) = if input == output {
+            (Vec::new(), Vec::new())
+        } else {
+            (
+                vec![buffer.next().unwrap(), buffer.next().unwrap()],
+                vec![buffer.next().unwrap(), buffer.next().unwrap()],
+            )
+        };
+
+        Self {
+            probed,
+            decoder,
+            buffer,
+            input: input / gcd,
+            output: output / gcd,
+            current_frame_pos_in_chunk: 0,
+            next_output_frame_pos_in_chunk: 0,
+            current_frame,
+            next_frame,
+            output_buffer: None,
+            volume: 15.0 / VOLUME_REDUCTION,
+            duration,
+            elapsed,
+            time_base,
+            finished: false,
+            gain,
+        }
+    }
+
+    pub fn next(&mut self) -> f32 {
+        if let Some(smp) = self.next_sample() {
+            if self.gain == 0.0 {
+                //Reduce the volume a little to match
+                //songs with replay gain information.
+                smp * self.volume * 0.75
+            } else {
+                smp * self.volume * self.gain
+            }
+        } else {
+            let next_packet = self.probed.format.next_packet().unwrap();
+            let decoded = self.decoder.decode(&next_packet).unwrap();
+            let mut buffer = SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
+            buffer.copy_interleaved_ref(decoded);
+            self.buffer = buffer.samples().to_vec().into_iter();
+
+            let ts = next_packet.ts();
+            let t = self.time_base.calc_time(ts);
+            self.elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
 
-        let config = device.default_output_config().unwrap();
-        let rate = config.sample_rate().0;
+            if self.input == self.output {
+                self.current_frame = Vec::new();
+                self.next_frame = Vec::new();
+            } else {
+                self.current_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
+                self.next_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
+            }
+
+            self.current_frame_pos_in_chunk = 0;
+            self.next_output_frame_pos_in_chunk = 0;
+
+            debug_assert!(self.output_buffer.is_none());
+
+            self.next()
+        }
+    }
+
+    fn next_input_frame(&mut self) {
+        self.current_frame = std::mem::take(&mut self.next_frame);
+
+        if let Some(sample) = self.buffer.next() {
+            self.next_frame.push(sample);
+        }
+
+        if let Some(sample) = self.buffer.next() {
+            self.next_frame.push(sample);
+        }
+
+        self.current_frame_pos_in_chunk += 1;
+    }
+
+    fn next_sample(&mut self) -> Option<f32> {
+        if self.input == self.output {
+            return self.buffer.next();
+        } else if let Some(sample) = self.output_buffer.take() {
+            return Some(sample);
+        }
+
+        if self.next_output_frame_pos_in_chunk == self.output {
+            self.next_output_frame_pos_in_chunk = 0;
+
+            self.next_input_frame();
+            while self.current_frame_pos_in_chunk != self.input {
+                self.next_input_frame();
+            }
+            self.current_frame_pos_in_chunk = 0;
+        } else {
+            let req_left_sample =
+                (self.input * self.next_output_frame_pos_in_chunk / self.output) % self.input;
+
+            while self.current_frame_pos_in_chunk != req_left_sample {
+                self.next_input_frame();
+                debug_assert!(self.current_frame_pos_in_chunk < self.input);
+            }
+        }
+
+        let numerator = (self.input * self.next_output_frame_pos_in_chunk) % self.output;
+
+        self.next_output_frame_pos_in_chunk += 1;
+
+        if self.current_frame.is_empty() && self.next_frame.is_empty() {
+            return None;
+        }
+
+        if self.next_frame.is_empty() {
+            let r = self.current_frame.remove(0);
+            self.output_buffer = self.current_frame.first().cloned();
+            self.current_frame.clear();
+            Some(r)
+        } else {
+            let ratio = numerator as f32 / self.output as f32;
+            self.output_buffer = Some(lerp(self.current_frame[1], self.next_frame[1], ratio));
+            Some(lerp(self.current_frame[0], self.next_frame[0], ratio))
+        }
+    }
+
+    pub fn volume_up(&mut self) {
+        self.volume += VOLUME_STEP as f32 / VOLUME_REDUCTION;
 
-        let generator = Arc::new(RwLock::new(Generator::new(
-            rate,
-            volume as f32 / VOLUME_REDUCTION,
-        )));
-        let gen = generator.clone();
-        let g = generator.clone();
+        if self.volume > MAX_VOLUME {
+            self.volume = MAX_VOLUME;
+        }
+    }
+
+    pub fn volume_down(&mut self) {
+        self.volume -= VOLUME_STEP as f32 / VOLUME_REDUCTION;
 
-        let elapsed = Arc::new(RwLock::new(Duration::default()));
-        let e = elapsed.clone();
+        if self.volume < MIN_VOLUME {
+            self.volume = MIN_VOLUME;
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum State {
+    Playing,
+    Paused,
+    Stopped,
+}
+
+pub struct Player {
+    pub stream: Stream,
+    pub sample_rate: usize,
+    pub state: State,
+    pub songs: Index<Song>,
+}
 
-        let (s, r) = unbounded();
+impl Player {
+    pub fn new(_device: String, _volume: u16, _cache: &[Song]) -> Self {
+        let device = cpal::default_host().default_output_device().unwrap();
+        let config = device.default_output_config().unwrap().config();
 
-        thread::spawn(move || {
-            let stream = device
-                .build_output_stream(
-                    &config.config(),
-                    move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
+        let stream = device
+            .build_output_stream(
+                &config,
+                move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
+                    if let Some(resampler) = unsafe { &mut RESAMPLER } {
                         for frame in data.chunks_mut(2) {
                             for sample in frame.iter_mut() {
-                                *sample = g.write().unwrap().next();
+                                *sample = resampler.next();
                             }
                         }
-                    },
-                    |err| panic!("{}", err),
-                )
-                .unwrap();
-
-            stream.play().unwrap();
-
-            loop {
-                *e.write().unwrap() = gen.read().unwrap().elapsed();
-
-                if let Ok(event) = r.recv_timeout(Duration::from_millis(8)) {
-                    match event {
-                        Event::Play => stream.play().unwrap(),
-                        Event::Pause => stream.pause().unwrap(),
-                        Event::SeekBy(duration) => gen.write().unwrap().seek_by(duration).unwrap(),
-                        Event::SeekTo(duration) => gen.write().unwrap().seek_to(duration).unwrap(),
-                        Event::Volume(volume) => gen.write().unwrap().set_volume(volume),
                     }
-                }
-            }
-        });
+                },
+                |err| panic!("{}", err),
+            )
+            .unwrap();
+
+        stream.play().unwrap();
 
         Self {
-            s,
-            playing: false,
-            volume,
-            elapsed,
-            duration: Duration::default(),
+            sample_rate: config.sample_rate.0 as usize,
+            stream,
+            state: State::Stopped,
             songs: Index::default(),
-            generator,
         }
     }
-    //TODO: Run update loop in the player not in the client.
+
     pub fn update(&mut self) {
-        if self.generator.read().unwrap().is_done() {
-            self.next();
+        if let Some(resampler) = unsafe { RESAMPLER.as_ref() } {
+            if resampler.finished {
+                self.next();
+            }
         }
     }
-    pub fn duration(&self) -> Duration {
-        self.duration
-    }
-    pub fn elapsed(&self) -> Duration {
-        *self.elapsed.read().unwrap()
-    }
-    pub fn is_empty(&self) -> bool {
-        self.songs.is_empty()
-    }
+
     pub fn add_songs(&mut self, songs: &[Song]) {
         self.songs.data.extend(songs.to_vec());
         if self.songs.selected().is_none() {
@@ -125,22 +313,42 @@ impl Player {
             self.play_selected();
         }
     }
-    pub fn get_volume(&self) -> u16 {
-        self.volume
+
+    pub fn previous(&mut self) {
+        self.songs.up();
+        self.play_selected();
+    }
+
+    pub fn next(&mut self) {
+        self.songs.down();
+        self.play_selected();
     }
+
+    pub fn play(&mut self, path: &str) {
+        unsafe {
+            RESAMPLER = Some(Resampler::new(self.sample_rate, path, 0.0));
+        }
+        self.state = State::Playing;
+    }
+
     pub fn play_selected(&mut self) {
         if let Some(song) = self.songs.selected() {
-            self.playing = true;
-            let mut gen = self.generator.write().unwrap();
-            gen.update(&song.path.clone());
-            gen.set_volume(self.real_volume());
-            self.duration = gen.duration();
+            unsafe {
+                RESAMPLER = Some(Resampler::new(
+                    self.sample_rate,
+                    song.path.to_str().unwrap(),
+                    song.gain as f32,
+                ));
+            }
+            self.state = State::Playing;
         }
     }
+
     pub fn play_index(&mut self, i: usize) {
         self.songs.select(Some(i));
         self.play_selected();
     }
+
     pub fn delete_index(&mut self, i: usize) {
         self.songs.data.remove(i);
 
@@ -163,113 +371,113 @@ impl Player {
             }
         };
     }
+
     pub fn clear(&mut self) {
         self.songs = Index::default();
-        self.generator.write().unwrap().stop();
+        self.state = State::Stopped;
     }
+
     pub fn clear_except_playing(&mut self) {
-        let selected = self.songs.selected().cloned();
-        let mut i = 0;
-        while i < self.songs.len() {
-            if Some(&self.songs.data[i]) != selected.as_ref() {
-                self.songs.data.remove(i);
-            } else {
-                i += 1;
-            }
+        if let Some(index) = self.songs.index() {
+            let playing = self.songs.data.remove(index);
+            self.songs = Index::new(vec![playing], Some(0));
         }
-        self.songs.select(Some(0));
     }
-    pub fn toggle_playback(&mut self) {
-        if self.playing {
-            self.pause();
-        } else {
-            self.play();
+
+    pub fn volume_up(&self) {
+        unsafe {
+            if let Some(resampler) = RESAMPLER.as_mut() {
+                resampler.volume_up();
+            }
         }
     }
-    fn play(&mut self) {
-        self.s.send(Event::Play).unwrap();
-        self.playing = true;
+
+    pub fn volume_down(&self) {
+        unsafe {
+            if let Some(resampler) = RESAMPLER.as_mut() {
+                resampler.volume_down();
+            }
+        }
     }
-    fn pause(&mut self) {
-        self.s.send(Event::Pause).unwrap();
-        self.playing = false;
+
+    pub fn volume(&self) -> u16 {
+        //TODO: Volume needs to be stored in the player
+        //otherwise it will go away when the next song is played.
+        unsafe {
+            let volume = RESAMPLER.as_ref().unwrap().volume;
+            (volume * VOLUME_REDUCTION).round() as u16
+        }
     }
-    pub fn previous(&mut self) {
-        self.songs.up();
-        self.play_selected();
+
+    pub fn duration(&self) -> Duration {
+        unsafe {
+            match RESAMPLER.as_ref() {
+                Some(resampler) => resampler.duration,
+                None => Duration::default(),
+            }
+        }
     }
-    pub fn next(&mut self) {
-        self.songs.down();
-        self.play_selected();
+
+    pub fn elapsed(&self) -> Duration {
+        unsafe {
+            match RESAMPLER.as_ref() {
+                Some(resampler) => resampler.elapsed,
+                None => Duration::default(),
+            }
+        }
     }
-    pub fn volume_up(&mut self) {
-        self.volume += VOLUME_STEP;
 
-        if self.volume > 100 {
-            self.volume = 100;
+    pub fn toggle_playback(&mut self) {
+        match self.state {
+            State::Playing => self.stream.pause().unwrap(),
+            State::Paused => self.stream.play().unwrap(),
+            State::Stopped => (),
         }
+    }
 
-        self.update_volume();
+    pub fn seek_by(&mut self, time: f32) {
+        unsafe {
+            if RESAMPLER.is_none() {
+                return;
+            }
+
+            self.seek_to(RESAMPLER.as_ref().unwrap().elapsed.as_secs_f32() + time);
+        }
     }
-    pub fn volume_down(&mut self) {
-        if self.volume != 0 {
-            self.volume -= VOLUME_STEP;
+
+    pub fn seek_to(&mut self, time: f32) {
+        if unsafe { RESAMPLER.is_none() } {
+            return;
         }
 
-        self.update_volume();
-    }
-    fn update_volume(&self) {
-        self.s.send(Event::Volume(self.real_volume())).unwrap();
-    }
-    fn real_volume(&self) -> f32 {
-        if let Some(song) = self.songs.selected() {
-            let volume = self.volume as f32 / VOLUME_REDUCTION;
-            //Calculate the volume with gain
-            if song.gain == 0.0 {
-                //Reduce the volume a little to match
-                //songs with replay gain information.
-                volume * 0.75
-            } else {
-                volume * song.gain as f32
+        let time = if time.is_sign_negative() { 0.0 } else { time };
+
+        let time = Duration::from_secs_f32(time);
+        unsafe {
+            match RESAMPLER.as_mut().unwrap().probed.format.seek(
+                SeekMode::Coarse,
+                SeekTo::Time {
+                    time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / 1_000_000_000.0),
+                    track_id: None,
+                },
+            ) {
+                Ok(_) => (),
+                Err(e) => match e {
+                    Error::SeekError(e) => match e {
+                        SeekErrorKind::OutOfRange => {
+                            self.next();
+                        }
+                        _ => panic!("{:?}", e),
+                    },
+                    _ => panic!("{}", e),
+                },
             }
-        } else {
-            self.volume as f32 / VOLUME_REDUCTION
         }
     }
-    pub fn is_playing(&self) -> bool {
-        self.playing
-    }
-    pub fn total_songs(&self) -> usize {
-        self.songs.len()
-    }
-    pub fn get_index(&self) -> &Index<Song> {
-        &self.songs
-    }
 
-    pub fn selected_song(&self) -> Option<&Song> {
-        self.songs.selected()
-    }
-    pub fn seek_by(&self, duration: f32) {
-        self.s.send(Event::SeekBy(duration)).unwrap();
-    }
-    pub fn seek_to(&self, duration: f32) {
-        self.s.send(Event::SeekTo(duration)).unwrap();
-    }
-    //TODO: Remove?
-    pub fn audio_devices() -> Vec<Device> {
-        let host_id = cpal::default_host().id();
-        let host = cpal::host_from_id(host_id).unwrap();
-
-        //FIXME: Getting just the output devies was too slow(150ms).
-        //Collecting every device is still slow but it's not as bad.
-        host.devices().unwrap().collect()
-    }
-    pub fn default_device() -> Device {
-        cpal::default_host().default_output_device().unwrap()
-    }
-    pub fn change_output_device(&mut self, _device: &Device) -> Result<(), StreamError> {
-        todo!()
+    pub fn is_playing(&self) -> bool {
+        State::Playing == self.state
     }
 }
 
-unsafe impl Sync for Player {}
+unsafe impl Send for Player {}
diff --git a/gonk-player/src/sample_processor.rs b/gonk-player/src/sample_processor.rs
deleted file mode 100644
index 9c5284c1..00000000
--- a/gonk-player/src/sample_processor.rs
+++ /dev/null
@@ -1,246 +0,0 @@
-use crate::sample_rate::SampleRateConverter;
-use std::{fs::File, io::ErrorKind, path::Path, time::Duration};
-use symphonia::{
-    core::{
-        audio::{SampleBuffer, SignalSpec},
-        codecs::{Decoder, DecoderOptions},
-        errors::{Error, SeekErrorKind},
-        formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
-        io::MediaSourceStream,
-        meta::MetadataOptions,
-        probe::Hint,
-        units::Time,
-    },
-    default::get_probe,
-};
-
-pub struct Generator {
-    processor: Option<Processor>,
-    sample_rate: u32,
-    volume: f32,
-}
-
-impl Generator {
-    pub fn new(sample_rate: u32, volume: f32) -> Self {
-        Self {
-            processor: None,
-            sample_rate,
-            volume,
-        }
-    }
-    pub fn next(&mut self) -> f32 {
-        if let Some(processor) = &mut self.processor {
-            if processor.finished {
-                0.0
-            } else {
-                processor.next_sample()
-            }
-        } else {
-            0.0
-        }
-    }
-    pub fn seek_to(&mut self, time: f32) -> Result<(), ()> {
-        if let Some(processor) = &mut self.processor {
-            processor.seek_to(time);
-            Ok(())
-        } else {
-            Err(())
-        }
-    }
-    pub fn elapsed(&self) -> Duration {
-        if let Some(processor) = &self.processor {
-            processor.elapsed
-        } else {
-            Duration::default()
-        }
-    }
-    pub fn duration(&self) -> Duration {
-        if let Some(processor) = &self.processor {
-            processor.duration
-        } else {
-            Duration::default()
-        }
-    }
-    pub fn seek_by(&mut self, time: f32) -> Result<(), ()> {
-        if let Some(processor) = &mut self.processor {
-            processor.seek_by(time);
-            Ok(())
-        } else {
-            Err(())
-        }
-    }
-    pub fn set_volume(&mut self, volume: f32) {
-        self.volume = volume;
-        if let Some(processor) = &mut self.processor {
-            processor.volume = volume;
-        }
-    }
-    pub fn update(&mut self, path: &Path) {
-        self.processor = Some(Processor::new(self.sample_rate, path, self.volume));
-    }
-    pub fn is_done(&self) -> bool {
-        if let Some(processor) = &self.processor {
-            processor.finished
-        } else {
-            false
-        }
-    }
-    pub fn stop(&mut self) {
-        self.processor = None;
-    }
-}
-
-pub struct Processor {
-    pub decoder: Box<dyn Decoder>,
-    pub format: Box<dyn FormatReader>,
-    pub spec: SignalSpec,
-    pub capacity: u64,
-    pub converter: SampleRateConverter,
-    pub finished: bool,
-    pub duration: Duration,
-    pub elapsed: Duration,
-    pub volume: f32,
-
-    pub input: u32,
-    pub output: u32,
-}
-
-impl Processor {
-    pub fn new(output_rate: u32, path: &Path, volume: f32) -> Self {
-        let source = Box::new(File::open(path).unwrap());
-
-        let mss = MediaSourceStream::new(source, Default::default());
-
-        let mut probed = get_probe()
-            .format(
-                &Hint::default(),
-                mss,
-                &FormatOptions {
-                    prebuild_seek_index: true,
-                    seek_index_fill_rate: 1,
-                    ..Default::default()
-                },
-                &MetadataOptions::default(),
-            )
-            .unwrap();
-
-        let track = probed.format.default_track().unwrap();
-
-        let duration = if let Some(tb) = track.codec_params.time_base {
-            let n_frames = track.codec_params.n_frames.unwrap();
-            let time = tb.calc_time(n_frames);
-            Duration::from_secs(time.seconds) + Duration::from_secs_f64(time.frac)
-        } else {
-            panic!("Could not decode track duration.");
-        };
-
-        let mut decoder = symphonia::default::get_codecs()
-            .make(&track.codec_params, &DecoderOptions::default())
-            .unwrap();
-
-        let current_frame = probed.format.next_packet().unwrap();
-        let decoded = decoder.decode(&current_frame).unwrap();
-
-        let spec = decoded.spec().to_owned();
-        let capacity = decoded.capacity() as u64;
-
-        let mut sample_buffer = SampleBuffer::<f32>::new(capacity, spec);
-        sample_buffer.copy_interleaved_ref(decoded);
-
-        Self {
-            format: probed.format,
-            decoder,
-            spec,
-            capacity,
-            duration,
-            elapsed: Duration::default(),
-            converter: SampleRateConverter::new(
-                sample_buffer.samples().to_vec().into_iter(),
-                spec.rate,
-                output_rate,
-            ),
-            finished: false,
-            volume,
-            input: spec.rate,
-            output: output_rate,
-        }
-    }
-    pub fn next_sample(&mut self) -> f32 {
-        loop {
-            if self.finished {
-                return 0.0;
-            } else if let Some(sample) = self.converter.next() {
-                return sample * self.volume;
-            } else {
-                self.update();
-            }
-        }
-    }
-    pub fn update(&mut self) {
-        if self.finished {
-            return;
-        }
-
-        let mut decode_errors: usize = 0;
-        const MAX_DECODE_ERRORS: usize = 3;
-        loop {
-            match self.format.next_packet() {
-                Ok(packet) => {
-                    let decoded = self.decoder.decode(&packet).unwrap();
-                    let mut buffer = SampleBuffer::<f32>::new(self.capacity, self.spec);
-                    buffer.copy_interleaved_ref(decoded);
-
-                    self.converter = SampleRateConverter::new(
-                        buffer.samples().to_vec().into_iter(),
-                        self.input,
-                        self.output,
-                    );
-
-                    //Update elapsed
-                    let ts = packet.ts();
-                    let track = self.format.default_track().unwrap();
-                    let tb = track.codec_params.time_base.unwrap();
-                    let t = tb.calc_time(ts);
-                    self.elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
-                    return;
-                }
-                Err(e) => match e {
-                    Error::DecodeError(e) => {
-                        decode_errors += 1;
-                        if decode_errors > MAX_DECODE_ERRORS {
-                            panic!("{:?}", e);
-                        }
-                    }
-                    Error::IoError(e) if e.kind() == ErrorKind::UnexpectedEof => {
-                        self.finished = true;
-                        return;
-                    }
-                    _ => panic!("{:?}", e),
-                },
-            };
-        }
-    }
-    pub fn seek_by(&mut self, time: f32) {
-        let time = self.elapsed.as_secs_f32() + time;
-        self.seek_to(time);
-    }
-    pub fn seek_to(&mut self, time: f32) {
-        let time = Duration::from_secs_f32(time);
-        match self.format.seek(
-            SeekMode::Coarse,
-            SeekTo::Time {
-                time: Time::new(time.as_secs(), time.subsec_nanos() as f64 / 1_000_000_000.0),
-                track_id: None,
-            },
-        ) {
-            Ok(_) => (),
-            Err(e) => match e {
-                Error::SeekError(e) => match e {
-                    SeekErrorKind::OutOfRange => self.finished = true,
-                    _ => panic!("{:?}", e),
-                },
-                _ => panic!("{}", e),
-            },
-        }
-    }
-}
diff --git a/gonk-player/src/sample_rate.rs b/gonk-player/src/sample_rate.rs
deleted file mode 100644
index df7738c6..00000000
--- a/gonk-player/src/sample_rate.rs
+++ /dev/null
@@ -1,140 +0,0 @@
-use std::{mem, vec::IntoIter};
-
-#[inline]
-const fn gcd(a: u32, b: u32) -> u32 {
-    if b == 0 {
-        a
-    } else {
-        gcd(b, a % b)
-    }
-}
-
-#[inline]
-fn lerp(a: f32, b: f32, t: f32) -> f32 {
-    a + t * (b - a)
-}
-
-pub struct SampleRateConverter {
-    /// The iterator that gives us samples.
-    buffer: IntoIter<f32>,
-
-    ///Input sample rate - interpolation factor.
-    input: u32,
-    ///Output sample rate - decimation factor.
-    output: u32,
-
-    /// One sample per channel, extracted from `input`.
-    current_frame: Vec<f32>,
-    /// Position of `current_sample` modulo `from`.
-    ///
-    /// `0..input / gcd`
-    current_frame_pos_in_chunk: u32,
-
-    /// The samples right after `current_sample` (one per channel), extracted from `input`.
-    next_frame: Vec<f32>,
-    /// The position of the next sample that the iterator should return, modulo `to`.
-    /// This counter is incremented (modulo `to`) every time the iterator is called.
-    ///
-    /// `0..output / gcd`
-    next_output_frame_pos_in_chunk: u32,
-
-    output_buffer: Option<f32>,
-}
-
-impl SampleRateConverter {
-    pub fn new(mut buffer: IntoIter<f32>, input: u32, output: u32) -> SampleRateConverter {
-        debug_assert!(input >= 1);
-        debug_assert!(output >= 1);
-
-        let gcd = gcd(input, output);
-        let (current_frame, next_frame) = if input == output {
-            (Vec::new(), Vec::new())
-        } else {
-            (
-                vec![buffer.next().unwrap(), buffer.next().unwrap()],
-                vec![buffer.next().unwrap(), buffer.next().unwrap()],
-            )
-        };
-
-        SampleRateConverter {
-            buffer,
-            input: input / gcd,
-            output: output / gcd,
-            current_frame_pos_in_chunk: 0,
-            next_output_frame_pos_in_chunk: 0,
-            current_frame,
-            next_frame,
-            output_buffer: None,
-        }
-    }
-
-    fn next_input_frame(&mut self) {
-        self.current_frame = mem::take(&mut self.next_frame);
-
-        if let Some(sample) = self.buffer.next() {
-            self.next_frame.push(sample);
-        }
-
-        if let Some(sample) = self.buffer.next() {
-            self.next_frame.push(sample);
-        }
-
-        self.current_frame_pos_in_chunk += 1;
-    }
-
-    pub fn next(&mut self) -> Option<f32> {
-        if self.input == self.output {
-            return self.buffer.next();
-        } else if let Some(sample) = self.output_buffer.take() {
-            return Some(sample);
-        }
-
-        // The frame we are going to return from this function will be a linear interpolation
-        // between `self.current_frame` and `self.next_frame`.
-        if self.next_output_frame_pos_in_chunk == self.output {
-            // If we jump to the next frame, we reset the whole state.
-            self.next_output_frame_pos_in_chunk = 0;
-
-            self.next_input_frame();
-            while self.current_frame_pos_in_chunk != self.input {
-                self.next_input_frame();
-            }
-            self.current_frame_pos_in_chunk = 0;
-        } else {
-            // Finding the position of the first sample of the linear interpolation.
-            let req_left_sample =
-                (self.input * self.next_output_frame_pos_in_chunk / self.output) % self.input;
-
-            // Advancing `self.current_frame`, `self.next_frame` and
-            // `self.current_frame_pos_in_chunk` until the latter variable
-            // matches `req_left_sample`.
-            while self.current_frame_pos_in_chunk != req_left_sample {
-                self.next_input_frame();
-                debug_assert!(self.current_frame_pos_in_chunk < self.input);
-            }
-        }
-
-        // Merging `self.current_frame` and `self.next_frame` into `self.output_buffer`.
-        // Note that `self.output_buffer` can be truncated if there is not enough data in
-        // `self.next_frame`.
-        let numerator = (self.input * self.next_output_frame_pos_in_chunk) % self.output;
-
-        // Incrementing the counter for the next iteration.
-        self.next_output_frame_pos_in_chunk += 1;
-
-        if self.current_frame.is_empty() && self.next_frame.is_empty() {
-            return None;
-        }
-
-        if self.next_frame.is_empty() {
-            let r = self.current_frame.remove(0);
-            self.output_buffer = self.current_frame.first().cloned();
-            self.current_frame.clear();
-            Some(r)
-        } else {
-            let ratio = numerator as f32 / self.output as f32;
-            self.output_buffer = Some(lerp(self.current_frame[1], self.next_frame[1], ratio));
-            Some(lerp(self.current_frame[0], self.next_frame[0], ratio))
-        }
-    }
-}
diff --git a/gonk-player/src/song.rs b/gonk-player/src/song.rs
index b4d1d764..78fd6eb2 100644
--- a/gonk-player/src/song.rs
+++ b/gonk-player/src/song.rs
@@ -12,8 +12,8 @@ use symphonia::{
     default::get_probe,
 };
 
-fn db_to_amplitude(db: f64) -> f64 {
-    10.0_f64.powf(db / 20.0_f64)
+fn db_to_amplitude(db: f32) -> f32 {
+    10.0_f32.powf(db / 20.0)
 }
 
 #[derive(Debug, Clone, Default)]
@@ -22,7 +22,7 @@ pub struct Song {
     pub disc: u64,
     pub number: u64,
     pub path: PathBuf,
-    pub gain: f64,
+    pub gain: f32,
     pub album: String,
     pub artist: String,
     pub id: Option<usize>,
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index 02674abd..4a71aae4 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -381,7 +381,7 @@ fn main() {
         }
     }
 
-    query::set_volume(player.volume);
+    query::set_volume(player.volume());
 
     let ids: Vec<usize> = player
         .songs
diff --git a/gonk/src/queue.rs b/gonk/src/queue.rs
index 4f9b9700..efdfb58f 100644
--- a/gonk/src/queue.rs
+++ b/gonk/src/queue.rs
@@ -84,7 +84,7 @@ pub fn draw(queue: &mut Queue, player: &mut Player, f: &mut Frame, event: Option
     draw_seeker(player, f, chunks[2]);
 
     //Don't handle mouse input when the queue is empty.
-    if player.is_empty() {
+    if player.songs.is_empty() {
         return;
     }
 
@@ -100,7 +100,7 @@ pub fn draw(queue: &mut Queue, player: &mut Player, f: &mut Frame, event: Option
             && size.height > 15
         {
             let ratio = x as f32 / size.width as f32;
-            let duration = player.duration.as_secs_f32();
+            let duration = player.duration().as_secs_f32();
             player.seek_to(duration * ratio);
         }
 
@@ -145,7 +145,7 @@ fn draw_header(player: &mut Player, f: &mut Frame, area: Rect) {
         draw_title(player, f, area);
     }
 
-    let volume = Spans::from(format!("Vol: {}%─╮", player.volume));
+    let volume = Spans::from(format!("Vol: {}%─╮", player.volume()));
     f.render_widget(Paragraph::new(volume).alignment(Alignment::Right), area);
 }
 
@@ -326,8 +326,8 @@ fn draw_seeker(player: &mut Player, f: &mut Frame, area: Rect) {
         );
     }
 
-    let elapsed = player.elapsed().as_secs_f64();
-    let duration = player.duration.as_secs_f64();
+    let elapsed = player.elapsed().as_secs_f32();
+    let duration = player.duration().as_secs_f32();
 
     let seeker = format!(
         "{:02}:{:02}/{:02}:{:02}",
@@ -352,7 +352,7 @@ fn draw_seeker(player: &mut Player, f: &mut Frame, area: Rect) {
                     .border_type(BorderType::Rounded),
             )
             .gauge_style(Style::default().fg(COLORS.seeker))
-            .ratio(ratio)
+            .ratio(ratio as f64)
             .label(seeker),
         area,
     );
diff --git a/gonk/src/settings.rs b/gonk/src/settings.rs
index b9c2f0a4..16d9b862 100644
--- a/gonk/src/settings.rs
+++ b/gonk/src/settings.rs
@@ -1,3 +1,4 @@
+#![allow(unused)]
 use crate::{widgets::*, Frame, Input};
 use gonk_database::query;
 use gonk_player::{Device, DeviceTrait, Index, Player};
@@ -14,26 +15,28 @@ pub struct Settings {
 
 impl Settings {
     pub fn new() -> Self {
-        let default_device = Player::default_device();
-        let wanted_device = query::playback_device();
+        // let default_device = Player::default_device();
+        // let wanted_device = query::playback_device();
 
-        let devices = Player::audio_devices();
+        // let devices = Player::audio_devices();
 
-        let current_device = if devices
-            .iter()
-            .flat_map(DeviceTrait::name)
-            .any(|x| x == wanted_device)
-        {
-            wanted_device
-        } else {
-            let name = default_device.name().unwrap();
-            query::set_playback_device(&name);
-            name
-        };
+        // let current_device = if devices
+        //     .iter()
+        //     .flat_map(DeviceTrait::name)
+        //     .any(|x| x == wanted_device)
+        // {
+        //     wanted_device
+        // } else {
+        //     let name = default_device.name().unwrap();
+        //     query::set_playback_device(&name);
+        //     name
+        // };
 
         Self {
-            devices: Index::new(devices, Some(0)),
-            current_device,
+            // devices: Index::new(devices, Some(0)),
+            // current_device,
+            devices: Index::default(),
+            current_device: String::new(),
         }
     }
 }
@@ -53,17 +56,17 @@ impl Input for Settings {
 }
 
 pub fn on_enter(settings: &mut Settings, player: &mut Player) {
-    if let Some(device) = settings.devices.selected() {
-        match player.change_output_device(device) {
-            Ok(_) => {
-                let name = device.name().unwrap();
-                query::set_playback_device(&name);
-                settings.current_device = name;
-            }
-            //TODO: Print error in status bar
-            Err(e) => panic!("{:?}", e),
-        }
-    }
+    // if let Some(device) = settings.devices.selected() {
+    //     match player.change_output_device(device) {
+    //         Ok(_) => {
+    //             let name = device.name().unwrap();
+    //             query::set_playback_device(&name);
+    //             settings.current_device = name;
+    //         }
+    //         //TODO: Print error in status bar
+    //         Err(e) => panic!("{:?}", e),
+    //     }
+    // }
 }
 
 #[allow(unused)]
diff --git a/gonk/src/status_bar.rs b/gonk/src/status_bar.rs
index 28bf08b8..2d54bb6c 100644
--- a/gonk/src/status_bar.rs
+++ b/gonk/src/status_bar.rs
@@ -55,7 +55,7 @@ pub fn update(status_bar: &mut StatusBar, db_busy: bool, player: &Player) {
             //before triggering an update
             //the status bar will stay open
             //without the users permission.
-            if player.is_empty() {
+            if player.songs.is_empty() {
                 status_bar.hidden = true;
             }
         }
@@ -126,7 +126,7 @@ pub fn draw(status_bar: &mut StatusBar, area: Rect, f: &mut Frame, busy: bool, p
 
     //TODO: Draw mini progress bar here.
     let text = if player.is_playing() {
-        format!("Vol: {}% ", player.volume)
+        format!("Vol: {}% ", player.volume())
     } else {
         String::from("Paused ")
     };

From 1815fa211b9a107e791cdab11c90b60351303ce5 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 14:23:01 +0930
Subject: [PATCH 28/40] fix: volume was not saved when changing tracks

---
 gonk-player/src/lib.rs | 62 ++++++++++++++++--------------------------
 gonk/src/main.rs       |  2 +-
 gonk/src/queue.rs      |  2 +-
 gonk/src/status_bar.rs |  2 +-
 4 files changed, 26 insertions(+), 42 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 9892b424..072d3832 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -1,4 +1,3 @@
-#![feature(const_fn_floating_point_arithmetic)]
 use cpal::{
     traits::{HostTrait, StreamTrait},
     Stream,
@@ -35,7 +34,7 @@ const fn gcd(a: usize, b: usize) -> usize {
 }
 
 #[inline]
-const fn lerp(a: f32, b: f32, t: f32) -> f32 {
+fn lerp(a: f32, b: f32, t: f32) -> f32 {
     a + t * (b - a)
 }
 
@@ -43,8 +42,6 @@ static mut RESAMPLER: Option<Resampler> = None;
 
 const VOLUME_STEP: u16 = 5;
 const VOLUME_REDUCTION: f32 = 600.0;
-const MAX_VOLUME: f32 = 100.0 / VOLUME_REDUCTION;
-const MIN_VOLUME: f32 = 0.0 / VOLUME_REDUCTION;
 
 pub struct Resampler {
     probed: ProbeResult,
@@ -74,7 +71,7 @@ pub struct Resampler {
 }
 
 impl Resampler {
-    pub fn new(output: usize, path: &str, gain: f32) -> Self {
+    pub fn new(output: usize, path: &str, volume: u16, gain: f32) -> Self {
         let source = Box::new(File::open(path).unwrap());
         let mss = MediaSourceStream::new(source, Default::default());
 
@@ -131,7 +128,7 @@ impl Resampler {
             current_frame,
             next_frame,
             output_buffer: None,
-            volume: 15.0 / VOLUME_REDUCTION,
+            volume: volume as f32 / VOLUME_REDUCTION,
             duration,
             elapsed,
             time_base,
@@ -236,20 +233,8 @@ impl Resampler {
         }
     }
 
-    pub fn volume_up(&mut self) {
-        self.volume += VOLUME_STEP as f32 / VOLUME_REDUCTION;
-
-        if self.volume > MAX_VOLUME {
-            self.volume = MAX_VOLUME;
-        }
-    }
-
-    pub fn volume_down(&mut self) {
-        self.volume -= VOLUME_STEP as f32 / VOLUME_REDUCTION;
-
-        if self.volume < MIN_VOLUME {
-            self.volume = MIN_VOLUME;
-        }
+    pub fn set_volume(&mut self, volume: u16) {
+        self.volume = volume as f32 / VOLUME_REDUCTION;
     }
 }
 
@@ -265,10 +250,11 @@ pub struct Player {
     pub sample_rate: usize,
     pub state: State,
     pub songs: Index<Song>,
+    pub volume: u16,
 }
 
 impl Player {
-    pub fn new(_device: String, _volume: u16, _cache: &[Song]) -> Self {
+    pub fn new(_device: String, volume: u16, _cache: &[Song]) -> Self {
         let device = cpal::default_host().default_output_device().unwrap();
         let config = device.default_output_config().unwrap().config();
 
@@ -293,6 +279,7 @@ impl Player {
         Self {
             sample_rate: config.sample_rate.0 as usize,
             stream,
+            volume,
             state: State::Stopped,
             songs: Index::default(),
         }
@@ -326,7 +313,7 @@ impl Player {
 
     pub fn play(&mut self, path: &str) {
         unsafe {
-            RESAMPLER = Some(Resampler::new(self.sample_rate, path, 0.0));
+            RESAMPLER = Some(Resampler::new(self.sample_rate, path, self.volume, 0.0));
         }
         self.state = State::Playing;
     }
@@ -337,6 +324,7 @@ impl Player {
                 RESAMPLER = Some(Resampler::new(
                     self.sample_rate,
                     song.path.to_str().unwrap(),
+                    self.volume,
                     song.gain as f32,
                 ));
             }
@@ -384,28 +372,24 @@ impl Player {
         }
     }
 
-    pub fn volume_up(&self) {
-        unsafe {
-            if let Some(resampler) = RESAMPLER.as_mut() {
-                resampler.volume_up();
-            }
+    pub fn volume_up(&mut self) {
+        self.volume += VOLUME_STEP;
+        if self.volume > 100 {
+            self.volume = 100;
         }
-    }
 
-    pub fn volume_down(&self) {
-        unsafe {
-            if let Some(resampler) = RESAMPLER.as_mut() {
-                resampler.volume_down();
-            }
+        if let Some(resampler) = unsafe { RESAMPLER.as_mut() } {
+            resampler.set_volume(self.volume);
         }
     }
 
-    pub fn volume(&self) -> u16 {
-        //TODO: Volume needs to be stored in the player
-        //otherwise it will go away when the next song is played.
-        unsafe {
-            let volume = RESAMPLER.as_ref().unwrap().volume;
-            (volume * VOLUME_REDUCTION).round() as u16
+    pub fn volume_down(&mut self) {
+        if self.volume != 0 {
+            self.volume -= VOLUME_STEP;
+        }
+
+        if let Some(resampler) = unsafe { RESAMPLER.as_mut() } {
+            resampler.set_volume(self.volume);
         }
     }
 
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index 4a71aae4..02674abd 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -381,7 +381,7 @@ fn main() {
         }
     }
 
-    query::set_volume(player.volume());
+    query::set_volume(player.volume);
 
     let ids: Vec<usize> = player
         .songs
diff --git a/gonk/src/queue.rs b/gonk/src/queue.rs
index efdfb58f..0f612ac5 100644
--- a/gonk/src/queue.rs
+++ b/gonk/src/queue.rs
@@ -145,7 +145,7 @@ fn draw_header(player: &mut Player, f: &mut Frame, area: Rect) {
         draw_title(player, f, area);
     }
 
-    let volume = Spans::from(format!("Vol: {}%─╮", player.volume()));
+    let volume = Spans::from(format!("Vol: {}%─╮", player.volume));
     f.render_widget(Paragraph::new(volume).alignment(Alignment::Right), area);
 }
 
diff --git a/gonk/src/status_bar.rs b/gonk/src/status_bar.rs
index 2d54bb6c..b1c723ed 100644
--- a/gonk/src/status_bar.rs
+++ b/gonk/src/status_bar.rs
@@ -126,7 +126,7 @@ pub fn draw(status_bar: &mut StatusBar, area: Rect, f: &mut Frame, busy: bool, p
 
     //TODO: Draw mini progress bar here.
     let text = if player.is_playing() {
-        format!("Vol: {}% ", player.volume())
+        format!("Vol: {}% ", player.volume)
     } else {
         String::from("Paused ")
     };

From bbf47cf11ad7786a8b38a70cd05583f73d1cb437 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 14:44:26 +0930
Subject: [PATCH 29/40] fixed play and pause not working

---
 gonk-player/src/lib.rs | 105 +++++++++++++++++++++++++++--------------
 gonk/src/main.rs       |   2 +-
 2 files changed, 70 insertions(+), 37 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 072d3832..9065e879 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -2,7 +2,7 @@ use cpal::{
     traits::{HostTrait, StreamTrait},
     Stream,
 };
-use std::{fs::File, time::Duration, vec::IntoIter};
+use std::{fs::File, io::ErrorKind, time::Duration, vec::IntoIter};
 use symphonia::{
     core::{
         audio::SampleBuffer,
@@ -42,6 +42,7 @@ static mut RESAMPLER: Option<Resampler> = None;
 
 const VOLUME_STEP: u16 = 5;
 const VOLUME_REDUCTION: f32 = 600.0;
+const MAX_DECODE_ERRORS: usize = 3;
 
 pub struct Resampler {
     probed: ProbeResult,
@@ -138,7 +139,9 @@ impl Resampler {
     }
 
     pub fn next(&mut self) -> f32 {
-        if let Some(smp) = self.next_sample() {
+        if self.finished {
+            0.0
+        } else if let Some(smp) = self.next_sample() {
             if self.gain == 0.0 {
                 //Reduce the volume a little to match
                 //songs with replay gain information.
@@ -147,30 +150,53 @@ impl Resampler {
                 smp * self.volume * self.gain
             }
         } else {
-            let next_packet = self.probed.format.next_packet().unwrap();
-            let decoded = self.decoder.decode(&next_packet).unwrap();
-            let mut buffer = SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
-            buffer.copy_interleaved_ref(decoded);
-            self.buffer = buffer.samples().to_vec().into_iter();
-
-            let ts = next_packet.ts();
-            let t = self.time_base.calc_time(ts);
-            self.elapsed = Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
-
-            if self.input == self.output {
-                self.current_frame = Vec::new();
-                self.next_frame = Vec::new();
-            } else {
-                self.current_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
-                self.next_frame = vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
-            }
+            loop {
+                let mut decode_errors: usize = 0;
+                match self.probed.format.next_packet() {
+                    Ok(next_packet) => {
+                        let decoded = self.decoder.decode(&next_packet).unwrap();
+                        let mut buffer =
+                            SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
+                        buffer.copy_interleaved_ref(decoded);
+                        self.buffer = buffer.samples().to_vec().into_iter();
+
+                        let ts = next_packet.ts();
+                        let t = self.time_base.calc_time(ts);
+                        self.elapsed =
+                            Duration::from_secs(t.seconds) + Duration::from_secs_f64(t.frac);
+
+                        if self.input == self.output {
+                            self.current_frame = Vec::new();
+                            self.next_frame = Vec::new();
+                        } else {
+                            self.current_frame =
+                                vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
+                            self.next_frame =
+                                vec![self.buffer.next().unwrap(), self.buffer.next().unwrap()];
+                        }
 
-            self.current_frame_pos_in_chunk = 0;
-            self.next_output_frame_pos_in_chunk = 0;
+                        self.current_frame_pos_in_chunk = 0;
+                        self.next_output_frame_pos_in_chunk = 0;
 
-            debug_assert!(self.output_buffer.is_none());
+                        debug_assert!(self.output_buffer.is_none());
 
-            self.next()
+                        return self.next();
+                    }
+                    Err(e) => match e {
+                        Error::DecodeError(e) => {
+                            decode_errors += 1;
+                            if decode_errors > MAX_DECODE_ERRORS {
+                                panic!("{:?}", e);
+                            }
+                        }
+                        Error::IoError(e) if e.kind() == ErrorKind::UnexpectedEof => {
+                            self.finished = true;
+                            return 0.0;
+                        }
+                        _ => panic!("{:?}", e),
+                    },
+                }
+            }
         }
     }
 
@@ -254,7 +280,7 @@ pub struct Player {
 }
 
 impl Player {
-    pub fn new(_device: String, volume: u16, _cache: &[Song]) -> Self {
+    pub fn new(_device: String, volume: u16, cache: &[Song]) -> Self {
         let device = cpal::default_host().default_output_device().unwrap();
         let config = device.default_output_config().unwrap().config();
 
@@ -276,13 +302,16 @@ impl Player {
 
         stream.play().unwrap();
 
-        Self {
+        let mut player = Self {
             sample_rate: config.sample_rate.0 as usize,
             stream,
             volume,
             state: State::Stopped,
             songs: Index::default(),
-        }
+        };
+        player.add_songs(cache);
+        player.pause();
+        player
     }
 
     pub fn update(&mut self) {
@@ -299,6 +328,7 @@ impl Player {
             self.songs.select(Some(0));
             self.play_selected();
         }
+        // self.play();
     }
 
     pub fn previous(&mut self) {
@@ -311,13 +341,6 @@ impl Player {
         self.play_selected();
     }
 
-    pub fn play(&mut self, path: &str) {
-        unsafe {
-            RESAMPLER = Some(Resampler::new(self.sample_rate, path, self.volume, 0.0));
-        }
-        self.state = State::Playing;
-    }
-
     pub fn play_selected(&mut self) {
         if let Some(song) = self.songs.selected() {
             unsafe {
@@ -328,7 +351,7 @@ impl Player {
                     song.gain as f32,
                 ));
             }
-            self.state = State::Playing;
+            self.play();
         }
     }
 
@@ -413,12 +436,22 @@ impl Player {
 
     pub fn toggle_playback(&mut self) {
         match self.state {
-            State::Playing => self.stream.pause().unwrap(),
-            State::Paused => self.stream.play().unwrap(),
+            State::Playing => self.pause(),
+            State::Paused => self.play(),
             State::Stopped => (),
         }
     }
 
+    pub fn play(&mut self) {
+        self.stream.play().unwrap();
+        self.state = State::Playing;
+    }
+
+    pub fn pause(&mut self) {
+        self.stream.pause().unwrap();
+        self.state = State::Paused;
+    }
+
     pub fn seek_by(&mut self, time: f32) {
         unsafe {
             if RESAMPLER.is_none() {
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index 02674abd..ea4db85c 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -161,7 +161,7 @@ fn main() {
     let mut last_tick = Instant::now();
     let mut busy = false;
 
-    //Using the a thread here is roughly 7ms faster.
+    //Using the another thread here is roughly 7ms faster.
     let mut player = player.join().unwrap();
 
     loop {

From 41076f15bfce4d78ce805d3fc6ac42a2edce55cc Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 14:49:31 +0930
Subject: [PATCH 30/40] fixed player being unable to stop

---
 gonk-player/src/lib.rs | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 9065e879..971858d8 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -288,11 +288,14 @@ impl Player {
             .build_output_stream(
                 &config,
                 move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
-                    if let Some(resampler) = unsafe { &mut RESAMPLER } {
-                        for frame in data.chunks_mut(2) {
-                            for sample in frame.iter_mut() {
-                                *sample = resampler.next();
-                            }
+                    for frame in data.chunks_mut(2) {
+                        for sample in frame.iter_mut() {
+                            let smp = if let Some(resampler) = unsafe { &mut RESAMPLER } {
+                                resampler.next()
+                            } else {
+                                0.0
+                            };
+                            *sample = smp;
                         }
                     }
                 },
@@ -386,6 +389,9 @@ impl Player {
     pub fn clear(&mut self) {
         self.songs = Index::default();
         self.state = State::Stopped;
+        unsafe {
+            RESAMPLER = None;
+        }
     }
 
     pub fn clear_except_playing(&mut self) {

From f774172d589b0b6e765849354f628026e163fef0 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 14:59:25 +0930
Subject: [PATCH 31/40] fixed click at start up

---
 gonk-player/src/lib.rs | 23 +++++++++++++----------
 gonk/src/main.rs       |  5 +++++
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 971858d8..78ca3f32 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -305,16 +305,15 @@ impl Player {
 
         stream.play().unwrap();
 
-        let mut player = Self {
+        let index = if cache.is_empty() { None } else { Some(0) };
+
+        Self {
             sample_rate: config.sample_rate.0 as usize,
             stream,
             volume,
             state: State::Stopped,
-            songs: Index::default(),
-        };
-        player.add_songs(cache);
-        player.pause();
-        player
+            songs: Index::new(cache.to_vec(), index),
+        }
     }
 
     pub fn update(&mut self) {
@@ -441,10 +440,14 @@ impl Player {
     }
 
     pub fn toggle_playback(&mut self) {
-        match self.state {
-            State::Playing => self.pause(),
-            State::Paused => self.play(),
-            State::Stopped => (),
+        if unsafe { RESAMPLER.is_none() } {
+            self.play_selected()
+        } else {
+            match self.state {
+                State::Playing => self.pause(),
+                State::Paused => self.play(),
+                State::Stopped => (),
+            }
         }
     }
 
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index ea4db85c..f89dfe10 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -164,6 +164,11 @@ fn main() {
     //Using the another thread here is roughly 7ms faster.
     let mut player = player.join().unwrap();
 
+    //If there are songs in the queue, display the queue.
+    if !player.songs.is_empty() {
+        mode = Mode::Queue;
+    }
+
     loop {
         match db.state() {
             State::Busy => busy = true,

From 90ff212e0f2781fe3edb01fe7fc5a916dbf40558 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 18:33:47 +0930
Subject: [PATCH 32/40] fixed heap overrun

---
 gonk-player/src/index.rs |  6 ------
 gonk-player/src/lib.rs   | 36 ++++++++++++++++++++++++------------
 gonk/src/playlist.rs     |  1 +
 3 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/gonk-player/src/index.rs b/gonk-player/src/index.rs
index e779c135..c08d905c 100644
--- a/gonk-player/src/index.rs
+++ b/gonk-player/src/index.rs
@@ -90,12 +90,6 @@ impl<T> Index<T> {
     }
 }
 
-impl<T: Clone> Index<T> {
-    pub fn clone(&self) -> Vec<T> {
-        self.data.to_owned()
-    }
-}
-
 impl<T> Default for Index<T> {
     fn default() -> Self {
         Self {
diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 78ca3f32..b1f5e4f2 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -72,9 +72,8 @@ pub struct Resampler {
 }
 
 impl Resampler {
-    pub fn new(output: usize, path: &str, volume: u16, gain: f32) -> Self {
-        let source = Box::new(File::open(path).unwrap());
-        let mss = MediaSourceStream::new(source, Default::default());
+    pub fn new(output: usize, file: File, volume: u16, gain: f32) -> Self {
+        let mss = MediaSourceStream::new(Box::new(file), Default::default());
 
         let mut probed = get_probe()
             .format(
@@ -291,7 +290,14 @@ impl Player {
                     for frame in data.chunks_mut(2) {
                         for sample in frame.iter_mut() {
                             let smp = if let Some(resampler) = unsafe { &mut RESAMPLER } {
-                                resampler.next()
+                                //Makes sure that the next sample isn't
+                                //read in the middle of changing songs.
+                                //idk reliable this is.
+                                if resampler.finished {
+                                    0.0
+                                } else {
+                                    resampler.next()
+                                }
                             } else {
                                 0.0
                             };
@@ -330,7 +336,6 @@ impl Player {
             self.songs.select(Some(0));
             self.play_selected();
         }
-        // self.play();
     }
 
     pub fn previous(&mut self) {
@@ -345,10 +350,18 @@ impl Player {
 
     pub fn play_selected(&mut self) {
         if let Some(song) = self.songs.selected() {
+            let file = match File::open(&song.path) {
+                Ok(file) => file,
+                //TODO: Print to status-bar
+                Err(_) => panic!("Could not open path: {:?}", song.path),
+            };
             unsafe {
+                if let Some(resampler) = &mut RESAMPLER {
+                    resampler.finished = true;
+                }
                 RESAMPLER = Some(Resampler::new(
                     self.sample_rate,
-                    song.path.to_str().unwrap(),
+                    file,
                     self.volume,
                     song.gain as f32,
                 ));
@@ -406,7 +419,7 @@ impl Player {
             self.volume = 100;
         }
 
-        if let Some(resampler) = unsafe { RESAMPLER.as_mut() } {
+        if let Some(resampler) = unsafe { &mut RESAMPLER } {
             resampler.set_volume(self.volume);
         }
     }
@@ -416,7 +429,7 @@ impl Player {
             self.volume -= VOLUME_STEP;
         }
 
-        if let Some(resampler) = unsafe { RESAMPLER.as_mut() } {
+        if let Some(resampler) = unsafe { &mut RESAMPLER } {
             resampler.set_volume(self.volume);
         }
     }
@@ -463,7 +476,7 @@ impl Player {
 
     pub fn seek_by(&mut self, time: f32) {
         unsafe {
-            if RESAMPLER.is_none() {
+            if RESAMPLER.is_none() || self.state != State::Playing {
                 return;
             }
 
@@ -472,13 +485,12 @@ impl Player {
     }
 
     pub fn seek_to(&mut self, time: f32) {
-        if unsafe { RESAMPLER.is_none() } {
+        if unsafe { RESAMPLER.is_none() } || self.state != State::Playing {
             return;
         }
 
-        let time = if time.is_sign_negative() { 0.0 } else { time };
+        let time = Duration::from_secs_f32(time.clamp(0.0, f32::MAX));
 
-        let time = Duration::from_secs_f32(time);
         unsafe {
             match RESAMPLER.as_mut().unwrap().probed.format.seek(
                 SeekMode::Coarse,
diff --git a/gonk/src/playlist.rs b/gonk/src/playlist.rs
index 3df81fc9..09035b8a 100644
--- a/gonk/src/playlist.rs
+++ b/gonk/src/playlist.rs
@@ -301,6 +301,7 @@ pub fn draw(playlist: &mut Playlist, area: Rect, f: &mut Frame) {
 
     let items: Vec<ListItem> = playlist
         .playlists
+        .data
         .clone()
         .into_iter()
         .map(ListItem::new)

From 934d1bb174a0f53e26a28a9d7727313273971090 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 18:42:18 +0930
Subject: [PATCH 33/40] fixed an issue where seeking would cause EOF

---
 gonk-player/src/lib.rs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index b1f5e4f2..ccee1e21 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -149,8 +149,8 @@ impl Resampler {
                 smp * self.volume * self.gain
             }
         } else {
+            let mut decode_errors: usize = 0;
             loop {
-                let mut decode_errors: usize = 0;
                 match self.probed.format.next_packet() {
                     Ok(next_packet) => {
                         let decoded = self.decoder.decode(&next_packet).unwrap();
@@ -489,7 +489,9 @@ impl Player {
             return;
         }
 
-        let time = Duration::from_secs_f32(time.clamp(0.0, f32::MAX));
+        //Seeking at under 0.5 seconds causes an unexpected EOF.
+        //Could be because of the coarse seek.
+        let time = Duration::from_secs_f32(time.clamp(0.5, f32::MAX));
 
         unsafe {
             match RESAMPLER.as_mut().unwrap().probed.format.seek(

From 93eb0e4caf7c6a27532f2a583bec2ced8f71bbe3 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 18:52:14 +0930
Subject: [PATCH 34/40] prebuild the seek index

---
 gonk-player/src/lib.rs | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index ccee1e21..4a220a81 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -79,7 +79,11 @@ impl Resampler {
             .format(
                 &Hint::default(),
                 mss,
-                &FormatOptions::default(),
+                &FormatOptions {
+                    prebuild_seek_index: true,
+                    seek_index_fill_rate: 10,
+                    enable_gapless: false,
+                },
                 &MetadataOptions::default(),
             )
             .unwrap();

From 4e7b1df5709584256dc71a3867738782c5f388a1 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Mon, 11 Jul 2022 18:54:42 +0930
Subject: [PATCH 35/40] store replay gain as a float instead of a double

---
 gonk-database/src/lib.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gonk-database/src/lib.rs b/gonk-database/src/lib.rs
index aaf91424..fcfb446f 100644
--- a/gonk-database/src/lib.rs
+++ b/gonk-database/src/lib.rs
@@ -70,7 +70,7 @@ pub fn create_tables(conn: &Connection) {
                 disc INTEGER NOT NULL,
                 number INTEGER NOT NULL,
                 path TEXT NOT NULL,
-                gain DOUBLE NOT NULL,
+                gain FLOAT NOT NULL,
                 album TEXT NOT NULL,
                 artist TEXT NOT NULL,
                 folder TEXT NOT NULL,

From 04f41199dd1228098fc259d17e0d4f2f8fbd2a11 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 12 Jul 2022 11:11:38 +0930
Subject: [PATCH 36/40] fixed: output devices can now be selected in the
 settings

---
 gonk-player/src/lib.rs | 103 +++++++++++++++++++++++++++++------------
 gonk/src/main.rs       |   4 +-
 gonk/src/settings.rs   |  53 +++++++++------------
 3 files changed, 99 insertions(+), 61 deletions(-)

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index 4a220a81..d6549a2c 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -1,6 +1,6 @@
 use cpal::{
     traits::{HostTrait, StreamTrait},
-    Stream,
+    BuildStreamError, Stream, StreamConfig,
 };
 use std::{fs::File, io::ErrorKind, time::Duration, vec::IntoIter};
 use symphonia::{
@@ -283,36 +283,23 @@ pub struct Player {
 }
 
 impl Player {
-    pub fn new(_device: String, volume: u16, cache: &[Song]) -> Self {
-        let device = cpal::default_host().default_output_device().unwrap();
-        let config = device.default_output_config().unwrap().config();
+    pub fn new(wanted_device: String, volume: u16, cache: &[Song]) -> Self {
+        let mut device = None;
 
-        let stream = device
-            .build_output_stream(
-                &config,
-                move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
-                    for frame in data.chunks_mut(2) {
-                        for sample in frame.iter_mut() {
-                            let smp = if let Some(resampler) = unsafe { &mut RESAMPLER } {
-                                //Makes sure that the next sample isn't
-                                //read in the middle of changing songs.
-                                //idk reliable this is.
-                                if resampler.finished {
-                                    0.0
-                                } else {
-                                    resampler.next()
-                                }
-                            } else {
-                                0.0
-                            };
-                            *sample = smp;
-                        }
-                    }
-                },
-                |err| panic!("{}", err),
-            )
-            .unwrap();
+        for d in audio_devices() {
+            if d.name().unwrap() == wanted_device {
+                device = Some(d);
+            }
+        }
 
+        let device = if let Some(device) = device {
+            device
+        } else {
+            default_device()
+        };
+
+        let config = device.default_output_config().unwrap().config();
+        let stream = create_output_stream(&device, &config).unwrap();
         stream.play().unwrap();
 
         let index = if cache.is_empty() { None } else { Some(0) };
@@ -326,6 +313,22 @@ impl Player {
         }
     }
 
+    pub fn set_output_device(&mut self, device: &Device) -> Result<(), String> {
+        match device.default_output_config() {
+            Ok(supported_stream) => {
+                match create_output_stream(device, &supported_stream.config()) {
+                    Ok(stream) => {
+                        self.stream = stream;
+                        self.stream.play().unwrap();
+                        Ok(())
+                    }
+                    Err(e) => Err(format!("{}", e)),
+                }
+            }
+            Err(e) => Err(format!("{}", e)),
+        }
+    }
+
     pub fn update(&mut self) {
         if let Some(resampler) = unsafe { RESAMPLER.as_ref() } {
             if resampler.finished {
@@ -525,3 +528,45 @@ impl Player {
 }
 
 unsafe impl Send for Player {}
+
+fn create_output_stream(
+    device: &Device,
+    config: &StreamConfig,
+) -> Result<Stream, BuildStreamError> {
+    device.build_output_stream(
+        &config,
+        move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
+            for frame in data.chunks_mut(2) {
+                for sample in frame.iter_mut() {
+                    let smp = if let Some(resampler) = unsafe { &mut RESAMPLER } {
+                        //Makes sure that the next sample isn't
+                        //read in the middle of changing songs.
+                        //idk reliable this is.
+                        if resampler.finished {
+                            0.0
+                        } else {
+                            resampler.next()
+                        }
+                    } else {
+                        0.0
+                    };
+                    *sample = smp;
+                }
+            }
+        },
+        |err| panic!("{}", err),
+    )
+}
+
+pub fn audio_devices() -> Vec<Device> {
+    let host_id = cpal::default_host().id();
+    let host = cpal::host_from_id(host_id).unwrap();
+
+    //FIXME: Getting just the output devies was too slow(150ms).
+    //Collecting every device is still slow but it's not as bad.
+    host.devices().unwrap().collect()
+}
+
+pub fn default_device() -> Device {
+    cpal::default_host().default_output_device().unwrap()
+}
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index f89dfe10..b72796ee 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -136,8 +136,10 @@ fn main() {
     let cache = query::get_cache();
     let volume = query::volume();
 
+    let device = query::playback_device();
+
     //40ms
-    let player = thread::spawn(move || Player::new(String::new(), volume, &cache));
+    let player = thread::spawn(move || Player::new(device, volume, &cache));
 
     //3ms
     let mut browser = Browser::new();
diff --git a/gonk/src/settings.rs b/gonk/src/settings.rs
index 16d9b862..900893cb 100644
--- a/gonk/src/settings.rs
+++ b/gonk/src/settings.rs
@@ -1,4 +1,3 @@
-#![allow(unused)]
 use crate::{widgets::*, Frame, Input};
 use gonk_database::query;
 use gonk_player::{Device, DeviceTrait, Index, Player};
@@ -15,28 +14,26 @@ pub struct Settings {
 
 impl Settings {
     pub fn new() -> Self {
-        // let default_device = Player::default_device();
-        // let wanted_device = query::playback_device();
+        let default_device = gonk_player::default_device();
+        let wanted_device = query::playback_device();
 
-        // let devices = Player::audio_devices();
+        let devices = gonk_player::audio_devices();
 
-        // let current_device = if devices
-        //     .iter()
-        //     .flat_map(DeviceTrait::name)
-        //     .any(|x| x == wanted_device)
-        // {
-        //     wanted_device
-        // } else {
-        //     let name = default_device.name().unwrap();
-        //     query::set_playback_device(&name);
-        //     name
-        // };
+        let current_device = if devices
+            .iter()
+            .flat_map(DeviceTrait::name)
+            .any(|x| x == wanted_device)
+        {
+            wanted_device
+        } else {
+            let name = default_device.name().unwrap();
+            query::set_playback_device(&name);
+            name
+        };
 
         Self {
-            // devices: Index::new(devices, Some(0)),
-            // current_device,
-            devices: Index::default(),
-            current_device: String::new(),
+            devices: Index::new(devices, Some(0)),
+            current_device,
         }
     }
 }
@@ -56,20 +53,14 @@ impl Input for Settings {
 }
 
 pub fn on_enter(settings: &mut Settings, player: &mut Player) {
-    // if let Some(device) = settings.devices.selected() {
-    //     match player.change_output_device(device) {
-    //         Ok(_) => {
-    //             let name = device.name().unwrap();
-    //             query::set_playback_device(&name);
-    //             settings.current_device = name;
-    //         }
-    //         //TODO: Print error in status bar
-    //         Err(e) => panic!("{:?}", e),
-    //     }
-    // }
+    if let Some(device) = settings.devices.selected() {
+        match player.set_output_device(device) {
+            Ok(_) => (),
+            Err(e) => println!("{}", e),
+        }
+    }
 }
 
-#[allow(unused)]
 pub fn draw(settings: &mut Settings, area: Rect, f: &mut Frame) {
     let items: Vec<ListItem> = settings
         .devices

From bbd69367f03d9be313d39410dc1176296a9ddc8e Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 12 Jul 2022 11:57:14 +0930
Subject: [PATCH 37/40] scuffed error handling in the status bar

---
 gonk-player/src/lib.rs | 56 +++++++++++++++++-----------------
 gonk/src/error_bar.rs  | 44 +++++++++++++++++++++++++++
 gonk/src/main.rs       | 69 +++++++++++++++++++++++++++++++++++-------
 gonk/src/playlist.rs   | 12 ++++++--
 gonk/src/settings.rs   |  4 +--
 5 files changed, 142 insertions(+), 43 deletions(-)
 create mode 100644 gonk/src/error_bar.rs

diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index d6549a2c..cd4fb7d2 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -329,38 +329,40 @@ impl Player {
         }
     }
 
-    pub fn update(&mut self) {
+    pub fn update(&mut self) -> Result<(), String> {
         if let Some(resampler) = unsafe { RESAMPLER.as_ref() } {
             if resampler.finished {
-                self.next();
+                return self.next();
             }
         }
+        Ok(())
     }
 
-    pub fn add_songs(&mut self, songs: &[Song]) {
+    pub fn add_songs(&mut self, songs: &[Song]) -> Result<(), String> {
         self.songs.data.extend(songs.to_vec());
         if self.songs.selected().is_none() {
             self.songs.select(Some(0));
-            self.play_selected();
+            self.play_selected()
+        } else {
+            Ok(())
         }
     }
 
-    pub fn previous(&mut self) {
+    pub fn previous(&mut self) -> Result<(), String> {
         self.songs.up();
-        self.play_selected();
+        self.play_selected()
     }
 
-    pub fn next(&mut self) {
+    pub fn next(&mut self) -> Result<(), String> {
         self.songs.down();
-        self.play_selected();
+        self.play_selected()
     }
 
-    pub fn play_selected(&mut self) {
+    fn play_selected(&mut self) -> Result<(), String> {
         if let Some(song) = self.songs.selected() {
             let file = match File::open(&song.path) {
                 Ok(file) => file,
-                //TODO: Print to status-bar
-                Err(_) => panic!("Could not open path: {:?}", song.path),
+                Err(_) => return Err(format!("Could not open file: {:?}", song.path)),
             };
             unsafe {
                 if let Some(resampler) = &mut RESAMPLER {
@@ -375,34 +377,34 @@ impl Player {
             }
             self.play();
         }
+        Ok(())
     }
 
-    pub fn play_index(&mut self, i: usize) {
+    pub fn play_index(&mut self, i: usize) -> Result<(), String> {
         self.songs.select(Some(i));
-        self.play_selected();
+        self.play_selected()
     }
 
-    pub fn delete_index(&mut self, i: usize) {
+    pub fn delete_index(&mut self, i: usize) -> Result<(), String> {
         self.songs.data.remove(i);
 
         if let Some(playing) = self.songs.index() {
             let len = self.songs.len();
 
             if len == 0 {
-                return self.clear();
-            }
-
-            if i == playing && i == 0 {
+                self.clear();
+            } else if i == playing && i == 0 {
                 if i == 0 {
                     self.songs.select(Some(0));
                 }
-                self.play_selected();
+                return self.play_selected();
             } else if i == playing && i == len {
                 self.songs.select(Some(len - 1));
             } else if i < playing {
                 self.songs.select(Some(playing - 1));
             }
         };
+        Ok(())
     }
 
     pub fn clear(&mut self) {
@@ -461,7 +463,7 @@ impl Player {
 
     pub fn toggle_playback(&mut self) {
         if unsafe { RESAMPLER.is_none() } {
-            self.play_selected()
+            self.play_selected().unwrap()
         } else {
             match self.state {
                 State::Playing => self.pause(),
@@ -481,19 +483,19 @@ impl Player {
         self.state = State::Paused;
     }
 
-    pub fn seek_by(&mut self, time: f32) {
+    pub fn seek_by(&mut self, time: f32) -> Result<(), String> {
         unsafe {
             if RESAMPLER.is_none() || self.state != State::Playing {
-                return;
+                return Ok(());
             }
 
-            self.seek_to(RESAMPLER.as_ref().unwrap().elapsed.as_secs_f32() + time);
+            self.seek_to(RESAMPLER.as_ref().unwrap().elapsed.as_secs_f32() + time)
         }
     }
 
-    pub fn seek_to(&mut self, time: f32) {
+    pub fn seek_to(&mut self, time: f32) -> Result<(), String> {
         if unsafe { RESAMPLER.is_none() } || self.state != State::Playing {
-            return;
+            return Ok(());
         }
 
         //Seeking at under 0.5 seconds causes an unexpected EOF.
@@ -508,11 +510,11 @@ impl Player {
                     track_id: None,
                 },
             ) {
-                Ok(_) => (),
+                Ok(_) => Ok(()),
                 Err(e) => match e {
                     Error::SeekError(e) => match e {
                         SeekErrorKind::OutOfRange => {
-                            self.next();
+                            return self.next();
                         }
                         _ => panic!("{:?}", e),
                     },
diff --git a/gonk/src/error_bar.rs b/gonk/src/error_bar.rs
new file mode 100644
index 00000000..e5f84aa4
--- /dev/null
+++ b/gonk/src/error_bar.rs
@@ -0,0 +1,44 @@
+use crate::{Frame, ERROR, SHOW_ERROR};
+use std::time::{Duration, Instant};
+use tui::{
+    layout::{Alignment, Rect},
+    widgets::{Block, BorderType, Borders, Paragraph},
+};
+
+const WAIT_TIME: Duration = Duration::from_secs(2);
+
+pub struct ErrorBar {
+    pub timer: Option<Instant>,
+}
+
+impl ErrorBar {
+    pub fn new() -> Self {
+        Self { timer: None }
+    }
+    pub fn start(&mut self) {
+        self.timer = Some(Instant::now());
+    }
+}
+
+pub fn draw(error_bar: &mut ErrorBar, area: Rect, f: &mut Frame) {
+    if let Some(timer) = error_bar.timer {
+        if timer.elapsed() >= WAIT_TIME {
+            error_bar.timer = None;
+            unsafe {
+                SHOW_ERROR = false;
+                ERROR = String::new();
+            }
+        }
+    }
+
+    let message = unsafe { ERROR.as_str() };
+
+    f.render_widget(
+        Paragraph::new(message).alignment(Alignment::Left).block(
+            Block::default()
+                .borders(Borders::ALL)
+                .border_type(BorderType::Rounded),
+        ),
+        area,
+    );
+}
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index b72796ee..c4fd208a 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -1,5 +1,6 @@
 use browser::Browser;
 use crossterm::{event::*, terminal::*, *};
+use error_bar::ErrorBar;
 use gonk_database::{query, Database, State};
 use gonk_player::Player;
 use playlist::{Mode as PlaylistMode, Playlist};
@@ -16,6 +17,7 @@ use std::{
 use tui::{backend::CrosstermBackend, layout::*, style::Color, Terminal};
 
 mod browser;
+mod error_bar;
 mod playlist;
 mod queue;
 mod search;
@@ -25,6 +27,15 @@ mod widgets;
 
 type Frame<'a> = tui::Frame<'a, CrosstermBackend<Stdout>>;
 
+static mut SHOW_ERROR: bool = false;
+static mut ERROR: String = String::new();
+
+pub fn set_error(message: String) {
+    unsafe {
+        ERROR = message;
+    }
+}
+
 pub struct Colors {
     pub number: Color,
     pub name: Color,
@@ -150,6 +161,8 @@ fn main() {
     //200 ns
     let mut status_bar = StatusBar::new();
 
+    let mut error_bar = ErrorBar::new();
+
     //68 us
     let mut playlist = Playlist::new();
 
@@ -189,7 +202,18 @@ fn main() {
         }
 
         queue.len = player.songs.len();
-        player.update();
+
+        match player.update() {
+            Ok(_) => (),
+            Err(e) => set_error(e),
+        };
+
+        unsafe {
+            if !ERROR.is_empty() && SHOW_ERROR == false {
+                error_bar.start();
+                SHOW_ERROR = true;
+            }
+        }
 
         terminal
             .draw(|f| {
@@ -198,8 +222,8 @@ fn main() {
                     .constraints([Constraint::Min(2), Constraint::Length(3)])
                     .split(f.size());
 
-                let (top, bottom) = if status_bar.hidden {
-                    (f.size(), area[1])
+                let (top, bottom) = if status_bar.hidden && unsafe { !SHOW_ERROR } {
+                    (f.size(), Rect::default())
                 } else {
                     (area[0], area[1])
                 };
@@ -212,7 +236,9 @@ fn main() {
                     Mode::Settings => settings::draw(&mut settings, top, f),
                 };
 
-                if mode != Mode::Queue {
+                if unsafe { SHOW_ERROR } {
+                    error_bar::draw(&mut error_bar, bottom, f);
+                } else if mode != Mode::Queue {
                     status_bar::draw(&mut status_bar, bottom, f, busy, &player);
                 }
             })
@@ -269,10 +295,22 @@ fn main() {
                             _ => (),
                         },
                         KeyCode::Char('u') if mode == Mode::Browser => db.refresh(),
-                        KeyCode::Char('q') => player.seek_by(-10.0),
-                        KeyCode::Char('e') => player.seek_by(10.0),
-                        KeyCode::Char('a') => player.previous(),
-                        KeyCode::Char('d') => player.next(),
+                        KeyCode::Char('q') => match player.seek_by(-10.0) {
+                            Ok(_) => (),
+                            Err(e) => set_error(e),
+                        },
+                        KeyCode::Char('e') => match player.seek_by(10.0) {
+                            Ok(_) => (),
+                            Err(e) => set_error(e),
+                        },
+                        KeyCode::Char('a') => match player.previous() {
+                            Ok(_) => (),
+                            Err(e) => set_error(e),
+                        },
+                        KeyCode::Char('d') => match player.next() {
+                            Ok(_) => (),
+                            Err(e) => set_error(e),
+                        },
                         KeyCode::Char('w') => player.volume_up(),
                         KeyCode::Char('s') => player.volume_down(),
                         //TODO: Rework mode changing buttons
@@ -328,16 +366,25 @@ fn main() {
                         KeyCode::Enter => match mode {
                             Mode::Browser => {
                                 let songs = browser::get_selected(&browser);
-                                player.add_songs(&songs);
+                                match player.add_songs(&songs) {
+                                    Ok(_) => (),
+                                    Err(e) => set_error(e),
+                                }
                             }
                             Mode::Queue => {
                                 if let Some(i) = queue.ui.index() {
-                                    player.play_index(i);
+                                    match player.play_index(i) {
+                                        Ok(_) => (),
+                                        Err(e) => set_error(e),
+                                    }
                                 }
                             }
                             Mode::Search => {
                                 if let Some(songs) = search::on_enter(&mut search) {
-                                    player.add_songs(&songs);
+                                    match player.add_songs(&songs) {
+                                        Ok(_) => (),
+                                        Err(e) => set_error(e),
+                                    }
                                 }
                             }
                             Mode::Settings => settings::on_enter(&mut settings, &mut player),
diff --git a/gonk/src/playlist.rs b/gonk/src/playlist.rs
index 09035b8a..333992c3 100644
--- a/gonk/src/playlist.rs
+++ b/gonk/src/playlist.rs
@@ -1,4 +1,4 @@
-use crate::{widgets::*, Frame, Input, COLORS};
+use crate::{set_error, widgets::*, Frame, Input, COLORS};
 use gonk_database::playlist::PlaylistSong;
 use gonk_database::{playlist, query};
 use gonk_player::{Index, Player, Song};
@@ -94,12 +94,18 @@ pub fn on_enter(playlist: &mut Playlist, player: &mut Player) {
         Mode::Playlist => {
             let ids: Vec<usize> = playlist.songs.data.iter().map(|song| song.id).collect();
             let songs = query::songs_from_ids(&ids);
-            player.add_songs(&songs);
+            match player.add_songs(&songs) {
+                Ok(_) => (),
+                Err(e) => set_error(e),
+            }
         }
         Mode::Song => {
             if let Some(item) = playlist.songs.selected() {
                 let song = query::songs_from_ids(&[item.id]).remove(0);
-                player.add_songs(&[song]);
+                match player.add_songs(&[song]) {
+                    Ok(_) => (),
+                    Err(e) => set_error(e),
+                }
             }
         }
         Mode::Popup if !playlist.song_buffer.is_empty() => {
diff --git a/gonk/src/settings.rs b/gonk/src/settings.rs
index 900893cb..851d6829 100644
--- a/gonk/src/settings.rs
+++ b/gonk/src/settings.rs
@@ -1,4 +1,4 @@
-use crate::{widgets::*, Frame, Input};
+use crate::{set_error, widgets::*, Frame, Input};
 use gonk_database::query;
 use gonk_player::{Device, DeviceTrait, Index, Player};
 use tui::{
@@ -56,7 +56,7 @@ pub fn on_enter(settings: &mut Settings, player: &mut Player) {
     if let Some(device) = settings.devices.selected() {
         match player.set_output_device(device) {
             Ok(_) => (),
-            Err(e) => println!("{}", e),
+            Err(e) => set_error(e),
         }
     }
 }

From dfaeac3531b3fffdf7e09151d75db8a3b9753426 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 12 Jul 2022 11:58:11 +0930
Subject: [PATCH 38/40] cleanup error handling

---
 gonk/src/queue.rs | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/gonk/src/queue.rs b/gonk/src/queue.rs
index 0f612ac5..5a3c651f 100644
--- a/gonk/src/queue.rs
+++ b/gonk/src/queue.rs
@@ -58,7 +58,10 @@ pub fn constraint(queue: &mut Queue, row: usize, shift: bool) {
 
 pub fn delete(queue: &mut Queue, player: &mut Player) {
     if let Some(i) = queue.ui.index() {
-        player.delete_index(i);
+        match player.delete_index(i) {
+            Ok(_) => (),
+            Err(e) => set_error(e),
+        };
         //make sure the ui index is in sync
         let len = player.songs.len().saturating_sub(1);
         if i > len {
@@ -101,7 +104,10 @@ pub fn draw(queue: &mut Queue, player: &mut Player, f: &mut Frame, event: Option
         {
             let ratio = x as f32 / size.width as f32;
             let duration = player.duration().as_secs_f32();
-            player.seek_to(duration * ratio);
+            match player.seek_to(duration * ratio) {
+                Ok(_) => (),
+                Err(e) => set_error(e),
+            };
         }
 
         //Mouse support for the queue.

From 14d6c3fbd21003b7776bde17afb8e8b93ed4f98a Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 12 Jul 2022 12:05:31 +0930
Subject: [PATCH 39/40] more error handling

---
 gonk-database/src/lib.rs   |  2 +-
 gonk-database/src/query.rs |  2 +-
 gonk-player/src/lib.rs     |  7 ++++---
 gonk/src/main.rs           | 13 ++++++++++++-
 4 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/gonk-database/src/lib.rs b/gonk-database/src/lib.rs
index fcfb446f..99208f45 100644
--- a/gonk-database/src/lib.rs
+++ b/gonk-database/src/lib.rs
@@ -142,7 +142,7 @@ pub fn rescan_folders() {
 }
 
 pub fn add_folder(folder: &str) {
-    let folder = folder.replace('\\', "/");
+    let folder = folder.replace('/', "\\");
 
     conn()
         .execute(
diff --git a/gonk-database/src/query.rs b/gonk-database/src/query.rs
index 249459aa..ebf818b0 100644
--- a/gonk-database/src/query.rs
+++ b/gonk-database/src/query.rs
@@ -50,7 +50,7 @@ pub fn folders() -> Vec<String> {
 }
 
 pub fn remove_folder(path: &str) -> Result<(), &str> {
-    let path = path.replace('\\', "/");
+    let path = path.replace('/', "\\");
     let conn = conn();
 
     conn.execute("DELETE FROM song WHERE folder = ?", [&path])
diff --git a/gonk-player/src/lib.rs b/gonk-player/src/lib.rs
index cd4fb7d2..c925d4f9 100644
--- a/gonk-player/src/lib.rs
+++ b/gonk-player/src/lib.rs
@@ -461,15 +461,16 @@ impl Player {
         }
     }
 
-    pub fn toggle_playback(&mut self) {
+    pub fn toggle_playback(&mut self) -> Result<(), String> {
         if unsafe { RESAMPLER.is_none() } {
-            self.play_selected().unwrap()
+            self.play_selected()
         } else {
             match self.state {
                 State::Playing => self.pause(),
                 State::Paused => self.play(),
                 State::Stopped => (),
-            }
+            };
+            Ok(())
         }
     }
 
diff --git a/gonk/src/main.rs b/gonk/src/main.rs
index c4fd208a..ae7402ce 100644
--- a/gonk/src/main.rs
+++ b/gonk/src/main.rs
@@ -28,10 +28,12 @@ mod widgets;
 type Frame<'a> = tui::Frame<'a, CrosstermBackend<Stdout>>;
 
 static mut SHOW_ERROR: bool = false;
+static mut OLD_ERROR: String = String::new();
 static mut ERROR: String = String::new();
 
 pub fn set_error(message: String) {
     unsafe {
+        OLD_ERROR = ERROR.clone();
         ERROR = message;
     }
 }
@@ -209,6 +211,12 @@ fn main() {
         };
 
         unsafe {
+            if OLD_ERROR != ERROR {
+                error_bar.start();
+                SHOW_ERROR = true;
+                OLD_ERROR = ERROR.clone();
+            }
+
             if !ERROR.is_empty() && SHOW_ERROR == false {
                 error_bar.start();
                 SHOW_ERROR = true;
@@ -280,7 +288,10 @@ fn main() {
                                 playlist.search.push(c);
                             }
                         }
-                        KeyCode::Char(' ') => player.toggle_playback(),
+                        KeyCode::Char(' ') => match player.toggle_playback() {
+                            Ok(_) => (),
+                            Err(e) => set_error(e),
+                        },
                         KeyCode::Char('C') if shift => {
                             player.clear_except_playing();
                             queue.ui.select(Some(0));

From defb38701934b490e4a390e349b922c38b8f0401 Mon Sep 17 00:00:00 2001
From: zX3no <dr_xeno@hotmail.com.au>
Date: Tue, 12 Jul 2022 12:17:42 +0930
Subject: [PATCH 40/40] handle some edge cases for errors

---
 gonk/src/error_bar.rs | 3 ++-
 gonk/src/queue.rs     | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/gonk/src/error_bar.rs b/gonk/src/error_bar.rs
index e5f84aa4..e70d97a9 100644
--- a/gonk/src/error_bar.rs
+++ b/gonk/src/error_bar.rs
@@ -1,4 +1,4 @@
-use crate::{Frame, ERROR, SHOW_ERROR};
+use crate::{Frame, ERROR, OLD_ERROR, SHOW_ERROR};
 use std::time::{Duration, Instant};
 use tui::{
     layout::{Alignment, Rect},
@@ -27,6 +27,7 @@ pub fn draw(error_bar: &mut ErrorBar, area: Rect, f: &mut Frame) {
             unsafe {
                 SHOW_ERROR = false;
                 ERROR = String::new();
+                OLD_ERROR = String::new();
             }
         }
     }
diff --git a/gonk/src/queue.rs b/gonk/src/queue.rs
index 5a3c651f..8a5ba971 100644
--- a/gonk/src/queue.rs
+++ b/gonk/src/queue.rs
@@ -204,7 +204,7 @@ fn draw_body(
     f: &mut Frame,
     area: Rect,
 ) -> Option<(usize, usize)> {
-    if player.songs.is_empty() {
+    if player.songs.is_empty() && unsafe { ERROR.is_empty() } {
         f.render_widget(
             Block::default()
                 .border_type(BorderType::Rounded)