From 7a0de018001328e86725eff9fe8126cfb2807a19 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 17 Jan 2025 13:23:00 +0800 Subject: [PATCH 1/5] feat: reuse instance of pyfmodex system --- UnityPy/export/AudioClipConverter.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/UnityPy/export/AudioClipConverter.py b/UnityPy/export/AudioClipConverter.py index 94829f33..32fc33c3 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -111,6 +111,23 @@ def extract_audioclip_samples( return dump_samples(audio, audio_data, convert_pcm_float) +system_param = () +system_instance = None + +def get_pyfmodex_system_instance(maxchannels, flags): + global pyfmodex, system_param, system_instance + # cache strategy check + new_system_param = (maxchannels, flags, None) + if system_param != new_system_param or system_instance is None: + system_param = new_system_param + if system_instance is not None: + system_instance.release() + # create a new instance + system_instance = pyfmodex.System() + system_instance.init(*system_param) + return system_instance + + def dump_samples( clip: AudioClip, audio_data: bytes, convert_pcm_float: bool = True ) -> Dict[str, bytes]: @@ -119,8 +136,7 @@ def dump_samples( if not pyfmodex: return {} # init system - system = pyfmodex.System() - system.init(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL, None) + system = get_pyfmodex_system_instance(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL) sound = system.create_sound( bytes(audio_data), @@ -144,7 +160,6 @@ def dump_samples( subsound.release() sound.release() - system.release() return samples From c8f4afdbe2600e95780f35a25a26453cf809fcc0 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 17 Jan 2025 13:38:21 +0800 Subject: [PATCH 2/5] fix: styles in AudioClipConverter --- UnityPy/export/AudioClipConverter.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/UnityPy/export/AudioClipConverter.py b/UnityPy/export/AudioClipConverter.py index 32fc33c3..feb4292a 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -114,7 +114,7 @@ def extract_audioclip_samples( system_param = () system_instance = None -def get_pyfmodex_system_instance(maxchannels, flags): +def get_pyfmodex_system_instance(maxchannels: int, flags: int): global pyfmodex, system_param, system_instance # cache strategy check new_system_param = (maxchannels, flags, None) @@ -131,10 +131,12 @@ def get_pyfmodex_system_instance(maxchannels, flags): def dump_samples( clip: AudioClip, audio_data: bytes, convert_pcm_float: bool = True ) -> Dict[str, bytes]: + global pyfmodex if pyfmodex is None: import_pyfmodex() if not pyfmodex: return {} + # init system system = get_pyfmodex_system_instance(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL) @@ -177,10 +179,12 @@ def subsound_to_wav(subsound, convert_pcm_float: bool = True) -> bytes: pyfmodex.enums.SOUND_FORMAT.PCM24, pyfmodex.enums.SOUND_FORMAT.PCM32, ]: + # format is PCM integer audio_format = 1 wav_data_length = sound_data_length convert_pcm_float = False elif sound_format == pyfmodex.enums.SOUND_FORMAT.PCMFLOAT: + # format is IEEE 754 float if convert_pcm_float: audio_format = 1 bits = 16 @@ -220,11 +224,11 @@ def subsound_to_wav(subsound, convert_pcm_float: bool = True) -> bytes: if convert_pcm_float: if np is not None: ptr_data = np.frombuffer(ptr_data, dtype=np.float32) - ptr_data = (ptr_data * 2**15).astype(np.int16).tobytes() + ptr_data = (ptr_data * (1 << 15)).astype(np.int16).tobytes() else: ptr_data = struct.unpack("<%df" % (len(ptr_data) // 4), ptr_data) ptr_data = struct.pack( - "<%dh" % len(ptr_data), *[int(f * 2**15) for f in ptr_data] + "<%dh" % len(ptr_data), *[int(f * (1 << 15)) for f in ptr_data] ) w.write(ptr_data) From 092947f31b9cb07db652dbd4a220756e93c072a1 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 17 Jan 2025 13:50:51 +0800 Subject: [PATCH 3/5] fix: concurrent issue to pyfmodex system --- UnityPy/export/AudioClipConverter.py | 50 +++++++++++++++------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/UnityPy/export/AudioClipConverter.py b/UnityPy/export/AudioClipConverter.py index feb4292a..ba77d1d8 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -3,6 +3,7 @@ import ctypes import os import platform +from threading import Lock from typing import TYPE_CHECKING, Dict, Union from UnityPy.streams import EndianBinaryWriter @@ -111,6 +112,7 @@ def extract_audioclip_samples( return dump_samples(audio, audio_data, convert_pcm_float) +system_lock = Lock() system_param = () system_instance = None @@ -137,31 +139,33 @@ def dump_samples( if not pyfmodex: return {} - # init system - system = get_pyfmodex_system_instance(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL) - - sound = system.create_sound( - bytes(audio_data), - pyfmodex.flags.MODE.OPENMEMORY, - exinfo=pyfmodex.structure_declarations.CREATESOUNDEXINFO( - length=len(audio_data), - numchannels=clip.m_Channels, - defaultfrequency=clip.m_Frequency, - ), - ) + # avoid asynchronous access to pyfmodex + with system_lock: + # init system + system = get_pyfmodex_system_instance(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL) + + sound = system.create_sound( + bytes(audio_data), + pyfmodex.flags.MODE.OPENMEMORY, + exinfo=pyfmodex.structure_declarations.CREATESOUNDEXINFO( + length=len(audio_data), + numchannels=clip.m_Channels, + defaultfrequency=clip.m_Frequency, + ), + ) - # iterate over subsounds - samples = {} - for i in range(sound.num_subsounds): - if i > 0: - filename = "%s-%i.wav" % (clip.m_Name, i) - else: - filename = "%s.wav" % clip.m_Name - subsound = sound.get_subsound(i) - samples[filename] = subsound_to_wav(subsound, convert_pcm_float) - subsound.release() + # iterate over subsounds + samples = {} + for i in range(sound.num_subsounds): + if i > 0: + filename = "%s-%i.wav" % (clip.m_Name, i) + else: + filename = "%s.wav" % clip.m_Name + subsound = sound.get_subsound(i) + samples[filename] = subsound_to_wav(subsound, convert_pcm_float) + subsound.release() - sound.release() + sound.release() return samples From 482f40ebb8dc317e07076329688cebd5d379bcd0 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 17 Jan 2025 23:51:03 +0800 Subject: [PATCH 4/5] feat: optimize reuse strategy of pyfmodex system --- UnityPy/export/AudioClipConverter.py | 39 ++++++++++++---------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/UnityPy/export/AudioClipConverter.py b/UnityPy/export/AudioClipConverter.py index ba77d1d8..d779867f 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -112,23 +112,21 @@ def extract_audioclip_samples( return dump_samples(audio, audio_data, convert_pcm_float) -system_lock = Lock() -system_param = () -system_instance = None - -def get_pyfmodex_system_instance(maxchannels: int, flags: int): - global pyfmodex, system_param, system_instance - # cache strategy check - new_system_param = (maxchannels, flags, None) - if system_param != new_system_param or system_instance is None: - system_param = new_system_param - if system_instance is not None: - system_instance.release() - # create a new instance - system_instance = pyfmodex.System() - system_instance.init(*system_param) - return system_instance - +SYSTEM_INSTANCES = {} # (channels, flags) -> (pyfmodex_system_instance, lock) +SYSTEM_GLOBAL_LOCK = Lock() + +def get_pyfmodex_system_instance(channels: int, flags: int): + global pyfmodex, SYSTEM_INSTANCES, SYSTEM_GLOBAL_LOCK + with SYSTEM_GLOBAL_LOCK: + instance_key = (channels, flags) + if instance_key in SYSTEM_INSTANCES: + return SYSTEM_INSTANCES[instance_key] + + system = pyfmodex.System() + system.init(channels, flags, None) + lock = Lock() + SYSTEM_INSTANCES[instance_key] = (system, lock) + return system, lock def dump_samples( clip: AudioClip, audio_data: bytes, convert_pcm_float: bool = True @@ -139,11 +137,8 @@ def dump_samples( if not pyfmodex: return {} - # avoid asynchronous access to pyfmodex - with system_lock: - # init system - system = get_pyfmodex_system_instance(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL) - + system, lock = get_pyfmodex_system_instance(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL) + with lock: sound = system.create_sound( bytes(audio_data), pyfmodex.flags.MODE.OPENMEMORY, From 86f0cc2f5dc0cfdefa29f585d2eb55118a1cd729 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 17 Jan 2025 23:52:11 +0800 Subject: [PATCH 5/5] fix: incorrect type hint in AudioClipConverter --- UnityPy/export/AudioClipConverter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UnityPy/export/AudioClipConverter.py b/UnityPy/export/AudioClipConverter.py index d779867f..7b417a4b 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -4,7 +4,7 @@ import os import platform from threading import Lock -from typing import TYPE_CHECKING, Dict, Union +from typing import TYPE_CHECKING, Dict from UnityPy.streams import EndianBinaryWriter @@ -28,15 +28,14 @@ def get_fmod_path( - system: Union["Windows", "Linux", "Darwin"], arch: ["x64", "x86", "arm", "arm64"] + system: str, # "Windows", "Linux", "Darwin" + arch: str, # "x64", "x86", "arm", "arm64" ) -> str: if system == "Darwin": # universal dylib return "lib/FMOD/Darwin/libfmod.dylib" - if system == "Windows": return f"lib/FMOD/Windows/{arch}/fmod.dll" - if system == "Linux": if arch == "x64": arch = "x86_64" @@ -128,6 +127,7 @@ def get_pyfmodex_system_instance(channels: int, flags: int): SYSTEM_INSTANCES[instance_key] = (system, lock) return system, lock + def dump_samples( clip: AudioClip, audio_data: bytes, convert_pcm_float: bool = True ) -> Dict[str, bytes]: