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 *
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. *
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. *
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 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
30 #include <SDL_audio.h>
33 #include <samplerate.h>
36 #include <speex/speex_resampler.h>
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"
47 #include "osal_dynamiclib.h"
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
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
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
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
74 /* number of bytes per sample */
75 #define N64_SAMPLE_BYTES 4
76 #define SDL_SAMPLE_BYTES 4
78 /* volume mixer types */
79 #define VOLUME_TYPE_SDL 1
80 #define VOLUME_TYPE_OSS 2
83 static void (*l_DebugCallback)(void *, int, const char *) = NULL;
84 static void *l_DebugCallContext = NULL;
85 static int l_PluginInit = 0;
86 static int l_PausedForSync = 1; /* Audio is started in paused state after SDL initialization */
87 static m64p_handle l_ConfigAudio;
99 /* Read header for type definition */
100 static AUDIO_INFO AudioInfo;
101 /* The hardware specifications we are using */
102 static SDL_AudioSpec *hardware_spec;
103 /* Pointer to the primary audio buffer */
104 static unsigned char *primaryBuffer = NULL;
105 static unsigned int primaryBufferBytes = 0;
106 /* Pointer to the mixing buffer for voume control*/
107 static unsigned char *mixBuffer = NULL;
108 /* Position in buffer array where next audio chunk should be placed */
109 static unsigned int buffer_pos = 0;
110 /* Audio frequency, this is usually obtained from the game, but for compatibility we set default value */
111 static int GameFreq = DEFAULT_FREQUENCY;
112 /* timestamp for the last time that our audio callback was called */
113 static unsigned int last_callback_ticks = 0;
114 /* SpeedFactor is used to increase/decrease game playback speed */
115 static unsigned int speed_factor = 100;
116 // If this is true then left and right channels are swapped */
117 static int SwapChannels = 0;
118 // Size of Primary audio buffer in equivalent output samples
119 static unsigned int PrimaryBufferSize = PRIMARY_BUFFER_SIZE;
120 // Fullness level target for Primary audio buffer, in equivalent output samples
121 static unsigned int PrimaryBufferTarget = PRIMARY_BUFFER_TARGET;
122 // Size of Secondary audio buffer in output samples
123 static unsigned int SecondaryBufferSize = SECONDARY_BUFFER_SIZE;
125 static enum resampler_type Resample = RESAMPLER_TRIVIAL;
126 // Resampler specific quality
127 static int ResampleQuality = 3;
128 // volume to scale the audio by, range of 0..100
129 // if muted, this holds the volume when not muted
130 static int VolPercent = 80;
131 // how much percent to increment/decrement volume by
132 static int VolDelta = 5;
133 // the actual volume passed into SDL, range of 0..SDL_MIX_MAXVOLUME
134 static int VolSDL = SDL_MIX_MAXVOLUME;
136 static int VolIsMuted = 0;
137 //which type of volume control to use
138 static int VolumeControlType = VOLUME_TYPE_SDL;
140 static int OutputFreq;
142 // Prototype of local functions
143 static void my_audio_callback(void *userdata, unsigned char *stream, int len);
144 static void InitializeAudio(int freq);
145 static void ReadConfig(void);
146 static void InitializeSDL(void);
148 static int critical_failure = 0;
150 /* definitions of pointers to Core config functions */
151 ptr_ConfigOpenSection ConfigOpenSection = NULL;
152 ptr_ConfigDeleteSection ConfigDeleteSection = NULL;
153 ptr_ConfigSaveSection ConfigSaveSection = NULL;
154 ptr_ConfigSetParameter ConfigSetParameter = NULL;
155 ptr_ConfigGetParameter ConfigGetParameter = NULL;
156 ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL;
157 ptr_ConfigSetDefaultInt ConfigSetDefaultInt = NULL;
158 ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat = NULL;
159 ptr_ConfigSetDefaultBool ConfigSetDefaultBool = NULL;
160 ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL;
161 ptr_ConfigGetParamInt ConfigGetParamInt = NULL;
162 ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL;
163 ptr_ConfigGetParamBool ConfigGetParamBool = NULL;
164 ptr_ConfigGetParamString ConfigGetParamString = NULL;
166 /* Global functions */
167 static void DebugMessage(int level, const char *message, ...)
172 if (l_DebugCallback == NULL)
175 va_start(args, message);
176 vsprintf(msgbuf, message, args);
178 (*l_DebugCallback)(l_DebugCallContext, level, msgbuf);
183 /* Mupen64Plus plugin functions */
184 EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context,
185 void (*DebugCallback)(void *, int, const char *))
187 ptr_CoreGetAPIVersions CoreAPIVersionFunc;
189 int ConfigAPIVersion, DebugAPIVersion, VidextAPIVersion, bSaveConfig;
190 float fConfigParamsVersion = 0.0f;
193 return M64ERR_ALREADY_INIT;
195 /* first thing is to set the callback function for debug info */
196 l_DebugCallback = DebugCallback;
197 l_DebugCallContext = Context;
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)
203 DebugMessage(M64MSG_ERROR, "Core emulator broken; no CoreAPIVersionFunc() function found.");
204 return M64ERR_INCOMPATIBLE;
207 (*CoreAPIVersionFunc)(&ConfigAPIVersion, &DebugAPIVersion, &VidextAPIVersion, NULL);
208 if ((ConfigAPIVersion & 0xffff0000) != (CONFIG_API_VERSION & 0xffff0000))
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;
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");
230 if (!ConfigOpenSection || !ConfigDeleteSection || !ConfigSetParameter || !ConfigGetParameter ||
231 !ConfigSetDefaultInt || !ConfigSetDefaultFloat || !ConfigSetDefaultBool || !ConfigSetDefaultString ||
232 !ConfigGetParamInt || !ConfigGetParamFloat || !ConfigGetParamBool || !ConfigGetParamString)
233 return M64ERR_INCOMPATIBLE;
235 /* ConfigSaveSection was added in Config API v2.1.0 */
236 if (ConfigAPIVersion >= 0x020100 && !ConfigSaveSection)
237 return M64ERR_INCOMPATIBLE;
239 /* get a configuration section handle */
240 if (ConfigOpenSection("Audio-SDL", &l_ConfigAudio) != M64ERR_SUCCESS)
242 DebugMessage(M64MSG_ERROR, "Couldn't open config section 'Audio-SDL'");
243 return M64ERR_INPUT_NOT_FOUND;
246 /* check the section version number */
248 if (ConfigGetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fConfigParamsVersion, sizeof(float)) != M64ERR_SUCCESS)
250 DebugMessage(M64MSG_WARNING, "No version number in 'Audio-SDL' config section. Setting defaults.");
251 ConfigDeleteSection("Audio-SDL");
252 ConfigOpenSection("Audio-SDL", &l_ConfigAudio);
255 else if (((int) fConfigParamsVersion) != ((int) CONFIG_PARAM_VERSION))
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);
262 else if ((CONFIG_PARAM_VERSION - fConfigParamsVersion) >= 0.0001f)
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);
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");
283 if (bSaveConfig && ConfigAPIVersion >= 0x020100)
284 ConfigSaveSection("Audio-SDL");
287 return M64ERR_SUCCESS;
290 EXPORT m64p_error CALL PluginShutdown(void)
293 return M64ERR_NOT_INIT;
295 /* reset some local variables */
296 l_DebugCallback = NULL;
297 l_DebugCallContext = NULL;
299 /* make sure our buffer is freed */
300 if (mixBuffer != NULL)
307 return M64ERR_SUCCESS;
310 EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
312 /* set version info */
313 if (PluginType != NULL)
314 *PluginType = M64PLUGIN_AUDIO;
316 if (PluginVersion != NULL)
317 *PluginVersion = SDL_AUDIO_PLUGIN_VERSION;
319 if (APIVersion != NULL)
320 *APIVersion = AUDIO_PLUGIN_API_VERSION;
322 if (PluginNamePtr != NULL)
323 *PluginNamePtr = "Mupen64Plus SDL Audio Plugin";
325 if (Capabilities != NULL)
330 return M64ERR_SUCCESS;
333 /* ----------- Audio Functions ------------- */
334 EXPORT void CALL AiDacrateChanged( int SystemType )
344 f = 48681812 / (*AudioInfo.AI_DACRATE_REG + 1);
347 f = 49656530 / (*AudioInfo.AI_DACRATE_REG + 1);
350 f = 48628316 / (*AudioInfo.AI_DACRATE_REG + 1);
357 EXPORT void CALL AiLenChanged( void )
361 unsigned int CurrLevel, CurrTime, ExpectedLevel, ExpectedTime;
363 if (critical_failure == 1)
368 LenReg = *AudioInfo.AI_LEN_REG;
369 p = AudioInfo.RDRAM + (*AudioInfo.AI_DRAM_ADDR_REG & 0xFFFFFF);
371 if (buffer_pos + LenReg < primaryBufferBytes)
376 for ( i = 0 ; i < LenReg ; i += 4 )
379 if(SwapChannels == 0)
382 primaryBuffer[ buffer_pos + i ] = p[ i + 2 ];
383 primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 3 ];
386 primaryBuffer[ buffer_pos + i + 2 ] = p[ i ];
387 primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 1 ];
390 primaryBuffer[ buffer_pos + i ] = p[ i ];
391 primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 1 ];
394 primaryBuffer[ buffer_pos + i + 2 ] = p[ i + 2];
395 primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 3 ];
403 DebugMessage(M64MSG_WARNING, "AiLenChanged(): Audio buffer overflow.");
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)
422 unsigned int WaitTime = (ExpectedLevel - PrimaryBufferTarget) * 1000 / OutputFreq;
423 DebugMessage(M64MSG_VERBOSE, " AiLenChanged(): Waiting %ims", WaitTime);
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)
433 DebugMessage(M64MSG_VERBOSE, " AiLenChanged(): Possible underflow at next audio callback; pausing playback");
434 if (!l_PausedForSync)
438 /* otherwise the predicted buffer level is within our tolerance, so everything is okay */
447 EXPORT int CALL InitiateAudio( AUDIO_INFO Audio_Info )
452 AudioInfo = Audio_Info;
456 static int underrun_count = 0;
459 static float *_src = NULL;
460 static unsigned int _src_len = 0;
461 static float *_dest = NULL;
462 static unsigned int _dest_len = 0;
464 static SRC_STATE *src_state;
465 static SRC_DATA src_data;
468 SpeexResamplerState* spx_state = NULL;
472 static int resample(unsigned char *input, int input_avail, int oldsamplerate, unsigned char *output, int output_needed, int newsamplerate)
474 int *psrc = (int*)input;
475 int *pdest = (int*)output;
479 spx_uint32_t in_len, out_len;
480 if(Resample == RESAMPLER_SPEEX)
482 if(spx_state == NULL)
484 spx_state = speex_resampler_init(2, oldsamplerate, newsamplerate, ResampleQuality, &error);
485 if(spx_state == NULL)
487 memset(output, 0, output_needed);
491 speex_resampler_set_rate(spx_state, oldsamplerate, newsamplerate);
492 in_len = input_avail / 4;
493 out_len = output_needed / 4;
495 if ((error = speex_resampler_process_interleaved_int(spx_state, (const spx_int16_t *)input, &in_len, (spx_int16_t *)output, &out_len)))
497 memset(output, 0, output_needed);
498 return input_avail; // number of bytes consumed
504 if(Resample == RESAMPLER_SRC)
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)
512 _src_len = input_avail*2;
513 _src = malloc(_src_len);
515 if (_dest_len < output_needed*2 && output_needed > 0)
517 if(_dest) free(_dest);
518 _dest_len = output_needed*2;
519 _dest = malloc(_dest_len);
521 memset(_src,0,_src_len);
522 memset(_dest,0,_dest_len);
523 if(src_state == NULL)
525 src_state = src_new (ResampleQuality, 2, &error);
526 if(src_state == NULL)
528 memset(output, 0, output_needed);
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)))
541 memset(output, 0, output_needed);
542 return input_avail; // number of bytes consumed
544 src_float_to_short_array (_dest, (short *) output, output_needed/2);
545 return src_data.input_frames_used * 4;
548 // RESAMPLE == TRIVIAL
549 if (newsamplerate >= oldsamplerate)
551 int sldf = oldsamplerate;
553 int dldf = newsamplerate;
554 int const1 = const2 - 2*dldf;
555 int criteria = const2 - dldf;
556 for (i = 0; i < output_needed/4; i++)
564 else criteria += const2;
566 return j * 4; //number of bytes consumed
568 // newsamplerate < oldsamplerate, this only happens when speed_factor > 1
569 for (i = 0; i < output_needed/4; i++)
571 j = i * oldsamplerate / newsamplerate;
574 return j * 4; //number of bytes consumed
577 static void my_audio_callback(void *userdata, unsigned char *stream, int len)
579 int oldsamplerate, newsamplerate;
584 /* mark the time, for synchronization on the input side */
585 last_callback_ticks = SDL_GetTicks();
587 newsamplerate = OutputFreq * 100 / speed_factor;
588 oldsamplerate = GameFreq;
590 if (buffer_pos > (unsigned int) (len * oldsamplerate) / newsamplerate)
593 #if defined(HAS_OSS_SUPPORT)
594 if (VolumeControlType == VOLUME_TYPE_OSS)
596 input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, stream, len, newsamplerate);
601 input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, mixBuffer, len, newsamplerate);
602 memset(stream, 0, len);
603 SDL_MixAudio(stream, mixBuffer, len, VolSDL);
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);
612 unsigned int SamplesNeeded = (len * oldsamplerate) / (newsamplerate * SDL_SAMPLE_BYTES);
613 unsigned int SamplesPresent = buffer_pos / N64_SAMPLE_BYTES;
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);
620 EXPORT int CALL RomOpen(void)
626 InitializeAudio(GameFreq);
630 static void InitializeSDL(void)
632 DebugMessage(M64MSG_INFO, "Initializing SDL audio subsystem...");
634 if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0)
636 DebugMessage(M64MSG_ERROR, "Failed to initialize SDL audio subsystem; forcing exit.\n");
637 critical_failure = 1;
640 critical_failure = 0;
644 static void CreatePrimaryBuffer(void)
646 unsigned int newPrimaryBytes = (unsigned int) ((long long) PrimaryBufferSize * GameFreq * speed_factor /
647 (OutputFreq * 100)) * N64_SAMPLE_BYTES;
648 if (primaryBuffer == NULL)
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;
655 else if (newPrimaryBytes > primaryBufferBytes) /* primary buffer only grows; there's no point in shrinking it */
657 unsigned char *newPrimaryBuffer = (unsigned char*) malloc(newPrimaryBytes);
658 unsigned char *oldPrimaryBuffer = primaryBuffer;
660 memcpy(newPrimaryBuffer, oldPrimaryBuffer, primaryBufferBytes);
661 memset(newPrimaryBuffer + primaryBufferBytes, 0, newPrimaryBytes - primaryBufferBytes);
662 primaryBuffer = newPrimaryBuffer;
663 primaryBufferBytes = newPrimaryBytes;
665 free(oldPrimaryBuffer);
669 static void InitializeAudio(int freq)
671 SDL_AudioSpec *desired, *obtained;
673 if(SDL_WasInit(SDL_INIT_AUDIO|SDL_INIT_TIMER) == (SDL_INIT_AUDIO|SDL_INIT_TIMER) )
675 DebugMessage(M64MSG_VERBOSE, "InitializeAudio(): SDL Audio sub-system already initialized.");
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);
687 if (critical_failure == 1)
689 GameFreq = freq; // This is important for the sync
690 if(hardware_spec != NULL) free(hardware_spec);
692 // Allocate space for SDL_AudioSpec
693 desired = malloc(sizeof(SDL_AudioSpec));
694 obtained = malloc(sizeof(SDL_AudioSpec));
696 if(freq < 11025) OutputFreq = 11025;
697 else if(freq < 22050) OutputFreq = 22050;
698 else OutputFreq = 44100;
700 desired->freq = OutputFreq;
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);
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;
717 /* Open the audio device */
719 if (SDL_OpenAudio(desired, obtained) < 0)
721 DebugMessage(M64MSG_ERROR, "Couldn't open audio: %s", SDL_GetError());
722 critical_failure = 1;
725 if (desired->format != obtained->format)
727 DebugMessage(M64MSG_WARNING, "Obtained audio format differs from requested.");
729 if (desired->freq != obtained->freq)
731 DebugMessage(M64MSG_WARNING, "Obtained frequency differs from requested.");
734 /* desired spec is no longer needed */
736 hardware_spec=obtained;
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)
750 mixBuffer = (unsigned char*) malloc(SecondaryBufferSize * SDL_SAMPLE_BYTES);
752 /* preset the last callback time */
753 if (last_callback_ticks == 0)
754 last_callback_ticks = SDL_GetTicks();
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);
763 /* set playback volume */
764 #if defined(HAS_OSS_SUPPORT)
765 if (VolumeControlType == VOLUME_TYPE_OSS)
767 VolPercent = volGet();
772 VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
776 EXPORT void CALL RomClosed( void )
780 if (critical_failure == 1)
782 DebugMessage(M64MSG_VERBOSE, "Cleaning up SDL sound plugin...");
784 // Shut down SDL Audio output
788 // Delete the buffer, as we are done producing sound
789 if (primaryBuffer != NULL)
791 primaryBufferBytes = 0;
793 primaryBuffer = NULL;
795 if (mixBuffer != NULL)
801 // Delete the hardware spec struct
802 if(hardware_spec != NULL) free(hardware_spec);
803 hardware_spec = NULL;
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);
810 EXPORT void CALL ProcessAList(void)
814 EXPORT void CALL SetSpeedFactor(int percentage)
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();
824 static void ReadConfig(void)
826 const char *resampler_id;
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");
840 Resample = RESAMPLER_TRIVIAL;
841 DebugMessage(M64MSG_WARNING, "Could not find RESAMPLE configuration; use trivial resampler");
844 if (strcmp(resampler_id, "trivial") == 0) {
845 Resample = RESAMPLER_TRIVIAL;
849 if (strncmp(resampler_id, "speex-fixed-", strlen("speex-fixed-")) == 0) {
851 static const char *speex_quality[] = {
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) {
871 DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use speex-fixed-4 resampler", resampler_id);
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;
883 if (strcmp(resampler_id, "src-sinc-medium-quality") == 0) {
884 ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
887 if (strcmp(resampler_id, "src-sinc-fastest") == 0) {
888 ResampleQuality = SRC_SINC_FASTEST;
891 if (strcmp(resampler_id, "src-zero-order-hold") == 0) {
892 ResampleQuality = SRC_ZERO_ORDER_HOLD;
895 if (strcmp(resampler_id, "src-linear") == 0) {
896 ResampleQuality = SRC_LINEAR;
899 DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use src-sinc-medium-quality resampler", resampler_id);
900 ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
904 DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use trivial resampler", resampler_id);
905 Resample = RESAMPLER_TRIVIAL;
908 // Returns the most recent ummuted volume level.
909 static int VolumeGetUnmutedLevel(void)
911 #if defined(HAS_OSS_SUPPORT)
912 // reload volume if we're using OSS
913 if (!VolIsMuted && VolumeControlType == VOLUME_TYPE_OSS)
922 // Sets the volume level based on the contents of VolPercent and VolIsMuted
923 static void VolumeCommit(void)
925 int levelToCommit = VolIsMuted ? 0 : VolPercent;
927 #if defined(HAS_OSS_SUPPORT)
928 if (VolumeControlType == VOLUME_TYPE_OSS)
931 volSet(levelToCommit);
936 VolSDL = SDL_MIX_MAXVOLUME * levelToCommit / 100;
940 EXPORT void CALL VolumeMute(void)
945 // Store the volume level in order to restore it later
947 VolPercent = VolumeGetUnmutedLevel();
950 VolIsMuted = !VolIsMuted;
954 EXPORT void CALL VolumeUp(void)
959 VolumeSetLevel(VolumeGetUnmutedLevel() + VolDelta);
962 EXPORT void CALL VolumeDown(void)
967 VolumeSetLevel(VolumeGetUnmutedLevel() - VolDelta);
970 EXPORT int CALL VolumeGetLevel(void)
972 return VolIsMuted ? 0 : VolumeGetUnmutedLevel();
975 EXPORT void CALL VolumeSetLevel(int level)
980 //if muted, unmute first
987 else if (VolPercent > 100)
993 EXPORT const char * CALL VolumeGetString(void)
995 static char VolumeString[32];
999 strcpy(VolumeString, "Mute");
1003 sprintf(VolumeString, "%i%%", VolPercent);
1006 return VolumeString;