99
1010from ..helpers .ResourceReader import get_resource_data
1111
12+ try :
13+ import numpy as np
14+ except ImportError :
15+ np = None
16+ import struct
17+
1218if 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