diff --git a/.vscode/launch.json b/.vscode/launch.json index bff2c17..8e76531 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -42,7 +42,7 @@ "type": "cppvsdbg", "request": "launch", "args": [], - "program": "${workspaceFolder}/example/build/windows/runner/Debug/flutter_soloud_example.exe", + "program": "${workspaceFolder}/example/build/windows/x64/runner/Debug/flutter_soloud_example.exe", "cwd": "${workspaceFolder}" }, { @@ -51,7 +51,7 @@ "type": "cppvsdbg", "request": "launch", "args": [], - "program": "${workspaceFolder}/example/build/windows/runner/Debug/flutter_soloud_example.exe", + "program": "${workspaceFolder}/example/build/windows/x64//runner/Debug/flutter_soloud_example.exe", "cwd": "${workspaceFolder}" }, { @@ -70,6 +70,14 @@ "request": "launch", "program": "${workspaceFolder}/example/build/linux/x64/debug/bundle/flutter_soloud_example", "cwd": "${workspaceFolder}" + }, + { + "name": "Debug native Linux test", + "preLaunchTask": "compile linux test debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/example/build/linux/x64/debug/bundle/flutter_soloud_example", + "cwd": "${workspaceFolder}" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7761131..3c6802d 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,6 +13,12 @@ // "args": ["build", "linux"], "type": "shell" }, + { + "label": "compile linux test debug", + "command": "cd ${workspaceFolder}/example; flutter build linux -t tests/tests.dart --debug", + // "args": ["build", "linux"], + "type": "shell" + }, { "label": "compile windows debug verbose", "command": "cd ${workspaceFolder}/example; flutter build windows -t lib/main.dart --debug --verbose", diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a31ce..51168a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### 2.1.0 +- added `getPan()`, `setPan()` and `setPanAbsolute()`. + ### 2.0.2 (23 May 2024) - Fixed wrong exception raised by `setVolume()` when a handle is no more valid. diff --git a/example/tests/tests.dart b/example/tests/tests.dart index ab62e07..78e3a92 100644 --- a/example/tests/tests.dart +++ b/example/tests/tests.dart @@ -38,6 +38,7 @@ void main() async { testAllInstancesFinished, testCreateNotes, testPlaySeekPause, + testPan, testHandles, loopingTests, ]; @@ -398,6 +399,30 @@ Future testPlaySeekPause() async { deinit(); } +/// Test instancing playing handles and their disposal +Future testPan() async { + /// Start audio isolate + await initialize(); + + final song = + await SoLoud.instance.loadAsset('assets/audio/8_bit_mentality.mp3'); + + final handle = await SoLoud.instance.play(song, volume: 0.5); + + SoLoud.instance.setPan(handle, -0.8); + var pan = SoLoud.instance.getPan(handle); + assert(closeTo(pan, -0.8, 0.00001), 'setPan() or getPan() failed!'); + + await delay(1000); + + SoLoud.instance.setPan(handle, 0.8); + pan = SoLoud.instance.getPan(handle); + assert(closeTo(pan, 0.8, 0.00001), 'setPan() or getPan() failed!'); + await delay(1000); + + deinit(); +} + /// Test instancing playing handles and their disposal Future testHandles() async { /// Start audio isolate @@ -501,6 +526,10 @@ Future loadAsset() async { }); } +bool closeTo(num value, num expected, num epsilon) { + return (value - expected).abs() <= epsilon.abs(); +} + void printError(Object error, StackTrace stack) { stderr.writeln('TEST error: $error\nstack: $stack'); exitCode = 1; diff --git a/lib/src/bindings_player_ffi.dart b/lib/src/bindings_player_ffi.dart index 56eb1f1..285b435 100644 --- a/lib/src/bindings_player_ffi.dart +++ b/lib/src/bindings_player_ffi.dart @@ -739,6 +739,53 @@ class FlutterSoLoudFfi { 'setVolume'); late final _setVolume = _setVolumePtr.asFunction(); + /// Get a sound's current pan setting. + /// + /// [handle] the sound handle. + /// Returns the range of the pan values is -1 to 1, where -1 is left, 0 is + /// middle and and 1 is right. + double getPan(int handle) { + // Note that because of the float<=>double conversion precision error + // (SoLoud lib uses floats), the returned value is not precise. + return _getPan(handle); + } + + late final _getPanPtr = + _lookup>( + 'getPan'); + late final _getPan = _getPanPtr.asFunction(); + + /// Set a sound's current pan setting. + /// + /// [handle] the sound handle. + /// [pan] the range of the pan values is -1 to 1, where -1 is left, 0 is + /// middle and and 1 is right. + void setPan(int handle, double pan) { + return _setPan(handle, pan); + } + + late final _setPanPtr = _lookup< + ffi.NativeFunction>( + 'setPan'); + late final _setPan = _setPanPtr.asFunction(); + + /// Set the left/right volumes directly. + /// Note that this does not affect the value returned by getPan. + /// + /// [handle] the sound handle. + /// [panLeft] value for the left pan. + /// [panRight] value for the right pan. + void setPanAbsolute(int handle, double panLeft, double panRight) { + return _setPanAbsolute(handle, panLeft, panRight); + } + + late final _setPanAbsolutePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.UnsignedInt, ffi.Double, ffi.Double)>>('setPanAbsolute'); + late final _setPanAbsolute = + _setPanAbsolutePtr.asFunction(); + /// Check if a handle is still valid. /// /// [handle] handle to check diff --git a/lib/src/soloud.dart b/lib/src/soloud.dart index bca1728..41fd463 100644 --- a/lib/src/soloud.dart +++ b/lib/src/soloud.dart @@ -1179,6 +1179,12 @@ interface class SoLoud { /// and `1.0` meaning full volume. /// /// Throws [SoLoudNotInitializedException] if the engine is not initialized. + /// + /// Note that if you `setGlobalVolume()` to `0.8` and then + /// `getGlobalVolume()`, you might get a slightly different number, + /// such as `0.800000042353`. + /// This is expected since the internal audio engine uses float + /// instead of double, and so there are rounding errors. double getGlobalVolume() { if (!isInitialized) { throw const SoLoudNotInitializedException(); @@ -1192,6 +1198,12 @@ interface class SoLoud { /// to `1.0` (meaning full volume). /// /// Throws [SoLoudNotInitializedException] if the engine is not initialized. + /// + /// Note that if you `setGlobalVolume()` to `0.8` and then + /// `getGlobalVolume()`, you might get a slightly different number, + /// such as `0.800000042353`. + /// This is expected since the internal audio engine uses float + /// instead of double, and so there are rounding errors. void setGlobalVolume(double volume) { if (!isInitialized) { throw const SoLoudNotInitializedException(); @@ -1211,6 +1223,11 @@ interface class SoLoud { /// and `1.0` means its playing at full volume. /// /// Throws [SoLoudNotInitializedException] if the engine is not initialized. + /// + /// Note that if you `setVolume()` to `0.8` and then `getVolume()`, you might + /// get a slightly different number, such as `0.800000042353`. + /// This is expected since the internal audio engine uses float + /// instead of double, and so there are rounding errors. double getVolume(SoundHandle handle) { if (!isInitialized) { throw const SoLoudNotInitializedException(); @@ -1225,6 +1242,11 @@ interface class SoLoud { /// to `1.0` (meaning it should play at full volume). /// /// Throws [SoLoudNotInitializedException] if the engine is not initialized. + /// + /// Note that if you `setVolume()` to `0.8` and then `getVolume()`, you might + /// get a slightly different number, such as `0.800000042353`. + /// This is expected since the internal audio engine uses float + /// instead of double, and so there are rounding errors. void setVolume(SoundHandle handle, double volume) { if (!isInitialized) { throw const SoLoudNotInitializedException(); @@ -1232,6 +1254,75 @@ interface class SoLoud { SoLoudController().soLoudFFI.setVolume(handle, volume); } + /// Get a sound's current pan setting. + /// + /// [handle] the sound handle. + /// Returns the range of the pan values is -1 to 1, where -1 is left, 0 is + /// middle and and 1 is right. + /// + /// Throws [SoLoudNotInitializedException] if the engine is not initialized. + /// + /// Note that if you `setPan()` to `0.8` and then `getPan()`, you might + /// get a slightly different number, such as `0.800000042353`. + /// This is expected since the internal audio engine uses float + /// instead of double, and so there are rounding errors. + double getPan(SoundHandle handle) { + if (!isInitialized) { + throw const SoLoudNotInitializedException(); + } + return SoLoudController().soLoudFFI.getPan(handle.id); + } + + /// Set a sound's current pan setting. + /// + /// [handle] the sound handle. + /// [pan] the range of the pan values is -1 to 1, where -1 is left, 0 is + /// middle and and 1 is right. + /// + /// Throws [SoLoudNotInitializedException] if the engine is not initialized. + /// + /// Note that if you `setPan()` to `0.8` and then `getPan()`, you might + /// get a slightly different number, such as `0.800000042353`. + /// This is expected since the internal audio engine uses float + /// instead of double, and so there are rounding errors. + void setPan(SoundHandle handle, double pan) { + if (!isInitialized) { + throw const SoLoudNotInitializedException(); + } + assert( + pan >= -1 && pan <= 1, + 'The pan argument must be in range -1 to 1 inclusive!', + ); + return SoLoudController().soLoudFFI.setPan(handle.id, pan.clamp(-1, 1)); + } + + /// Set the left/right volumes directly. + /// Note that this does not affect the value returned by getPan. + /// + /// [handle] the sound handle. + /// [panLeft] value for the left pan. Must be >= -1 and <= 1. + /// [panRight] value for the right pan. Must be >= -1 and <= 1. + /// + /// Throws [SoLoudNotInitializedException] if the engine is not initialized. + void setPanAbsolute(SoundHandle handle, double panLeft, double panRight) { + if (!isInitialized) { + throw const SoLoudNotInitializedException(); + } + assert( + panLeft >= -1 && panLeft <= 1, + 'The panLeft argument must be in range -1 to 1 inclusive!', + ); + assert( + panRight >= -1 && panRight <= 1, + 'The panRight argument must be in range -1 to 1 inclusive!', + ); + return SoLoudController().soLoudFFI.setPanAbsolute( + handle.id, + panLeft.clamp(-1, 1), + panRight.clamp(-1, 1), + ); + } + /// Check if the [handle] is still valid. /// /// Returns `true` if the sound instance identified by its [handle] is diff --git a/src/bindings.cpp b/src/bindings.cpp index c1ba152..ad26bd6 100644 --- a/src/bindings.cpp +++ b/src/bindings.cpp @@ -587,6 +587,43 @@ extern "C" return noError; } + /// Get a sound's current pan setting. + /// + /// [handle] the sound handle. + /// Returns the range of the pan values is -1 to 1, where -1 is left, 0 is middle and and 1 is right. + FFI_PLUGIN_EXPORT double getPan(unsigned int handle) + { + if (player.get() == nullptr || !player.get()->isInited()) + return 0.0f; + + return player.get()->getPan(handle); + } + + /// Set a sound's current pan setting. + /// + /// [handle] the sound handle. + /// [pan] the range of the pan values is -1 to 1, where -1 is left, 0 is middle and and 1 is right. + FFI_PLUGIN_EXPORT void setPan(unsigned int handle, double pan) + { + if (player.get() == nullptr || !player.get()->isInited()) + return; + // Rounding to 6 decimal to work around the float to double precision. + player.get()->setPan(handle, pan); + } + + /// Set the left/right volumes directly. + /// Note that this does not affect the value returned by getPan. + /// + /// [handle] the sound handle. + /// [panLeft] value for the left pan. + /// [panRight] value for the right pan. + FFI_PLUGIN_EXPORT void setPanAbsolute(unsigned int handle, double panLeft, double panRight) + { + if (player.get() == nullptr || !player.get()->isInited()) + return; + player.get()->setPanAbsolute(handle, panLeft, panRight); + } + /// Check if a handle is still valid. /// /// [handle] handle to check diff --git a/src/player.cpp b/src/player.cpp index a385e59..064cec8 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -443,6 +443,30 @@ void Player::setVolume(SoLoud::handle handle, float volume) return soloud.setVolume(handle, volume); } +float Player::getPan(SoLoud::handle handle) +{ + return soloud.getPan(handle); +} + +void Player::setPan(SoLoud::handle handle, float pan) +{ + if (pan > 1.0f) + pan = 1.0f; + if (pan < -1.0f) + pan = -1.0f; + soloud.setPan(handle, pan); +} + +void Player::setPanAbsolute(SoLoud::handle handle, float panLeft, float panRight) +{ + if (panLeft > 1.0f) panLeft = 1.0f; + if (panLeft < -1.0f) panLeft = -1.0f; + if (panRight > 1.0f) panRight = 1.0f; + if (panRight < -1.0f) panRight = -1.0f; + soloud.setPanAbsolute(handle, panLeft, panRight); +} + + bool Player::isValidVoiceHandle(SoLoud::handle handle) { return soloud.isValidVoiceHandle(handle); diff --git a/src/player.h b/src/player.h index cecec95..9e70390 100644 --- a/src/player.h +++ b/src/player.h @@ -264,6 +264,25 @@ class Player /// @param volume the new volume to set. void setVolume(SoLoud::handle handle, float volume); + /// @brief Get a sound's current pan setting. + /// @param handle the sound handle. + /// @return the range of the pan values is -1 to 1, where -1 is left, 0 is middle and and 1 is right. + float getPan(SoLoud::handle handle); + + /// @brief Set a sound's current pan setting. + /// @param handle the sound handle. + /// @param pan the range of the pan values is -1 to 1, where -1 is left, 0 is middle and and 1 is right. + void setPan(SoLoud::handle handle, float pan); + + /// @brief Set the left/right volumes directly. + /// Note that this does not affect the value returned by getPan. + /// @param handle the sound handle. + /// @param panLeft value for the left pan. + /// @param panRight value for the right pan. + void setPanAbsolute(SoLoud::handle handle, float panLeft, float panRight); + + + /// @brief Check if a handle is still valid. /// @param handle handle to check. /// @return true if it still exists.