# See the License for the specific language governing permissions and
# limitations under the License.
-# Linux audio services
+# `app::audio` implementation for GNU/Linux using SDL2 mixer
module audio
import app::audio
-import mnit::linux::linux_assets
+import sdl2::mixer
+import linux
-# Simple audio asset
-redef class Sound
+redef class PlayableAudio
+ redef var error = null
+
+ # Real file system path to this asset
+ private fun fs_path: String do return app.assets_dir / path
- redef fun play do
- if name.has_suffix(".wav") then
- sys.system "aplay -q {app.assets_dir}{name} &"
- else if name.has_suffix(".mp3") then
- sys.system "mpg123 -q {app.assets_dir}{name} &"
+ # 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
-
- redef fun load do end
- redef fun pause do end
- redef fun resume do end
end
-redef class Music
+redef class Sound
+
+ private var native: nullable MixChunk = null
+
+ redef fun load
+ do
+ if not fs_path_exists then return
- redef fun play do
- if name.has_suffix(".wav") then
- sys.system "aplay -q {app.assets_dir}{name} &"
- else if name.has_suffix(".mp3") then
- sys.system "mpg123 -q {app.assets_dir}{name} &"
+ # 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 load do end
- redef fun pause do end
- redef fun resume do end
+ redef fun play do play_channel(-1, 0)
+
+ # Play this sound on `channel` (or any channel if -1) and return the channel
+ #
+ # Repeat the sound `loops` times, `loops == 0` plays it once,
+ # `loops == 1` plays it twice and `loops == -1` loops infinitely.
+ fun play_channel(channel, loops: Int): Int
+ 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 -1
+ native = self.native
+ assert native != null
+
+ # Play on any available channel
+ return mix.play_channel(channel, native, loops)
+ end
end
-redef class App
- redef fun load_sound(name)
+redef class Music
+
+ private var native: nullable MixMusic = null
+
+ redef fun load
do
- return new Sound(name)
+ 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 load_music(name)
+ redef fun play
do
- return new Music(name)
+ 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 pause do mix.pause_music
+
+ redef fun resume do mix.resume_music
end