Skip to content

Commit 9475c47

Browse files
committed
AudioClipConverter - refactor sound format dependent export and add optional (default) float to 16 converter
1 parent 09919e1 commit 9475c47

File tree

1 file changed

+80
-58
lines changed

1 file changed

+80
-58
lines changed

UnityPy/export/AudioClipConverter.py

+80-58
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99

1010
from ..helpers.ResourceReader import get_resource_data
1111

12+
try:
13+
import numpy as np
14+
except ImportError:
15+
np = None
16+
import struct
17+
1218
if TYPE_CHECKING:
1319
from ..classes import AudioClip
1420

@@ -48,16 +54,18 @@ def import_pyfmodex():
4854
arch = platform.architecture()[0]
4955
machine = platform.machine()
5056

51-
if "arm" in machine or "aarch64" in machine:
57+
if "arm" in machine:
5258
arch = "arm"
59+
elif "aarch64" in machine:
60+
if system == "Linux":
61+
arch = "arm64"
62+
else:
63+
arch = "arm"
5364
elif arch == "32bit":
5465
arch = "x86"
5566
elif arch == "64bit":
5667
arch = "x64"
5768

58-
if system == "Linux" and "aarch64" in platform.machine():
59-
arch = "arm64"
60-
6169
fmod_rel_path = get_fmod_path(system, arch)
6270
fmod_path = os.path.join(
6371
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), fmod_rel_path
@@ -73,7 +81,9 @@ def import_pyfmodex():
7381
import pyfmodex
7482

7583

76-
def extract_audioclip_samples(audio: AudioClip) -> Dict[str, bytes]:
84+
def extract_audioclip_samples(
85+
audio: AudioClip, convert_pcm_float: bool = True
86+
) -> Dict[str, bytes]:
7787
"""extracts all the samples from an AudioClip
7888
:param audio: AudioClip
7989
:type audio: AudioClip
@@ -98,10 +108,12 @@ def extract_audioclip_samples(audio: AudioClip) -> Dict[str, bytes]:
98108
return {f"{audio.m_Name}.wav": audio_data}
99109
elif magic[4:8] == b"ftyp":
100110
return {f"{audio.m_Name}.m4a": audio_data}
101-
return dump_samples(audio, audio_data)
111+
return dump_samples(audio, audio_data, convert_pcm_float)
102112

103113

104-
def dump_samples(clip: AudioClip, audio_data: bytes) -> Dict[str, bytes]:
114+
def dump_samples(
115+
clip: AudioClip, audio_data: bytes, convert_pcm_float: bool = True
116+
) -> Dict[str, bytes]:
105117
if pyfmodex is None:
106118
import_pyfmodex()
107119
if not pyfmodex:
@@ -128,69 +140,79 @@ def dump_samples(clip: AudioClip, audio_data: bytes) -> Dict[str, bytes]:
128140
else:
129141
filename = "%s.wav" % clip.m_Name
130142
subsound = sound.get_subsound(i)
131-
samples[filename] = subsound_to_wav(subsound)
143+
samples[filename] = subsound_to_wav(subsound, convert_pcm_float)
132144
subsound.release()
133145

134146
sound.release()
135147
system.release()
136148
return samples
137149

138150

139-
def subsound_to_wav(subsound) -> bytes:
151+
def subsound_to_wav(subsound, convert_pcm_float: bool = True) -> bytes:
140152
# get sound settings
141153
sound_format = subsound.format.format
142-
length = subsound.get_length(pyfmodex.enums.TIMEUNIT.PCMBYTES)
154+
sound_data_length = subsound.get_length(pyfmodex.enums.TIMEUNIT.PCMBYTES)
143155
channels = subsound.format.channels
144156
bits = subsound.format.bits
145157
sample_rate = int(subsound.default_frequency)
146158

147-
148-
if sound_format == pyfmodex.enums.SOUND_FORMAT.PCM16:
149-
# write to buffer
150-
w = EndianBinaryWriter(endian="<")
151-
# riff chucnk
152-
w.write(b"RIFF")
153-
w.write_int(length + 36) # sizeof(FmtChunk) + sizeof(RiffChunk) + length
154-
w.write(b"WAVE")
155-
# fmt chunck
156-
w.write(b"fmt ")
157-
w.write_int(16) # sizeof(FmtChunk) - sizeof(RiffChunk)
158-
w.write_short(1)
159-
w.write_short(channels)
160-
w.write_int(sample_rate)
161-
w.write_int(sample_rate * channels * bits // 8)
162-
w.write_short(channels * bits // 8)
163-
w.write_short(bits)
164-
# data chunck
165-
w.write(b"data")
166-
w.write_int(length)
167-
# data
168-
lock = subsound.lock(0, length)
169-
for ptr, length in lock:
170-
ptr_data = ctypes.string_at(ptr, length.value)
171-
w.write(ptr_data)
172-
subsound.unlock(*lock)
173-
return w.bytes
174-
elif sound_format== pyfmodex.enums.SOUND_FORMAT.PCMFLOAT:
175-
w = EndianBinaryWriter(endian="<")
176-
w.write(b"RIFF")
177-
w.write_int(length + 44)
178-
w.write(b"WAVE")
179-
w.write(b"fmt ")
180-
w.write_int(16)
181-
w.write_short(3)
182-
w.write_short(subsound.format.channels)
183-
w.write_int(int(subsound.default_frequency))
184-
w.write_int(int(subsound.default_frequency * subsound.format.channels * subsound.format.bits/8))
185-
w.write_short(int(subsound.format.channels * subsound.format.bits/8))
186-
w.write_short(32)
187-
w.write(b"data")
188-
w.write_int(length)
189-
lock = subsound.lock(0, length)
190-
for ptr, length in lock:
191-
ptr_data = ctypes.string_at(ptr, length.value)
192-
w.write(ptr_data)
193-
subsound.unlock(*lock)
194-
return w.bytes
159+
if sound_format in [
160+
pyfmodex.enums.SOUND_FORMAT.PCM8,
161+
pyfmodex.enums.SOUND_FORMAT.PCM16,
162+
pyfmodex.enums.SOUND_FORMAT.PCM24,
163+
pyfmodex.enums.SOUND_FORMAT.PCM32,
164+
]:
165+
audio_format = 1
166+
wav_data_length = sound_data_length
167+
convert_pcm_float = False
168+
elif sound_format == pyfmodex.enums.SOUND_FORMAT.PCMFLOAT:
169+
if convert_pcm_float:
170+
audio_format = 1
171+
bits = 16
172+
wav_data_length = sound_data_length // 2
173+
else:
174+
audio_format = 3
175+
wav_data_length = sound_data_length
195176
else:
196177
raise NotImplementedError("Sound format " + sound_format + " is not supported.")
178+
179+
w = EndianBinaryWriter(endian="<")
180+
181+
# RIFF header
182+
w.write(b"RIFF") # chunk id
183+
w.write_int(
184+
wav_data_length + 36
185+
) # chunk size - 4 + (8 + 16 (sub chunk 1 size)) + (8 + length (sub chunk 2 size))
186+
w.write(b"WAVE") # format
187+
188+
# fmt chunk - sub chunk 1
189+
w.write(b"fmt ") # sub chunk 1 id
190+
w.write_int(16) # sub chunk 1 size, 16 for PCM
191+
w.write_short(audio_format) # audio format, 1: PCM integer, 3: IEEE 754 float
192+
w.write_short(channels) # number of channels
193+
w.write_int(sample_rate) # sample rate
194+
w.write_int(sample_rate * channels * bits // 8) # byte rate
195+
w.write_short(channels * bits // 8) # block align
196+
w.write_short(bits) # bits per sample
197+
198+
# data chunk - sub chunk 2
199+
w.write(b"data") # sub chunk 2 id
200+
w.write_int(wav_data_length) # sub chunk 2 size
201+
# sub chunk 2 data
202+
lock = subsound.lock(0, sound_data_length)
203+
for ptr, sound_data_length in lock:
204+
ptr_data = ctypes.string_at(ptr, sound_data_length.value)
205+
if convert_pcm_float:
206+
if np is not None:
207+
ptr_data = np.frombuffer(ptr_data, dtype=np.float32)
208+
ptr_data = (ptr_data * 2**15).astype(np.int16).tobytes()
209+
else:
210+
ptr_data = struct.unpack("<%df" % (len(ptr_data) // 4), ptr_data)
211+
ptr_data = struct.pack(
212+
"<%dh" % len(ptr_data), *[int(f * 2**15) for f in ptr_data]
213+
)
214+
215+
w.write(ptr_data)
216+
subsound.unlock(*lock)
217+
218+
return w.bytes

0 commit comments

Comments
 (0)