diff --git a/src/sdl2/mixer/.gitignore b/src/sdl2/mixer/.gitignore new file mode 100644 index 00000000..43e19a85 --- /dev/null +++ b/src/sdl2/mixer/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +*~ +*# +*.o +*.so +*.swp +*.dylib +*.dSYM +*.dll +*.rlib +*.dummy +*.exe +*-test +/bin/main +/bin/test-internal +/bin/test-external +/doc/ +/target/ +/build/ +/.rust/ +rusti.sh + +Cargo.lock diff --git a/src/sdl2/mixer/.travis.yml b/src/sdl2/mixer/.travis.yml new file mode 100644 index 00000000..40e4f20e --- /dev/null +++ b/src/sdl2/mixer/.travis.yml @@ -0,0 +1,28 @@ +language: rust +rust: + - beta + - stable +env: + global: + - secure: JLix99cbDVr9+s9QOux8FmnyEHGmpNHavKoO7lo1dG4Em8XyeByIXeBNggYX3d/vpdQ+FrqNO+3HrRJpZvXl1OO3uPGFtIQLH6bXLTaggC3z/67maEQSQQS8rcSHImkg1CDKoPWKvZfnB6IqWRYLTBJWILwBLoocure7/+/8III= + - LD_LIBRARY_PATH: /usr/local/lib +install: + - cd .. + - time wget -q https://www.libsdl.org/release/SDL2-2.0.3.tar.gz + - time wget -q https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-2.0.0.tar.gz + - time tar xf SDL2-*.tar.gz + - time tar xf SDL2_mixer-*.tar.gz + - cd SDL2-* + - ./configure && make && sudo make install + - cd - + - cd SDL2_mixer-* + - ./configure && make && sudo make install + - cd - + - cd rust-sdl2_mixer +script: + - cargo build -v + - cargo test -v + - cargo doc -v +after_script: + - cp -R target/doc doc + - curl https://www.rust-ci.org/artifacts/put?t=$RUSTCI_TOKEN | sh diff --git a/src/sdl2/mixer/Cargo.toml b/src/sdl2/mixer/Cargo.toml new file mode 100644 index 00000000..f5812422 --- /dev/null +++ b/src/sdl2/mixer/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "sdl2_mixer" +description = "SDL2_mixer bindings for Rust" +repository = "https://github.com/andelf/rust-sdl2_mixer" +version = "0.25.0" +license = "MIT/Apache-2.0" +authors = ["ShuYu Wang "] +keywords = ["SDL", "windowing", "graphics", "music", "sound"] + +[lib] +name = "sdl2_mixer" +path = "src/sdl2_mixer/lib.rs" + +[dependencies] +bitflags = "0.6.0" +sdl2 = "0.25.0" +sdl2-sys = "0.25.0" +libc = "0.2" + +# [dependencies.sdl2] +# git = "https://github.com/AngryLawyer/rust-sdl2" + +# [dependencies.sdl2-sys] +# git = "https://github.com/AngryLawyer/rust-sdl2" diff --git a/src/sdl2/mixer/LICENSE-APACHE b/src/sdl2/mixer/LICENSE-APACHE new file mode 100644 index 00000000..16fe87b0 --- /dev/null +++ b/src/sdl2/mixer/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/sdl2/mixer/LICENSE-MIT b/src/sdl2/mixer/LICENSE-MIT new file mode 100644 index 00000000..25597d58 --- /dev/null +++ b/src/sdl2/mixer/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2010 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/sdl2/mixer/README.md b/src/sdl2/mixer/README.md new file mode 100644 index 00000000..e4d2c2fc --- /dev/null +++ b/src/sdl2/mixer/README.md @@ -0,0 +1,105 @@ +Rust-SDL2_mixer +============= + +[![Build Status](https://travis-ci.org/andelf/rust-sdl2_mixer.svg?branch=master)](https://travis-ci.org/andelf/rust-sdl2_mixer) +[![crates.io](http://meritbadge.herokuapp.com/sdl2_mixer)](https://crates.io/crates/sdl2_mixer) +[![Crates.io](https://img.shields.io/crates/l/sdl2_mixer.svg)](https://github.com/andelf/rust-sdl2_mixer/blob/master/LICENSE-MIT) + +Rust bindings for SDL2_mixer. + +## Overview + +Rust-SDL2_mixer is a library for talking to the new SDL2_mixer library from Rust. + +Rust-SDL2_mixer uses the MIT licence. + +## Requirements + +* [Rust-SDL2](https://github.com/AngryLawyer/rust-sdl2) +* SDL2_mixer development libraries +* Latest stable Rust master + +#### OSX + +```bash +brew install sdl2_mixer --with-flac --with-fluid-synth --with-libmikmod --with-libmodplug --with-libvorbis --with-smpeg2 +``` + +#### Windows + +1. Download mingw development libraries from https://www.libsdl.org/projects/SDL_mixer/ +(`SDL2_mixer-devel-2.0.x-mingw.tar.gz`) +2. Unpack to a folder of your choosing (You can delete it afterwards). +3. Copy all lib files from + > SDL2_mixer-2.0.x\x86_64-w64-mingw32\lib\ + + to (for Rust 1.6 and above) + > C:\Program Files\Rust\\**lib**\rustlib\x86_64-pc-windows-gnu\lib + + or to (for Rust versions 1.5 and below) + > C:\Program Files\Rust\\**bin**\rustlib\x86_64-pc-windows-gnu\lib + + or to your library folder of choice, and ensure you have a system environment variable of + > LIBRARY_PATH = C:\your\rust\library\folder + + For Multirust Users, this folder will be in + > C:\Users\{Your Username}\AppData\Local\.multirust\toolchains\{current toolchain}\lib\rustlib\x86_64-pc-windows-gnu\lib +4. Copy `SDL2_mixer.dll` and `smpeg2.dll` from + > SDL2_mixer-2.0.x\x86_64-w64-mingw32\bin\ + + into your cargo project, right next to your `Cargo.toml`. + +## Installation + +Place the following into your project's Cargo.toml file: + +```toml +[dependencies] +sdl2_mixer = "0.24.0" +``` + +Or, to depend on the newest rust-sdl2_mixer, reference the repository: + +```toml +[dependencies.sdl2_mixer] +git = "https://github.com/andelf/rust-sdl2_mixer" +``` + +You can also just clone and build the library yourself: + +```bash +git clone https://github.com/andelf/rust-sdl2_mixer +cd rust-sdl2_mixer +cargo build +# TODO: OR if you are using the mac framework version +rustc --cfg mac_framework src/sdl2_mixer/lib.rs +``` + +If you're not using Cargo, you can compile the library manually: + +```bash +git clone https://github.com/andelf/rust-sdl2_mixer +cd rust-sdl2_mixer +rustc src/sdl2_mixer/lib.rs +``` + +## Demo + +A simple demo that plays out a portion of a given music file is included: + +```bash +cargo run --example demo path/to/music.(mp3|flac|ogg|wav) +``` + +## License + +Licensed under either of + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/src/sdl2/mixer/examples/demo.rs b/src/sdl2/mixer/examples/demo.rs new file mode 100644 index 00000000..469bbddf --- /dev/null +++ b/src/sdl2/mixer/examples/demo.rs @@ -0,0 +1,86 @@ +extern crate sdl2; +extern crate sdl2_mixer; + +use std::env; +use std::path::Path; +use sdl2_mixer::{INIT_MP3, INIT_FLAC, INIT_MOD, INIT_FLUIDSYNTH, INIT_MODPLUG, INIT_OGG, + AUDIO_S16LSB}; + +fn main() { + + let args: Vec<_> = env::args().collect(); + + if args.len() < 2 { + println!("Usage: ./demo audio.[mp3|wav|ogg]") + } else { + demo(Path::new(&args[1])); + } +} + +fn demo(filename: &Path) { + + println!("linked version: {}", sdl2_mixer::get_linked_version()); + + let sdl = sdl2::init().unwrap(); + let _audio = sdl.audio().unwrap(); + let mut timer = sdl.timer().unwrap(); + let _mixer_context = sdl2_mixer::init(INIT_MP3 | INIT_FLAC | INIT_MOD | INIT_FLUIDSYNTH | + INIT_MODPLUG | + INIT_OGG) + .unwrap(); + + let frequency = 44100; + let format = AUDIO_S16LSB; // signed 16 bit samples, in little-endian byte order + let channels = 2; // Stereo + let chunk_size = 1024; + let _ = sdl2_mixer::open_audio(frequency, format, channels, chunk_size).unwrap(); + sdl2_mixer::allocate_channels(0); + + { + let n = sdl2_mixer::get_chunk_decoders_number(); + println!("available chunk(sample) decoders: {}", n); + for i in 0..n { + println!(" decoder {} => {}", i, sdl2_mixer::get_chunk_decoder(i)); + } + } + + { + let n = sdl2_mixer::get_music_decoders_number(); + println!("available music decoders: {}", n); + for i in 0..n { + println!(" decoder {} => {}", i, sdl2_mixer::get_music_decoder(i)); + } + } + + println!("query spec => {:?}", sdl2_mixer::query_spec()); + + + let music = sdl2_mixer::Music::from_file(filename).unwrap(); + + fn hook_finished() { + println!("play ends! from rust cb"); + } + + sdl2_mixer::Music::hook_finished(hook_finished); + + println!("music => {:?}", music); + println!("music type => {:?}", music.get_type()); + println!("music volume => {:?}", sdl2_mixer::Music::get_volume()); + println!("play => {:?}", music.play(1)); + + timer.delay(10000); + + println!("fading out ... {:?}", sdl2_mixer::Music::fade_out(4000)); + + timer.delay(5000); + + println!("fading in from pos ... {:?}", + music.fade_in_from_pos(1, 10000, 100.0)); + + timer.delay(5000); + sdl2_mixer::Music::halt(); + timer.delay(1000); + + + println!("quitting sdl"); +} diff --git a/src/sdl2/mixer/src/sdl2_mixer/ffi.rs b/src/sdl2/mixer/src/sdl2_mixer/ffi.rs new file mode 100644 index 00000000..4cc2b0a2 --- /dev/null +++ b/src/sdl2/mixer/src/sdl2_mixer/ffi.rs @@ -0,0 +1,156 @@ +extern crate sdl2_sys as sys; + +use std::os::raw::{c_uint, c_int, c_char, c_double, c_void}; +use self::sys::rwops::SDL_RWops; +use self::sys::version::SDL_version; + +pub type MIX_InitFlags = c_uint; +pub const MIX_INIT_FLAC: c_uint = 1; +pub const MIX_INIT_MOD: c_uint = 2; +pub const MIX_INIT_MODPLUG: c_uint = 4; +pub const MIX_INIT_MP3: c_uint = 8; +pub const MIX_INIT_OGG: c_uint = 16; +pub const MIX_INIT_FLUIDSYNTH: c_uint = 32; +#[repr(C)] +pub struct Struct_Mix_Chunk { + pub allocated: c_int, + pub abuf: *const u8, + pub alen: u32, + pub volume: u8, +} +pub type Mix_Chunk = Struct_Mix_Chunk; +pub type Mix_Fading = c_uint; +pub const MIX_NO_FADING: c_uint = 0; +pub const MIX_FADING_OUT: c_uint = 1; +pub const MIX_FADING_IN: c_uint = 2; +pub type Mix_MusicType = c_uint; +pub const MUS_NONE: c_uint = 0; +pub const MUS_CMD: c_uint = 1; +pub const MUS_WAV: c_uint = 2; +pub const MUS_MOD: c_uint = 3; +pub const MUS_MID: c_uint = 4; +pub const MUS_OGG: c_uint = 5; +pub const MUS_MP3: c_uint = 6; +pub const MUS_MP3_MAD: c_uint = 7; +pub const MUS_FLAC: c_uint = 8; +pub const MUS_MODPLUG: c_uint = 9; +pub type Struct__Mix_Music = c_void; +pub type Mix_Music = Struct__Mix_Music; +pub type Mix_EffectFunc_t = ::std::option::Option; +pub type Mix_EffectDone_t = ::std::option::Option; +extern "C" { + pub fn Mix_Linked_Version() -> *const SDL_version; + pub fn Mix_Init(flags: c_int) -> c_int; + pub fn Mix_Quit(); + pub fn Mix_OpenAudio(frequency: c_int, + format: u16, + channels: c_int, + chunksize: c_int) + -> c_int; + pub fn Mix_AllocateChannels(numchans: c_int) -> c_int; + pub fn Mix_QuerySpec(frequency: *mut c_int, + format: *mut u16, + channels: *mut c_int) + -> c_int; + pub fn Mix_LoadWAV_RW(src: *mut SDL_RWops, freesrc: c_int) -> *mut Mix_Chunk; + pub fn Mix_LoadMUS(file: *const c_char) -> *mut Mix_Music; + pub fn Mix_LoadMUS_RW(src: *mut SDL_RWops, freesrc: c_int) -> *mut Mix_Music; + pub fn Mix_LoadMUSType_RW(src: *mut SDL_RWops, + type_: Mix_MusicType, + freesrc: c_int) + -> *mut Mix_Music; + pub fn Mix_QuickLoad_WAV(mem: *mut u8) -> *mut Mix_Chunk; + pub fn Mix_QuickLoad_RAW(mem: *mut u8, len: u32) -> *mut Mix_Chunk; + pub fn Mix_FreeChunk(chunk: *mut Mix_Chunk); + pub fn Mix_FreeMusic(music: *mut Mix_Music); + pub fn Mix_GetNumChunkDecoders() -> c_int; + pub fn Mix_GetChunkDecoder(index: c_int) -> *const c_char; + pub fn Mix_GetNumMusicDecoders() -> c_int; + pub fn Mix_GetMusicDecoder(index: c_int) -> *const c_char; + pub fn Mix_GetMusicType(music: *const Mix_Music) -> Mix_MusicType; + pub fn Mix_SetPostMix(mix_func: Option, + arg: *mut c_void); + pub fn Mix_HookMusic(mix_func: Option, + arg: *mut c_void); + pub fn Mix_HookMusicFinished(music_finished: Option); + pub fn Mix_GetMusicHookData() -> *mut c_void; + pub fn Mix_ChannelFinished(channel_finished: Option); + pub fn Mix_RegisterEffect(chan: c_int, + f: Mix_EffectFunc_t, + d: Mix_EffectDone_t, + arg: *mut c_void) + -> c_int; + pub fn Mix_UnregisterEffect(channel: c_int, f: Mix_EffectFunc_t) -> c_int; + pub fn Mix_UnregisterAllEffects(channel: c_int) -> c_int; + pub fn Mix_SetPanning(channel: c_int, left: u8, right: u8) -> c_int; + pub fn Mix_SetPosition(channel: c_int, angle: i16, distance: u8) -> c_int; + pub fn Mix_SetDistance(channel: c_int, distance: u8) -> c_int; + pub fn Mix_SetReverseStereo(channel: c_int, flip: c_int) -> c_int; + pub fn Mix_ReserveChannels(num: c_int) -> c_int; + pub fn Mix_GroupChannel(which: c_int, tag: c_int) -> c_int; + pub fn Mix_GroupChannels(from: c_int, to: c_int, tag: c_int) -> c_int; + pub fn Mix_GroupAvailable(tag: c_int) -> c_int; + pub fn Mix_GroupCount(tag: c_int) -> c_int; + pub fn Mix_GroupOldest(tag: c_int) -> c_int; + pub fn Mix_GroupNewer(tag: c_int) -> c_int; + pub fn Mix_PlayChannelTimed(channel: c_int, + chunk: *mut Mix_Chunk, + loops: c_int, + ticks: c_int) + -> c_int; + pub fn Mix_PlayMusic(music: *mut Mix_Music, loops: c_int) -> c_int; + pub fn Mix_FadeInMusic(music: *mut Mix_Music, loops: c_int, ms: c_int) -> c_int; + pub fn Mix_FadeInMusicPos(music: *mut Mix_Music, + loops: c_int, + ms: c_int, + position: c_double) + -> c_int; + pub fn Mix_FadeInChannelTimed(channel: c_int, + chunk: *mut Mix_Chunk, + loops: c_int, + ms: c_int, + ticks: c_int) + -> c_int; + pub fn Mix_Volume(channel: c_int, volume: c_int) -> c_int; + pub fn Mix_VolumeChunk(chunk: *mut Mix_Chunk, volume: c_int) -> c_int; + pub fn Mix_VolumeMusic(volume: c_int) -> c_int; + pub fn Mix_HaltChannel(channel: c_int) -> c_int; + pub fn Mix_HaltGroup(tag: c_int) -> c_int; + pub fn Mix_HaltMusic() -> c_int; + pub fn Mix_ExpireChannel(channel: c_int, ticks: c_int) -> c_int; + pub fn Mix_FadeOutChannel(which: c_int, ms: c_int) -> c_int; + pub fn Mix_FadeOutGroup(tag: c_int, ms: c_int) -> c_int; + pub fn Mix_FadeOutMusic(ms: c_int) -> c_int; + pub fn Mix_FadingMusic() -> Mix_Fading; + pub fn Mix_FadingChannel(which: c_int) -> Mix_Fading; + pub fn Mix_Pause(channel: c_int); + pub fn Mix_Resume(channel: c_int); + pub fn Mix_Paused(channel: c_int) -> c_int; + pub fn Mix_PauseMusic(); + pub fn Mix_ResumeMusic(); + pub fn Mix_RewindMusic(); + pub fn Mix_PausedMusic() -> c_int; + pub fn Mix_SetMusicPosition(position: c_double) -> c_int; + pub fn Mix_Playing(channel: c_int) -> c_int; + pub fn Mix_PlayingMusic() -> c_int; + pub fn Mix_SetMusicCMD(command: *const c_char) -> c_int; + pub fn Mix_SetSynchroValue(value: c_int) -> c_int; + pub fn Mix_GetSynchroValue() -> c_int; + pub fn Mix_SetSoundFonts(paths: *const c_char) -> c_int; + pub fn Mix_GetSoundFonts() -> *const c_char; + pub fn Mix_EachSoundFont(function: Option c_int>, + data: *mut c_void) -> c_int; + pub fn Mix_GetChunk(channel: c_int) -> *mut Mix_Chunk; + pub fn Mix_CloseAudio(); +} diff --git a/src/sdl2/mixer/src/sdl2_mixer/lib.rs b/src/sdl2/mixer/src/sdl2_mixer/lib.rs new file mode 100644 index 00000000..54bcc155 --- /dev/null +++ b/src/sdl2/mixer/src/sdl2_mixer/lib.rs @@ -0,0 +1,951 @@ +//! A binding for `SDL2_mixer`. +//! + +#![crate_name = "sdl2_mixer"] +#![crate_type = "lib"] + +#[macro_use] +extern crate bitflags; +extern crate libc; +extern crate sdl2; + +use std::default; +use std::fmt; +use std::ffi::{CString, CStr}; +use std::str::from_utf8; +use std::borrow::ToOwned; +use std::path::Path; +use libc::{c_int, uint16_t, c_double, c_uint}; +use sdl2::get_error; +use sdl2::rwops::RWops; +use sdl2::version::Version; + +// Setup linking for all targets. +#[cfg(target_os="macos")] +mod mac { + #[cfg(mac_framework)] + #[link(kind="framework", name="SDL2_mixer")] + extern "C" { + } + + #[cfg(not(mac_framework))] + #[link(name="SDL2_mixer")] + extern "C" { + } +} + +#[cfg(any(target_os="windows", target_os="linux", target_os="freebsd"))] +mod others { + #[link(name="SDL2_mixer")] + extern "C" { + } +} + +#[allow(non_camel_case_types, dead_code)] +mod ffi; + +// This comes from SDL_audio.h +#[allow(non_camel_case_types)] +mod ll { + use libc::uint16_t; + + pub const AUDIO_U8: uint16_t = 0x0008; + pub const AUDIO_S8: uint16_t = 0x8008; + pub const AUDIO_U16LSB: uint16_t = 0x0010; + pub const AUDIO_S16LSB: uint16_t = 0x8010; + pub const AUDIO_U16MSB: uint16_t = 0x1010; + pub const AUDIO_S16MSB: uint16_t = 0x9010; + pub const AUDIO_U16: uint16_t = AUDIO_U16LSB; + pub const AUDIO_S16: uint16_t = AUDIO_S16LSB; + pub const AUDIO_S32LSB: uint16_t = 0x8020; + pub const AUDIO_S32MSB: uint16_t = 0x9020; + pub const AUDIO_S32: uint16_t = AUDIO_S32LSB; + pub const AUDIO_F32LSB: uint16_t = 0x8120; + pub const AUDIO_F32MSB: uint16_t = 0x9120; + pub const AUDIO_F32: uint16_t = AUDIO_F32LSB; + pub const AUDIO_U16SYS: uint16_t = AUDIO_U16LSB; + pub const AUDIO_S16SYS: uint16_t = AUDIO_S16LSB; + pub const AUDIO_S32SYS: uint16_t = AUDIO_S32LSB; + pub const AUDIO_F32SYS: uint16_t = AUDIO_F32LSB; +} + +pub type AudioFormat = uint16_t; + +pub const AUDIO_U8: AudioFormat = ll::AUDIO_U8; +pub const AUDIO_S8: AudioFormat = ll::AUDIO_S8; +pub const AUDIO_U16LSB: AudioFormat = ll::AUDIO_U16LSB; +pub const AUDIO_S16LSB: AudioFormat = ll::AUDIO_S16LSB; +pub const AUDIO_U16MSB: AudioFormat = ll::AUDIO_U16MSB; +pub const AUDIO_S16MSB: AudioFormat = ll::AUDIO_S16MSB; +pub const AUDIO_U16: AudioFormat = ll::AUDIO_U16; +pub const AUDIO_S16: AudioFormat = ll::AUDIO_S16; +pub const AUDIO_S32LSB: AudioFormat = ll::AUDIO_S32LSB; +pub const AUDIO_S32MSB: AudioFormat = ll::AUDIO_S32MSB; +pub const AUDIO_S32: AudioFormat = ll::AUDIO_S32; +pub const AUDIO_F32LSB: AudioFormat = ll::AUDIO_F32LSB; +pub const AUDIO_F32MSB: AudioFormat = ll::AUDIO_F32MSB; +pub const AUDIO_F32: AudioFormat = ll::AUDIO_F32; +pub const AUDIO_U16SYS: AudioFormat = ll::AUDIO_U16SYS; +pub const AUDIO_S16SYS: AudioFormat = ll::AUDIO_S16SYS; +pub const AUDIO_S32SYS: AudioFormat = ll::AUDIO_S32SYS; +pub const AUDIO_F32SYS: AudioFormat = ll::AUDIO_F32SYS; + +/// The suggested default is signed 16bit samples in host byte order. +pub const DEFAULT_FORMAT: AudioFormat = ll::AUDIO_S16SYS; +/// Defualt channels: Stereo. +pub const DEFAULT_CHANNELS: isize = 2; +/// Good default sample rate in Hz (samples per second) for PC sound cards. +pub const DEFAULT_FREQUENCY: isize = 22050; +/// Maximum value for any volume setting. +pub const MAX_VOLUME: isize = 128; + +/// Returns the version of the dynamically linked `SDL_mixer` library +pub fn get_linked_version() -> Version { + + unsafe { Version::from_ll(*ffi::Mix_Linked_Version()) } +} + +bitflags!(pub flags InitFlag : u32 { + const INIT_FLAC = ::ffi::MIX_INIT_FLAC as u32, + const INIT_MOD = ::ffi::MIX_INIT_MOD as u32, + const INIT_MODPLUG = ::ffi::MIX_INIT_MODPLUG as u32, + const INIT_MP3 = ::ffi::MIX_INIT_MP3 as u32, + const INIT_OGG = ::ffi::MIX_INIT_OGG as u32, + const INIT_FLUIDSYNTH = ::ffi::MIX_INIT_FLUIDSYNTH as u32 +}); + +impl ToString for InitFlag { + fn to_string(&self) -> String { + let mut string = "".to_string(); + if self.contains(INIT_FLAC) { + string = string + &"INIT_FLAC ".to_string(); + } + if self.contains(INIT_MOD) { + string = string + &"INIT_MOD ".to_string(); + } + if self.contains(INIT_MODPLUG) { + string = string + &"INIT_MODPLUG ".to_string(); + } + if self.contains(INIT_MP3) { + string = string + &"INIT_MP3 ".to_string(); + } + if self.contains(INIT_OGG) { + string = string + &"INIT_OGG ".to_string(); + } + if self.contains(INIT_FLUIDSYNTH) { + string = string + &"INIT_FLUIDSYNTH ".to_string(); + } + string + } +} + +/// Context manager for `sdl2_mixer` to manage init and quit +pub struct Sdl2MixerContext; + +/// Cleans up all dynamically loaded library handles, freeing memory. +impl Drop for Sdl2MixerContext { + fn drop(&mut self) { + unsafe { + ffi::Mix_Quit(); + } + } +} + +/// Loads dynamic libraries and prepares them for use. Flags should be +/// one or more flags from `InitFlag`. +pub fn init(flags: InitFlag) -> Result { + let return_flags = unsafe { + let ret = ffi::Mix_Init(flags.bits() as c_int); + InitFlag::from_bits_truncate(ret as u32) + }; + // Check if all init flags were set + if flags.intersects(return_flags) { + Ok(Sdl2MixerContext) + } else { + // Flags not matching won't always set the error message text + // according to sdl docs + if get_error() == "" { + let un_init_flags = return_flags ^ flags; + let error_str = &("Could not init: ".to_string() + &un_init_flags.to_string()); + let _ = sdl2::set_error(error_str); + } + Err(get_error()) + } +} + + +/// Open the mixer with a certain audio format. +pub fn open_audio(frequency: isize, + format: AudioFormat, + channels: isize, + chunksize: isize) + -> Result<(), String> { + let ret = unsafe { + ffi::Mix_OpenAudio(frequency as c_int, + format, + channels as c_int, + chunksize as c_int) + }; + if ret == 0 { + Ok(()) + } else { + Err(get_error()) + } +} + +/// Shutdown and cleanup the mixer API. +pub fn close_audio() { + unsafe { ffi::Mix_CloseAudio() } +} + +/// Get the actual audio format in use by the opened audio device. +pub fn query_spec() -> Result<(isize, AudioFormat, isize), String> { + let mut frequency: c_int = 0; + let mut format: uint16_t = 0; + let mut channels: c_int = 0; + let ret = unsafe { ffi::Mix_QuerySpec(&mut frequency, &mut format, &mut channels) }; + if ret == 0 { + Err(get_error()) + } else { + Ok((frequency as isize, format as AudioFormat, channels as isize)) + } +} + +// 4.2 Samples + +/// Get the number of sample chunk decoders available from the `Mix_GetChunkDecoder` function. +pub fn get_chunk_decoders_number() -> isize { + unsafe { ffi::Mix_GetNumChunkDecoders() as isize } +} + +/// Get the name of the indexed sample chunk decoder. +pub fn get_chunk_decoder(index: isize) -> String { + unsafe { + let name = ffi::Mix_GetChunkDecoder(index as c_int); + from_utf8(CStr::from_ptr(name).to_bytes()).unwrap().to_owned() + } +} + +/// The internal format for an audio chunk. +#[derive(PartialEq)] +pub struct Chunk { + pub raw: *mut ffi::Mix_Chunk, + pub owned: bool, +} + +impl Drop for Chunk { + fn drop(&mut self) { + if self.owned { + unsafe { ffi::Mix_FreeChunk(self.raw) } + } + } +} + +impl Chunk { + /// Load file for use as a sample. + pub fn from_file(path: &Path) -> Result { + let raw = unsafe { ffi::Mix_LoadWAV_RW(try!(RWops::from_file(path, "rb")).raw(), 0) }; + if raw.is_null() { + Err(get_error()) + } else { + Ok(Chunk { + raw: raw, + owned: true, + }) + } + } + + /// Set chunk->volume to volume. + pub fn set_volume(&mut self, volume: isize) -> isize { + unsafe { ffi::Mix_VolumeChunk(self.raw, volume as c_int) as isize } + } + + /// current volume for the chunk. + pub fn get_volume(&self) -> isize { + unsafe { ffi::Mix_VolumeChunk(self.raw, -1) as isize } + } +} + +/// Loader trait for `RWops` +pub trait LoaderRWops { + /// Load src for use as a sample. + fn load_wav(&self) -> Result; + + fn load_music(&self) -> Result; +} + +impl<'a> LoaderRWops for RWops<'a> { + /// Load src for use as a sample. + fn load_wav(&self) -> Result { + let raw = unsafe { ffi::Mix_LoadWAV_RW(self.raw(), 0) }; + if raw.is_null() { + Err(get_error()) + } else { + Ok(Chunk { + raw: raw, + owned: true, + }) + } + } + + /// Load src for use as music. + fn load_music(&self) -> Result { + let raw = unsafe { ffi::Mix_LoadMUS_RW(self.raw(), 0) }; + if raw.is_null() { + Err(get_error()) + } else { + Ok(Music { + raw: raw, + owned: true, + }) + } + } + +} + + +// 4.3 Channels + +/// Fader effect type enumerations +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum Fading { + NoFading = ffi::MIX_NO_FADING as isize, + FadingOut = ffi::MIX_FADING_OUT as isize, + FadingIn = ffi::MIX_FADING_IN as isize, +} + +/// Sound effect channel. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Channel(isize); + +/// Return a channel object. +pub fn channel(chan: isize) -> Channel { + Channel(chan) +} + +/// Set the number of channels being mixed. +pub fn allocate_channels(numchans: isize) -> isize { + unsafe { ffi::Mix_AllocateChannels(numchans as c_int) as isize } +} + +static mut channel_finished_callback: Option = None; + +extern "C" fn c_channel_finished_callback(ch: c_int) { + unsafe { + match channel_finished_callback { + None => (), + Some(cb) => cb(Channel(ch as isize)), + } + } +} + +/// When channel playback is halted, then the specified `channel_finished` function is called. +pub fn set_channel_finished(f: fn(Channel)) { + unsafe { + channel_finished_callback = Some(f); + ffi::Mix_ChannelFinished(Some(c_channel_finished_callback as extern "C" fn(ch: c_int))); + } +} + +/// Unhooks the specified function set before, so no function is called when channel playback is +/// halted. +pub fn unset_channel_finished() { + unsafe { + ffi::Mix_ChannelFinished(None); + channel_finished_callback = None; + } +} + +impl Channel { + /// Represent for all channels (-1) + pub fn all() -> Channel { + Channel(-1) + } + + /// This is the MIX_CHANNEL_POST (-2) + pub fn post() -> Channel { + Channel(-2) + } + + /// Set the volume for any allocated channel. + pub fn set_volume(self, volume: isize) -> isize { + let Channel(ch) = self; + unsafe { ffi::Mix_Volume(ch as c_int, volume as c_int) as isize } + } + + /// Returns the channels volume on scale of 0 to 128. + pub fn get_volume(self) -> isize { + let Channel(ch) = self; + unsafe { ffi::Mix_Volume(ch as c_int, -1) as isize } + } + + /// Play chunk on channel, or if channel is -1, pick the first free unreserved channel. + pub fn play(self, chunk: &Chunk, loops: isize) -> Result { + self.play_timed(chunk, loops, -1) + } + + pub fn play_timed(self, chunk: &Chunk, loops: isize, ticks: isize) -> Result { + let Channel(ch) = self; + let ret = unsafe { + ffi::Mix_PlayChannelTimed(ch as c_int, chunk.raw, loops as c_int, ticks as c_int) + }; + if ret == -1 { + Err(get_error()) + } else { + Ok(Channel(ret as isize)) + } + } + + /// Play chunk on channel, or if channel is -1, pick the first free unreserved channel. + pub fn fade_in(self, chunk: &Chunk, loops: isize, ms: isize) -> Result { + self.fade_in_timed(chunk, loops, ms, -1) + } + + pub fn fade_in_timed(self, + chunk: &Chunk, + loops: isize, + ms: isize, + ticks: isize) + -> Result { + let Channel(ch) = self; + let ret = unsafe { + ffi::Mix_FadeInChannelTimed(ch as c_int, + chunk.raw, + loops as c_int, + ms as c_int, + ticks as c_int) + }; + if ret == -1 { + Err(get_error()) + } else { + Ok(Channel(ret as isize)) + } + } + + /// Pause channel, or all playing channels if -1 is passed in. + pub fn pause(self) { + let Channel(ch) = self; + unsafe { + ffi::Mix_Pause(ch as c_int); + } + } + + /// Unpause channel, or all playing and paused channels if -1 is passed in. + pub fn resume(self) { + let Channel(ch) = self; + unsafe { + ffi::Mix_Resume(ch as c_int); + } + } + + /// Halt channel playback + pub fn halt(self) { + let Channel(ch) = self; + unsafe { + ffi::Mix_HaltChannel(ch as c_int); + } + } + + /// Halt channel playback, after ticks milliseconds. + pub fn expire(self, ticks: isize) -> isize { + let Channel(ch) = self; + unsafe { ffi::Mix_ExpireChannel(ch as c_int, ticks as c_int) as isize } + } + + /// Gradually fade out which channel over ms milliseconds starting from now. + pub fn fade_out(self, ms: isize) -> isize { + let Channel(ch) = self; + unsafe { ffi::Mix_FadeOutChannel(ch as c_int, ms as c_int) as isize } + } + + /// if channel is playing, or not. + pub fn is_playing(self) -> bool { + let Channel(ch) = self; + unsafe { ffi::Mix_Playing(ch as c_int) != 0 } + } + + /// if channel is paused, or not. + pub fn is_paused(self) -> bool { + let Channel(ch) = self; + unsafe { ffi::Mix_Paused(ch as c_int) != 0 } + } + + /// if channel is fading in, out, or not + pub fn get_fading(self) -> Fading { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_FadingChannel(ch as c_int) as c_uint }; + match ret { + ffi::MIX_FADING_OUT => Fading::FadingOut, + ffi::MIX_FADING_IN => Fading::FadingIn, + ffi::MIX_NO_FADING | _ => Fading::NoFading + } + } + + /// Get the most recent sample chunk pointer played on channel. + pub fn get_chunk(self) -> Option { + let Channel(ch) = self; + let raw = unsafe { ffi::Mix_GetChunk(ch as c_int) }; + if raw.is_null() { + None + } else { + Some(Chunk { + raw: raw, + owned: false, + }) + } + } + + /// This removes all effects registered to channel. + pub fn unregister_all_effects(self) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_UnregisterAllEffects(ch as c_int) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Sets a panning effect, where left and right is the volume of the left and right channels. + /// They range from 0 (silence) to 255 (loud). + pub fn set_panning(self, left: u8, right: u8) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_SetPanning(ch as c_int, left, right) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Unregisters panning effect. + pub fn unset_panning(self) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_SetPanning(ch as c_int, 255, 255) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// This effect simulates a simple attenuation of volume due to distance. + /// distance ranges from 0 (close/loud) to 255 (far/quiet). + pub fn set_distance(self, distance: u8) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_SetDistance(ch as c_int, distance) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Unregisters distance effect. + pub fn unset_distance(self) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_SetDistance(ch as c_int, 0) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// This effect emulates a simple 3D audio effect. + /// angle ranges from 0 to 360 degrees going clockwise, where 0 is directly in front. + /// distance ranges from 0 (close/loud) to 255 (far/quiet). + pub fn set_position(self, angle: i16, distance: u8) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_SetPosition(ch as c_int, angle, distance) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Unregisters position effect. + pub fn unset_position(self) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_SetPosition(ch as c_int, 0, 0) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Simple reverse stereo, swaps left and right channel sound. + /// true for reverse, false to unregister effect. + pub fn set_reverse_stereo(self, flip: bool) -> Result<(), String> { + let Channel(ch) = self; + let ret = unsafe { ffi::Mix_SetReverseStereo(ch as c_int, flip as c_int) }; + if ret == 0 { + Err(get_error()) + } else { + Ok(()) + } + } +} + +/// Returns how many channels are currently playing. +pub fn get_playing_channels_number() -> isize { + unsafe { ffi::Mix_Playing(-1) as isize } +} + +/// Returns how many channels are currently paused. +pub fn get_paused_channels_number() -> isize { + unsafe { ffi::Mix_Paused(-1) as isize } +} + +// 4.4 Groups + +/// Reserve num channels from being used when playing samples when +/// passing in -1 as a channel number to playback functions. +pub fn reserve_channels(num: isize) -> isize { + unsafe { ffi::Mix_ReserveChannels(num as c_int) as isize } +} + +/// Sound effect channel grouping. +#[derive(Copy, Clone)] +pub struct Group(isize); + +impl default::Default for Group { + fn default() -> Group { + Group(-1) + } +} + +impl Group { + /// Add channels starting at from up through to to group tag, + /// or reset it's group to the default group tag (-1). + pub fn add_channels_range(self, from: isize, to: isize) -> isize { + let Group(g) = self; + unsafe { ffi::Mix_GroupChannels(from as c_int, to as c_int, g as c_int) as isize } + } + + /// Add which channel to group tag, or reset it's group to the default group tag + pub fn add_channel(self, Channel(ch): Channel) -> bool { + let Group(g) = self; + unsafe { ffi::Mix_GroupChannel(ch as c_int, g as c_int) == 1 } + } + + /// Count the number of channels in group + pub fn count(self) -> isize { + let Group(g) = self; + unsafe { ffi::Mix_GroupCount(g as c_int) as isize } + } + + /// Find the first available (not playing) channel in group + pub fn find_available(self) -> Option { + let Group(g) = self; + let ret = unsafe { ffi::Mix_GroupAvailable(g as c_int) as isize }; + if ret == -1 { + None + } else { + Some(Channel(ret)) + } + } + + /// Find the oldest actively playing channel in group + pub fn find_oldest(self) -> Option { + let Group(g) = self; + let ret = unsafe { ffi::Mix_GroupOldest(g as c_int) as isize }; + if ret == -1 { + None + } else { + Some(Channel(ret)) + } + } + + /// Find the newest, most recently started, actively playing channel in group. + pub fn find_newest(self) -> Option { + let Group(g) = self; + let ret = unsafe { ffi::Mix_GroupNewer(g as c_int) as isize }; + if ret == -1 { + None + } else { + Some(Channel(ret)) + } + } + + /// Gradually fade out channels in group over some milliseconds starting from now. + /// Returns the number of channels set to fade out. + pub fn fade_out(self, ms: isize) -> isize { + let Group(g) = self; + unsafe { ffi::Mix_FadeOutGroup(g as c_int, ms as c_int) as isize } + } + + /// Halt playback on all channels in group. + pub fn halt(self) { + let Group(g) = self; + unsafe { + ffi::Mix_HaltGroup(g as c_int); + } + } +} + +// 4.5 Music + +/// Get the number of music decoders available. +pub fn get_music_decoders_number() -> isize { + unsafe { ffi::Mix_GetNumMusicDecoders() as isize } +} + +/// Get the name of the indexed music decoder. +pub fn get_music_decoder(index: isize) -> String { + unsafe { + let name = ffi::Mix_GetMusicDecoder(index as c_int); + from_utf8(CStr::from_ptr(name).to_bytes()).unwrap().to_owned() + } +} + +/// Music type enumerations +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Hash, Debug)] +pub enum MusicType { + MusicNone = ffi::MUS_NONE as isize, + MusicCmd = ffi::MUS_CMD as isize, + MusicWav = ffi::MUS_WAV as isize, + MusicMod = ffi::MUS_MOD as isize, + MusicMid = ffi::MUS_MID as isize, + MusicOgg = ffi::MUS_OGG as isize, + MusicMp3 = ffi::MUS_MP3 as isize, + MusicMp3Mad = ffi::MUS_MP3_MAD as isize, + MusicFlac = ffi::MUS_FLAC as isize, + MusicModPlug = ffi::MUS_MODPLUG as isize, +} + +// hooks +static mut music_finished_hook: Option = None; + +extern "C" fn c_music_finished_hook() { + unsafe { + match music_finished_hook { + None => (), + Some(f) => f(), + } + } +} + +/// This is an opaque data type used for Music data. +#[derive(PartialEq)] +pub struct Music { + pub raw: *mut ffi::Mix_Music, + pub owned: bool, +} + +impl Drop for Music { + fn drop(&mut self) { + if self.owned { + unsafe { ffi::Mix_FreeMusic(self.raw) }; + } + } +} + +impl fmt::Debug for Music { + /// Shows the original regular expression. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "") + } +} + +impl Music { + /// Load music file to use. + pub fn from_file(path: &Path) -> Result { + let raw = unsafe { + let c_path = CString::new(path.to_str().unwrap()).unwrap(); + ffi::Mix_LoadMUS(c_path.as_ptr()) + }; + if raw.is_null() { + Err(get_error()) + } else { + Ok(Music { + raw: raw, + owned: true, + }) + } + } + + /// The file format encoding of the music. + pub fn get_type(&self) -> MusicType { + let ret = unsafe { ffi::Mix_GetMusicType(self.raw) as isize } as c_uint; + match ret { + ffi::MUS_CMD => MusicType::MusicCmd, + ffi::MUS_WAV => MusicType::MusicWav, + ffi::MUS_MOD => MusicType::MusicMod, + ffi::MUS_MID => MusicType::MusicMid, + ffi::MUS_OGG => MusicType::MusicOgg, + ffi::MUS_MP3 => MusicType::MusicMp3, + ffi::MUS_MP3_MAD => MusicType::MusicMp3Mad, + ffi::MUS_FLAC => MusicType::MusicFlac, + ffi::MUS_MODPLUG => MusicType::MusicModPlug, + ffi::MUS_NONE | _ => MusicType::MusicNone + } + } + + /// Play the loaded music loop times through from start to finish. + pub fn play(&self, loops: isize) -> Result<(), String> { + let ret = unsafe { ffi::Mix_PlayMusic(self.raw, loops as c_int) }; + if ret == -1 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Fade in over ms milliseconds of time, the loaded music, + /// playing it loop times through from start to finish. + pub fn fade_in(&self, loops: isize, ms: isize) -> Result<(), String> { + let ret = unsafe { ffi::Mix_FadeInMusic(self.raw, loops as c_int, ms as c_int) }; + if ret == -1 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Fade in over ms milliseconds of time, from position. + pub fn fade_in_from_pos(&self, loops: isize, ms: isize, position: f64) -> Result<(), String> { + let ret = unsafe { + ffi::Mix_FadeInMusicPos(self.raw, loops as c_int, ms as c_int, position as c_double) + }; + if ret == -1 { + Err(get_error()) + } else { + Ok(()) + } + } + + // FIXME: make these class method? + /// Returns current volume + pub fn get_volume() -> isize { + unsafe { ffi::Mix_VolumeMusic(-1) as isize } + } + + /// Set the volume on a scale of 0 to 128. + /// Values greater than 128 will use 128. + pub fn set_volume(volume: isize) { + // This shouldn't return anything. Use get_volume instead + let _ = unsafe { ffi::Mix_VolumeMusic(volume as c_int) as isize }; + } + + /// Pause the music playback. + pub fn pause() { + unsafe { + ffi::Mix_PauseMusic(); + } + } + + /// Unpause the music. + pub fn resume() { + unsafe { + ffi::Mix_ResumeMusic(); + } + } + + /// Rewind the music to the start. + pub fn rewind() { + unsafe { + ffi::Mix_RewindMusic(); + } + } + + /// Set the position of the currently playing music. + pub fn set_pos(position: f64) -> Result<(), String> { + let ret = unsafe { ffi::Mix_SetMusicPosition(position as c_double) }; + if ret == -1 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Setup a command line music player to use to play music. + pub fn set_command(command: &str) -> Result<(), String> { + let ret = unsafe { + let c_command = CString::new(command).unwrap(); + ffi::Mix_SetMusicCMD(c_command.as_ptr()) + }; + if ret == -1 { + Err(get_error()) + } else { + Ok(()) + } + } + + /// Halt playback of music. + pub fn halt() { + unsafe { + ffi::Mix_HaltMusic(); + } + } + + /// Gradually fade out the music over ms milliseconds starting from now. + pub fn fade_out(ms: isize) -> Result<(), String> { + let ret = unsafe { ffi::Mix_FadeOutMusic(ms as c_int) }; + if ret == -1 { + Err(get_error()) + } else { + Ok(()) + } + } + + // TODO: Mix_HookMusic + // TODO: Mix_GetMusicHookData + + /// Sets up a function to be called when music playback is halted. + /// + /// # Examples + /// + /// ``` + /// fn after_music() { + /// println!("Music has ended"); + /// } + /// + /// sdl2_mixer::Music::hook_finished(after_music); + /// ``` + pub fn hook_finished(f: fn()) { + unsafe { + music_finished_hook = Some(f); + ffi::Mix_HookMusicFinished(Some(c_music_finished_hook as extern "C" fn())); + } + } + + /// A previously set up function would no longer be called when music playback is halted. + pub fn unhook_finished() { + unsafe { + ffi::Mix_HookMusicFinished(None); + // unset from c, then rust, to avoid race condiction + music_finished_hook = None; + } + } + + /// If music is actively playing, or not. + pub fn is_playing() -> bool { + unsafe { ffi::Mix_PlayingMusic() == 1 } + } + + /// If music is paused, or not. + pub fn is_paused() -> bool { + unsafe { ffi::Mix_PausedMusic() == 1 } + } + + /// If music is fading, or not. + pub fn get_fading() -> Fading { + let ret = unsafe { ffi::Mix_FadingMusic() as isize } as c_uint; + match ret { + ffi::MIX_FADING_OUT => Fading::FadingOut, + ffi::MIX_FADING_IN => Fading::FadingIn, + ffi::MIX_NO_FADING | _ => Fading::NoFading + } + } +} + +// 4.6 Effects + +// TODO: Mix_RegisterEffect +// TODO: Mix_UnregisterEffect +// TODO: Mix_SetPostMix