From c58c3c03fd42580641e4710bd9403efc898e99ff Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:43:47 +0700 Subject: [PATCH 1/3] Update OpenAL-Soft, & use SDL3 as an audio backend if compiled with SDL3... ... Also remove the codes that manage the device events to auto-reconnect as SDL3 now manage that with "Default Device" --- NOTICE.md | 5 +-- .../platforms/android/config_backends.h | 12 ++++- .../include/platforms/apple/config_backends.h | 12 ++++- .../include/platforms/linux/config_backends.h | 20 +++++++-- .../platforms/windows/config_backends.h | 18 ++++++-- project/lib/custom/openal/include/version.h | 2 +- project/lib/openal | 2 +- project/lib/openal-files.xml | 15 ++++--- src/lime/media/AudioManager.hx | 44 ++----------------- 9 files changed, 64 insertions(+), 66 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index 516e5677f2..aeb97eefa7 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -34,12 +34,9 @@ public domain. For details, see [project/lib/lzma/](project/lzma). This product bundles mbedTLS 2.6.0, which is available under an "Apache 2.0" license. For details, see [project/lib/mbedtls/](project/lib). -This product bundles OpenAL-Soft 1.19.0, which is available under an +This product bundles OpenAL-Soft 1.25.1, which is available under an "LGPLv2" license. For details, see [project/lib/openal/](project/lib). -_OpenAL-Soft is only included in dynamically-linked builds, it is excluded -from Lime static builds in order to preserve Lime's permissive nature._ - This product bundles pixman 0.32.8, which is available under an "MIT" license. For details, see [project/lib/pixman/](project/lib). diff --git a/project/lib/custom/openal/include/platforms/android/config_backends.h b/project/lib/custom/openal/include/platforms/android/config_backends.h index 846380f595..e3de1614db 100644 --- a/project/lib/custom/openal/include/platforms/android/config_backends.h +++ b/project/lib/custom/openal/include/platforms/android/config_backends.h @@ -1,3 +1,13 @@ +#ifdef NATIVE_TOOLKIT_HAVE_SDL + +#define HAVE_OPENSL 0 + +#else + +#define HAVE_OPENSL 1 + +#endif + #define HAVE_ALSA 0 #define HAVE_OSS 0 @@ -22,6 +32,4 @@ #define HAVE_COREAUDIO 0 -#define HAVE_OPENSL 1 - #define HAVE_OBOE 0 diff --git a/project/lib/custom/openal/include/platforms/apple/config_backends.h b/project/lib/custom/openal/include/platforms/apple/config_backends.h index 0bb6c9b8e0..5a58db4e98 100644 --- a/project/lib/custom/openal/include/platforms/apple/config_backends.h +++ b/project/lib/custom/openal/include/platforms/apple/config_backends.h @@ -1,3 +1,13 @@ +#ifdef NATIVE_TOOLKIT_HAVE_SDL + +#define HAVE_COREAUDIO 0 + +#else + +#define HAVE_COREAUDIO 1 + +#endif + #define HAVE_ALSA 0 #define HAVE_OSS 0 @@ -20,8 +30,6 @@ #define HAVE_JACK 0 -#define HAVE_COREAUDIO 1 - #define HAVE_OPENSL 0 #define HAVE_OBOE 0 diff --git a/project/lib/custom/openal/include/platforms/linux/config_backends.h b/project/lib/custom/openal/include/platforms/linux/config_backends.h index c2a67ff433..8fde1bce3b 100644 --- a/project/lib/custom/openal/include/platforms/linux/config_backends.h +++ b/project/lib/custom/openal/include/platforms/linux/config_backends.h @@ -1,9 +1,23 @@ -#define HAVE_ALSA 1 +#ifdef NATIVE_TOOLKIT_HAVE_SDL -#define HAVE_OSS 0 +#define HAVE_ALSA 0 + +#define HAVE_PIPEWIRE 0 + +#define HAVE_PULSEAUDIO 0 + +#else + +#define HAVE_ALSA 1 #define HAVE_PIPEWIRE 1 +#define HAVE_PULSEAUDIO 1 + +#endif + +#define HAVE_OSS 0 + #define HAVE_SOLARIS 0 #define HAVE_SNDIO 0 @@ -16,8 +30,6 @@ #define HAVE_PORTAUDIO 0 -#define HAVE_PULSEAUDIO 1 - #define HAVE_JACK 0 #define HAVE_COREAUDIO 0 diff --git a/project/lib/custom/openal/include/platforms/windows/config_backends.h b/project/lib/custom/openal/include/platforms/windows/config_backends.h index 55bf169b75..39ce4b1bad 100644 --- a/project/lib/custom/openal/include/platforms/windows/config_backends.h +++ b/project/lib/custom/openal/include/platforms/windows/config_backends.h @@ -1,3 +1,17 @@ +#ifdef NATIVE_TOOLKIT_HAVE_SDL + +#define HAVE_WASAPI 0 + +#define HAVE_DSOUND 0 + +#else + +#define HAVE_WASAPI 1 + +#define HAVE_DSOUND 1 + +#endif + #define HAVE_ALSA 0 #define HAVE_OSS 0 @@ -8,10 +22,6 @@ #define HAVE_SNDIO 0 -#define HAVE_WASAPI 1 - -#define HAVE_DSOUND 1 - #define HAVE_WINMM 0 #define HAVE_PORTAUDIO 0 diff --git a/project/lib/custom/openal/include/version.h b/project/lib/custom/openal/include/version.h index 46d0fe2c94..9eb13e4cab 100644 --- a/project/lib/custom/openal/include/version.h +++ b/project/lib/custom/openal/include/version.h @@ -6,4 +6,4 @@ #define ALSOFT_GIT_BRANCH "master" /* Define the hash of the head commit */ -#define ALSOFT_GIT_COMMIT_HASH "1d52120" +#define ALSOFT_GIT_COMMIT_HASH "0887e589" diff --git a/project/lib/openal b/project/lib/openal index e9c479eb41..0887e589fe 160000 --- a/project/lib/openal +++ b/project/lib/openal @@ -1 +1 @@ -Subproject commit e9c479eb4190101bc51179afae56fc6dd5d26066 +Subproject commit 0887e589fe2cc973fe0f5b9b61105711f2b35d79 diff --git a/project/lib/openal-files.xml b/project/lib/openal-files.xml index c021774e52..cd7c0ce42e 100644 --- a/project/lib/openal-files.xml +++ b/project/lib/openal-files.xml @@ -131,13 +131,15 @@
-
+ + + - - - +
-
+ + +
@@ -158,8 +160,6 @@
- -
@@ -201,6 +201,7 @@ + diff --git a/src/lime/media/AudioManager.hx b/src/lime/media/AudioManager.hx index c755401941..c683fbcae7 100644 --- a/src/lime/media/AudioManager.hx +++ b/src/lime/media/AudioManager.hx @@ -54,15 +54,10 @@ class AudioManager alc.makeContextCurrent(ctx); alc.processContext(ctx); - #if (lime_openalsoft && !mobile) - if (alc.isExtensionPresent('ALC_SOFT_system_events', device) && alc.isExtensionPresent('ALC_SOFT_reopen_device', device)) + #if (lime_openalsoft) + if (alc.isExtensionPresent('AL_SOFT_hold_on_disconnect')) { - if (alc.isExtensionPresent('AL_SOFT_hold_on_disconnect')) - alc.disable(AL.STOP_SOURCES_ON_DISCONNECT_SOFT); - - alc.eventControlSOFT([ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT, ALC.EVENT_TYPE_DEVICE_ADDED_SOFT, ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT], true); - - alc.eventCallbackSOFT(deviceEventCallback); + alc.disable(AL.STOP_SOURCES_ON_DISCONNECT_SOFT); } #end } @@ -139,39 +134,6 @@ class AudioManager #end } - #if lime_openalsoft - @:noCompletion - private static function deviceEventCallback(eventType:Int, deviceType:Int, handle:CFFIPointer, message:#if hl hl.Bytes #else String #end):Void - { - #if !lime_doc_gen - if (eventType == ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT && deviceType == ALC.PLAYBACK_DEVICE_SOFT) - { - var device = new ALDevice(handle); - - MainLoop.runInMainThread(function():Void - { - var alc = context.openal; - - if (device == null) - { - var currentContext = alc.getCurrentContext(); - - var device = alc.getContextsDevice(currentContext); - - if (device != null) - alc.reopenDeviceSOFT(device, null, null); - } - else - { - alc.reopenDeviceSOFT(device, null, null); - } - - }); - } - #end - } - #end - @:noCompletion private static function setupConfig():Void { From e486c473c73af16ea39945085a594290979443ac Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 18 Apr 2026 21:39:26 +0700 Subject: [PATCH 2/3] `AudioManager`: Clean-up, Documentation, Implement `refresh`, Fix `resume`, `shutdown`, `suspend`, for Web --- src/lime/media/AudioManager.hx | 248 ++++++++++++++++++++---------- src/lime/media/WebAudioContext.hx | 10 ++ 2 files changed, 181 insertions(+), 77 deletions(-) diff --git a/src/lime/media/AudioManager.hx b/src/lime/media/AudioManager.hx index c683fbcae7..303ff5062b 100644 --- a/src/lime/media/AudioManager.hx +++ b/src/lime/media/AudioManager.hx @@ -1,143 +1,236 @@ package lime.media; -import lime.system.CFFIPointer; import haxe.MainLoop; -#if (windows || mac || linux || android || ios) +import lime.system.CFFIPointer; +#if lime_openal +import lime.media.openal.AL; +import lime.media.openal.ALC; +import lime.media.openal.ALContext; +import lime.media.openal.ALDevice; +#if lime_openalsoft import haxe.io.Path; import lime.system.System; import sys.FileSystem; import sys.io.File; #end -import haxe.Timer; -import lime._internal.backend.native.NativeCFFI; -import lime.media.openal.AL; -import lime.media.openal.ALC; -import lime.media.openal.ALContext; -import lime.media.openal.ALDevice; -import lime.app.Application; -#if (js && html5) -import js.Browser; +#elseif lime_howlerjs +import lime.media.howlerjs.Howler; #end #if !lime_debug @:fileXml('tags="haxe,release"') @:noDebug #end -@:access(lime._internal.backend.native.NativeCFFI) @:access(lime.media.openal.ALDevice) class AudioManager { + /** + The current used context to use for the audio manager. + **/ public static var context:AudioContext; - public static function init(context:AudioContext = null) + #if lime_openal + @:noCompletion private static var __disconnectExtSupported:Bool; + @:noCompletion private static var __reopenDeviceSupported:Bool; + @:noCompletion private static var __systemEventsSupported:Bool; + #end + + /** + Initializes an `AudioManager` to playback to and capture from audio devices. + Automatically dispatched when Application is constructed. + + @param context Optional; An Audio Context to initalize the `AudioManager` with. + **/ + public static function init(?context:AudioContext) { - if (AudioManager.context == null) + if (AudioManager.context != null) return; + + if (context == null) { - if (context == null) - { - AudioManager.context = new AudioContext(); - - context = AudioManager.context; - - #if !lime_doc_gen - if (context.type == OPENAL) - { - #if (windows || mac || linux || android || ios) - setupConfig(); - #end - - var alc = context.openal; - var device = alc.openDevice(); - if (device != null) - { - var ctx = alc.createContext(device); - alc.makeContextCurrent(ctx); - alc.processContext(ctx); - - #if (lime_openalsoft) - if (alc.isExtensionPresent('AL_SOFT_hold_on_disconnect')) - { - alc.disable(AL.STOP_SOURCES_ON_DISCONNECT_SOFT); - } - #end - } - } - #end - } + context = new AudioContext(); + } + + AudioManager.context = context; - AudioManager.context = context; + #if lime_openal + if (context.type == OPENAL) + { + #if lime_openalsoft + __setupConfig(); + #end + + refresh(); + + #if lime_openalsoft + if (__reopenDeviceSupported) AL.disable(AL.STOP_SOURCES_ON_DISCONNECT_SOFT); + #end } + #end } + /** + Refresh the context with optionally to different device. + + Only works on Native target. + + @param deviceName Optional; The device name to use to for playbacking the audios. + **/ + public static function refresh(?deviceName:String):Bool + { + #if (lime_openal && !lime_doc_gen) + if (context == null || context.type != OPENAL) return false; + + var currentContext = ALC.getCurrentContext(); + var device = currentContext != null ? ALC.getContextsDevice(currentContext) : null; + + #if (lime_openalsoft && !mobile) + if (device != null && __reopenDeviceSupported && ALC.reopenDeviceSOFT(device, deviceName, null)) + { + __refresh(); + return true; + } + #end + + if (currentContext != null) + { + ALC.destroyContext(currentContext); + currentContext = null; + } + + if (device != null) + { + ALC.closeDevice(device); + device = null; + } + + if ((device = ALC.openDevice()) == null || (currentContext = ALC.createContext(device)) == null + || !ALC.makeContextCurrent(currentContext)) + { + return false; + } + + ALC.processContext(currentContext); + __refresh(); + return true; + #else + return false; + #end + } + + /** + Resumes the current `AudioManager` context. + **/ public static function resume():Void { - #if !lime_doc_gen + #if lime_openal if (context != null && context.type == OPENAL) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); - + var currentContext = ALC.getCurrentContext(); if (currentContext != null) { - var device = alc.getContextsDevice(currentContext); - alc.resumeDevice(device); - alc.processContext(currentContext); + var device = ALC.getContextsDevice(currentContext); + if (device != null) ALC.resumeDevice(device); + + ALC.processContext(currentContext); } } + #elseif (js && html5) + #if lime_howlerjs + if (untyped Howler.ctx) + { + Howler.ctx.resume(); + } + #end + if (context != null && context.type == WEB) + { + context.web.resume(); + } #end } + /** + Shutdowns the current `AudioManager` context. + **/ public static function shutdown():Void { - #if !lime_doc_gen + #if lime_openal if (context != null && context.type == OPENAL) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); - var device = alc.getContextsDevice(currentContext); - + var currentContext = ALC.getCurrentContext(); if (currentContext != null) { - alc.makeContextCurrent(null); - alc.destroyContext(currentContext); + ALC.makeContextCurrent(null); + ALC.destroyContext(currentContext); - if (device != null) - { - alc.closeDevice(device); - } + var device = ALC.getContextsDevice(currentContext); + if (device != null) ALC.closeDevice(device); } } + #elseif (js && html5) + #if lime_howlerjs + if (untyped Howler.ctx) + { + Howler.ctx.unload(); + } + #end + if (context != null && context.type == WEB) + { + context.web.close(); + } #end context = null; } + /** + Pauses the current `AudioManager` context. + **/ public static function suspend():Void { - #if !lime_doc_gen + #if lime_openal if (context != null && context.type == OPENAL) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); - var device = alc.getContextsDevice(currentContext); - + var currentContext = ALC.getCurrentContext(); if (currentContext != null) { - alc.suspendContext(currentContext); + ALC.suspendContext(currentContext); - if (device != null) - { - alc.pauseDevice(device); - } + var device = ALC.getContextsDevice(currentContext); + if (device != null) ALC.pauseDevice(device); } } + #elseif (js && html5) + #if lime_howlerjs + if (untyped Howler.ctx) + { + Howler.ctx.suspend(); + } + #end + if (context != null && context.type == WEB) + { + context.web.suspend(); + } #end } + #if (lime_openal && !lime_doc_gen) + @:noCompletion private static function __refresh():Void + { + var currentContext = ALC.getCurrentContext(); + if (currentContext == null) return; + + var device = ALC.getContextsDevice(currentContext); + if (device == null) return; + + __disconnectExtSupported = ALC.isExtensionPresent(null, 'ALC_EXT_disconnect'); + __reopenDeviceSupported = ALC.isExtensionPresent(null, 'ALC_SOFT_reopen_device'); + __systemEventsSupported = ALC.isExtensionPresent(null, 'ALC_SOFT_system_events'); + } + + #if lime_openalsoft @:noCompletion - private static function setupConfig():Void + private static function __setupConfig():Void { - #if (lime_openal && (windows || mac || linux || android || ios)) final alConfig:Array = []; alConfig.push('[general]'); @@ -171,6 +264,7 @@ class AudioManager Sys.putEnv('ALSOFT_CONF', path); } catch (e:Dynamic) {} - #end } + #end + #end } diff --git a/src/lime/media/WebAudioContext.hx b/src/lime/media/WebAudioContext.hx index 89dac24a39..d0215203fa 100644 --- a/src/lime/media/WebAudioContext.hx +++ b/src/lime/media/WebAudioContext.hx @@ -20,6 +20,16 @@ class WebAudioContext } #end + public function suspend():Dynamic /*Promise*/ + { + return null; + } + + public function close():Dynamic /*Promise*/ + { + return null; + } + public function createAnalyser():Dynamic /*AnalyserNode*/ { return null; From 70a3d98f1e13193b899b16277a4aca97de3d2a49 Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 18 Apr 2026 21:48:37 +0700 Subject: [PATCH 3/3] `AudioManager`: Implement `automaticDefaultPlaybackDevice`, `gain`, `muted`, device events & functions --- src/lime/media/AudioManager.hx | 381 +++++++++++++++++++++++++++++++++ 1 file changed, 381 insertions(+) diff --git a/src/lime/media/AudioManager.hx b/src/lime/media/AudioManager.hx index 303ff5062b..a8f1c04715 100644 --- a/src/lime/media/AudioManager.hx +++ b/src/lime/media/AudioManager.hx @@ -1,6 +1,7 @@ package lime.media; import haxe.MainLoop; +import lime.app.Event; import lime.system.CFFIPointer; #if lime_openal import lime.media.openal.AL; @@ -12,6 +13,7 @@ import haxe.io.Path; import lime.system.System; import sys.FileSystem; import sys.io.File; +import sys.thread.Mutex; #end #elseif lime_howlerjs import lime.media.howlerjs.Howler; @@ -24,13 +26,94 @@ import lime.media.howlerjs.Howler; @:access(lime.media.openal.ALDevice) class AudioManager { + /** + Should it automatically switch to the default playback device whenever it changes. + + Only works on Native target. + **/ + public static var automaticDefaultPlaybackDevice:Bool = true; + /** The current used context to use for the audio manager. **/ public static var context:AudioContext; + /** + The gain (volume) of the audio manager. A value of `1.0` represents the default volume. + Property is in a linear scale. + **/ + public static var gain(get, set):Float; + + /** + Mutes the audio manager playback. + **/ + public static var muted(get, set):Bool; + + /** + Dispatched when the default for the playback device is changed. + 'Device Name' -> Void. + + Only works on Native target. + **/ + public static var onDefaultPlaybackDeviceChanged = new EventVoid>(); + + /** + Dispatched whenever a playback device is added. + 'Device Name' -> Void. + + Only works on Native target. + **/ + public static var onPlaybackDeviceAdded = new EventVoid>(); + + /** + Dispatched whenever a playback device is removed. + 'Device Name' -> Void. + + Only works on Native target. + **/ + public static var onPlaybackDeviceRemoved = new EventVoid>(); + + /** + Dispatched when the default for the capture device is changed. + 'Device Name' -> Void. + + Only works on Native target. + **/ + public static var onDefaultCaptureDeviceChanged = new EventVoid>(); + + /** + Dispatched whenever a capture device is added. + 'Device Name' -> Void. + + Only works on Native target. + **/ + public static var onCaptureDeviceAdded = new EventVoid>(); + + /** + Dispatched whenever a capture device is removed. + 'Device Name' -> Void. + + Only works on Native target. + **/ + public static var onCaptureDeviceRemoved = new EventVoid>(); + + @:noCompletion private static var __gain:Float = 1; + @:noCompletion private static var __muted:Bool = false; #if lime_openal + #if lime_openalsoft + @:noCompletion private static var __pendingDeviceEventMutex:Mutex = new Mutex(); + @:noCompletion private static var __pendingDeviceEventCheck:Bool; + @:noCompletion private static var __pendingPlaybackDevicesAddition:Array; + @:noCompletion private static var __pendingPlaybackDevicesRemoval:Array; + @:noCompletion private static var __pendingCaptureDevicesAddition:Array; + @:noCompletion private static var __pendingCaptureDevicesRemoval:Array; + @:noCompletion private static var __pendingDefaultPlaybackDevice:Bool; + @:noCompletion private static var __pendingDefaultCaptureDevice:Bool; + #end + + @:noCompletion private static var __captureExtSupported:Bool; @:noCompletion private static var __disconnectExtSupported:Bool; + @:noCompletion private static var __enumerateAllSupported:Bool; @:noCompletion private static var __reopenDeviceSupported:Bool; @:noCompletion private static var __systemEventsSupported:Bool; #end @@ -63,9 +146,136 @@ class AudioManager #if lime_openalsoft if (__reopenDeviceSupported) AL.disable(AL.STOP_SOURCES_ON_DISCONNECT_SOFT); + #if !mobile + if (__systemEventsSupported) { + ALC.eventControlSOFT([ + ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT, + ALC.EVENT_TYPE_DEVICE_ADDED_SOFT, + ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT], + true); + ALC.eventCallbackSOFT(__deviceEventCallback); + } #end + #end + } + #end + } + + /** + Gets the default capture audio device name from the host operating system. + + Only works on Native target. + + @return The default capture audio device name. + **/ + public static function getCaptureDefaultDeviceName():String + { + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL && __captureExtSupported) + { + return __formatDeviceName(ALC.getString(null, ALC.CAPTURE_DEFAULT_DEVICE_SPECIFIER)); + } + #end + return ''; + } + + /** + Gets all of the available capture audio device names from the host operating system. + Note: "Default Device" is only exclusive in SDL3, this does not appear in every other OpenAL Soft backends. + + Only works on Native target. + + @return An array containing available capture audio device names. + **/ + public static function getCaptureDeviceNames():Array + { + #if (lime_openal && !lime_doc_gen) + if (context == null || context.type != OPENAL || !__captureExtSupported) return []; + + final arr = ALC.getStringList(null, ALC.CAPTURE_DEVICE_SPECIFIER); + for (i in 0...arr.length) arr[i] = __formatDeviceName(arr[i]); + + return arr; + #else + return []; + #end + } + + /** + Gets the current used playback audio device name. + + Only works on Native target. + + @return Current playback audio device name. + **/ + public static function getCurrentPlaybackDeviceName():String + { + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL) + { + var currentContext = ALC.getCurrentContext(); + if (currentContext != null) + { + var device = ALC.getContextsDevice(currentContext); + if (device != null) + { + if (__enumerateAllSupported) return __formatDeviceName(ALC.getString(device, ALC.ALL_DEVICES_SPECIFIER)); + else return __formatDeviceName(ALC.getString(device, ALC.DEVICE_SPECIFIER)); + } + } + } + #end + return ''; + } + + /** + Gets the default playback audio device name from the host operating system. + + Only works on Native target. + + @return The default playback audio device name. + **/ + public static function getPlaybackDefaultDeviceName():String + { + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL) + { + var deviceName:String; + if (__enumerateAllSupported) deviceName = __formatDeviceName(ALC.getString(null, ALC.DEFAULT_ALL_DEVICES_SPECIFIER)); + else deviceName = __formatDeviceName(ALC.getString(null, ALC.DEFAULT_DEVICE_SPECIFIER)); + + if (deviceName == "Default Device") return getPlaybackDeviceNames()[0]; + else return deviceName; } #end + return ''; + } + + /** + Gets all of the available playback audio device names from the host operating system. + + Only works on Native target. + + @return An array containing available playback audio device names. + **/ + public static function getPlaybackDeviceNames():Array + { + #if (lime_openal && !lime_doc_gen) + if (context == null || context.type != OPENAL) return []; + else if (!__enumerateAllSupported) return [__formatDeviceName(ALC.getString(null, ALC.DEVICE_SPECIFIER))]; + + final arr = ALC.getStringList(null, ALC.ALL_DEVICES_SPECIFIER); + for (i in 0...arr.length) arr[i] = __formatDeviceName(arr[i]); + + // SDL3 Backend Exclusive, apparently not a bug but it acts as a virtual device to automatically + // switch to a default device without having to code it on your own, except that it's + // been already coded so this isn't needed anymore. + arr.remove("Default Device"); + + return arr; + #else + return []; + #end } /** @@ -80,6 +290,8 @@ class AudioManager #if (lime_openal && !lime_doc_gen) if (context == null || context.type != OPENAL) return false; + if (deviceName == null) deviceName = getPlaybackDefaultDeviceName(); + var currentContext = ALC.getCurrentContext(); var device = currentContext != null ? ALC.getContextsDevice(currentContext) : null; @@ -213,6 +425,46 @@ class AudioManager #end } + @:noCompletion private static inline function get_muted():Bool + { + return __muted; + } + + @:noCompletion private static inline function set_muted(value:Bool):Bool + { + __muted = value; + if (context == null) return __muted; + + #if !lime_doc_gen + #if lime_openal + if (context.type == OPENAL) AL.listenerf(AL.GAIN, value ? 0 : __gain); + #elseif (js && html5 && lime_howlerjs) + Howler.mute(value); + #end + #end + return value; + } + + @:noCompletion private static inline function get_gain():Float + { + return __gain; + } + + @:noCompletion private static inline function set_gain(value:Float):Float + { + __gain = value; + if (context == null) return __gain; + + #if !lime_doc_gen + #if lime_openal + if (context.type == OPENAL) AL.listenerf(AL.GAIN, __muted ? 0 : value); + #elseif (js && html5 && lime_howlerjs) + Howler.volume(value); + #end + #end + return value; + } + #if (lime_openal && !lime_doc_gen) @:noCompletion private static function __refresh():Void { @@ -222,12 +474,141 @@ class AudioManager var device = ALC.getContextsDevice(currentContext); if (device == null) return; + __captureExtSupported = ALC.isExtensionPresent(null, 'ALC_EXT_CAPTURE'); __disconnectExtSupported = ALC.isExtensionPresent(null, 'ALC_EXT_disconnect'); + __enumerateAllSupported = ALC.isExtensionPresent(null, 'ALC_ENUMERATE_ALL_EXT'); __reopenDeviceSupported = ALC.isExtensionPresent(null, 'ALC_SOFT_reopen_device'); __systemEventsSupported = ALC.isExtensionPresent(null, 'ALC_SOFT_system_events'); + + gain = __gain; + AL.distanceModel(AL.NONE); + } + + @:noCompletion private static function __formatDeviceName(deviceName:String) + { + #if lime_openalsoft + if (StringTools.startsWith(deviceName, 'OpenAL Soft on ')) return deviceName.substr(15); + #elseif lime_openal + if (StringTools.startsWith(deviceName, 'OpenAL on ')) return deviceName.substr(10); + #end + else if (StringTools.startsWith(deviceName, 'Generic Software on ')) return deviceName.substr(20); + else return deviceName; } #if lime_openalsoft + @:noCompletion + private static function __deviceEventRun():Void + { + __pendingDeviceEventMutex.acquire(); + + if (__pendingDefaultPlaybackDevice) + { + __pendingDefaultPlaybackDevice = false; + if (automaticDefaultPlaybackDevice) refresh(); + onDefaultPlaybackDeviceChanged.dispatch(getPlaybackDefaultDeviceName()); + } + + if (__pendingDefaultCaptureDevice) + { + __pendingDefaultCaptureDevice = false; + onDefaultCaptureDeviceChanged.dispatch(getCaptureDefaultDeviceName()); + } + + if (__pendingPlaybackDevicesAddition != null) + { + for (deviceName in __pendingPlaybackDevicesAddition) onPlaybackDeviceAdded.dispatch(deviceName); + __pendingPlaybackDevicesAddition = null; + } + + if (__pendingPlaybackDevicesRemoval != null) + { + for (deviceName in __pendingPlaybackDevicesRemoval) onPlaybackDeviceRemoved.dispatch(deviceName); + __pendingPlaybackDevicesRemoval = null; + } + + if (__pendingCaptureDevicesAddition != null) + { + for (deviceName in __pendingCaptureDevicesAddition) onCaptureDeviceAdded.dispatch(deviceName); + __pendingCaptureDevicesAddition = null; + } + + if (__pendingCaptureDevicesRemoval != null) + { + for (deviceName in __pendingCaptureDevicesRemoval) onCaptureDeviceRemoved.dispatch(deviceName); + __pendingCaptureDevicesRemoval = null; + } + + __pendingDeviceEventCheck = false; + __pendingDeviceEventMutex.release(); + } + + @:noCompletion + private static function __deviceEventCallback(eventType:Int, deviceType:Int, handle:CFFIPointer, + #if hl _message:hl.Bytes #else message:String #end) + { + #if hl var message:String = CFFI.stringValue(_message); #end + var device:ALDevice = handle != null ? new ALDevice(handle) : null; + var deviceName = __getDeviceNameFromMessage(message); + + var currentContext = ALC.getCurrentContext(); + var currentDevice = currentContext != null ? ALC.getContextsDevice(currentContext) : null; + + __pendingDeviceEventMutex.acquire(); + + if (deviceType == ALC.PLAYBACK_DEVICE_SOFT) + { + switch (eventType) + { + case ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: + __pendingDefaultPlaybackDevice = true; + case ALC.EVENT_TYPE_DEVICE_ADDED_SOFT: + var formattedDeviceName = __formatDeviceName(deviceName); + if (__pendingPlaybackDevicesRemoval != null) __pendingPlaybackDevicesRemoval.remove(formattedDeviceName); + if (__pendingPlaybackDevicesAddition == null) __pendingPlaybackDevicesAddition = []; + __pendingPlaybackDevicesAddition.push(formattedDeviceName); + case ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT: + var formattedDeviceName = __formatDeviceName(deviceName); + if (__pendingPlaybackDevicesAddition != null) __pendingPlaybackDevicesAddition.remove(formattedDeviceName); + if (__pendingPlaybackDevicesRemoval == null) __pendingPlaybackDevicesRemoval = []; + __pendingPlaybackDevicesRemoval.push(formattedDeviceName); + } + } + else if (deviceType == ALC.CAPTURE_DEVICE_SOFT) + { + switch (eventType) + { + case ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: + __pendingDefaultCaptureDevice = true; + case ALC.EVENT_TYPE_DEVICE_ADDED_SOFT: + var formattedDeviceName = __formatDeviceName(deviceName); + if (__pendingCaptureDevicesRemoval != null) __pendingCaptureDevicesRemoval.remove(formattedDeviceName); + if (__pendingCaptureDevicesAddition == null) __pendingCaptureDevicesAddition = []; + __pendingCaptureDevicesAddition.push(formattedDeviceName); + case ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT: + var formattedDeviceName = __formatDeviceName(deviceName); + if (__pendingCaptureDevicesAddition != null) __pendingCaptureDevicesAddition.remove(formattedDeviceName); + if (__pendingCaptureDevicesRemoval == null) __pendingCaptureDevicesRemoval = []; + __pendingCaptureDevicesRemoval.push(formattedDeviceName); + } + } + + if (!__pendingDeviceEventCheck) + { + __pendingDeviceEventCheck = true; + MainLoop.runInMainThread(__deviceEventRun); + } + + __pendingDeviceEventMutex.release(); + } + + @:noCompletion + private static function __getDeviceNameFromMessage(message:String):Null + { + if (StringTools.startsWith(message, 'Device removed: ')) return message.substr(16); + else if (StringTools.startsWith(message, 'Device added: ')) return message.substr(14); + else return null; + } + @:noCompletion private static function __setupConfig():Void {