mirror of
https://github.com/mupen64plus/mupen64plus-oldsvn.git
synced 2025-04-02 10:52:35 -04:00
983 lines
32 KiB
C
983 lines
32 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* Mupen64plus - main.c *
|
|
* Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
|
|
* Copyright (C) 2007-2008 Richard42 Ebenblues *
|
|
* Copyright (C) 2003 JttL *
|
|
* Copyright (C) 2002 Hactarux *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_audio.h>
|
|
#include <SDL_thread.h>
|
|
|
|
#ifdef USE_SRC
|
|
#include <samplerate.h>
|
|
#endif
|
|
|
|
#include "../main/winlnxdefs.h"
|
|
|
|
#include "Audio_1.2.h"
|
|
#include "gui.h"
|
|
#include "volume.h"
|
|
#include "main.h"
|
|
|
|
#include "../main/version.h"
|
|
#include "../main/translate.h"
|
|
|
|
/* Size of primary buffer in bytes. This is the buffer where audio is loaded
|
|
after it's extracted from n64's memory. */
|
|
#define PRIMARY_BUFFER_SIZE 65536
|
|
|
|
/* If buffer load goes under LOW_BUFFER_LOAD_LEVEL then game is speeded up to
|
|
fill the buffer. If buffer load exeeds HIGH_BUFFER_LOAD_LEVEL then some
|
|
extra slowdown is added to prevent buffer overflow (which is not supposed
|
|
to happen in any circumstanses if syncronization is working but because
|
|
computer's clock is such inaccurate (10ms) that might happen. I'm planning
|
|
to add support for Real Time Clock for greater accuracy but we will see.
|
|
|
|
The plugin tries to keep the buffer's load always between these values.
|
|
So if you change only PRIMARY_BUFFER_SIZE, nothing changes. You have to
|
|
adjust these values instead. You propably want to play with
|
|
LOW_BUFFER_LOAD_LEVEL if you get dropouts. */
|
|
#define LOW_BUFFER_LOAD_LEVEL 16384
|
|
#define HIGH_BUFFER_LOAD_LEVEL 32768
|
|
|
|
/* Size of secondary buffer. This is actually SDL's hardware buffer. This is
|
|
amount of samples, so final bufffer size is four times this. */
|
|
#define SECONDARY_BUFFER_SIZE 4096
|
|
|
|
/* This sets default frequency what is used if rom doesn't want to change it.
|
|
Popably only game that needs this is Zelda: Ocarina Of Time Master Quest
|
|
*NOTICE* We should try to find out why Demos' frequencies are always wrong
|
|
They tend to rely on a default frequency, apparently, never the same one ;)*/
|
|
#define DEFAULT_FREQUENCY 33600
|
|
|
|
/* Name of config file */
|
|
#define CONFIG_FILE "jttl_audio.conf"
|
|
|
|
/* volume mixer types */
|
|
#define VOLUME_TYPE_SDL 1
|
|
#define VOLUME_TYPE_OSS 2
|
|
|
|
/*--------------- VARIABLE DEFINITIONS ----------------*/
|
|
|
|
/* Read header for type definition */
|
|
static AUDIO_INFO AudioInfo;
|
|
/* The hardware specifications we are using */
|
|
static SDL_AudioSpec *hardware_spec;
|
|
/* Pointer to the primary audio buffer */
|
|
static Uint8 *buffer = NULL;
|
|
/* Pointer to the mixing buffer for voume control*/
|
|
static Uint8 *mixBuffer = NULL;
|
|
/* Position in buffer array where next audio chunk should be placed */
|
|
static unsigned int buffer_pos = 0;
|
|
/* Audio frequency, this is usually obtained from the game, but for compatibility we set default value */
|
|
int GameFreq = DEFAULT_FREQUENCY;
|
|
/* This is for syncronization, it's ticks saved just before AiLenChanged() returns. */
|
|
static Uint32 last_ticks = 0;
|
|
/* SpeedFactor is used to increase/decrease game playback speed */
|
|
static Uint32 speed_factor = 100;
|
|
// AI_LEN_REG at previous round */
|
|
static DWORD prev_len_reg = 0;
|
|
// If this is true then left and right channels are swapped */
|
|
BOOL SwapChannels = FALSE;
|
|
// Size of Primary audio buffer
|
|
Uint32 PrimaryBufferSize = PRIMARY_BUFFER_SIZE;
|
|
// Size of Secondary audio buffer
|
|
Uint32 SecondaryBufferSize = SECONDARY_BUFFER_SIZE;
|
|
// Lowest buffer load before we need to speed things up
|
|
Uint32 LowBufferLoadLevel = LOW_BUFFER_LOAD_LEVEL;
|
|
// Highest buffer load before we need to slow things down
|
|
Uint32 HighBufferLoadLevel = HIGH_BUFFER_LOAD_LEVEL;
|
|
// Resample or not
|
|
Uint8 Resample = 1;
|
|
// volume to scale the audio by, range of 0..100
|
|
int VolPercent = 80;
|
|
// how much percent to increment/decrement volume by
|
|
int VolDelta = 5;
|
|
// the actual volume passed into SDL, range of 0..SDL_MIX_MAXVOLUME
|
|
static int VolSDL = SDL_MIX_MAXVOLUME;
|
|
// stores the previous volume when it is muted
|
|
static int VolMutedSave = -1;
|
|
//which type of volume control to use
|
|
int VolumeControlType = VOLUME_TYPE_OSS;
|
|
|
|
static int OutputFreq;
|
|
static char configdir[PATH_MAX] = {0};
|
|
|
|
/* ----------- FUNCTIONS ------------- */
|
|
/* This function closes the audio device and reinitializes it with requested frequency */
|
|
void InitializeAudio(int freq);
|
|
void ReadConfig();
|
|
void InitializeSDL();
|
|
|
|
int critical_failure = 0;
|
|
|
|
EXPORT void CALL AiDacrateChanged( int SystemType )
|
|
{
|
|
int f = GameFreq;
|
|
switch (SystemType)
|
|
{
|
|
case SYSTEM_NTSC:
|
|
f = 48681812 / (*AudioInfo.AI_DACRATE_REG + 1);
|
|
break;
|
|
case SYSTEM_PAL:
|
|
f = 49656530 / (*AudioInfo.AI_DACRATE_REG + 1);
|
|
break;
|
|
case SYSTEM_MPAL:
|
|
f = 48628316 / (*AudioInfo.AI_DACRATE_REG + 1);
|
|
break;
|
|
}
|
|
InitializeAudio(f);
|
|
}
|
|
|
|
|
|
EXPORT void CALL AiLenChanged( void )
|
|
{
|
|
if (critical_failure == 1)
|
|
return;
|
|
DWORD LenReg = *AudioInfo.AI_LEN_REG;
|
|
Uint8 *p = (Uint8*)(AudioInfo.RDRAM + (*AudioInfo.AI_DRAM_ADDR_REG & 0xFFFFFF));
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: New audio chunk, %i bytes\n", LenReg);
|
|
#endif
|
|
if(buffer_pos + LenReg < PrimaryBufferSize)
|
|
{
|
|
register unsigned int i;
|
|
|
|
SDL_LockAudio();
|
|
for ( i = 0 ; i < LenReg ; i += 4 )
|
|
{
|
|
|
|
if(SwapChannels == FALSE)
|
|
{
|
|
// Left channel
|
|
buffer[ buffer_pos + i ] = p[ i + 2 ];
|
|
buffer[ buffer_pos + i + 1 ] = p[ i + 3 ];
|
|
|
|
// Right channel
|
|
buffer[ buffer_pos + i + 2 ] = p[ i ];
|
|
buffer[ buffer_pos + i + 3 ] = p[ i + 1 ];
|
|
} else {
|
|
// Left channel
|
|
buffer[ buffer_pos + i ] = p[ i ];
|
|
buffer[ buffer_pos + i + 1 ] = p[ i + 1 ];
|
|
|
|
// Right channel
|
|
buffer[ buffer_pos + i + 2 ] = p[ i + 2];
|
|
buffer[ buffer_pos + i + 3 ] = p[ i + 3 ];
|
|
}
|
|
}
|
|
buffer_pos += i;
|
|
SDL_UnlockAudio();
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Debug: Audio buffer overflow.\n");
|
|
}
|
|
#endif
|
|
|
|
// Time that should be sleeped to keep game in sync.
|
|
int wait_time = 0;
|
|
|
|
// And then syncronization */
|
|
|
|
/* If buffer is running slow we speed up the game a bit. Actually we skip the syncronization. */
|
|
if (buffer_pos < LowBufferLoadLevel)
|
|
{
|
|
wait_time = -1;
|
|
if(buffer_pos < SecondaryBufferSize*4)
|
|
SDL_PauseAudio(1);
|
|
}
|
|
else
|
|
SDL_PauseAudio(0);
|
|
|
|
if (wait_time != -1)
|
|
{
|
|
/* Adjust the game frequency by the playback speed factor for the purposes of timing */
|
|
int InputFreq = GameFreq * speed_factor / 100;
|
|
|
|
/* If for some reason game is runnin extremely fast and there is risk buffer is going to
|
|
overflow, we slow down the game a bit to keep sound smooth. The overspeed is caused
|
|
by inaccuracy in machines clock. */
|
|
if (buffer_pos > HighBufferLoadLevel)
|
|
{
|
|
int overflow = (buffer_pos - HIGH_BUFFER_LOAD_LEVEL) / 4; /* in samples */
|
|
wait_time += overflow * 1000 / InputFreq; /* in milliseconds */
|
|
}
|
|
|
|
/* calculate how many milliseconds should have elapsed since the last audio chunk was added */
|
|
int prev_samples = prev_len_reg / 4;
|
|
int expected_ticks = prev_samples * 1000 / InputFreq; /* in milliseconds */
|
|
|
|
/* now determine if we are ahead of schedule, and if so, wait */
|
|
int cur_ticks = SDL_GetTicks();
|
|
if (last_ticks + expected_ticks > cur_ticks)
|
|
{
|
|
wait_time += (last_ticks + expected_ticks) - cur_ticks;
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: wait_time: %i, Buffer: %i/%i\n", wait_time, buffer_pos, PrimaryBufferSize);
|
|
#endif
|
|
SDL_Delay(wait_time);
|
|
}
|
|
}
|
|
|
|
last_ticks = SDL_GetTicks();
|
|
prev_len_reg = LenReg;
|
|
}
|
|
|
|
EXPORT DWORD CALL AiReadLength( void )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
EXPORT void CALL CloseDLL( void )
|
|
{
|
|
if (mixBuffer != NULL)
|
|
{
|
|
free(mixBuffer);
|
|
mixBuffer = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT void CALL DllTest ( HWND hParent )
|
|
{
|
|
// Defining flags for tests
|
|
BOOL init_audio = FALSE;
|
|
BOOL init_timer = FALSE;
|
|
BOOL open_audio_device = FALSE;
|
|
BOOL format_match = FALSE;
|
|
BOOL freq_match = FALSE;
|
|
|
|
// Storage for SDL_Errors.
|
|
char *sdl_error[3];
|
|
|
|
// Clear the pointers (Should not be truly necessary unless something horrible goes wrong)
|
|
memset(sdl_error, 0, sizeof(char*[3]));
|
|
|
|
// Print out inital message
|
|
printf("[JttL's SDL Audio plugin] Starting Audio Test.\n");
|
|
|
|
// Make Sure SDL Audio is disabled so we can restart fresh
|
|
SDL_PauseAudio(1);
|
|
SDL_CloseAudio();
|
|
|
|
// Quit the subsystems before attempting to reinitalize them, if either are initalized already
|
|
if(SDL_WasInit(SDL_INIT_AUDIO) != 0) SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
if(SDL_WasInit(SDL_INIT_TIMER) != 0) SDL_QuitSubSystem(SDL_INIT_TIMER);
|
|
|
|
// Attempt to initialize SDL Audio
|
|
if(SDL_Init(SDL_INIT_AUDIO) < 0 )
|
|
{
|
|
sdl_error[0] = SDL_GetError();
|
|
printf("[JttL's SDL Audio plugin] Error: Couldn't initialize audio subsystem: %s\n", sdl_error[0]);
|
|
init_audio = FALSE;
|
|
}
|
|
else
|
|
{
|
|
printf("[JttL's SDL Audio plugin] Audio subsystem initialized.\n");
|
|
init_audio = TRUE;
|
|
}
|
|
|
|
// Attempt to initialize SDL Timer
|
|
if(SDL_InitSubSystem(SDL_INIT_TIMER) < 0 )
|
|
{
|
|
sdl_error[1] = SDL_GetError();
|
|
printf("[JttL's SDL Audio plugin] Error: Couldn't initialize timer subsystem: %s\n", sdl_error[1]);
|
|
init_timer = FALSE;
|
|
}
|
|
else
|
|
{
|
|
printf("[JttL's SDL Audio plugin] Timer subsystem initialized.\n");
|
|
init_timer = TRUE;
|
|
}
|
|
|
|
// Close the audio device
|
|
SDL_PauseAudio(1);
|
|
SDL_CloseAudio();
|
|
|
|
// Prototype of our callback function
|
|
void my_audio_callback(void *userdata, Uint8 *stream, int len);
|
|
|
|
// Open the audio device
|
|
SDL_AudioSpec *desired, *obtained;
|
|
|
|
// Allocate a desired SDL_AudioSpec
|
|
desired = malloc(sizeof(SDL_AudioSpec));
|
|
|
|
// Allocate space for the obtained SDL_AudioSpec
|
|
obtained = malloc(sizeof(SDL_AudioSpec));
|
|
|
|
// 22050Hz - FM Radio quality
|
|
desired->freq=GameFreq;
|
|
|
|
// Print out message for frequency
|
|
printf("[JttL's SDL Audio plugin] Requesting frequency: %iHz.\n", desired->freq);
|
|
|
|
// 16-bit signed audio
|
|
desired->format=AUDIO_S16SYS;
|
|
|
|
// Print out message for format
|
|
printf("[JttL's SDL Audio plugin] Requesting format: %i.\n", desired->format);
|
|
|
|
// Enable two hardware channels (for Stereo output)
|
|
desired->channels=2;
|
|
|
|
// Large audio buffer reduces risk of dropouts but increases response time
|
|
desired->samples=SecondaryBufferSize;
|
|
|
|
// Our callback function
|
|
desired->callback=my_audio_callback;
|
|
desired->userdata=NULL;
|
|
|
|
// Open the audio device
|
|
if ( SDL_OpenAudio(desired, obtained) < 0 )
|
|
{
|
|
sdl_error[2] = SDL_GetError();
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Couldn't open audio device: %s\n", sdl_error[2]);
|
|
open_audio_device = FALSE;
|
|
}
|
|
else
|
|
{
|
|
open_audio_device = TRUE;
|
|
}
|
|
|
|
// Check to see if we have the audio format we requested.
|
|
if(desired->format != obtained->format)
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Obtained audio format differs from requested.\n");
|
|
format_match = FALSE;
|
|
}
|
|
else
|
|
{
|
|
format_match = TRUE;
|
|
}
|
|
|
|
// Check to see if we have the frequency we requested.
|
|
if(desired->freq != obtained->freq)
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Obtained frequency differs from requested.\n");
|
|
freq_match = FALSE;
|
|
}
|
|
else
|
|
{
|
|
freq_match = TRUE;
|
|
}
|
|
|
|
// Free no longer needed objects used for testing the specifications.
|
|
free(desired);
|
|
free(obtained);
|
|
|
|
// Uninitialize SDL audio, as it is no longer needed.
|
|
SDL_PauseAudio(1);
|
|
SDL_CloseAudio();
|
|
|
|
// Quit the Audio and Timer subsystems if they are enabled. (They should be, unless something went horribly wrong.)
|
|
if(SDL_WasInit(SDL_INIT_AUDIO) != 0) SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
if(SDL_WasInit(SDL_INIT_TIMER) != 0) SDL_QuitSubSystem(SDL_INIT_TIMER);
|
|
|
|
char tMsg[1024];
|
|
|
|
if((init_audio == TRUE) && ( init_timer == TRUE ) && ( open_audio_device == TRUE ) && (format_match == TRUE) && (freq_match == TRUE))
|
|
{
|
|
sprintf(tMsg,"[JttL's SDL Audio plugin] Audio test successful.");
|
|
critical_failure = 0;
|
|
}
|
|
else
|
|
{
|
|
sprintf(tMsg,"[JttL's SDL Audio plugin] Test Results\n--\n");
|
|
if(init_audio != TRUE)
|
|
{
|
|
sprintf(tMsg, "%sError initalizing SDL Audio:\n - %s\n", tMsg,sdl_error[0]);
|
|
}
|
|
if(init_timer != TRUE)
|
|
{
|
|
sprintf(tMsg, "%sError initalizing SDL Timer:\n - %s\n", tMsg,sdl_error[1]);
|
|
}
|
|
if(open_audio_device != TRUE)
|
|
{
|
|
sprintf(tMsg, "%sError opening audio device:\n - %s\n", tMsg,sdl_error[2]);
|
|
}
|
|
if(format_match != TRUE)
|
|
{
|
|
sprintf(tMsg, "%sUnable to get the requested output audio format.\n", tMsg);
|
|
}
|
|
if(freq_match != TRUE)
|
|
{
|
|
sprintf(tMsg, "%sUnable to get the requested output frequency.\n", tMsg);
|
|
}
|
|
critical_failure = 1;
|
|
}
|
|
|
|
display_test(tMsg);
|
|
}
|
|
|
|
EXPORT void CALL GetDllInfo( PLUGIN_INFO * PluginInfo )
|
|
{
|
|
PluginInfo->Version = 0x0101;
|
|
PluginInfo->Type = PLUGIN_TYPE_AUDIO;
|
|
sprintf(PluginInfo->Name,"JttL's SDL Audio %s", PLUGIN_VERSION);
|
|
PluginInfo->NormalMemory = TRUE;
|
|
PluginInfo->MemoryBswaped = TRUE;
|
|
}
|
|
|
|
EXPORT BOOL CALL InitiateAudio( AUDIO_INFO Audio_Info )
|
|
{
|
|
AudioInfo = Audio_Info;
|
|
return TRUE;
|
|
}
|
|
#ifdef DEBUG
|
|
static int underrun_count = 0;
|
|
#endif
|
|
|
|
#ifdef USE_SRC
|
|
float *_src = 0;
|
|
unsigned int _src_len = 0;
|
|
float *_dest = 0;
|
|
unsigned int _dest_len = 0;
|
|
int error;
|
|
SRC_STATE *src_state;
|
|
SRC_DATA src_data;
|
|
#endif
|
|
|
|
static int resample(Uint8 *input, int input_avail, int oldsamplerate, Uint8 *output, int output_needed, int newsamplerate)
|
|
{
|
|
|
|
#ifdef USE_SRC
|
|
if(Resample == 2)
|
|
{
|
|
// the high quality resampler needs more input than the samplerate ratio would indicate to work properly
|
|
if (input_avail > output_needed * 3 / 2)
|
|
input_avail = output_needed * 3 / 2; // just to avoid too much short-float-short conversion time
|
|
if (_src_len < input_avail*2 && input_avail > 0)
|
|
{
|
|
if(_src) free(_src);
|
|
_src_len = input_avail*2;
|
|
_src = malloc(_src_len);
|
|
}
|
|
if (_dest_len < output_needed*2 && output_needed > 0)
|
|
{
|
|
if(_dest) free(_dest);
|
|
_dest_len = output_needed*2;
|
|
_dest = malloc(_dest_len);
|
|
}
|
|
memset(_src,0,_src_len);
|
|
memset(_dest,0,_dest_len);
|
|
if(src_state == NULL)
|
|
{
|
|
src_state = src_new (SRC_SINC_BEST_QUALITY, 2, &error);
|
|
if(src_state == NULL)
|
|
{
|
|
memset(output, 0, output_needed);
|
|
return;
|
|
}
|
|
}
|
|
src_short_to_float_array ((short *) input, _src, input_avail/2);
|
|
src_data.end_of_input = 0;
|
|
src_data.data_in = _src;
|
|
src_data.input_frames = input_avail/4;
|
|
src_data.src_ratio = (float) newsamplerate / oldsamplerate;
|
|
src_data.data_out = _dest;
|
|
src_data.output_frames = output_needed/4;
|
|
if ((error = src_process (src_state, &src_data)))
|
|
{
|
|
memset(output, 0, output_needed);
|
|
return input_avail; // number of bytes consumed
|
|
}
|
|
src_float_to_short_array (_dest, (short *) output, output_needed/2);
|
|
return src_data.input_frames_used * 4;
|
|
}
|
|
#endif
|
|
// RESAMPLE == 1
|
|
int *psrc = (int*)input;
|
|
int *pdest = (int*)output;
|
|
int i;
|
|
int j=0;
|
|
int sldf = oldsamplerate;
|
|
int const2 = 2*sldf;
|
|
int dldf = newsamplerate;
|
|
int const1 = const2 - 2*dldf;
|
|
int criteria = const2 - dldf;
|
|
for(i = 0; i < output_needed/4; i++)
|
|
{
|
|
pdest[i] = psrc[j];
|
|
if(criteria >= 0)
|
|
{
|
|
++j;
|
|
criteria += const1;
|
|
}
|
|
else criteria += const2;
|
|
}
|
|
return j * 4; //number of bytes consumed
|
|
}
|
|
|
|
void my_audio_callback(void *userdata, Uint8 *stream, int len)
|
|
{
|
|
int newsamplerate = OutputFreq * 100 / speed_factor;
|
|
int oldsamplerate = GameFreq;
|
|
|
|
if (buffer_pos > (len * oldsamplerate) / newsamplerate)
|
|
{
|
|
int input_used;
|
|
if (VolumeControlType == VOLUME_TYPE_SDL)
|
|
{
|
|
input_used = resample(buffer, buffer_pos, oldsamplerate, mixBuffer, len, newsamplerate);
|
|
SDL_MixAudio(stream, mixBuffer, len, VolSDL);
|
|
}
|
|
else
|
|
{
|
|
input_used = resample(buffer, buffer_pos, oldsamplerate, stream, len, newsamplerate);
|
|
}
|
|
memmove(buffer, &buffer[input_used], buffer_pos - input_used);
|
|
buffer_pos -= input_used;
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
underrun_count++;
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Debug: Audio buffer underrun (%i).\n",underrun_count);
|
|
#endif
|
|
memset(stream , 0, len);
|
|
buffer_pos = 0;
|
|
}
|
|
}
|
|
EXPORT void CALL RomOpen()
|
|
{
|
|
/* This function is for compatibility with Mupen64. */
|
|
ReadConfig();
|
|
InitializeAudio( GameFreq );
|
|
}
|
|
void InitializeSDL()
|
|
{
|
|
ReadConfig();
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Sound plugin started in debug mode.\n");
|
|
#endif
|
|
printf("[JttL's SDL Audio plugin] version %s initalizing.\n", MUPEN_VERSION);
|
|
printf("[JttL's SDL Audio plugin] Initializing SDL audio subsystem...\n");
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: Primary buffer: %i bytes.\n", PrimaryBufferSize);
|
|
printf("[JttL's SDL Audio plugin] Debug: Secondary buffer: %i bytes.\n", SecondaryBufferSize * 4);
|
|
printf("[JttL's SDL Audio plugin] Debug: Low buffer level: %i bytes.\n", LowBufferLoadLevel);
|
|
printf("[JttL's SDL Audio plugin] Debug: High buffer level: %i bytes.\n", HighBufferLoadLevel);
|
|
#endif
|
|
if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0)
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Failed to initialize SDL audio subsystem.\n[JttL's SDL Audio plugin] Error: Forcing exit.\n");
|
|
critical_failure = 1;
|
|
return;
|
|
}
|
|
critical_failure = 0;
|
|
|
|
}
|
|
void InitializeAudio(int freq)
|
|
{
|
|
if(SDL_WasInit(SDL_INIT_AUDIO|SDL_INIT_TIMER) == (SDL_INIT_AUDIO|SDL_INIT_TIMER) )
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: Audio and timer allready initialized.\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: Audio and timer not yet initialized. Initializing...\n");
|
|
#endif
|
|
InitializeSDL();
|
|
}
|
|
if (critical_failure == 1)
|
|
return;
|
|
GameFreq = freq; // This is important for the sync
|
|
if(hardware_spec != NULL) free(hardware_spec);
|
|
SDL_PauseAudio(1);
|
|
SDL_CloseAudio();
|
|
|
|
// Prototype of our callback function
|
|
void my_audio_callback(void *userdata, Uint8 *stream, int len);
|
|
|
|
// Open the audio device
|
|
SDL_AudioSpec *desired, *obtained;
|
|
|
|
// Allocate a desired SDL_AudioSpec
|
|
desired = malloc(sizeof(SDL_AudioSpec));
|
|
|
|
// Allocate space for the obtained SDL_AudioSpec
|
|
obtained = malloc(sizeof(SDL_AudioSpec));
|
|
|
|
// 22050Hz - FM Radio quality
|
|
//desired->freq=freq;
|
|
|
|
if(freq < 11025) OutputFreq = 11025;
|
|
else if(freq < 22050) OutputFreq = 22050;
|
|
else OutputFreq = 44100;
|
|
|
|
desired->freq = OutputFreq;
|
|
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: Requesting frequency: %iHz.\n", desired->freq);
|
|
#endif
|
|
/* 16-bit signed audio */
|
|
desired->format=AUDIO_S16SYS;
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: Requesting format: %i.\n", desired->format);
|
|
#endif
|
|
/* Stereo */
|
|
desired->channels=2;
|
|
/* Large audio buffer reduces risk of dropouts but increases response time */
|
|
desired->samples=SecondaryBufferSize;
|
|
|
|
/* Our callback function */
|
|
desired->callback=my_audio_callback;
|
|
desired->userdata=NULL;
|
|
|
|
if(buffer == NULL)
|
|
{
|
|
printf("[JttL's SDL Audio plugin] Allocating memory for audio buffer: %i bytes.\n", (int) (PrimaryBufferSize*sizeof(Uint8)));
|
|
buffer = (Uint8*) malloc(PrimaryBufferSize);
|
|
}
|
|
|
|
if (mixBuffer == NULL)
|
|
{
|
|
//this should be the size of the SDL audio buffer
|
|
mixBuffer = (Uint8*) malloc(SecondaryBufferSize * 4);
|
|
}
|
|
|
|
memset(buffer, 0, PrimaryBufferSize * sizeof(Uint8));
|
|
|
|
/* Open the audio device */
|
|
if ( SDL_OpenAudio(desired, obtained) < 0 )
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Couldn't open audio: %s\n", SDL_GetError());
|
|
critical_failure = 1;
|
|
return;
|
|
}
|
|
/* desired spec is no longer needed */
|
|
|
|
if(desired->format != obtained->format)
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Obtained audio format differs from requested.\n");
|
|
}
|
|
if(desired->freq != obtained->freq)
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Obtained frequency differs from requested.\n");
|
|
}
|
|
free(desired);
|
|
hardware_spec=obtained;
|
|
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: Frequency: %i\n", hardware_spec->freq);
|
|
printf("[JttL's SDL Audio plugin] Debug: Format: %i\n", hardware_spec->format);
|
|
printf("[JttL's SDL Audio plugin] Debug: Channels: %i\n", hardware_spec->channels);
|
|
printf("[JttL's SDL Audio plugin] Debug: Silence: %i\n", hardware_spec->silence);
|
|
printf("[JttL's SDL Audio plugin] Debug: Samples: %i\n", hardware_spec->samples);
|
|
printf("[JttL's SDL Audio plugin] Debug: Size: %i\n", hardware_spec->size);
|
|
#endif
|
|
SDL_PauseAudio(0);
|
|
|
|
/* set playback volume */
|
|
if (VolumeControlType == VOLUME_TYPE_SDL)
|
|
{
|
|
VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
|
|
}
|
|
else
|
|
{
|
|
VolPercent = volGet();
|
|
}
|
|
|
|
}
|
|
EXPORT void CALL RomClosed( void )
|
|
{
|
|
if (critical_failure == 1)
|
|
return;
|
|
printf("[JttL's SDL Audio plugin] Cleaning up SDL sound plugin...\n");
|
|
|
|
// Shut down SDL Audio output
|
|
SDL_PauseAudio(1);
|
|
SDL_CloseAudio();
|
|
|
|
// Delete the buffer, as we are done producing sound
|
|
if (buffer != NULL)
|
|
{
|
|
free(buffer);
|
|
buffer = NULL;
|
|
}
|
|
if (mixBuffer != NULL)
|
|
{
|
|
free(mixBuffer);
|
|
mixBuffer = NULL;
|
|
}
|
|
|
|
// Delete the hardware spec struct
|
|
if(hardware_spec != NULL) free(hardware_spec);
|
|
hardware_spec = NULL;
|
|
buffer = NULL;
|
|
|
|
// Shutdown the respective subsystems
|
|
if(SDL_WasInit(SDL_INIT_AUDIO) != 0) SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
if(SDL_WasInit(SDL_INIT_TIMER) != 0) SDL_QuitSubSystem(SDL_INIT_TIMER);
|
|
}
|
|
|
|
EXPORT void CALL SetConfigDir(char* configDir)
|
|
{
|
|
strncpy(configdir, configDir, PATH_MAX);
|
|
}
|
|
|
|
EXPORT void CALL ProcessAList()
|
|
{
|
|
}
|
|
|
|
EXPORT void CALL SetSpeedFactor(int percentage)
|
|
{
|
|
if (percentage >= 10 && percentage <= 300)
|
|
speed_factor = percentage;
|
|
}
|
|
|
|
void SaveConfig()
|
|
{
|
|
FILE *config_file;
|
|
char path[PATH_MAX];
|
|
|
|
if(strlen(configdir) > 0) strncpy(path, configdir, PATH_MAX);
|
|
|
|
// Ensure that there's a trailing '/'
|
|
if(path[strlen(path)-1] != '/') strncat(path, "/", PATH_MAX - strlen(path));
|
|
|
|
strncat(path, CONFIG_FILE, PATH_MAX - strlen(path));
|
|
if ((config_file = fopen(path, "w")) == NULL)
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Cannot open config file for saving.\n");
|
|
return;
|
|
}
|
|
fprintf(config_file, "# SDL sound plugin's config-file\n\n"
|
|
"# This sets default frequency which is used if rom doesn't want to change it.\n"
|
|
"# Probably only game that needs this is Zelda: Ocarina Of Time Master Quest\n");
|
|
fprintf(config_file, "DEFAULT_FREQUENCY %d\n\n", GameFreq);
|
|
|
|
fprintf(config_file, "# Swaps left and right channels ( 0 = no, 1 = yes )\n");
|
|
fprintf(config_file, "SWAP_CHANNELS %d\n\n", SwapChannels);
|
|
|
|
fprintf(config_file, "# Size of primary buffer in bytes. This is the buffer where audio is loaded\n"
|
|
"# after it's extracted from n64's memory.\n");
|
|
fprintf(config_file, "PRIMARY_BUFFER_SIZE %d\n\n", PrimaryBufferSize);
|
|
|
|
fprintf(config_file, "# If buffer load goes under LOW_BUFFER_LOAD_LEVEL then game is speeded up to\n"
|
|
"# fill the buffer. If buffer load exeeds HIGH_BUFFER_LOAD_LEVEL then some\n"
|
|
"# extra slowdown is added to prevent buffer overflow (which is not supposed\n"
|
|
"# to happen in any circumstanses if syncronization is working but because\n"
|
|
"# computer's clock is such inaccurate (10ms) that might happen. I'm planning\n"
|
|
"# to add support for Real Time Clock for greater accuracy but we will see.\n\n"
|
|
"# The plugin tries to keep the buffer's load always between these values.\n"
|
|
"# So if you change only PRIMARY_BUFFER_SIZE, nothing changes. You have to\n"
|
|
"# adjust these values instead. You probably want to play with\n"
|
|
"# LOW_BUFFER_LOAD_LEVEL if you get dropouts.\n\n");
|
|
fprintf(config_file, "LOW_BUFFER_LOAD_LEVEL %d\n", LowBufferLoadLevel);
|
|
fprintf(config_file, "HIGH_BUFFER_LOAD_LEVEL %d\n\n", HighBufferLoadLevel);
|
|
|
|
fprintf(config_file, "# Size of secondary buffer. This is actually SDL's hardware buffer. This is\n"
|
|
"# amount of samples, so final buffer size is four times this.\n\n");
|
|
fprintf(config_file, "SECONDARY_BUFFER_SIZE %d\n\n", SecondaryBufferSize);
|
|
|
|
fprintf(config_file, "# Enable Linear Resampling.\n"
|
|
"# Possible values:\n"
|
|
"# 1. Unfiltered resampling (very fast, okay quality)\n"
|
|
"# 2. SINC resampling (Best Quality, requires libsamplerate)\n");
|
|
fprintf(config_file, "RESAMPLE %d\n\n", Resample);
|
|
|
|
fprintf(config_file, "# Select volume control type\n"
|
|
"# Possible values:\n"
|
|
"# 1. Use internal SDL volume control. Changing the volume will only affect\n"
|
|
"# the volume of mupen64plus and works independently of the hardware mixer.\n"
|
|
"# 2. Use the OSS mixer. This directly controls the OSS mixer, adjusting the\n"
|
|
"# master volume for PC\n");
|
|
fprintf(config_file, "VOLUME_CONTROL_TYPE %d\n\n", VolumeControlType);
|
|
|
|
fprintf(config_file, "# Default Volume (0-100%%)\n"
|
|
"# Only used if you set VOLUME_CONTROL_TYPE to 1. Otherwise the default volume\n"
|
|
"# is the volume that the harware mixer is set to when mupen64plus loads.\n");
|
|
fprintf(config_file, "VOLUME_DEFAULT %d\n\n", VolPercent);
|
|
|
|
fprintf(config_file, "# Volume increment/decrement\n"
|
|
"# Set the percentage change each time the volume is increased or decreased.\n");
|
|
fprintf(config_file, "VOLUME_ADJUST %d\n\n", VolDelta);
|
|
|
|
fclose(config_file);
|
|
}
|
|
|
|
void ReadConfig()
|
|
{
|
|
FILE * config_file;
|
|
char line[256];
|
|
char param[128];
|
|
char *value;
|
|
char path[PATH_MAX];
|
|
|
|
if(strlen(configdir) > 0) strncpy(path, configdir, PATH_MAX);
|
|
|
|
// Ensure that there's a trailing '/'
|
|
if(path[strlen(path)-1] != '/') strncat(path, "/", PATH_MAX - strlen(path));
|
|
|
|
strncat(path, CONFIG_FILE, PATH_MAX - strlen(path));
|
|
|
|
if ((config_file = fopen(path, "r")) == NULL)
|
|
{
|
|
fprintf(stderr, "[JttL's SDL Audio plugin] Error: Cannot open config file.\n");
|
|
return;
|
|
}
|
|
|
|
while(!feof(config_file))
|
|
{
|
|
fgets(line, 256, config_file);
|
|
if((line[0] != '#') && (strlen(line) > 1))
|
|
{
|
|
value = strchr(line, ' ');
|
|
if (value[strlen(value)-1] == '\n') value[strlen(value)-1] = '\0';
|
|
|
|
strncpy(param, line, (strlen(line) - strlen(value)));
|
|
param[(strlen(line) - strlen(value))] = '\0';
|
|
#ifdef DEBUG
|
|
printf("[JttL's SDL Audio plugin] Debug: Parameter \"%s\", value: \"%i\"\n", ¶m, atoi(&value[1]));
|
|
#endif
|
|
if(strcasecmp(param, "DEFAULT_FREQUENCY") == 0) GameFreq = atoi(value);
|
|
if(strcasecmp(param, "SWAP_CHANNELS") == 0) SwapChannels = atoi(value);
|
|
if(strcasecmp(param, "PRIMARY_BUFFER_SIZE") == 0) PrimaryBufferSize = atoi(value);
|
|
if(strcasecmp(param, "SECONDARY_BUFFER_SIZE") == 0) SecondaryBufferSize = atoi(value);
|
|
if(strcasecmp(param, "LOW_BUFFER_LOAD_LEVEL") == 0) LowBufferLoadLevel = atoi(value);
|
|
if(strcasecmp(param, "HIGH_BUFFER_LOAD_LEVEL") == 0) HighBufferLoadLevel = atoi(value);
|
|
if(strcasecmp(param, "RESAMPLE") == 0) Resample = atoi(value);
|
|
if(strcasecmp(param, "VOLUME_CONTROL_TYPE") == 0) VolumeControlType = atoi(value);
|
|
if(strcasecmp(param, "VOLUME_ADJUST") == 0) VolDelta = atoi(value);
|
|
if(strcasecmp(param, "VOLUME_DEFAULT") == 0) VolPercent = atoi(value);
|
|
}
|
|
}
|
|
fclose(config_file);
|
|
}
|
|
|
|
EXPORT void CALL VolumeUp(void)
|
|
{
|
|
//if muted, unmute first
|
|
if (VolMutedSave > -1)
|
|
VolumeMute();
|
|
|
|
// reload volume if we're using OSS
|
|
if (VolumeControlType == VOLUME_TYPE_OSS)
|
|
{
|
|
VolPercent = volGet();
|
|
}
|
|
|
|
// adjust volume variable
|
|
VolPercent += VolDelta;
|
|
if (VolPercent > 100)
|
|
VolPercent = 100;
|
|
|
|
if (VolumeControlType == VOLUME_TYPE_SDL)
|
|
{
|
|
VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
|
|
}
|
|
else
|
|
{
|
|
//OSS mixer volume
|
|
volSet(VolPercent);
|
|
}
|
|
}
|
|
|
|
EXPORT void CALL VolumeDown(void)
|
|
{
|
|
//if muted, unmute first
|
|
if (VolMutedSave > -1)
|
|
VolumeMute();
|
|
|
|
// reload volume if we're using OSS
|
|
if (VolumeControlType == VOLUME_TYPE_OSS)
|
|
{
|
|
VolPercent = volGet();
|
|
}
|
|
|
|
// adjust volume variable
|
|
VolPercent -= VolDelta;
|
|
if (VolPercent < 0)
|
|
VolPercent = 0;
|
|
|
|
if (VolumeControlType == VOLUME_TYPE_SDL)
|
|
{
|
|
VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
|
|
}
|
|
else
|
|
{
|
|
//OSS mixer volume
|
|
volSet(VolPercent);
|
|
}
|
|
}
|
|
|
|
|
|
EXPORT void CALL VolumeMute(void)
|
|
{
|
|
if (VolMutedSave > -1)
|
|
{
|
|
//unmute
|
|
VolPercent = VolMutedSave;
|
|
VolMutedSave = -1;
|
|
if (VolumeControlType == VOLUME_TYPE_SDL)
|
|
{
|
|
VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
|
|
}
|
|
else
|
|
{
|
|
//OSS mixer volume
|
|
volSet(VolPercent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//mute
|
|
VolMutedSave = VolPercent;
|
|
VolPercent = 0;
|
|
if (VolumeControlType == VOLUME_TYPE_SDL)
|
|
{
|
|
VolSDL = 0;
|
|
}
|
|
else
|
|
{
|
|
//OSS mixer volume
|
|
volSet(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static char VolumeString[32];
|
|
|
|
EXPORT const char * CALL VolumeGetString(void)
|
|
{
|
|
if (VolMutedSave > -1)
|
|
{
|
|
strcpy(VolumeString, "Mute");
|
|
}
|
|
else
|
|
{
|
|
sprintf(VolumeString, "%i%%", VolPercent);
|
|
}
|
|
|
|
return VolumeString;
|
|
}
|
|
|