From 8aee625a1476bda09217f380c2caab073f9eda75 Mon Sep 17 00:00:00 2001
From: SachinVin <26602104+SachinVin@users.noreply.github.com>
Date: Sun, 17 Sep 2023 03:16:32 +0530
Subject: [PATCH] externals: Add option to use system SoundTouch (#6971)

---
 CMakeLists.txt                               |  9 ++++
 externals/CMakeLists.txt                     | 11 +++--
 externals/cmake-modules/FindSoundTouch.cmake | 27 ++++++++++++
 src/audio_core/CMakeLists.txt                |  1 -
 src/audio_core/time_stretch.cpp              | 43 +++++++++++++++++++-
 5 files changed, 84 insertions(+), 7 deletions(-)
 create mode 100644 externals/cmake-modules/FindSoundTouch.cmake

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7bd7edf13..e0cecc686 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -86,6 +86,7 @@ option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OF
 option(USE_SYSTEM_BOOST "Use the system Boost libs (instead of the bundled ones)" OFF)
 option(USE_SYSTEM_OPENSSL "Use the system OpenSSL libs (instead of the bundled LibreSSL)" OFF)
 option(USE_SYSTEM_LIBUSB "Use the system libusb (instead of the bundled libusb)" OFF)
+option(USE_SYSTEM_SOUNDTOUCH "Use the system SoundTouch (instead of the bundled one)" OFF)
 
 if (CITRA_USE_PRECOMPILED_HEADERS)
     message(STATUS "Using Precompiled Headers.")
@@ -389,6 +390,14 @@ if (ENABLE_LIBUSB AND USE_SYSTEM_LIBUSB)
     find_package(LibUSB)
 endif()
 
+if (USE_SYSTEM_SOUNDTOUCH)
+    include(FindPkgConfig)
+    find_package(SoundTouch REQUIRED)
+    add_library(SoundTouch INTERFACE)
+    target_link_libraries(SoundTouch INTERFACE "${SOUNDTOUCH_LIBRARIES}")
+    target_include_directories(SoundTouch INTERFACE "${SOUNDTOUCH_INCLUDE_DIRS}")
+endif()
+
 add_subdirectory(src)
 add_subdirectory(dist/installer)
 
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 7777a6125..f11cb2996 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -122,10 +122,13 @@ add_subdirectory(open_source_archives)
 add_subdirectory(library-headers EXCLUDE_FROM_ALL)
 
 # SoundTouch
-set(INTEGER_SAMPLES ON CACHE BOOL "")
-set(SOUNDSTRETCH OFF CACHE BOOL "")
-set(SOUNDTOUCH_DLL OFF CACHE BOOL "")
-add_subdirectory(soundtouch EXCLUDE_FROM_ALL)
+if(NOT USE_SYSTEM_SOUNDTOUCH)
+    set(INTEGER_SAMPLES ON CACHE BOOL "")
+    set(SOUNDSTRETCH OFF CACHE BOOL "")
+    set(SOUNDTOUCH_DLL OFF CACHE BOOL "")
+    add_subdirectory(soundtouch EXCLUDE_FROM_ALL)
+    target_compile_definitions(SoundTouch PUBLIC SOUNDTOUCH_INTEGER_SAMPLES)
+endif()
 
 # sirit
 add_subdirectory(sirit EXCLUDE_FROM_ALL)
diff --git a/externals/cmake-modules/FindSoundTouch.cmake b/externals/cmake-modules/FindSoundTouch.cmake
new file mode 100644
index 000000000..69792f738
--- /dev/null
+++ b/externals/cmake-modules/FindSoundTouch.cmake
@@ -0,0 +1,27 @@
+if(NOT SOUNDTOUCH_FOUND)
+    pkg_check_modules(SOUNDTOUCH_TMP soundtouch)
+    
+    find_path(SOUNDTOUCH_INCLUDE_DIRS NAMES SoundTouch.h
+        PATHS
+        ${SOUNDTOUCH_TMP_INCLUDE_DIRS}
+        /usr/include/soundtouch
+        /usr/include
+        /usr/local/include/soundtouch
+        /usr/local/include
+    )
+    
+    find_library(SOUNDTOUCH_LIBRARIES NAMES SoundTouch
+        PATHS
+        ${SOUNDTOUCH_TMP_LIBRARY_DIRS}
+        /usr/lib
+        /usr/local/lib
+    )
+    
+    if(SOUNDTOUCH_INCLUDE_DIRS AND SOUNDTOUCH_LIBRARIES)
+        set(SOUNDTOUCH_FOUND TRUE CACHE INTERNAL "SoundTouch found")
+        message(STATUS "Found SoundTouch: ${SOUNDTOUCH_INCLUDE_DIRS}, ${SOUNDTOUCH_LIBRARIES}")
+    else()
+        set(SOUNDTOUCH_FOUND FALSE CACHE INTERNAL "SoundTouch found")
+        message(STATUS "SoundTouch not found.")
+    endif()
+endif()
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 003ebf181..46967735d 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -49,7 +49,6 @@ create_target_directory_groups(audio_core)
 
 target_link_libraries(audio_core PUBLIC citra_common citra_core)
 target_link_libraries(audio_core PRIVATE SoundTouch teakra)
-add_definitions(-DSOUNDTOUCH_INTEGER_SAMPLES)
 
 if(ENABLE_MF)
     target_sources(audio_core PRIVATE
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
index 4e50b9fa9..ce8908874 100644
--- a/src/audio_core/time_stretch.cpp
+++ b/src/audio_core/time_stretch.cpp
@@ -5,7 +5,10 @@
 #include <algorithm>
 #include <cmath>
 #include <cstddef>
+#include <limits>
 #include <memory>
+#include <type_traits>
+#include <vector>
 #include <SoundTouch.h>
 #include "audio_core/audio_types.h"
 #include "audio_core/time_stretch.h"
@@ -62,8 +65,44 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
     LOG_TRACE(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, stretch_ratio,
               backlog_fullness);
 
-    sound_touch->putSamples(in, static_cast<u32>(num_in));
-    return sound_touch->receiveSamples(out, static_cast<u32>(num_out));
+    if constexpr (std::is_floating_point<soundtouch::SAMPLETYPE>()) {
+        // The SoundTouch library on most systems expects float samples
+        // use this vector to store input if soundtouch::SAMPLETYPE is a float
+        std::vector<soundtouch::SAMPLETYPE> float_in(2 * num_in);
+        std::vector<soundtouch::SAMPLETYPE> float_out(2 * num_out);
+
+        for (std::size_t i = 0; i < (2 * num_in); i++) {
+            // Conventional integer PCM uses a range of -32768 to 32767,
+            // but float samples use -1 to 1
+            // As a result we need to scale sample values during conversion
+            const float temp = static_cast<float>(in[i]) / std::numeric_limits<s16>::max();
+            float_in[i] = static_cast<soundtouch::SAMPLETYPE>(temp);
+        }
+
+        sound_touch->putSamples(float_in.data(), static_cast<u32>(num_in));
+
+        const std::size_t samples_received =
+            sound_touch->receiveSamples(float_out.data(), static_cast<u32>(num_out));
+
+        // Converting output samples back to shorts so we can use them
+        for (std::size_t i = 0; i < (2 * num_out); i++) {
+            const s16 temp = static_cast<s16>(float_out[i] * std::numeric_limits<s16>::max());
+            out[i] = temp;
+        }
+
+        return samples_received;
+    } else if (std::is_same<soundtouch::SAMPLETYPE, s16>()) {
+        // Use reinterpret_cast to workaround compile error when SAMPLETYPE is float.
+        sound_touch->putSamples(reinterpret_cast<const soundtouch::SAMPLETYPE*>(in),
+                                static_cast<u32>(num_in));
+        return sound_touch->receiveSamples(reinterpret_cast<soundtouch::SAMPLETYPE*>(out),
+                                           static_cast<u32>(num_out));
+    } else {
+        static_assert(std::is_floating_point<soundtouch::SAMPLETYPE>() ||
+                      std::is_same<soundtouch::SAMPLETYPE, s16>());
+        UNREACHABLE_MSG("Invalid SAMPLETYPE {}", typeid(soundtouch::SAMPLETYPE).name());
+        return 0;
+    }
 }
 
 void TimeStretcher::Clear() {