diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..db2d2b4561354d10c675fcd4b51495b1b9c33696
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.mp3 filter=lfs diff=lfs merge=lfs -text
+*.ttf filter=lfs diff=lfs merge=lfs -text
diff --git a/Cargo.toml b/Cargo.toml
index 8a2e85580ca953efb1b1b93aa0ee44d3c0d678e3..a2318e628ba62ff3bdde6314e64da3be196adab3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,13 +1,21 @@
 [package]
 name = "micro_musicbox"
-version = "0.2.0-pre.1"
+version = "0.3.0"
 edition = "2021"
 license = "Apache-2.0"
 authors = ["Louis Capitanchik <louis@microhacks.co.uk>"]
 repository = "https://lab.lcr.gr/microhacks/micro_bevy_musicbox"
 description = "Opinionated service interface for bevy_kira_audio"
 
+[features]
+default-features = []
+serde = ["dep:serde"]
+
 [dependencies]
 bevy = { version = "0.8", default-features = false }
-bevy_kira_audio = { version = "0.11", features = ["mp3"] }
-serde = "1"
+bevy_kira_audio = { version = "0.12", features = ["mp3"] }
+serde = { version = "1", optional = true }
+
+[dev_dependencies]
+bevy = "0.8"
+log = "0.4"
\ No newline at end of file
diff --git a/README.md b/README.md
index 1b26d21091c3c2dd6c15961dda9fd03234627b3e..fe9ea227c2266405a8359f18f41022dcfbac5f78 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,53 @@
-# Micro Bevy MusicBox
+# Micro MusicBox
 
 Play some tunes
 
 ## What?
 
-This library provides a convenience wrapper around bevy_kira_audio, handling all of the
-setup for the common game audio scenario for you.
+This library provides a convenience wrapper around bevy_kira_audio, handling all the
+setup for the common game audio scenario. This includes channel management, giving you
+control of the audio levels for your music, ambiance, sound effects and UI separately
+from the start.
 
-There are 4 types of audio channel that will be added to your game, with crossfade tracks
-for 2 of those; this means a total of 6 `AudioChannel` resources are created.
+### Channel Types
 
-* `Music` - The main music for your game (includes A/B for crossfading)
-* `Ambient` - Any long and/or looping background ambience that you might want to layer on top of music (includes A/B for crossfading)
-* `Sfx` - Any short sound effects
-* `UI Sfx` - Any sound effects that specifically relate to the UI, allowing for more fine-grained control of audio levels
+Musicbox is configured with 4 channels, which are split into two types:
 
-Track volume is controlled by a resource, and is calculated as the individual track's volume settings multiplied by the master volume
-setting
+- Main channels; Play a single looped audio track. Starting another track on this channel will stop the currently active one. Supports cross-fade between the current track and the next track. This includes the "Music" and "Ambiance" channels
+- Background Channels; Plays any number of one-shot sounds. The sounds will not interfere with each other or the main channels. This includes the "SFX" and "UI SFX" channels
+
+### Volume
+
+Volume is calculated by multiplying a given channel's volume setting by the master volume setting.
+This means you can let players adjust either the overall game volume, or each of the three types
+individually. The 4 channels are assigned as such:
+
+- "music" -> music volume * master volume
+- "ambiance" & "sfx" -> sfx volume * master volume
+- "ui sfx" -> ui volume * master volume
+
+There are two types of channel: Singleton "main" channels, and multi-sound channels. Audio
+played on a main channel will loop until stopped, and will replace any currently playing audio
+on that same channel. multi-sound channels work exactly as they, well, sound - play one-off sounds
+without worrying about coordinating them with other sources.
+
+Main channels also expose a cross-fade feature - either a basic cross-fade, in which the same
+audio tween is applied to the outgoing and incoming music tracks, or an in-out fade where the two
+tracks can have independent tweens.
 
 ## How?
 
 ### Quickstart
 
-- Include the MusixBocPlugin plugin, or the CombinedAudioPlugins plugin group in your app
-- Implement `SuppliesAudio` for a resource (or use the built in impl on `AssetServer`)
+- Implement `SuppliesAudio` for a resource (or use the built-in impl on `AssetServer`)
+- Include the MusixBocPlugin plugin, or the CombinedAudioPlugins plugin group in your app, providing your `SuppliesAudio` impl as the generic parameter
 - Use `MusicBox<T: SuppliesAudio>` as a parameter on a system
 - Call one of the `MusicBox::play_*` methods to play sounds
 
 ```rust
 fn main() {
     App::new()
-        .add_plugins(CombinedAudioPlugins)
+        .add_plugins(CombinedAudioPlugins::<AssetServer>::new())
         .add_startup_system(|mut music_box: MusicBox<AssetServer>| {
             music_box.play_music("music/bing_bong.mp3");
         });
@@ -104,4 +121,12 @@ pub fn play_sounds(
         music_box.play_effect_once(sound);
     }
 }
-```
\ No newline at end of file
+```
+
+## Asset Licenses
+
+The examples in this repository use assets available under the following licenses:
+
+- The-Great-Madeja.mp3 by [Rolemusic](http://rolemusic.sawsquarenoise.com/) is licensed under a [CC-BY-4.0 Attribution License](https://creativecommons.org/licenses/by/4.0/).
+- The-White-Kitty.mp3 by [Rolemusic](http://rolemusic.sawsquarenoise.com/) is licensed under a [CC-BY-4.0 Attribution License](https://creativecommons.org/licenses/by/4.0/).
+- KenneyBlocks.ttf by [Kenney](https://www.kenney.nl) is licensed under a [CC-0 Public Domain License](http://creativecommons.org/publicdomain/zero/1.0/) 
diff --git a/assets/KenneyBlocks.ttf b/assets/KenneyBlocks.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..059ebf3c09541d6fbaf9d6d6f0ccda6431dcea6c
--- /dev/null
+++ b/assets/KenneyBlocks.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bb2f9ba39f4fa7d8dbb3d3d1af05c2517b848f278bbe4f7ab53e518f2429bad5
+size 30508
diff --git a/assets/The-Great-Madeja.mp3 b/assets/The-Great-Madeja.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..3007ea4bcbe3741c758fbda157ee571a7de20223
--- /dev/null
+++ b/assets/The-Great-Madeja.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:46b627c7df1d95034b70bc4de7a9ed74be344bee132d41bfa7fb15a8356d3921
+size 1932180
diff --git a/assets/The-White-Kitty.mp3 b/assets/The-White-Kitty.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..a8424cb0ab99e3ffc9045cd9cdd004fa32af1071
--- /dev/null
+++ b/assets/The-White-Kitty.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7804ea15d05840abfb0240455266d4ab1269dce99a12c8df846a97ed14131600
+size 1552452
diff --git a/examples/basic.rs b/examples/basic.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ee5f2950be03bf5a2b0437bfe30cd6389588fd75
--- /dev/null
+++ b/examples/basic.rs
@@ -0,0 +1,15 @@
+use bevy::prelude::*;
+use micro_musicbox::prelude::*;
+use micro_musicbox::CombinedAudioPlugins;
+
+fn main() {
+	App::new()
+		.add_plugins(DefaultPlugins)
+		.add_plugins(CombinedAudioPlugins::<AssetServer>::new())
+		.add_startup_system(play_audio)
+		.run();
+}
+
+pub fn play_audio(mut music_box: MusicBox<AssetServer>) {
+	music_box.play_music("The-Great-Madeja.mp3");
+}
diff --git a/examples/kitchen_sink.rs b/examples/kitchen_sink.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cf4dff5e4784d569583d57eab6fcf1b0aec8ab8d
--- /dev/null
+++ b/examples/kitchen_sink.rs
@@ -0,0 +1,124 @@
+// This example shows off a number of the features that micro_musicbox provides:
+// - Basic music player
+// - Channel based audio volume mixing w/ master volume
+// - Cross fade audio
+
+use std::time::Duration;
+
+use bevy::ecs::schedule::ShouldRun;
+use bevy::prelude::*;
+use micro_musicbox::prelude::AudioSource;
+use micro_musicbox::prelude::*;
+use micro_musicbox::CombinedAudioPlugins;
+
+use crate::utilities::{AppState, DetailsMarker, TextMarker};
+
+mod utilities;
+
+pub fn main() {
+	App::new()
+		.add_plugin(utilities::SetupPlugin) // Loads resources
+		.add_plugins(DefaultPlugins)
+		.add_plugins(CombinedAudioPlugins::<AssetServer>::new())
+		.add_system_set(SystemSet::on_enter(AppState::Running).with_system(setup_audio).with_system(set_instructions))
+		.add_system_set(SystemSet::on_update(AppState::Running).with_run_criteria(has_music_state).with_system(cross_fade_tracks))
+		.run();
+}
+
+/// A resource that we'll use to keep track of which track is currently the active one, for fading
+/// in and out
+pub struct MusicState {
+	pub playing_first: bool,
+}
+
+pub fn has_music_state(state: Option<Res<MusicState>>) -> ShouldRun {
+	state.is_some().into()
+}
+
+pub fn set_instructions(
+	mut instructions: Query<&mut Text, (With<TextMarker>, Without<DetailsMarker>)>,
+	mut details: Query<&mut Text, (With<DetailsMarker>, Without<TextMarker>)>,
+) {
+	for mut text in &mut instructions {
+		text.sections[0].value = String::from("Press Right Shift To Change Tracks")
+	}
+	for mut text in &mut details {
+		text.sections[0].value = String::from("Press 1-9 to change volume (10% - 90%)")
+	}
+}
+
+pub fn setup_audio(mut commands: Commands, mut musicbox: MusicBox<AssetServer>) {
+	musicbox.play_music("The-White-Kitty.mp3");
+	commands.insert_resource(MusicState {
+		playing_first: true,
+	});
+}
+
+// pub fn
+
+pub fn cross_fade_tracks(
+	input: Res<Input<KeyCode>>,
+	mut music_box: MusicBox<AssetServer>,
+	mut music_state: ResMut<MusicState>,
+) {
+	if input.just_released(KeyCode::RShift) {
+		if music_state.playing_first {
+			music_box.cross_fade_music(
+				"The-Great-Madeja.mp3",
+				AudioTween::new(Duration::from_millis(500), AudioEasing::InOutPowf(0.6)),
+			);
+		} else {
+			music_box.cross_fade_music(
+				"The-White-Kitty.mp3",
+				AudioTween::new(Duration::from_millis(500), AudioEasing::InOutPowf(0.6)),
+			);
+		}
+		music_state.playing_first = !music_state.playing_first;
+	}
+
+	if let Some(value) = map_key_values(&input) {
+		//
+		// You can set the volume of a channel with a convenience method
+		//
+		music_box.set_music_volume(value as f32 / 10.0);
+	}
+
+	if input.just_released(KeyCode::Return) {
+		//
+		// You can also get a mutable ref for the settings, in case you
+		// need to do any maths with them. If you don't need `MusicBox`
+		// in a system, then you can directly access the settings with
+		// `Res<AudioSettings>`, or the ResMut equivalent
+		//
+		let mut settings = music_box.settings_mut();
+		if settings.master_volume < 1.0 {
+			settings.master_volume = 1.0;
+		} else {
+			settings.master_volume = 0.5;
+		}
+	}
+}
+
+fn map_key_values(input: &Res<Input<KeyCode>>) -> Option<usize> {
+	if input.just_released(KeyCode::Key1) {
+		Some(1)
+	} else if input.just_released(KeyCode::Key2) {
+		Some(2)
+	} else if input.just_released(KeyCode::Key3) {
+		Some(3)
+	} else if input.just_released(KeyCode::Key4) {
+		Some(4)
+	} else if input.just_released(KeyCode::Key5) {
+		Some(5)
+	} else if input.just_released(KeyCode::Key6) {
+		Some(6)
+	} else if input.just_released(KeyCode::Key7) {
+		Some(7)
+	} else if input.just_released(KeyCode::Key8) {
+		Some(8)
+	} else if input.just_released(KeyCode::Key9) {
+		Some(9)
+	} else {
+		None
+	}
+}
diff --git a/examples/utilities/mod.rs b/examples/utilities/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..554f5661e3673d3e539e7e57f943e971e0affe66
--- /dev/null
+++ b/examples/utilities/mod.rs
@@ -0,0 +1,128 @@
+use bevy::app::Plugin;
+use bevy::asset::{Handle, LoadState};
+use bevy::ecs::schedule::ShouldRun;
+use bevy::prelude::*;
+use bevy_kira_audio::AudioSource;
+
+/// We store our asset handles in this to avoid Bevy from dropping the assets and reloading
+/// when we switch tracks
+pub struct AudioResources {
+	pub white_kitty: Handle<AudioSource>,
+	pub great_madeja: Handle<AudioSource>,
+}
+
+#[derive(Default, Eq, PartialEq, Debug, Clone, Hash)]
+pub enum AppState {
+	#[default]
+	Loading,
+	Running,
+}
+
+pub fn load_resources(mut commands: Commands, assets: Res<AssetServer>) {
+	let white_kitty = assets.load("The-White-Kitty.mp3");
+	let great_madeja = assets.load("The-Great-Madeja.mp3");
+
+	commands.insert_resource(AudioResources {
+		white_kitty,
+		great_madeja,
+	})
+}
+
+pub fn check_load_state(
+	assets: Res<AssetServer>,
+	resources: Res<AudioResources>,
+	mut appstate: ResMut<State<AppState>>,
+) {
+	let load_state =
+		assets.get_group_load_state(vec![resources.white_kitty.id, resources.great_madeja.id]);
+
+	match load_state {
+		LoadState::Loaded => {
+			appstate.set(AppState::Running);
+		}
+		LoadState::Loading => {}
+		_ => {
+			log::error!("The resources are in a bad state! This is a problem");
+		}
+	}
+}
+
+pub fn has_audio_resources(res: Option<Res<AudioResources>>) -> ShouldRun {
+	res.is_some().into()
+}
+pub fn is_state_loading(state: Res<AppState>) -> ShouldRun {
+	(*state == AppState::Loading).into()
+}
+pub fn is_state_running(state: Res<AppState>) -> ShouldRun {
+	(*state == AppState::Running).into()
+}
+
+/// This component allows us to easily grab the on screen text
+#[derive(Component)]
+pub struct TextMarker;
+/// This component allows us to easily grab the blank details text area
+#[derive(Component)]
+pub struct DetailsMarker;
+
+pub fn create_ui(mut commands: Commands, assets: Res<AssetServer>) {
+	commands.spawn_bundle(Camera2dBundle::default());
+	commands
+		.spawn_bundle(NodeBundle {
+			style: Style {
+				size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
+				justify_content: JustifyContent::Center,
+				align_items: AlignItems::Center,
+				flex_direction: FlexDirection::Column,
+				..Default::default()
+			},
+			..Default::default()
+		})
+		.with_children(|children| {
+			children
+				.spawn_bundle(TextBundle {
+					text: Text::from_section(
+						"Loading Audio Tracks",
+						TextStyle {
+							color: Color::BLACK,
+							font_size: 48.0,
+							font: assets.load("KenneyBlocks.ttf"),
+						},
+					),
+					..Default::default()
+				})
+				.insert(TextMarker);
+			children
+				.spawn_bundle(TextBundle {
+					text: Text::from_section(
+						"...",
+						TextStyle {
+							color: Color::BLACK,
+							font_size: 32.0,
+							font: assets.load("KenneyBlocks.ttf"),
+						},
+					),
+					..Default::default()
+				})
+				.insert(DetailsMarker);
+		});
+}
+
+pub struct SetupPlugin;
+impl Plugin for SetupPlugin {
+	fn build(&self, app: &mut App) {
+		app.add_state(AppState::Loading)
+			.insert_resource(WindowDescriptor {
+				width: 800.0,
+				height: 600.0,
+				title: String::from("Kitchen Sink Example"),
+				..Default::default()
+			})
+			.add_startup_system(load_resources)
+			.add_startup_system(create_ui)
+			.add_system_set(
+				SystemSet::on_update(AppState::Loading)
+					.with_run_criteria(has_audio_resources)
+					.with_system(check_load_state),
+			);
+	}
+}
diff --git a/src/channels.rs b/src/channels.rs
index dd056a2dbc33d1b5e3c0ebd6971c8ed34a9d64db..eb49fc74b93611a51c6f9af93ae153ff9be08a66 100644
--- a/src/channels.rs
+++ b/src/channels.rs
@@ -1,23 +1,29 @@
-/// The first channel to use for main music tracks. Combined with
-/// `music audio channel b` to perform cross-fades
-/// *Volume Type:* Music
-pub struct MusicAudioChannelA;
-/// The backup channel to use for main music tracks. Combined with
-/// `music audio channel a` to perform cross-fades
-/// *Volume Type:* Music
-pub struct MusicAudioChannelB;
-/// The first channel to use for background ambiance tracks.
-/// Combined with `ambiance audio channel b` to perform cross-fades
-/// *Volume Type:* SFX
-pub struct AmbianceAudioChannelA;
-/// The backup channel to use for background ambiance tracks.
-/// Combined with `ambiance audio channel a` to perform cross-fades
-/// *Volume Type:* SFX
-pub struct AmbianceAudioChannelB;
-/// The channel used for generic sound effects, such as spells, hits,
-/// attacks, etc.
-/// *Volume Type:* SFX
+/// The channel used for playing main background music tracks. When a track is started in this
+/// channel, the currently playing track (if it exist) will be stopped. Tweens can be applied to
+/// control this transition
+///
+/// - *Volume Type:* Music
+/// - *Supports Concurrent Sounds:* No
+/// - *Supports Transitions*: Yes
+pub struct MusicAudioChannel;
+/// The channel used for playing an ambient background track. An ambient background track can
+/// be considered similar to a music track, but consisting of composite sound effects to create
+/// a certain feeling, often boosting immersion.
+///
+/// - *Volume Type:* SFX
+/// - *Supports Concurrent Sounds:* No
+/// - *Supports Transitions*: Yes
+pub struct AmbianceAudioChannel;
+/// The channel used for generic one shot sound effects, such as spells, hits, attacks, grunts, etc.
+///
+/// - *Volume Type:* SFX
+/// - *Supports Concurrent Sounds:* Yes
+/// - *Supports Transitions:* Yes
 pub struct SfxAudioChannel;
-/// The channel used for any UI related sound effects
-/// *Volume Type:* UI SFX
+/// The channel used for any UI related one-shot sound effects. The volume of ui sfx can be
+/// controlled separately from game related sound effects
+///
+/// - *Volume Type:* UI SFX
+/// - *Supports Concurrent Sounds:* Yes
+/// - *Supports Transitions:* Yes
 pub struct UiSfxAudioChannel;
diff --git a/src/lib.rs b/src/lib.rs
index 28a8637fa49144ea618d86300d2ea70c51deab5b..2a52e91f53d8132d9708af96d184dd0be81c2a75 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,41 +1,81 @@
-use bevy::app::{App, Plugin, PluginGroup, PluginGroupBuilder};
+use std::marker::PhantomData;
+
+use bevy::app::{App, CoreStage, Plugin, PluginGroup, PluginGroupBuilder};
 use bevy_kira_audio::{AudioApp, AudioPlugin};
 
 use crate::channels::{
-	AmbianceAudioChannelA, AmbianceAudioChannelB, MusicAudioChannelA, MusicAudioChannelB,
-	SfxAudioChannel, UiSfxAudioChannel,
+	AmbianceAudioChannel, MusicAudioChannel, SfxAudioChannel, UiSfxAudioChannel,
 };
-use crate::utilities::{AudioCrossFade, AudioSettings};
+use crate::music_box::MusicBoxState;
+use crate::utilities::{AudioSettings, SuppliesAudio};
 
+/// Musicbox encapsulates the concept of different sound types in a number of preset channels, that
+/// can be used in certain ways
 pub mod channels;
 pub mod music_box;
 pub mod utilities;
 
 pub mod prelude {
+	pub use bevy_kira_audio::{
+		AudioChannel, AudioControl, AudioEasing, AudioInstance, AudioSettings as KiraAudioSettings,
+		AudioSource, AudioTween,
+	};
+
 	pub use super::channels::*;
 	pub use super::music_box::MusicBox;
-	pub use bevy_kira_audio::{AudioSource, AudioChannel};
+	pub use super::utilities::AudioSettings;
 }
 
-pub struct MusicBoxPlugin;
+pub struct MusicBoxPlugin<T: SuppliesAudio> {
+	_t: PhantomData<T>,
+}
+
+impl<T: SuppliesAudio> Default for MusicBoxPlugin<T> {
+	fn default() -> Self {
+		Self {
+			_t: PhantomData::default(),
+		}
+	}
+}
+
+impl<T: SuppliesAudio> MusicBoxPlugin<T> {
+	pub fn new() -> MusicBoxPlugin<T> {
+		Default::default()
+	}
+}
 
-impl Plugin for MusicBoxPlugin {
+impl<T: SuppliesAudio> Plugin for MusicBoxPlugin<T> {
 	fn build(&self, app: &mut App) {
-		app.add_audio_channel::<MusicAudioChannelA>()
-			.add_audio_channel::<MusicAudioChannelB>()
-			.add_audio_channel::<AmbianceAudioChannelA>()
-			.add_audio_channel::<AmbianceAudioChannelB>()
+		app.add_audio_channel::<MusicAudioChannel>()
+			.add_audio_channel::<AmbianceAudioChannel>()
 			.add_audio_channel::<SfxAudioChannel>()
 			.add_audio_channel::<UiSfxAudioChannel>()
 			.insert_resource(AudioSettings::default())
-			.insert_resource(AudioCrossFade::default());
+			.insert_resource(MusicBoxState::default())
+			.add_system_to_stage(CoreStage::Last, utilities::sync_music_volume::<T>);
 	}
 }
 
-pub struct CombinedAudioPlugins;
-impl PluginGroup for CombinedAudioPlugins {
+pub struct CombinedAudioPlugins<T: SuppliesAudio> {
+	_t: PhantomData<T>,
+}
+
+impl<T: SuppliesAudio> Default for CombinedAudioPlugins<T> {
+	fn default() -> Self {
+		Self {
+			_t: PhantomData::default(),
+		}
+	}
+}
+
+impl<T: SuppliesAudio> CombinedAudioPlugins<T> {
+	pub fn new() -> CombinedAudioPlugins<T> {
+		Default::default()
+	}
+}
+
+impl<T: SuppliesAudio> PluginGroup for CombinedAudioPlugins<T> {
 	fn build(&mut self, group: &mut PluginGroupBuilder) {
-		group.add(AudioPlugin)
-			.add(MusicBoxPlugin);
+		group.add(AudioPlugin).add(MusicBoxPlugin::<T>::new());
 	}
-}
\ No newline at end of file
+}
diff --git a/src/music_box.rs b/src/music_box.rs
index 7647ff1f8ef33015cabee05b0bb6d2992ed86566..7028bf29ee860dd41d263c5c570e63adf3339705 100644
--- a/src/music_box.rs
+++ b/src/music_box.rs
@@ -1,21 +1,16 @@
 use std::marker::PhantomData;
 
 use bevy::ecs::system::SystemParam;
-use bevy::prelude::*;
-use bevy_kira_audio::{AudioChannel, InstanceHandle};
+use bevy::prelude::{Assets, Commands, Handle, Res, ResMut};
+use bevy_kira_audio::{AudioChannel, AudioControl, AudioInstance, AudioSource, AudioTween};
 
-use crate::utilities::{AudioSettings, CrossFadeTrack, SuppliesAudio, TrackType};
-use crate::{
-	AmbianceAudioChannelA, AmbianceAudioChannelB, AudioCrossFade, MusicAudioChannelA,
-	MusicAudioChannelB, SfxAudioChannel, UiSfxAudioChannel,
-};
+use crate::utilities::{AudioSettings, SuppliesAudio, TrackType};
+use crate::{AmbianceAudioChannel, MusicAudioChannel, SfxAudioChannel, UiSfxAudioChannel};
 
 #[derive(SystemParam)]
 pub struct AudioChannels<'w, 's> {
-	pub music_channel_a: Res<'w, AudioChannel<MusicAudioChannelA>>,
-	pub music_channel_b: Res<'w, AudioChannel<MusicAudioChannelB>>,
-	pub ambiance_channel_a: Res<'w, AudioChannel<AmbianceAudioChannelA>>,
-	pub ambiance_channel_b: Res<'w, AudioChannel<AmbianceAudioChannelB>>,
+	pub music_channel: Res<'w, AudioChannel<MusicAudioChannel>>,
+	pub ambiance_channel: Res<'w, AudioChannel<AmbianceAudioChannel>>,
 	pub sfx_channel: Res<'w, AudioChannel<SfxAudioChannel>>,
 	pub ui_sfx_channel: Res<'w, AudioChannel<UiSfxAudioChannel>>,
 
@@ -25,100 +20,211 @@ pub struct AudioChannels<'w, 's> {
 
 #[derive(SystemParam)]
 pub struct MusicBox<'w, 's, T: SuppliesAudio> {
-	pub commands: Commands<'w, 's>,
-	pub channels: AudioChannels<'w, 's>,
-	pub handles: Res<'w, T>,
-	pub settings: Res<'w, AudioSettings>,
-	pub fade_state: Res<'w, AudioCrossFade>,
+	channels: AudioChannels<'w, 's>,
+	handles: Res<'w, T>,
+	settings: ResMut<'w, AudioSettings>,
+	state: ResMut<'w, MusicBoxState>,
+	audio_instances: ResMut<'w, Assets<AudioInstance>>,
 }
 
-pub enum MusicTrackState {
-	Pending,
-	Playing,
-	FadeOut { progress: f32 },
-	FadeIn { progress: f32 },
-	CrossFade { out_progress: f32, in_progress: f32 },
+/// Tracks the currently active audio instance singleton channels, to allow
+/// for transitions
+#[derive(Debug, Default)]
+pub struct MusicBoxState {
+	pub active_music: Option<Handle<AudioInstance>>,
+	pub active_ambiance: Option<Handle<AudioInstance>>,
 }
 
-impl Default for MusicTrackState {
-	fn default() -> Self {
-		Self::Pending
+impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> {
+	pub fn cross_fade_music<Name: ToString>(
+		&mut self,
+		name: Name,
+		fade: AudioTween,
+	) -> Option<Handle<AudioInstance>> {
+		self.fade_out_music(fade.clone());
+		self.fade_in_music(name, fade)
 	}
-}
 
-impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> {
-	pub fn play_looped_music<Name: ToString>(&self, name: Name) -> Option<InstanceHandle> {
-		self.channels.music_channel_a.stop();
-		self.channels.music_channel_b.stop();
-
-		match self.resolve_track_name(name) {
-			TrackType::Single(track) => match self.fade_state.music.active {
-				CrossFadeTrack::A => Some(
-					self.channels
-						.music_channel_a
-						.play_looped(track.clone_weak()),
-				),
-				CrossFadeTrack::B => Some(
-					self.channels
-						.music_channel_b
-						.play_looped(track.clone_weak()),
-				),
-			},
-			TrackType::WithIntro(intro, looped) => match self.fade_state.music.active {
-				CrossFadeTrack::A => Some(
-					self.channels
-						.music_channel_a
-						.play_looped_with_intro(intro.clone_weak(), looped.clone_weak()),
-				),
-				CrossFadeTrack::B => Some(
-					self.channels
-						.music_channel_b
-						.play_looped_with_intro(intro.clone_weak(), looped.clone_weak()),
-				),
-			},
+	pub fn in_out_fade_music<Name: ToString>(
+		&mut self,
+		name: Name,
+		in_tween: AudioTween,
+		out_tween: AudioTween,
+	) -> Option<Handle<AudioInstance>> {
+		self.fade_out_music(out_tween);
+		self.fade_in_music(name, in_tween)
+	}
+
+	pub fn play_music<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> {
+		self.stop_music();
+		self.fade_in_music(name, AudioTween::default())
+	}
+
+	pub fn stop_music(&mut self) {
+		self.fade_out_music(AudioTween::default());
+	}
+
+	pub fn fade_out_music(&mut self, fade: AudioTween) {
+		let handle = std::mem::replace(&mut self.state.active_music, None)
+			.and_then(|handle| self.audio_instances.get_mut(&handle));
+		if let Some(current) = handle {
+			current.stop(fade);
+		}
+	}
+
+	pub fn fade_in_music<Name: ToString>(
+		&mut self,
+		name: Name,
+		fade: AudioTween,
+	) -> Option<Handle<AudioInstance>> {
+		match self.map_tracks(name) {
+			TrackType::WithIntro(_, track) | TrackType::Single(track) => {
+				let next = self
+					.channels
+					.music_channel
+					.play(track)
+					.fade_in(fade)
+					.looped()
+					.handle();
+				self.state.active_music = Some(next.clone());
+
+				Some(next)
+			}
+			TrackType::Missing => None,
+		}
+	}
+
+	pub fn cross_fade_ambiance<Name: ToString>(
+		&mut self,
+		name: Name,
+		fade: AudioTween,
+	) -> Option<Handle<AudioInstance>> {
+		self.fade_out_ambiance(fade.clone());
+		self.fade_in_ambiance(name, fade)
+	}
+
+	pub fn in_out_fade_ambiance<Name: ToString>(
+		&mut self,
+		name: Name,
+		in_tween: AudioTween,
+		out_tween: AudioTween,
+	) -> Option<Handle<AudioInstance>> {
+		self.fade_out_ambiance(out_tween);
+		self.fade_in_ambiance(name, in_tween)
+	}
+
+	pub fn play_ambiance<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> {
+		self.stop_ambiance();
+		self.fade_in_ambiance(name, AudioTween::default())
+	}
+
+	pub fn stop_ambiance(&mut self) {
+		self.fade_out_ambiance(AudioTween::default());
+	}
+
+	pub fn fade_out_ambiance(&mut self, fade: AudioTween) {
+		let handle = std::mem::replace(&mut self.state.active_ambiance, None)
+			.and_then(|handle| self.audio_instances.get_mut(&handle));
+		if let Some(current) = handle {
+			current.stop(fade);
+		}
+	}
+
+	pub fn fade_in_ambiance<Name: ToString>(
+		&mut self,
+		name: Name,
+		fade: AudioTween,
+	) -> Option<Handle<AudioInstance>> {
+		match self.map_tracks(name) {
+			TrackType::WithIntro(_, track) | TrackType::Single(track) => {
+				let next = self
+					.channels
+					.ambiance_channel
+					.play(track)
+					.fade_in(fade)
+					.looped()
+					.handle();
+				self.state.active_ambiance = Some(next.clone());
+
+				Some(next)
+			}
 			TrackType::Missing => None,
 		}
 	}
 
-	pub fn play_effect_once<Name: ToString>(&mut self, name: Name) -> Option<InstanceHandle> {
-		let name = name.to_string();
+	pub fn play_sfx<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> {
+		match self.map_tracks(name) {
+			TrackType::WithIntro(_, track) | TrackType::Single(track) => {
+				let instance = self.channels.sfx_channel.play(track).handle();
+				Some(instance)
+			}
+			TrackType::Missing => None,
+		}
+	}
 
-		match self.handles.get_audio_track(&name) {
-			Some(track) => Some(self.channels.sfx_channel.play(track)),
-			None => None,
+	pub fn play_ui_sfx<Name: ToString>(&mut self, name: Name) -> Option<Handle<AudioInstance>> {
+		match self.map_tracks(name) {
+			TrackType::WithIntro(_, track) | TrackType::Single(track) => {
+				let instance = self.channels.ui_sfx_channel.play(track).handle();
+				Some(instance)
+			}
+			TrackType::Missing => None,
 		}
 	}
 
-	pub fn play_ambiance<Name: ToString>(&self, name: Name) -> Option<InstanceHandle> {
-		self.channels.ambiance_channel_a.stop();
-		self.channels.ambiance_channel_b.stop();
-
-		match self.resolve_track_name(name) {
-			TrackType::Single(track) => match self.fade_state.ambiance.active {
-				CrossFadeTrack::A => Some(
-					self.channels
-						.ambiance_channel_a
-						.play_looped(track.clone_weak()),
-				),
-				CrossFadeTrack::B => Some(
-					self.channels
-						.ambiance_channel_b
-						.play_looped(track.clone_weak()),
-				),
+	pub fn sync_settings(&self) {
+		self.channels
+			.music_channel
+			.set_volume((self.settings.music_volume * self.settings.master_volume) as f64);
+		self.channels
+			.ambiance_channel
+			.set_volume((self.settings.ambiance_volume * self.settings.master_volume) as f64);
+		self.channels
+			.sfx_channel
+			.set_volume((self.settings.sfx_volume * self.settings.master_volume) as f64);
+		self.channels
+			.ui_sfx_channel
+			.set_volume((self.settings.ui_volume * self.settings.master_volume) as f64);
+	}
+
+	pub fn settings(&self) -> &AudioSettings {
+		&self.settings
+	}
+
+	pub fn settings_mut(&mut self) -> &mut AudioSettings {
+		&mut self.settings
+	}
+
+	pub fn set_master_volume(&mut self, level: f32) {
+		self.settings.master_volume = level;
+	}
+	pub fn set_music_volume(&mut self, level: f32) {
+		self.settings.music_volume = level;
+	}
+	pub fn set_ambiance_volume(&mut self, level: f32) {
+		self.settings.ambiance_volume = level;
+	}
+	pub fn set_sfx_volume(&mut self, level: f32) {
+		self.settings.sfx_volume = level;
+	}
+	pub fn set_ui_sfx_volume(&mut self, level: f32) {
+		self.settings.ui_volume = level;
+	}
+
+	fn map_tracks<Name: ToString>(&'w self, name: Name) -> TrackType<Handle<AudioSource>> {
+		match self.handles.resolve_track_name(name) {
+			TrackType::Single(name) => match self.handles.get_audio_track(name) {
+				Some(handle) => TrackType::Single(handle),
+				None => TrackType::Missing,
 			},
-			TrackType::WithIntro(intro, looped) => match self.fade_state.ambiance.active {
-				CrossFadeTrack::A => Some(
-					self.channels
-						.ambiance_channel_a
-						.play_looped_with_intro(intro.clone_weak(), looped.clone_weak()),
-				),
-				CrossFadeTrack::B => Some(
-					self.channels
-						.ambiance_channel_b
-						.play_looped_with_intro(intro.clone_weak(), looped.clone_weak()),
-				),
+			TrackType::WithIntro(intro, looper) => match (
+				self.handles.get_audio_track(intro),
+				self.handles.get_audio_track(looper),
+			) {
+				(Some(intro), Some(looper)) => TrackType::WithIntro(intro, looper),
+				_ => TrackType::Missing,
 			},
-			TrackType::Missing => None,
+			TrackType::Missing => TrackType::Missing,
 		}
 	}
 }
diff --git a/src/utilities.rs b/src/utilities.rs
index e41343106240795ce92bf0f05e73ba24e22abd31..1c02aa9404439422c19953095a4b73501bab1ed2 100644
--- a/src/utilities.rs
+++ b/src/utilities.rs
@@ -1,91 +1,52 @@
 use bevy::ecs::system::Resource;
 use bevy::prelude::*;
 use bevy_kira_audio::AudioSource;
-use serde::{Deserialize, Serialize};
 
 use crate::music_box::MusicBox;
 
 pub trait SuppliesAudio: Resource {
+	fn resolve_track_name<T: ToString>(&self, name: T) -> TrackType<String>;
 	fn get_audio_track<T: ToString>(&self, name: T) -> Option<Handle<AudioSource>>;
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct AudioSettings {
-	master_volume: f32,
-	music_volume: f32,
-	sfx_volume: f32,
-	ui_volume: f32,
+	pub master_volume: f32,
+	pub music_volume: f32,
+	pub ambiance_volume: f32,
+	pub sfx_volume: f32,
+	pub ui_volume: f32,
 }
 
 impl Default for AudioSettings {
 	fn default() -> Self {
 		Self {
 			master_volume: 1.0,
-			music_volume: 0.0,
-			sfx_volume: 0.0,
-			ui_volume: 0.0,
+			music_volume: 1.0,
+			ambiance_volume: 1.0,
+			sfx_volume: 1.0,
+			ui_volume: 1.0,
 		}
 	}
 }
 
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
-pub enum CrossFadeTrack {
-	#[default]
-	A,
-	B,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
-pub struct CrossFadeState {
-	pub active: CrossFadeTrack,
-	pub next: CrossFadeTrack,
-	pub progress: f32,
-}
-
-impl Default for CrossFadeState {
-	fn default() -> Self {
-		Self {
-			active: CrossFadeTrack::A,
-			next: CrossFadeTrack::B,
-			progress: 0.0,
-		}
-	}
-}
-
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
-pub struct AudioCrossFade {
-	pub music: CrossFadeState,
-	pub ambiance: CrossFadeState,
-}
-
-pub enum TrackType {
-	Single(Handle<AudioSource>),
-	WithIntro(Handle<AudioSource>, Handle<AudioSource>),
+pub enum TrackType<T> {
+	Single(T),
+	WithIntro(T, T),
 	Missing,
 }
 
-impl<'w, 's, T: SuppliesAudio> MusicBox<'w, 's, T> {
-	pub fn resolve_track_name<Name: ToString>(&'w self, name: Name) -> TrackType {
-		let name = name.to_string();
-
-		if let (Some(intro), Some(looped)) = (
-			self.handles.get_audio_track(format!("{}_intro", &name)),
-			self.handles.get_audio_track(format!("{}_loop", &name)),
-		) {
-			TrackType::WithIntro(intro, looped)
-		} else if let Some(track) = self.handles.get_audio_track(name.clone()) {
-			TrackType::Single(track)
-		} else if let Some(track) = self.handles.get_audio_track(format!("{}_looped", name)) {
-			TrackType::Single(track)
-		} else {
-			TrackType::Missing
-		}
+impl SuppliesAudio for AssetServer {
+	fn resolve_track_name<T: ToString>(&self, name: T) -> TrackType<String> {
+		TrackType::Single(name.to_string())
 	}
-}
 
-
-impl SuppliesAudio for AssetServer {
 	fn get_audio_track<T: ToString>(&self, name: T) -> Option<Handle<AudioSource>> {
 		Some(self.load(&name.to_string()))
 	}
-}
\ No newline at end of file
+}
+
+pub fn sync_music_volume<T: SuppliesAudio>(music: MusicBox<T>) {
+	music.sync_settings();
+}