Audio SDL plugin. Compile and run on the OpenPandora
[mupen64plus-pandora.git] / source / mupen64plus-audio-sdl / src / main.c
CommitLineData
852ee1c3 1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus-sdl-audio - main.c *
3 * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
4 * Copyright (C) 2007-2009 Richard Goedeken *
5 * Copyright (C) 2007-2008 Ebenblues *
6 * Copyright (C) 2003 JttL *
7 * Copyright (C) 2002 Hacktarux *
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 * This program is distributed in the hope that it will be useful, *
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17 * GNU General Public License for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License *
20 * along with this program; if not, write to the *
21 * Free Software Foundation, Inc., *
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
23 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include <SDL.h>
30#include <SDL_audio.h>
31
32#ifdef USE_SRC
33#include <samplerate.h>
34#endif
35#ifdef USE_SPEEX
36#include <speex/speex_resampler.h>
37#endif
38
39#define M64P_PLUGIN_PROTOTYPES 1
40#include "m64p_types.h"
41#include "m64p_plugin.h"
42#include "m64p_common.h"
43#include "m64p_config.h"
44
45#include "main.h"
46#include "volume.h"
47#include "osal_dynamiclib.h"
48
49/* Default start-time size of primary buffer (in equivalent output samples).
50 This is the buffer where audio is loaded after it's extracted from n64's memory.
51 This value must be larger than PRIMARY_BUFFER_TARGET */
52#define PRIMARY_BUFFER_SIZE 16384
53
54/* this is the buffer fullness level (in equivalent output samples) which is targeted
55 for the primary audio buffer (by inserting delays) each time data is received from
56 the running N64 program. This value must be larger than the SECONDARY_BUFFER_SIZE.
57 Decreasing this value will reduce audio latency but requires a faster PC to avoid
58 choppiness. Increasing this will increase audio latency but reduce the chance of
59 drop-outs. The default value 10240 gives a 232ms maximum A/V delay at 44.1khz */
60#define PRIMARY_BUFFER_TARGET 10240
61
62/* Size of secondary buffer, in output samples. This is the requested size of SDL's
63 hardware buffer, and the size of the mix buffer for doing SDL volume control. The
64 SDL documentation states that this should be a power of two between 512 and 8192. */
65#define SECONDARY_BUFFER_SIZE 8192
66/*SEB 2048 before*/
67
68/* This sets default frequency what is used if rom doesn't want to change it.
69 Probably only game that needs this is Zelda: Ocarina Of Time Master Quest
70 *NOTICE* We should try to find out why Demos' frequencies are always wrong
71 They tend to rely on a default frequency, apparently, never the same one ;)*/
72#define DEFAULT_FREQUENCY 33600
73
74/* number of bytes per sample */
75#define N64_SAMPLE_BYTES 4
76#define SDL_SAMPLE_BYTES 4
77
78/* volume mixer types */
79#define VOLUME_TYPE_SDL 1
80#define VOLUME_TYPE_OSS 2
81
82/* local variables */
83static void (*l_DebugCallback)(void *, int, const char *) = NULL;
84static void *l_DebugCallContext = NULL;
85static int l_PluginInit = 0;
86static int l_PausedForSync = 1; /* Audio is started in paused state after SDL initialization */
87static m64p_handle l_ConfigAudio;
88
89enum resampler_type {
90 RESAMPLER_TRIVIAL,
91#ifdef USE_SRC
92 RESAMPLER_SRC,
93#endif
94#ifdef USE_SPEEX
95 RESAMPLER_SPEEX,
96#endif
97};
98
99/* Read header for type definition */
100static AUDIO_INFO AudioInfo;
101/* The hardware specifications we are using */
102static SDL_AudioSpec *hardware_spec;
103/* Pointer to the primary audio buffer */
104static unsigned char *primaryBuffer = NULL;
105static unsigned int primaryBufferBytes = 0;
106/* Pointer to the mixing buffer for voume control*/
107static unsigned char *mixBuffer = NULL;
108/* Position in buffer array where next audio chunk should be placed */
109static unsigned int buffer_pos = 0;
110/* Audio frequency, this is usually obtained from the game, but for compatibility we set default value */
111static int GameFreq = DEFAULT_FREQUENCY;
112/* timestamp for the last time that our audio callback was called */
113static unsigned int last_callback_ticks = 0;
114/* SpeedFactor is used to increase/decrease game playback speed */
115static unsigned int speed_factor = 100;
116// If this is true then left and right channels are swapped */
117static int SwapChannels = 0;
118// Size of Primary audio buffer in equivalent output samples
119static unsigned int PrimaryBufferSize = PRIMARY_BUFFER_SIZE;
120// Fullness level target for Primary audio buffer, in equivalent output samples
121static unsigned int PrimaryBufferTarget = PRIMARY_BUFFER_TARGET;
122// Size of Secondary audio buffer in output samples
123static unsigned int SecondaryBufferSize = SECONDARY_BUFFER_SIZE;
124// Resample type
125static enum resampler_type Resample = RESAMPLER_TRIVIAL;
126// Resampler specific quality
127static int ResampleQuality = 3;
128// volume to scale the audio by, range of 0..100
129// if muted, this holds the volume when not muted
130static int VolPercent = 80;
131// how much percent to increment/decrement volume by
132static int VolDelta = 5;
133// the actual volume passed into SDL, range of 0..SDL_MIX_MAXVOLUME
134static int VolSDL = SDL_MIX_MAXVOLUME;
135// Muted or not
136static int VolIsMuted = 0;
137//which type of volume control to use
138static int VolumeControlType = VOLUME_TYPE_SDL;
139
140static int OutputFreq;
141
142// Prototype of local functions
143static void my_audio_callback(void *userdata, unsigned char *stream, int len);
144static void InitializeAudio(int freq);
145static void ReadConfig(void);
146static void InitializeSDL(void);
147
148static int critical_failure = 0;
149
150/* definitions of pointers to Core config functions */
151ptr_ConfigOpenSection ConfigOpenSection = NULL;
152ptr_ConfigDeleteSection ConfigDeleteSection = NULL;
153ptr_ConfigSaveSection ConfigSaveSection = NULL;
154ptr_ConfigSetParameter ConfigSetParameter = NULL;
155ptr_ConfigGetParameter ConfigGetParameter = NULL;
156ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL;
157ptr_ConfigSetDefaultInt ConfigSetDefaultInt = NULL;
158ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat = NULL;
159ptr_ConfigSetDefaultBool ConfigSetDefaultBool = NULL;
160ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL;
161ptr_ConfigGetParamInt ConfigGetParamInt = NULL;
162ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL;
163ptr_ConfigGetParamBool ConfigGetParamBool = NULL;
164ptr_ConfigGetParamString ConfigGetParamString = NULL;
165
166/* Global functions */
167static void DebugMessage(int level, const char *message, ...)
168{
169 char msgbuf[1024];
170 va_list args;
171
172 if (l_DebugCallback == NULL)
173 return;
174
175 va_start(args, message);
176 vsprintf(msgbuf, message, args);
177
178 (*l_DebugCallback)(l_DebugCallContext, level, msgbuf);
179
180 va_end(args);
181}
182
183/* Mupen64Plus plugin functions */
184EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context,
185 void (*DebugCallback)(void *, int, const char *))
186{
187 ptr_CoreGetAPIVersions CoreAPIVersionFunc;
188
189 int ConfigAPIVersion, DebugAPIVersion, VidextAPIVersion, bSaveConfig;
190 float fConfigParamsVersion = 0.0f;
191
192 if (l_PluginInit)
193 return M64ERR_ALREADY_INIT;
194
195 /* first thing is to set the callback function for debug info */
196 l_DebugCallback = DebugCallback;
197 l_DebugCallContext = Context;
198
199 /* attach and call the CoreGetAPIVersions function, check Config API version for compatibility */
200 CoreAPIVersionFunc = (ptr_CoreGetAPIVersions) osal_dynlib_getproc(CoreLibHandle, "CoreGetAPIVersions");
201 if (CoreAPIVersionFunc == NULL)
202 {
203 DebugMessage(M64MSG_ERROR, "Core emulator broken; no CoreAPIVersionFunc() function found.");
204 return M64ERR_INCOMPATIBLE;
205 }
206
207 (*CoreAPIVersionFunc)(&ConfigAPIVersion, &DebugAPIVersion, &VidextAPIVersion, NULL);
208 if ((ConfigAPIVersion & 0xffff0000) != (CONFIG_API_VERSION & 0xffff0000))
209 {
210 DebugMessage(M64MSG_ERROR, "Emulator core Config API (v%i.%i.%i) incompatible with plugin (v%i.%i.%i)",
211 VERSION_PRINTF_SPLIT(ConfigAPIVersion), VERSION_PRINTF_SPLIT(CONFIG_API_VERSION));
212 return M64ERR_INCOMPATIBLE;
213 }
214
215 /* Get the core config function pointers from the library handle */
216 ConfigOpenSection = (ptr_ConfigOpenSection) osal_dynlib_getproc(CoreLibHandle, "ConfigOpenSection");
217 ConfigDeleteSection = (ptr_ConfigDeleteSection) osal_dynlib_getproc(CoreLibHandle, "ConfigDeleteSection");
218 ConfigSaveSection = (ptr_ConfigSaveSection) osal_dynlib_getproc(CoreLibHandle, "ConfigSaveSection");
219 ConfigSetParameter = (ptr_ConfigSetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigSetParameter");
220 ConfigGetParameter = (ptr_ConfigGetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParameter");
221 ConfigSetDefaultInt = (ptr_ConfigSetDefaultInt) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultInt");
222 ConfigSetDefaultFloat = (ptr_ConfigSetDefaultFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultFloat");
223 ConfigSetDefaultBool = (ptr_ConfigSetDefaultBool) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultBool");
224 ConfigSetDefaultString = (ptr_ConfigSetDefaultString) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultString");
225 ConfigGetParamInt = (ptr_ConfigGetParamInt) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamInt");
226 ConfigGetParamFloat = (ptr_ConfigGetParamFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamFloat");
227 ConfigGetParamBool = (ptr_ConfigGetParamBool) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamBool");
228 ConfigGetParamString = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamString");
229
230 if (!ConfigOpenSection || !ConfigDeleteSection || !ConfigSetParameter || !ConfigGetParameter ||
231 !ConfigSetDefaultInt || !ConfigSetDefaultFloat || !ConfigSetDefaultBool || !ConfigSetDefaultString ||
232 !ConfigGetParamInt || !ConfigGetParamFloat || !ConfigGetParamBool || !ConfigGetParamString)
233 return M64ERR_INCOMPATIBLE;
234
235 /* ConfigSaveSection was added in Config API v2.1.0 */
236 if (ConfigAPIVersion >= 0x020100 && !ConfigSaveSection)
237 return M64ERR_INCOMPATIBLE;
238
239 /* get a configuration section handle */
240 if (ConfigOpenSection("Audio-SDL", &l_ConfigAudio) != M64ERR_SUCCESS)
241 {
242 DebugMessage(M64MSG_ERROR, "Couldn't open config section 'Audio-SDL'");
243 return M64ERR_INPUT_NOT_FOUND;
244 }
245
246 /* check the section version number */
247 bSaveConfig = 0;
248 if (ConfigGetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fConfigParamsVersion, sizeof(float)) != M64ERR_SUCCESS)
249 {
250 DebugMessage(M64MSG_WARNING, "No version number in 'Audio-SDL' config section. Setting defaults.");
251 ConfigDeleteSection("Audio-SDL");
252 ConfigOpenSection("Audio-SDL", &l_ConfigAudio);
253 bSaveConfig = 1;
254 }
255 else if (((int) fConfigParamsVersion) != ((int) CONFIG_PARAM_VERSION))
256 {
257 DebugMessage(M64MSG_WARNING, "Incompatible version %.2f in 'Audio-SDL' config section: current is %.2f. Setting defaults.", fConfigParamsVersion, (float) CONFIG_PARAM_VERSION);
258 ConfigDeleteSection("Audio-SDL");
259 ConfigOpenSection("Audio-SDL", &l_ConfigAudio);
260 bSaveConfig = 1;
261 }
262 else if ((CONFIG_PARAM_VERSION - fConfigParamsVersion) >= 0.0001f)
263 {
264 /* handle upgrades */
265 float fVersion = CONFIG_PARAM_VERSION;
266 ConfigSetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fVersion);
267 DebugMessage(M64MSG_INFO, "Updating parameter set version in 'Audio-SDL' config section to %.2f", fVersion);
268 bSaveConfig = 1;
269 }
270
271 /* set the default values for this plugin */
272 ConfigSetDefaultFloat(l_ConfigAudio, "Version", CONFIG_PARAM_VERSION, "Mupen64Plus SDL Audio Plugin config parameter version number");
273 ConfigSetDefaultInt(l_ConfigAudio, "DEFAULT_FREQUENCY", DEFAULT_FREQUENCY, "Frequency which is used if rom doesn't want to change it");
274 ConfigSetDefaultBool(l_ConfigAudio, "SWAP_CHANNELS", 0, "Swaps left and right channels");
275 ConfigSetDefaultInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE", PRIMARY_BUFFER_SIZE, "Size of primary buffer in output samples. This is where audio is loaded after it's extracted from n64's memory.");
276 ConfigSetDefaultInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET", PRIMARY_BUFFER_TARGET, "Fullness level target for Primary audio buffer, in equivalent output samples");
277 ConfigSetDefaultInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE", SECONDARY_BUFFER_SIZE, "Size of secondary buffer in output samples. This is SDL's hardware buffer.");
278 ConfigSetDefaultString(l_ConfigAudio, "RESAMPLE", "trivial", "Audio resampling algorithm. src-sinc-best-quality, src-sinc-medium-quality, src-sinc-fastest, src-zero-order-hold, src-linear, speex-fixed-{10-0}, trivial");
279 ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_CONTROL_TYPE", VOLUME_TYPE_SDL, "Volume control type: 1 = SDL (only affects Mupen64Plus output) 2 = OSS mixer (adjusts master PC volume)");
280 ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_ADJUST", 5, "Percentage change each time the volume is increased or decreased");
281 ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_DEFAULT", 80, "Default volume when a game is started. Only used if VOLUME_CONTROL_TYPE is 1");
282
283 if (bSaveConfig && ConfigAPIVersion >= 0x020100)
284 ConfigSaveSection("Audio-SDL");
285
286 l_PluginInit = 1;
287 return M64ERR_SUCCESS;
288}
289
290EXPORT m64p_error CALL PluginShutdown(void)
291{
292 if (!l_PluginInit)
293 return M64ERR_NOT_INIT;
294
295 /* reset some local variables */
296 l_DebugCallback = NULL;
297 l_DebugCallContext = NULL;
298
299 /* make sure our buffer is freed */
300 if (mixBuffer != NULL)
301 {
302 free(mixBuffer);
303 mixBuffer = NULL;
304 }
305
306 l_PluginInit = 0;
307 return M64ERR_SUCCESS;
308}
309
310EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
311{
312 /* set version info */
313 if (PluginType != NULL)
314 *PluginType = M64PLUGIN_AUDIO;
315
316 if (PluginVersion != NULL)
317 *PluginVersion = SDL_AUDIO_PLUGIN_VERSION;
318
319 if (APIVersion != NULL)
320 *APIVersion = AUDIO_PLUGIN_API_VERSION;
321
322 if (PluginNamePtr != NULL)
323 *PluginNamePtr = "Mupen64Plus SDL Audio Plugin";
324
325 if (Capabilities != NULL)
326 {
327 *Capabilities = 0;
328 }
329
330 return M64ERR_SUCCESS;
331}
332
333/* ----------- Audio Functions ------------- */
334EXPORT void CALL AiDacrateChanged( int SystemType )
335{
336 int f = GameFreq;
337
338 if (!l_PluginInit)
339 return;
340
341 switch (SystemType)
342 {
343 case SYSTEM_NTSC:
344 f = 48681812 / (*AudioInfo.AI_DACRATE_REG + 1);
345 break;
346 case SYSTEM_PAL:
347 f = 49656530 / (*AudioInfo.AI_DACRATE_REG + 1);
348 break;
349 case SYSTEM_MPAL:
350 f = 48628316 / (*AudioInfo.AI_DACRATE_REG + 1);
351 break;
352 }
353 InitializeAudio(f);
354}
355
356
357EXPORT void CALL AiLenChanged( void )
358{
359 unsigned int LenReg;
360 unsigned char *p;
361 unsigned int CurrLevel, CurrTime, ExpectedLevel, ExpectedTime;
362
363 if (critical_failure == 1)
364 return;
365 if (!l_PluginInit)
366 return;
367
368 LenReg = *AudioInfo.AI_LEN_REG;
369 p = AudioInfo.RDRAM + (*AudioInfo.AI_DRAM_ADDR_REG & 0xFFFFFF);
370
371 if (buffer_pos + LenReg < primaryBufferBytes)
372 {
373 unsigned int i;
374
375 SDL_LockAudio();
376 for ( i = 0 ; i < LenReg ; i += 4 )
377 {
378
379 if(SwapChannels == 0)
380 {
381 // Left channel
382 primaryBuffer[ buffer_pos + i ] = p[ i + 2 ];
383 primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 3 ];
384
385 // Right channel
386 primaryBuffer[ buffer_pos + i + 2 ] = p[ i ];
387 primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 1 ];
388 } else {
389 // Left channel
390 primaryBuffer[ buffer_pos + i ] = p[ i ];
391 primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 1 ];
392
393 // Right channel
394 primaryBuffer[ buffer_pos + i + 2 ] = p[ i + 2];
395 primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 3 ];
396 }
397 }
398 buffer_pos += i;
399 SDL_UnlockAudio();
400 }
401 else
402 {
403 DebugMessage(M64MSG_WARNING, "AiLenChanged(): Audio buffer overflow.");
404 }
405
406 /* Now we need to handle synchronization, by inserting time delay to keep the emulator running at the correct speed */
407 /* Start by calculating the current Primary buffer fullness in terms of output samples */
408 CurrLevel = (unsigned int) (((long long) (buffer_pos/N64_SAMPLE_BYTES) * OutputFreq * 100) / (GameFreq * speed_factor));
409 /* Next, extrapolate to the buffer level at the expected time of the next audio callback, assuming that the
410 buffer is filled at the same rate as the output frequency */
411 CurrTime = SDL_GetTicks();
412 ExpectedTime = last_callback_ticks + ((1000 * SecondaryBufferSize) / OutputFreq);
413 ExpectedLevel = CurrLevel;
414 if (CurrTime < ExpectedTime)
415 ExpectedLevel += (ExpectedTime - CurrTime) * OutputFreq / 1000;
416 /* If the expected value of the Primary Buffer Fullness at the time of the next audio callback is more than 10
417 milliseconds ahead of our target buffer fullness level, then insert a delay now */
418 DebugMessage(M64MSG_VERBOSE, "%03i New audio bytes: %i Time to next callback: %i Current/Expected buffer level: %i/%i",
419 CurrTime % 1000, LenReg, (int) (ExpectedTime - CurrTime), CurrLevel, ExpectedLevel);
420 if (ExpectedLevel >= PrimaryBufferTarget + OutputFreq / 100)
421 {
422 unsigned int WaitTime = (ExpectedLevel - PrimaryBufferTarget) * 1000 / OutputFreq;
423 DebugMessage(M64MSG_VERBOSE, " AiLenChanged(): Waiting %ims", WaitTime);
424 if (l_PausedForSync)
425 SDL_PauseAudio(0);
426 l_PausedForSync = 0;
427 SDL_Delay(WaitTime);
428 }
429 /* Or if the expected level of the primary buffer is less than the secondary buffer size
430 (ie, predicting an underflow), then pause the audio to let the emulator catch up to speed */
431 else if (ExpectedLevel < SecondaryBufferSize)
432 {
433 DebugMessage(M64MSG_VERBOSE, " AiLenChanged(): Possible underflow at next audio callback; pausing playback");
434 if (!l_PausedForSync)
435 SDL_PauseAudio(1);
436 l_PausedForSync = 1;
437 }
438 /* otherwise the predicted buffer level is within our tolerance, so everything is okay */
439 else
440 {
441 if (l_PausedForSync)
442 SDL_PauseAudio(0);
443 l_PausedForSync = 0;
444 }
445}
446
447EXPORT int CALL InitiateAudio( AUDIO_INFO Audio_Info )
448{
449 if (!l_PluginInit)
450 return 0;
451
452 AudioInfo = Audio_Info;
453 return 1;
454}
455
456static int underrun_count = 0;
457
458#ifdef USE_SRC
459static float *_src = NULL;
460static unsigned int _src_len = 0;
461static float *_dest = NULL;
462static unsigned int _dest_len = 0;
463static int error;
464static SRC_STATE *src_state;
465static SRC_DATA src_data;
466#endif
467#ifdef USE_SPEEX
468SpeexResamplerState* spx_state = NULL;
469static int error;
470#endif
471
472static int resample(unsigned char *input, int input_avail, int oldsamplerate, unsigned char *output, int output_needed, int newsamplerate)
473{
474 int *psrc = (int*)input;
475 int *pdest = (int*)output;
476 int i = 0, j = 0;
477
478#ifdef USE_SPEEX
479 spx_uint32_t in_len, out_len;
480 if(Resample == RESAMPLER_SPEEX)
481 {
482 if(spx_state == NULL)
483 {
484 spx_state = speex_resampler_init(2, oldsamplerate, newsamplerate, ResampleQuality, &error);
485 if(spx_state == NULL)
486 {
487 memset(output, 0, output_needed);
488 return 0;
489 }
490 }
491 speex_resampler_set_rate(spx_state, oldsamplerate, newsamplerate);
492 in_len = input_avail / 4;
493 out_len = output_needed / 4;
494
495 if ((error = speex_resampler_process_interleaved_int(spx_state, (const spx_int16_t *)input, &in_len, (spx_int16_t *)output, &out_len)))
496 {
497 memset(output, 0, output_needed);
498 return input_avail; // number of bytes consumed
499 }
500 return in_len * 4;
501 }
502#endif
503#ifdef USE_SRC
504 if(Resample == RESAMPLER_SRC)
505 {
506 // the high quality resampler needs more input than the samplerate ratio would indicate to work properly
507 if (input_avail > output_needed * 3 / 2)
508 input_avail = output_needed * 3 / 2; // just to avoid too much short-float-short conversion time
509 if (_src_len < input_avail*2 && input_avail > 0)
510 {
511 if(_src) free(_src);
512 _src_len = input_avail*2;
513 _src = malloc(_src_len);
514 }
515 if (_dest_len < output_needed*2 && output_needed > 0)
516 {
517 if(_dest) free(_dest);
518 _dest_len = output_needed*2;
519 _dest = malloc(_dest_len);
520 }
521 memset(_src,0,_src_len);
522 memset(_dest,0,_dest_len);
523 if(src_state == NULL)
524 {
525 src_state = src_new (ResampleQuality, 2, &error);
526 if(src_state == NULL)
527 {
528 memset(output, 0, output_needed);
529 return 0;
530 }
531 }
532 src_short_to_float_array ((short *) input, _src, input_avail/2);
533 src_data.end_of_input = 0;
534 src_data.data_in = _src;
535 src_data.input_frames = input_avail/4;
536 src_data.src_ratio = (float) newsamplerate / oldsamplerate;
537 src_data.data_out = _dest;
538 src_data.output_frames = output_needed/4;
539 if ((error = src_process (src_state, &src_data)))
540 {
541 memset(output, 0, output_needed);
542 return input_avail; // number of bytes consumed
543 }
544 src_float_to_short_array (_dest, (short *) output, output_needed/2);
545 return src_data.input_frames_used * 4;
546 }
547#endif
548 // RESAMPLE == TRIVIAL
549 if (newsamplerate >= oldsamplerate)
550 {
551 int sldf = oldsamplerate;
552 int const2 = 2*sldf;
553 int dldf = newsamplerate;
554 int const1 = const2 - 2*dldf;
555 int criteria = const2 - dldf;
556 for (i = 0; i < output_needed/4; i++)
557 {
558 pdest[i] = psrc[j];
559 if(criteria >= 0)
560 {
561 ++j;
562 criteria += const1;
563 }
564 else criteria += const2;
565 }
566 return j * 4; //number of bytes consumed
567 }
568 // newsamplerate < oldsamplerate, this only happens when speed_factor > 1
569 for (i = 0; i < output_needed/4; i++)
570 {
571 j = i * oldsamplerate / newsamplerate;
572 pdest[i] = psrc[j];
573 }
574 return j * 4; //number of bytes consumed
575}
576
577static void my_audio_callback(void *userdata, unsigned char *stream, int len)
578{
579 int oldsamplerate, newsamplerate;
580
581 if (!l_PluginInit)
582 return;
583
584 /* mark the time, for synchronization on the input side */
585 last_callback_ticks = SDL_GetTicks();
586
587 newsamplerate = OutputFreq * 100 / speed_factor;
588 oldsamplerate = GameFreq;
589
590 if (buffer_pos > (unsigned int) (len * oldsamplerate) / newsamplerate)
591 {
592 int input_used;
593#if defined(HAS_OSS_SUPPORT)
594 if (VolumeControlType == VOLUME_TYPE_OSS)
595 {
596 input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, stream, len, newsamplerate);
597 }
598 else
599#endif
600 {
601 input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, mixBuffer, len, newsamplerate);
602 memset(stream, 0, len);
603 SDL_MixAudio(stream, mixBuffer, len, VolSDL);
604 }
605 memmove(primaryBuffer, &primaryBuffer[input_used], buffer_pos - input_used);
606 buffer_pos -= input_used;
607 DebugMessage(M64MSG_VERBOSE, "%03i my_audio_callback: used %i samples",
608 last_callback_ticks % 1000, len / SDL_SAMPLE_BYTES);
609 }
610 else
611 {
612 unsigned int SamplesNeeded = (len * oldsamplerate) / (newsamplerate * SDL_SAMPLE_BYTES);
613 unsigned int SamplesPresent = buffer_pos / N64_SAMPLE_BYTES;
614 underrun_count++;
615 DebugMessage(M64MSG_VERBOSE, "%03i Buffer underflow (%i). %i samples present, %i needed",
616 last_callback_ticks % 1000, underrun_count, SamplesPresent, SamplesNeeded);
617 memset(stream , 0, len);
618 }
619}
620EXPORT int CALL RomOpen(void)
621{
622 if (!l_PluginInit)
623 return 0;
624
625 ReadConfig();
626 InitializeAudio(GameFreq);
627 return 1;
628}
629
630static void InitializeSDL(void)
631{
632 DebugMessage(M64MSG_INFO, "Initializing SDL audio subsystem...");
633
634 if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0)
635 {
636 DebugMessage(M64MSG_ERROR, "Failed to initialize SDL audio subsystem; forcing exit.\n");
637 critical_failure = 1;
638 return;
639 }
640 critical_failure = 0;
641
642}
643
644static void CreatePrimaryBuffer(void)
645{
646 unsigned int newPrimaryBytes = (unsigned int) ((long long) PrimaryBufferSize * GameFreq * speed_factor /
647 (OutputFreq * 100)) * N64_SAMPLE_BYTES;
648 if (primaryBuffer == NULL)
649 {
650 DebugMessage(M64MSG_VERBOSE, "Allocating memory for audio buffer: %i bytes.", newPrimaryBytes);
651 primaryBuffer = (unsigned char*) malloc(newPrimaryBytes);
652 memset(primaryBuffer, 0, newPrimaryBytes);
653 primaryBufferBytes = newPrimaryBytes;
654 }
655 else if (newPrimaryBytes > primaryBufferBytes) /* primary buffer only grows; there's no point in shrinking it */
656 {
657 unsigned char *newPrimaryBuffer = (unsigned char*) malloc(newPrimaryBytes);
658 unsigned char *oldPrimaryBuffer = primaryBuffer;
659 SDL_LockAudio();
660 memcpy(newPrimaryBuffer, oldPrimaryBuffer, primaryBufferBytes);
661 memset(newPrimaryBuffer + primaryBufferBytes, 0, newPrimaryBytes - primaryBufferBytes);
662 primaryBuffer = newPrimaryBuffer;
663 primaryBufferBytes = newPrimaryBytes;
664 SDL_UnlockAudio();
665 free(oldPrimaryBuffer);
666 }
667}
668
669static void InitializeAudio(int freq)
670{
671 SDL_AudioSpec *desired, *obtained;
672
673 if(SDL_WasInit(SDL_INIT_AUDIO|SDL_INIT_TIMER) == (SDL_INIT_AUDIO|SDL_INIT_TIMER) )
674 {
675 DebugMessage(M64MSG_VERBOSE, "InitializeAudio(): SDL Audio sub-system already initialized.");
676 SDL_PauseAudio(1);
677 SDL_CloseAudio();
678 }
679 else
680 {
681 DebugMessage(M64MSG_VERBOSE, "InitializeAudio(): Initializing SDL Audio");
682 DebugMessage(M64MSG_VERBOSE, "Primary buffer: %i output samples.", PrimaryBufferSize);
683 DebugMessage(M64MSG_VERBOSE, "Primary target fullness: %i output samples.", PrimaryBufferTarget);
684 DebugMessage(M64MSG_VERBOSE, "Secondary buffer: %i output samples.", SecondaryBufferSize);
685 InitializeSDL();
686 }
687 if (critical_failure == 1)
688 return;
689 GameFreq = freq; // This is important for the sync
690 if(hardware_spec != NULL) free(hardware_spec);
691
692 // Allocate space for SDL_AudioSpec
693 desired = malloc(sizeof(SDL_AudioSpec));
694 obtained = malloc(sizeof(SDL_AudioSpec));
695
696 if(freq < 11025) OutputFreq = 11025;
697 else if(freq < 22050) OutputFreq = 22050;
698 else OutputFreq = 44100;
699
700 desired->freq = OutputFreq;
701
702 DebugMessage(M64MSG_VERBOSE, "Requesting frequency: %iHz.", desired->freq);
703 /* 16-bit signed audio */
704 desired->format=AUDIO_S16SYS;
705 DebugMessage(M64MSG_VERBOSE, "Requesting format: %i.", desired->format);
706 /* Stereo */
707 desired->channels=2;
708 /* reload these because they gets re-assigned from SDL data below, and InitializeAudio can be called more than once */
709 PrimaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE");
710 PrimaryBufferTarget = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET");
711 SecondaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE");
712 desired->samples = SecondaryBufferSize;
713 /* Our callback function */
714 desired->callback = my_audio_callback;
715 desired->userdata = NULL;
716
717 /* Open the audio device */
718 l_PausedForSync = 1;
719 if (SDL_OpenAudio(desired, obtained) < 0)
720 {
721 DebugMessage(M64MSG_ERROR, "Couldn't open audio: %s", SDL_GetError());
722 critical_failure = 1;
723 return;
724 }
725 if (desired->format != obtained->format)
726 {
727 DebugMessage(M64MSG_WARNING, "Obtained audio format differs from requested.");
728 }
729 if (desired->freq != obtained->freq)
730 {
731 DebugMessage(M64MSG_WARNING, "Obtained frequency differs from requested.");
732 }
733
734 /* desired spec is no longer needed */
735 free(desired);
736 hardware_spec=obtained;
737
738 /* allocate memory for audio buffers */
739 OutputFreq = hardware_spec->freq;
740 SecondaryBufferSize = hardware_spec->samples;
741 if (PrimaryBufferTarget < SecondaryBufferSize)
742 PrimaryBufferTarget = SecondaryBufferSize;
743 if (PrimaryBufferSize < PrimaryBufferTarget)
744 PrimaryBufferSize = PrimaryBufferTarget;
745 if (PrimaryBufferSize < SecondaryBufferSize * 2)
746 PrimaryBufferSize = SecondaryBufferSize * 2;
747 CreatePrimaryBuffer();
748 if (mixBuffer != NULL)
749 free(mixBuffer);
750 mixBuffer = (unsigned char*) malloc(SecondaryBufferSize * SDL_SAMPLE_BYTES);
751
752 /* preset the last callback time */
753 if (last_callback_ticks == 0)
754 last_callback_ticks = SDL_GetTicks();
755
756 DebugMessage(M64MSG_VERBOSE, "Frequency: %i", hardware_spec->freq);
757 DebugMessage(M64MSG_VERBOSE, "Format: %i", hardware_spec->format);
758 DebugMessage(M64MSG_VERBOSE, "Channels: %i", hardware_spec->channels);
759 DebugMessage(M64MSG_VERBOSE, "Silence: %i", hardware_spec->silence);
760 DebugMessage(M64MSG_VERBOSE, "Samples: %i", hardware_spec->samples);
761 DebugMessage(M64MSG_VERBOSE, "Size: %i", hardware_spec->size);
762
763 /* set playback volume */
764#if defined(HAS_OSS_SUPPORT)
765 if (VolumeControlType == VOLUME_TYPE_OSS)
766 {
767 VolPercent = volGet();
768 }
769 else
770#endif
771 {
772 VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
773 }
774
775}
776EXPORT void CALL RomClosed( void )
777{
778 if (!l_PluginInit)
779 return;
780 if (critical_failure == 1)
781 return;
782 DebugMessage(M64MSG_VERBOSE, "Cleaning up SDL sound plugin...");
783
784 // Shut down SDL Audio output
785 SDL_PauseAudio(1);
786 SDL_CloseAudio();
787
788 // Delete the buffer, as we are done producing sound
789 if (primaryBuffer != NULL)
790 {
791 primaryBufferBytes = 0;
792 free(primaryBuffer);
793 primaryBuffer = NULL;
794 }
795 if (mixBuffer != NULL)
796 {
797 free(mixBuffer);
798 mixBuffer = NULL;
799 }
800
801 // Delete the hardware spec struct
802 if(hardware_spec != NULL) free(hardware_spec);
803 hardware_spec = NULL;
804
805 // Shutdown the respective subsystems
806 if(SDL_WasInit(SDL_INIT_AUDIO) != 0) SDL_QuitSubSystem(SDL_INIT_AUDIO);
807 if(SDL_WasInit(SDL_INIT_TIMER) != 0) SDL_QuitSubSystem(SDL_INIT_TIMER);
808}
809
810EXPORT void CALL ProcessAList(void)
811{
812}
813
814EXPORT void CALL SetSpeedFactor(int percentage)
815{
816 if (!l_PluginInit)
817 return;
818 if (percentage >= 10 && percentage <= 300)
819 speed_factor = percentage;
820 // we need a different size primary buffer to store the N64 samples when the speed changes
821 CreatePrimaryBuffer();
822}
823
824static void ReadConfig(void)
825{
826 const char *resampler_id;
827
828 /* read the configuration values into our static variables */
829 GameFreq = ConfigGetParamInt(l_ConfigAudio, "DEFAULT_FREQUENCY");
830 SwapChannels = ConfigGetParamBool(l_ConfigAudio, "SWAP_CHANNELS");
831 PrimaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE");
832 PrimaryBufferTarget = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET");
833 SecondaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE");
834 resampler_id = ConfigGetParamString(l_ConfigAudio, "RESAMPLE");
835 VolumeControlType = ConfigGetParamInt(l_ConfigAudio, "VOLUME_CONTROL_TYPE");
836 VolDelta = ConfigGetParamInt(l_ConfigAudio, "VOLUME_ADJUST");
837 VolPercent = ConfigGetParamInt(l_ConfigAudio, "VOLUME_DEFAULT");
838
839 if (!resampler_id) {
840 Resample = RESAMPLER_TRIVIAL;
841 DebugMessage(M64MSG_WARNING, "Could not find RESAMPLE configuration; use trivial resampler");
842 return;
843 }
844 if (strcmp(resampler_id, "trivial") == 0) {
845 Resample = RESAMPLER_TRIVIAL;
846 return;
847 }
848#ifdef USE_SPEEX
849 if (strncmp(resampler_id, "speex-fixed-", strlen("speex-fixed-")) == 0) {
850 int i;
851 static const char *speex_quality[] = {
852 "speex-fixed-0",
853 "speex-fixed-1",
854 "speex-fixed-2",
855 "speex-fixed-3",
856 "speex-fixed-4",
857 "speex-fixed-5",
858 "speex-fixed-6",
859 "speex-fixed-7",
860 "speex-fixed-8",
861 "speex-fixed-9",
862 "speex-fixed-10",
863 };
864 Resample = RESAMPLER_SPEEX;
865 for (i = 0; i < sizeof(speex_quality) / sizeof(*speex_quality); i++) {
866 if (strcmp(speex_quality[i], resampler_id) == 0) {
867 ResampleQuality = i;
868 return;
869 }
870 }
871 DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use speex-fixed-4 resampler", resampler_id);
872 ResampleQuality = 4;
873 return;
874 }
875#endif
876#ifdef USE_SRC
877 if (strncmp(resampler_id, "src-", strlen("src-")) == 0) {
878 Resample = RESAMPLER_SRC;
879 if (strcmp(resampler_id, "src-sinc-best-quality") == 0) {
880 ResampleQuality = SRC_SINC_BEST_QUALITY;
881 return;
882 }
883 if (strcmp(resampler_id, "src-sinc-medium-quality") == 0) {
884 ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
885 return;
886 }
887 if (strcmp(resampler_id, "src-sinc-fastest") == 0) {
888 ResampleQuality = SRC_SINC_FASTEST;
889 return;
890 }
891 if (strcmp(resampler_id, "src-zero-order-hold") == 0) {
892 ResampleQuality = SRC_ZERO_ORDER_HOLD;
893 return;
894 }
895 if (strcmp(resampler_id, "src-linear") == 0) {
896 ResampleQuality = SRC_LINEAR;
897 return;
898 }
899 DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use src-sinc-medium-quality resampler", resampler_id);
900 ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
901 return;
902 }
903#endif
904 DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use trivial resampler", resampler_id);
905 Resample = RESAMPLER_TRIVIAL;
906}
907
908// Returns the most recent ummuted volume level.
909static int VolumeGetUnmutedLevel(void)
910{
911#if defined(HAS_OSS_SUPPORT)
912 // reload volume if we're using OSS
913 if (!VolIsMuted && VolumeControlType == VOLUME_TYPE_OSS)
914 {
915 return volGet();
916 }
917#endif
918
919 return VolPercent;
920}
921
922// Sets the volume level based on the contents of VolPercent and VolIsMuted
923static void VolumeCommit(void)
924{
925 int levelToCommit = VolIsMuted ? 0 : VolPercent;
926
927#if defined(HAS_OSS_SUPPORT)
928 if (VolumeControlType == VOLUME_TYPE_OSS)
929 {
930 //OSS mixer volume
931 volSet(levelToCommit);
932 }
933 else
934#endif
935 {
936 VolSDL = SDL_MIX_MAXVOLUME * levelToCommit / 100;
937 }
938}
939
940EXPORT void CALL VolumeMute(void)
941{
942 if (!l_PluginInit)
943 return;
944
945 // Store the volume level in order to restore it later
946 if (!VolIsMuted)
947 VolPercent = VolumeGetUnmutedLevel();
948
949 // Toogle mute
950 VolIsMuted = !VolIsMuted;
951 VolumeCommit();
952}
953
954EXPORT void CALL VolumeUp(void)
955{
956 if (!l_PluginInit)
957 return;
958
959 VolumeSetLevel(VolumeGetUnmutedLevel() + VolDelta);
960}
961
962EXPORT void CALL VolumeDown(void)
963{
964 if (!l_PluginInit)
965 return;
966
967 VolumeSetLevel(VolumeGetUnmutedLevel() - VolDelta);
968}
969
970EXPORT int CALL VolumeGetLevel(void)
971{
972 return VolIsMuted ? 0 : VolumeGetUnmutedLevel();
973}
974
975EXPORT void CALL VolumeSetLevel(int level)
976{
977 if (!l_PluginInit)
978 return;
979
980 //if muted, unmute first
981 VolIsMuted = 0;
982
983 // adjust volume
984 VolPercent = level;
985 if (VolPercent < 0)
986 VolPercent = 0;
987 else if (VolPercent > 100)
988 VolPercent = 100;
989
990 VolumeCommit();
991}
992
993EXPORT const char * CALL VolumeGetString(void)
994{
995 static char VolumeString[32];
996
997 if (VolIsMuted)
998 {
999 strcpy(VolumeString, "Mute");
1000 }
1001 else
1002 {
1003 sprintf(VolumeString, "%i%%", VolPercent);
1004 }
1005
1006 return VolumeString;
1007}
1008