1#![deny(missing_docs)]
2
3extern crate current;
6extern crate sdl2;
7
8use current::{Current, CurrentGuard};
9use sdl2::mixer;
10use std::any::Any;
11use std::collections::HashMap;
12use std::hash::Hash;
13use std::path::Path;
14
15pub const MIN_VOLUME: f64 = 0.0;
17
18pub const MAX_VOLUME: f64 = 1.0;
20
21fn init_audio(num_sound_channels: i32) {
23 let _ = mixer::init(
26 mixer::InitFlag::MP3 | mixer::InitFlag::FLAC | mixer::InitFlag::MOD | mixer::InitFlag::OGG,
27 );
28 mixer::open_audio(
29 mixer::DEFAULT_FREQUENCY,
30 mixer::DEFAULT_FORMAT,
31 mixer::DEFAULT_CHANNELS,
32 1024,
33 )
34 .unwrap();
35 mixer::allocate_channels(num_sound_channels);
38}
39
40unsafe fn current_music_tracks<T: 'static + Any>() -> Current<HashMap<T, mixer::Music<'static>>> {
41 Current::new()
42}
43
44unsafe fn current_sound_tracks<T: 'static + Any>() -> Current<HashMap<T, mixer::Chunk>> {
45 Current::new()
46}
47
48pub fn start<M: Eq + Hash + 'static + Any, S: Eq + Hash + 'static + Any, F: FnOnce()>(
53 num_sound_channels: i32,
54 f: F,
55) {
56 let sdl = sdl2::init().unwrap();
57 start_context::<M, S, _>(&sdl, num_sound_channels, f);
58 drop(sdl);
59}
60
61pub fn start_context<M: Eq + Hash + 'static + Any, S: Eq + Hash + 'static + Any, F: FnOnce()>(
66 sdl: &sdl2::Sdl,
67 num_sound_channels: i32,
68 f: F,
69) {
70 let audio = sdl.audio().unwrap();
71 let timer = sdl.timer().unwrap();
72
73 init_audio(num_sound_channels);
74 let mut music_tracks: HashMap<M, mixer::Music> = HashMap::new();
75 let music_tracks_guard = CurrentGuard::new(&mut music_tracks);
76
77 let mut sound_tracks: HashMap<S, mixer::Chunk> = HashMap::new();
78 let sound_tracks_guard = CurrentGuard::new(&mut sound_tracks);
79
80 f();
81
82 drop(sound_tracks_guard);
83 drop(music_tracks_guard);
84 drop(timer);
85 drop(audio);
86}
87
88pub fn bind_music_file<T, P>(val: T, file: P)
90where
91 T: 'static + Eq + Hash + Any,
92 P: AsRef<Path>,
93{
94 let track = mixer::Music::from_file(file.as_ref()).unwrap();
95 unsafe { current_music_tracks() }.insert(val, track);
96}
97
98pub fn bind_sound_file<T, P>(val: T, file: P)
100where
101 T: 'static + Eq + Hash + Any,
102 P: AsRef<Path>,
103{
104 let track = mixer::Chunk::from_file(file.as_ref()).unwrap();
105 unsafe { current_sound_tracks() }.insert(val, track);
106}
107
108#[derive(Copy, Clone)]
110pub enum Repeat {
111 Forever,
113 Times(u16),
115}
116
117impl Repeat {
118 fn to_sdl2_repeats(&self) -> i32 {
119 match *self {
120 Repeat::Forever => -1,
121 Repeat::Times(val) => val as i32,
122 }
123 }
124}
125
126pub fn set_volume(volume: f64) {
132 return mixer::Music::set_volume(to_sdl2_volume(volume));
133}
134
135fn to_sdl2_volume(volume: f64) -> i32 {
139 (volume.max(MIN_VOLUME).min(MAX_VOLUME) * mixer::MAX_VOLUME as f64) as i32
140}
141
142pub fn play_music<T: Eq + Hash + 'static + Any>(val: &T, repeat: Repeat) {
144 let _ = unsafe { current_music_tracks::<T>() }
145 .get(val)
146 .expect("music: Attempted to play value that is not bound to asset")
147 .play(repeat.to_sdl2_repeats());
148}
149
150pub fn play_sound<T: Eq + Hash + 'static + Any>(val: &T, repeat: Repeat, volume: f64) {
156 let channel = sdl2::mixer::Channel::all();
157 unsafe {
158 channel
159 .play(
160 current_sound_tracks::<T>()
161 .get(val)
162 .expect("music: Attempted to play value that is not bound to asset"),
163 repeat.to_sdl2_repeats(),
164 )
165 .unwrap()
166 .set_volume(to_sdl2_volume(volume));
167 }
168}