3636#include <string.h>
3737#include <stdarg.h>
3838
39+ #ifdef __APPLE__
40+ #include <AudioToolbox/AudioToolbox.h>
41+ #else
3942#include <audio/conversion/float_to_s16.h>
4043#include <audio/conversion/s16_to_float.h>
4144#include <audio/audio_resampler.h>
45+ #endif
4246
4347extern retro_audio_sample_batch_t audio_batch_cb ;
4448
4549static unsigned MAX_AUDIO_FRAMES = 2048 ;
4650
4751#define VI_INTR_TIME 500000
52+ #define OUTPUT_RATE 44100
4853
4954/* Read header for type definition */
5055static int GameFreq = 33600 ;
5156static unsigned CountsPerSecond ;
5257static unsigned BytesPerSecond ;
5358static unsigned CountsPerByte ;
5459
60+ #ifdef __APPLE__
61+ /* Apple AudioConverter - resamples int16 to 44100 Hz.
62+ * No float intermediates needed (unlike sinc resampler path). */
63+ static AudioConverterRef audio_converter ;
64+ static unsigned converter_input_rate ;
65+ static int16_t * audio_out_buffer_s16 ;
66+
67+ /* Context for AudioConverter input callback */
68+ typedef struct
69+ {
70+ const int16_t * data ;
71+ size_t frames_left ;
72+ } converter_ctx_t ;
73+
74+ static OSStatus converter_input_cb (
75+ AudioConverterRef converter ,
76+ UInt32 * ioNumberDataPackets ,
77+ AudioBufferList * ioData ,
78+ AudioStreamPacketDescription * * outDataPacketDescription ,
79+ void * inUserData )
80+ {
81+ converter_ctx_t * ctx = (converter_ctx_t * )inUserData ;
82+ UInt32 frames_to_provide ;
83+
84+ if (ctx -> frames_left == 0 )
85+ {
86+ * ioNumberDataPackets = 0 ;
87+ return noErr ;
88+ }
89+
90+ frames_to_provide = * ioNumberDataPackets ;
91+ if (frames_to_provide > ctx -> frames_left )
92+ frames_to_provide = (UInt32 )ctx -> frames_left ;
93+
94+ ioData -> mBuffers [0 ].mData = (void * )ctx -> data ;
95+ ioData -> mBuffers [0 ].mDataByteSize = frames_to_provide * 4 ; /* stereo int16 */
96+ ioData -> mBuffers [0 ].mNumberChannels = 2 ;
97+
98+ ctx -> data += frames_to_provide * 2 ; /* advance by samples */
99+ ctx -> frames_left -= frames_to_provide ;
100+ * ioNumberDataPackets = frames_to_provide ;
101+
102+ return noErr ;
103+ }
104+
105+ static int create_audio_converter (unsigned input_rate )
106+ {
107+ AudioStreamBasicDescription input_desc , output_desc ;
108+ OSStatus err ;
109+
110+ if (audio_converter )
111+ {
112+ AudioConverterDispose (audio_converter );
113+ audio_converter = NULL ;
114+ }
115+
116+ /* Input: native-endian int16 stereo at game's sample rate
117+ * (byte swap is done before calling AudioConverter) */
118+ memset (& input_desc , 0 , sizeof (input_desc ));
119+ input_desc .mSampleRate = input_rate ;
120+ input_desc .mFormatID = kAudioFormatLinearPCM ;
121+ input_desc .mFormatFlags = kAudioFormatFlagIsSignedInteger
122+ | kAudioFormatFlagIsPacked ;
123+ input_desc .mBytesPerPacket = 4 ;
124+ input_desc .mFramesPerPacket = 1 ;
125+ input_desc .mBytesPerFrame = 4 ;
126+ input_desc .mChannelsPerFrame = 2 ;
127+ input_desc .mBitsPerChannel = 16 ;
128+
129+ /* Output: little-endian int16 stereo at 44100 Hz */
130+ memset (& output_desc , 0 , sizeof (output_desc ));
131+ output_desc .mSampleRate = OUTPUT_RATE ;
132+ output_desc .mFormatID = kAudioFormatLinearPCM ;
133+ output_desc .mFormatFlags = kAudioFormatFlagIsSignedInteger
134+ | kAudioFormatFlagIsPacked ;
135+ output_desc .mBytesPerPacket = 4 ;
136+ output_desc .mFramesPerPacket = 1 ;
137+ output_desc .mBytesPerFrame = 4 ;
138+ output_desc .mChannelsPerFrame = 2 ;
139+ output_desc .mBitsPerChannel = 16 ;
140+
141+ err = AudioConverterNew (& input_desc , & output_desc , & audio_converter );
142+ if (err != noErr )
143+ return -1 ;
144+
145+ /* Set high quality resampling */
146+ UInt32 quality = kAudioConverterQuality_High ;
147+ AudioConverterSetProperty (audio_converter ,
148+ kAudioConverterSampleRateConverterQuality ,
149+ sizeof (quality ), & quality );
150+
151+ converter_input_rate = input_rate ;
152+ return 0 ;
153+ }
154+
155+ #else
156+ /* Non-Apple: use libretro-common sinc resampler */
55157static const retro_resampler_t * resampler ;
56158static void * resampler_audio_data ;
57159static float * audio_in_buffer_float ;
@@ -62,9 +164,19 @@ void (*audio_convert_s16_to_float_arm)(float *out,
62164 const int16_t * in , size_t samples , float gain );
63165void (* audio_convert_float_to_s16_arm )(int16_t * out ,
64166 const float * in , size_t samples );
167+ #endif
65168
66169void deinit_audio_libretro (void )
67170{
171+ #ifdef __APPLE__
172+ if (audio_converter )
173+ {
174+ AudioConverterDispose (audio_converter );
175+ audio_converter = NULL ;
176+ }
177+ free (audio_out_buffer_s16 );
178+ audio_out_buffer_s16 = NULL ;
179+ #else
68180 if (resampler && resampler_audio_data )
69181 {
70182 resampler -> free (resampler_audio_data );
@@ -74,20 +186,29 @@ void deinit_audio_libretro(void)
74186 free (audio_out_buffer_float );
75187 free (audio_out_buffer_s16 );
76188 }
189+ #endif
77190}
78191
79192void init_audio_libretro (unsigned max_audio_frames )
80193{
81- retro_resampler_realloc (& resampler_audio_data , & resampler , "sinc" , RESAMPLER_QUALITY_DONTCARE , 1.0 );
82-
83194 MAX_AUDIO_FRAMES = max_audio_frames ;
84195
196+ #ifdef __APPLE__
197+ /* Allocate output buffer - sized for maximum expansion ratio */
198+ audio_out_buffer_s16 = malloc (2 * MAX_AUDIO_FRAMES * 2 * sizeof (int16_t ));
199+ /* Converter will be created on first use when we know the input rate */
200+ audio_converter = NULL ;
201+ converter_input_rate = 0 ;
202+ #else
203+ retro_resampler_realloc (& resampler_audio_data , & resampler , "sinc" , RESAMPLER_QUALITY_DONTCARE , 1.0 );
204+
85205 audio_in_buffer_float = malloc (2 * MAX_AUDIO_FRAMES * sizeof (float ));
86206 audio_out_buffer_float = malloc (2 * MAX_AUDIO_FRAMES * sizeof (float ));
87207 audio_out_buffer_s16 = malloc (2 * MAX_AUDIO_FRAMES * sizeof (int16_t ));
88208
89209 convert_s16_to_float_init_simd ();
90210 convert_float_to_s16_init_simd ();
211+ #endif
91212}
92213
93214static void aiDacrateChanged (void * user_data , unsigned int frequency )
@@ -122,15 +243,13 @@ void set_audio_format_via_libretro(void* user_data,
122243
123244static void aiLenChanged (void * user_data , const void * buffer , size_t size )
124245{
125- size_t max_frames , remain_frames ;
126246 uint32_t i ;
127- double ratio ;
128- struct resampler_data data = {0 };
129247 int16_t * out = NULL ;
130248 int16_t * raw_data = (int16_t * )buffer ;
131249 size_t frames = size / 4 ;
132250 uint8_t * p = (uint8_t * )buffer ;
133251
252+ /* Byte swap - handles endianness and channel order from N64 format */
134253 for (i = 0 ; i < size ; i += 4 )
135254 {
136255 p [i ] ^= p [i + 2 ];
@@ -141,6 +260,57 @@ static void aiLenChanged(void* user_data, const void* buffer, size_t size)
141260 p [i + 1 ] ^= p [i + 3 ];
142261 }
143262
263+ #ifdef __APPLE__
264+ /* Apple path: AudioConverter resamples, no float conversion needed */
265+ converter_ctx_t ctx ;
266+ AudioBufferList output_buffer ;
267+ UInt32 output_frames ;
268+ OSStatus err ;
269+
270+ /* Create or recreate converter if sample rate changed */
271+ if (!audio_converter || converter_input_rate != (unsigned )GameFreq )
272+ {
273+ if (create_audio_converter (GameFreq ) != 0 )
274+ return ;
275+ }
276+
277+ /* Reset converter to clear end-of-stream state from previous call */
278+ AudioConverterReset (audio_converter );
279+
280+ ctx .data = raw_data ;
281+ ctx .frames_left = frames ;
282+
283+ /* Calculate expected output frames */
284+ output_frames = (UInt32 )((frames * OUTPUT_RATE ) / GameFreq + 1 );
285+ if (output_frames > MAX_AUDIO_FRAMES * 2 )
286+ output_frames = MAX_AUDIO_FRAMES * 2 ;
287+
288+ output_buffer .mNumberBuffers = 1 ;
289+ output_buffer .mBuffers [0 ].mNumberChannels = 2 ;
290+ output_buffer .mBuffers [0 ].mDataByteSize = output_frames * 4 ;
291+ output_buffer .mBuffers [0 ].mData = audio_out_buffer_s16 ;
292+
293+ err = AudioConverterFillComplexBuffer (audio_converter ,
294+ converter_input_cb , & ctx ,
295+ & output_frames , & output_buffer , NULL );
296+
297+ if (err != noErr && err != 1 )
298+ return ;
299+
300+ out = audio_out_buffer_s16 ;
301+ while (output_frames )
302+ {
303+ size_t ret = audio_batch_cb (out , output_frames );
304+ output_frames -= ret ;
305+ out += ret * 2 ;
306+ }
307+
308+ #else
309+ /* Non-Apple path: sinc resampler with float conversion */
310+ size_t max_frames , remain_frames ;
311+ double ratio ;
312+ struct resampler_data data = {0 };
313+
144314audio_batch :
145315 out = NULL ;
146316 ratio = 44100.0 / GameFreq ;
@@ -176,6 +346,7 @@ static void aiLenChanged(void* user_data, const void* buffer, size_t size)
176346 frames = remain_frames ;
177347 goto audio_batch ;
178348 }
349+ #endif
179350}
180351
181352/* Abuse core & audio plugin implementation details to obtain the desired effect. */
0 commit comments