music/
lib.rs

1#![deny(missing_docs)]
2
3//! A high level library for playing music
4
5extern 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
15/// Minimum value for playback volume parameter.
16pub const MIN_VOLUME: f64 = 0.0;
17
18/// Maximum value for playback volume parameter.
19pub const MAX_VOLUME: f64 = 1.0;
20
21/// Initializes the audio mixer and allocates the number of concurrent sound channels.
22fn init_audio(num_sound_channels: i32) {
23    // Load dynamic libraries.
24    // Ignore formats that are not built in.
25    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    // Sets the number of simultaneous sound effects channels
36    // that are available.
37    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
48/// Creates SDL context and starts the audio context
49///
50/// * `num_sound_channels`: The number of concurrent sound channels to allocate. This limits
51///  the number of sounds that can be played simultaneously.
52pub 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
61/// Initializes audio and sets up current objects
62///
63/// * `num_sound_channels`: The number of concurrent sound channels to allocate. This limits
64///  the number of sounds that can be played simultaneously.
65pub 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
88/// Binds a music file to value.
89pub 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
98/// Binds a sound file to value.
99pub 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/// Tells how many times to repeat.
109#[derive(Copy, Clone)]
110pub enum Repeat {
111    /// Repeats forever.
112    Forever,
113    /// Repeats amount of times.
114    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
126/// Sets the volume of the music mixer.
127///
128/// The volume is set on a scale of 0.0 to 1.0, which means 0-100%.
129/// Values greater than 1.0 will use 1.0.
130/// Values less than 0.0 will use 0.0.
131pub fn set_volume(volume: f64) {
132    return mixer::Music::set_volume(to_sdl2_volume(volume));
133}
134
135/// Converts from piston_music volume representation to SDL2 representation.
136///
137/// Map 0.0 - 1.0 to 0 - 128 (sdl2::mixer::MAX_VOLUME).
138fn to_sdl2_volume(volume: f64) -> i32 {
139    (volume.max(MIN_VOLUME).min(MAX_VOLUME) * mixer::MAX_VOLUME as f64) as i32
140}
141
142/// Plays a music track.
143pub 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
150/// Plays a sound effect track.
151///
152/// The volume is set on a scale of 0.0 to 1.0, which means 0-100%.
153/// Values greater than 1.0 will use 1.0.
154/// Values less than 0.0 will use 0.0.
155pub 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}