gamnit: implement app::audio for desktop using SDL2 mixer
authorAlexis Laferrière <alexis.laf@xymus.net>
Thu, 25 May 2017 16:56:08 +0000 (12:56 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Fri, 26 May 2017 13:28:23 +0000 (09:28 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/gamnit/README.md
lib/gamnit/display_linux.nit
lib/linux/audio.nit

index 5b12210..a289482 100644 (file)
@@ -9,13 +9,13 @@ To compile the _gamnit_ apps packaged with the Nit repositoy on GNU/Linux you ne
 Under Debian 8.2, this command should install everything needed:
 
 ~~~
-apt-get install libgles2-mesa-dev libsdl2-dev libsdl2-image-dev inkscape mpg123
+apt-get install libgles2-mesa-dev libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev inkscape mpg123
 ~~~
 
 Under Windows 64 bits, using msys2, you can install the required packages with:
 
 ~~~
-pacman -S mingw-w64-x86_64-angleproject-git mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image
+pacman -S mingw-w64-x86_64-angleproject-git mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer
 ~~~
 
 # Services by submodules
index 231ee17..634656b 100644 (file)
@@ -16,6 +16,7 @@
 module display_linux
 
 import sdl2::image
+import sdl2::mixer
 
 import egl # local to gamnit
 intrude import display
@@ -68,7 +69,7 @@ redef class GamnitDisplay
        # Setup the SDL display and lib
        fun setup_sdl(window_width, window_height: Int): SDLWindow
        do
-               assert sdl.initialize((new SDLInitFlags).video) else
+               assert sdl.initialize((new SDLInitFlags).video.audio) else
                        print_error "Failed to initialize SDL2: {sdl.error}"
                end
 
@@ -82,11 +83,33 @@ redef class GamnitDisplay
                        print_error "Failed to create SDL2 window: {sdl.error}"
                end
 
+               # Audio support
+               var inited = mix.initialize(mix_init_flags)
+               assert inited != mix_init_flags else
+                       print_error "Failed to load SDL2 mixer format supports: {mix.error}"
+               end
+
+               var opened = mix.open_audio(44100, mix.default_format, 2, 1024)
+               assert opened else
+                       print_error "Failed to initialize SDL2 mixer: {mix.error}"
+               end
+
                return sdl_window
        end
 
+       # Initialization flags passed to SDL2 mixer
+       #
+       # Defaults to all available formats.
+       var mix_init_flags: MixInitFlags = mix.flac | mix.mod | mix.mp3 | mix.ogg is lazy, writable
+
        # Close the SDL display
-       fun close_sdl do sdl_window.destroy
+       fun close_sdl
+       do
+               sdl_window.destroy
+               mix.close_audio
+               mix.quit
+               sdl.finalize
+       end
 end
 
 redef class TextureAsset
index 85f572a..a3bfb4f 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Linux audio implementation
+# `app::audio` implementation for GNU/Linux using SDL2 mixer
 module audio
 
 import app::audio
+import sdl2::mixer
 import linux
 
 redef class PlayableAudio
+       redef var error = null
 
-       redef fun play do
-               if path.has_suffix(".wav") then
-                       sys.system "aplay -q {app.assets_dir}{path} &"
-               else if path.has_suffix(".mp3") then
-                       sys.system "mpg123 -q {app.assets_dir}{path} &"
+       # Real file system path to this asset
+       private fun fs_path: String do return app.assets_dir / path
+
+       # Does `fs_path` exist?
+       private fun fs_path_exists: Bool
+       do
+               if not fs_path.file_exists then
+                       error = new Error("Failed to load audio '{path}': file not found")
+                       return false
+               end
+               return true
+       end
+end
+
+redef class Sound
+
+       private var native: nullable MixChunk = null
+
+       redef fun load
+       do
+               if not fs_path_exists then return
+
+               # SDL2 mixer load
+               var native = mix.load_wav(fs_path.to_cstring)
+               if native.address_is_null then
+                       error = new Error("Failed to load sound '{path}': {mix.error}")
+                       return
+               end
+
+               self.native = native
+       end
+
+       redef fun play
+       do
+               var native = native
+
+               if native == null and error == null then
+                       # Lazy load
+                       load
+
+                       # Auto print errors on lazy loading only
+                       var error = error
+                       if error != null then print_error error
+               end
+
+               # If there's an error, silently skip
+               if error != null then return
+               native = self.native
+               assert native != null
+
+               # Play on any available channel
+               mix.play_channel(-1, native, 0)
+       end
+end
+
+redef class Music
+
+       private var native: nullable MixMusic = null
+
+       redef fun load
+       do
+               if not fs_path_exists then return
+
+               # SDL2 mixer load
+               var native = mix.load_mus(fs_path.to_cstring)
+               if native.address_is_null then
+                       error = new Error("Failed to load music '{path}': {mix.error}")
+                       return
+               end
+
+               self.native = native
+       end
+
+       redef fun play
+       do
+               var native = native
+
+               if native == null and error == null then
+                       # Lazy load
+                       load
+
+                       # Auto print errors on lazy loading only
+                       var error = error
+                       if error != null then print_error error
                end
+
+               # If there's an error, silently skip
+               if error != null then return
+               native = self.native
+               assert native != null
+
+               # Play looping
+               mix.play_music(native, -1)
        end
 
-       redef fun load do end
-       redef fun pause do end
-       redef fun resume do end
+       redef fun pause do mix.pause_music
+
+       redef fun resume do mix.resume_music
 end