1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus - main.c *
3 * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
4 * notaz_audio: (c) notaz, 2010 *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
20 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
25 #include <sys/types.h>
29 #include <sys/ioctl.h>
30 #include <linux/soundcard.h>
33 #include "../main/winlnxdefs.h"
35 #include "Audio_1.2.h"
37 #define M64P_PLUGIN_PROTOTYPES 1
38 #include "m64p_types.h"
39 #include "m64p_plugin.h"
40 #include "m64p_common.h"
41 #include "m64p_config.h"
43 #include "osal_dynamiclib.h"
46 #define NOTAZ_AUDIO_PLUGIN_VERSION 0x020000
47 #define AUDIO_PLUGIN_API_VERSION 0x020000
48 #define CONFIG_API_VERSION 0x020100
49 #define CONFIG_PARAM_VERSION 1.00
51 #define VERSION_PRINTF_SPLIT(x) (((x) >> 16) & 0xffff), (((x) >> 8) & 0xff), ((x) & 0xff)
53 /* declarations of pointers to Core config functions */
54 extern ptr_ConfigListSections ConfigListSections;
55 extern ptr_ConfigOpenSection ConfigOpenSection;
56 extern ptr_ConfigDeleteSection ConfigDeleteSection;
57 extern ptr_ConfigSaveSection ConfigSaveSection;
58 extern ptr_ConfigListParameters ConfigListParameters;
59 extern ptr_ConfigSaveFile ConfigSaveFile;
60 extern ptr_ConfigSetParameter ConfigSetParameter;
61 extern ptr_ConfigGetParameter ConfigGetParameter;
62 extern ptr_ConfigGetParameterHelp ConfigGetParameterHelp;
63 extern ptr_ConfigSetDefaultInt ConfigSetDefaultInt;
64 extern ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat;
65 extern ptr_ConfigSetDefaultBool ConfigSetDefaultBool;
66 extern ptr_ConfigSetDefaultString ConfigSetDefaultString;
67 extern ptr_ConfigGetParamInt ConfigGetParamInt;
68 extern ptr_ConfigGetParamFloat ConfigGetParamFloat;
69 extern ptr_ConfigGetParamBool ConfigGetParamBool;
70 extern ptr_ConfigGetParamString ConfigGetParamString;
72 /* definitions of pointers to Core config functions */
73 ptr_ConfigOpenSection ConfigOpenSection = NULL;
74 ptr_ConfigDeleteSection ConfigDeleteSection = NULL;
75 ptr_ConfigSaveSection ConfigSaveSection = NULL;
76 ptr_ConfigSetParameter ConfigSetParameter = NULL;
77 ptr_ConfigGetParameter ConfigGetParameter = NULL;
78 ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL;
79 ptr_ConfigSetDefaultInt ConfigSetDefaultInt = NULL;
80 ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat = NULL;
81 ptr_ConfigSetDefaultBool ConfigSetDefaultBool = NULL;
82 ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL;
83 ptr_ConfigGetParamInt ConfigGetParamInt = NULL;
84 ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL;
85 ptr_ConfigGetParamBool ConfigGetParamBool = NULL;
86 ptr_ConfigGetParamString ConfigGetParamString = NULL;
89 static void (*l_DebugCallback)(void *, int, const char *) = NULL;
90 static void *l_DebugCallContext = NULL;
91 static int l_PluginInit = 0;
92 static int l_PausedForSync = 1; /* Audio is started in paused state after SDL initialization */
93 static m64p_handle l_ConfigAudio;
96 #define PLUGIN_VERSION "r2"
98 #define PREFIX "[audio] "
99 #define log(f, ...) printf(PREFIX f, ##__VA_ARGS__)
101 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
103 /* comment from jttl_audio:
104 * This sets default frequency what is used if rom doesn't want to change it.
105 * Popably only game that needs this is Zelda: Ocarina Of Time Master Quest
106 * *NOTICE* We should try to find out why Demos' frequencies are always wrong
107 * They tend to rely on a default frequency, apparently, never the same one ;)*/
108 #define DEFAULT_FREQUENCY 33600
110 #define OSS_FRAGMENT_COUNT 5
112 /* Read header for type definition */
113 static AUDIO_INFO audio_info;
114 /* Audio frequency, this is usually obtained from the game,
115 * but for compatibility we set default value */
116 static int input_freq = DEFAULT_FREQUENCY;
119 static int minimum_rate = 8000;
120 static int pich_percent = 100;
122 static unsigned int sound_out_buff[48000 * 4]; // ~4 sec, enough?
123 static unsigned int sound_silence_buff[48000 / 10];
125 static int sound_dev = -1;
126 static int output_freq;
127 static int resample_step;
128 static int silence_bytes;
130 static int fade_step;
132 /* rates Pandora supports natively, we'll need to do
133 * less resampling if we stick close to these */
134 static const int supported_rates[] = {
135 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
138 static int init(int freq)
140 static int output_freq_old;
141 int stereo, bits, rate;
147 minimum_rate = ConfigGetParamInt(l_ConfigAudio, "MINIMUM_RATE" );
148 output_freq = minimum_rate;
149 pich_percent = ConfigGetParamInt(l_ConfigAudio, "PITCH_PERCENT" );
151 // find lowest alowed rate that is higher than game's output
152 for (i = 0; i < ARRAY_SIZE(supported_rates); i++) {
153 int rate = supported_rates[i];
154 if (freq <= rate + 100 && rate >= minimum_rate) {
160 if (sound_dev >= 0) {
161 if (output_freq == output_freq_old)
166 sound_dev = open("/dev/dsp", O_WRONLY);
167 if (sound_dev == -1) {
168 perror(PREFIX "open(\"/dev/dsp\")");
169 sound_dev = open("/dev/dsp1", O_WRONLY);
170 if (sound_dev == -1) {
171 perror(PREFIX "open(\"/dev/dsp1\")");
176 bsize = output_freq / 20 * 4; // ~50ms
177 for (frag = 0; bsize; bsize >>= 1, frag++)
180 frag |= OSS_FRAGMENT_COUNT << 16; // fragment count
181 ret = ioctl(sound_dev, SNDCTL_DSP_SETFRAGMENT, &frag);
183 perror(PREFIX "SNDCTL_DSP_SETFRAGMENT failed");
185 silence_bytes = output_freq / 30 * 4; // ~ 25ms
186 memset(sound_silence_buff, 0, sizeof(sound_silence_buff));
191 ret = ioctl(sound_dev, SNDCTL_DSP_STEREO, &stereo);
193 ret = ioctl(sound_dev, SNDCTL_DSP_SETFMT, &bits);
195 ret = ioctl(sound_dev, SNDCTL_DSP_SPEED, &rate);
197 perror(PREFIX "failed to set audio format");
199 if (rate != output_freq)
200 log("warning: output rate %d differs from desired %d\n", rate, output_freq);
203 resample_step = freq * 1024 / output_freq;
204 if (pich_percent != 100)
205 resample_step = resample_step * pich_percent / 100;
206 fade_len = output_freq / 1000;
207 fade_step = 64 * 1024 / fade_len;
209 log("(re)started, rates: %d -> %d\n", freq, output_freq);
214 static unsigned int resample(unsigned int *input, int in_size, unsigned int *output, int out_size)
216 unsigned int i, j, t;
217 int step = resample_step;
221 t = (t << 16) | (t >> 16);
223 for (i = j = 0; i < out_size / 4;)
228 while (count >= 1024) {
231 if (j >= in_size / 4)
234 t = (t << 16) | (t >> 16);
239 return i * 4; // number of bytes to output
242 static void fade(unsigned int *buf, int buf_len, int up)
244 signed short *sb = (void *)buf;
246 int step = fade_step;
247 int counter = 0, mult, mult_step;
254 sb += buf_len / 2 - len * 2;
255 if (sb < (signed short *)buf)
261 for (i = 0; i < len; i++) {
263 while (counter >= 1024) {
267 sb[i * 2] = sb[i * 2] / 64 * mult;
268 sb[i * 2 + 1] = sb[i * 2] / 64 * mult;
272 EXPORT void CALL AiDacrateChanged( int SystemType )
278 f = 48681812 / (*audio_info.AI_DACRATE_REG + 1);
281 f = 49656530 / (*audio_info.AI_DACRATE_REG + 1);
284 f = 48628316 / (*audio_info.AI_DACRATE_REG + 1);
290 EXPORT void CALL AiLenChanged(void)
292 static int had_uflow;
293 unsigned int ret, len, *in, *part2, part2_len;
299 // XXX: what about alignment? len limit? rdram overflow?
300 in = (unsigned int *)(audio_info.RDRAM + (*audio_info.AI_DRAM_ADDR_REG & 0xFFFFFC));
301 len = *audio_info.AI_LEN_REG;
303 //log("AiLenChanged: %u\n", len);
305 ret = resample(in, len, sound_out_buff, sizeof(sound_out_buff));
306 if (ret >= sizeof(sound_out_buff))
307 log("overflow, in_len=%d\n", len);
310 fade(sound_out_buff, ret / 2, 1);
312 write(sound_dev, sound_out_buff, ret / 2);
313 part2 = sound_out_buff + (ret / 4) / 2;
314 part2_len = ret - ret / 2;
316 // try to keep at most 2 free fragments to avoid
317 // hardware underflows that cause crackling on pandora
318 // XXX: for some reason GETOSPACE only works after write?
319 // XXX: .fragments sometimes overflows? ALSA OSS emu bugs?
320 ret = ioctl(sound_dev, SNDCTL_DSP_GETOSPACE, &bi);
321 if (ret == 0 && 2 < bi.fragments && bi.fragments <= OSS_FRAGMENT_COUNT) {
322 fade(part2, part2_len, 0);
323 write(sound_dev, part2, part2_len);
324 write(sound_dev, sound_silence_buff, silence_bytes);
325 if (bi.fragments == 4)
326 write(sound_dev, sound_silence_buff, silence_bytes);
330 write(sound_dev, part2, part2_len);
336 EXPORT DWORD CALL AiReadLength(void)
340 EXPORT void CALL CloseDLL(void)
347 static char config_file[512];
349 static void read_config(void)
355 f = fopen(config_file, "r");
357 perror(PREFIX "can't open config");
362 p = fgets(cfg, sizeof(cfg), f);
368 if (sscanf(p, "pich_percent = %d", &val) == 1 && 10 <= val && val <= 1000) {
372 if (sscanf(p, "minimum_rate = %d", &val) == 1) {
380 EXPORT void CALL SetConfigDir(char *configDir)
382 snprintf(config_file, sizeof(config_file), "%snotaz_audio.conf", configDir);
386 EXPORT void CALL DllTest(HWND hParent)
390 EXPORT void CALL GetDllInfo(PLUGIN_INFO *PluginInfo)
392 PluginInfo->Version = 0x0101;
393 PluginInfo->Type = PLUGIN_TYPE_AUDIO;
394 strcpy(PluginInfo->Name, "notaz's OSS audio " PLUGIN_VERSION);
395 PluginInfo->NormalMemory = TRUE;
396 PluginInfo->MemoryBswaped = TRUE;
399 EXPORT void CALL DllAbout(HWND hParent)
403 EXPORT void CALL DllConfig(HWND hParent)
408 f = fopen(config_file, "r");
412 f = fopen(config_file, "w");
414 fprintf(f, "# minimum sample rate to use. Higher values sound better on Pandora's DAC.\n");
415 fprintf(f, "minimum_rate = %d\n\n", minimum_rate);
416 fprintf(f, "# sound playback speed compared to normal (10-200%%)\n");
417 fprintf(f, "# this will affect gameplay speed and sound pitch\n");
418 fprintf(f, "pich_percent = %d\n\n", pich_percent);
423 snprintf(cmd, sizeof(cmd), "mousepad \"%s\"", config_file);
429 EXPORT int CALL InitiateAudio(AUDIO_INFO Audio_Info)
434 audio_info = Audio_Info;
438 EXPORT int CALL RomOpen(void)
443 /* This function is for compatibility with Mupen64. */
448 EXPORT void CALL RomClosed(void)
452 EXPORT void CALL ProcessAList(void)
456 EXPORT void CALL SetSpeedFactor(int percentage)
460 EXPORT void CALL VolumeUp(void)
464 EXPORT void CALL VolumeDown(void)
468 EXPORT void CALL VolumeMute(void)
471 EXPORT int CALL VolumeGetLevel(void)
476 EXPORT void CALL VolumeSetLevel(int level)
480 EXPORT const char * CALL VolumeGetString(void)
485 /* Global functions */
486 static void DebugMessage(int level, const char *message, ...)
491 if (l_DebugCallback == NULL)
494 va_start(args, message);
495 vsprintf(msgbuf, message, args);
497 (*l_DebugCallback)(l_DebugCallContext, level, msgbuf);
502 /* Mupen64Plus plugin functions */
503 EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context,
504 void (*DebugCallback)(void *, int, const char *))
506 ptr_CoreGetAPIVersions CoreAPIVersionFunc;
508 int ConfigAPIVersion, DebugAPIVersion, VidextAPIVersion, bSaveConfig;
509 float fConfigParamsVersion = 0.0f;
512 return M64ERR_ALREADY_INIT;
514 /* first thing is to set the callback function for debug info */
515 l_DebugCallback = DebugCallback;
516 l_DebugCallContext = Context;
518 /* attach and call the CoreGetAPIVersions function, check Config API version for compatibility */
519 CoreAPIVersionFunc = (ptr_CoreGetAPIVersions) osal_dynlib_getproc(CoreLibHandle, "CoreGetAPIVersions");
520 if (CoreAPIVersionFunc == NULL)
522 DebugMessage(M64MSG_ERROR, "Core emulator broken; no CoreAPIVersionFunc() function found.");
523 return M64ERR_INCOMPATIBLE;
526 (*CoreAPIVersionFunc)(&ConfigAPIVersion, &DebugAPIVersion, &VidextAPIVersion, NULL);
527 if ((ConfigAPIVersion & 0xffff0000) != (CONFIG_API_VERSION & 0xffff0000))
529 DebugMessage(M64MSG_ERROR, "Emulator core Config API (v%i.%i.%i) incompatible with plugin (v%i.%i.%i)",
530 VERSION_PRINTF_SPLIT(ConfigAPIVersion), VERSION_PRINTF_SPLIT(CONFIG_API_VERSION));
531 return M64ERR_INCOMPATIBLE;
534 /* Get the core config function pointers from the library handle */
535 ConfigOpenSection = (ptr_ConfigOpenSection) osal_dynlib_getproc(CoreLibHandle, "ConfigOpenSection");
536 ConfigDeleteSection = (ptr_ConfigDeleteSection) osal_dynlib_getproc(CoreLibHandle, "ConfigDeleteSection");
537 ConfigSaveSection = (ptr_ConfigSaveSection) osal_dynlib_getproc(CoreLibHandle, "ConfigSaveSection");
538 ConfigSetParameter = (ptr_ConfigSetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigSetParameter");
539 ConfigGetParameter = (ptr_ConfigGetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParameter");
540 ConfigSetDefaultInt = (ptr_ConfigSetDefaultInt) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultInt");
541 ConfigSetDefaultFloat = (ptr_ConfigSetDefaultFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultFloat");
542 ConfigSetDefaultBool = (ptr_ConfigSetDefaultBool) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultBool");
543 ConfigSetDefaultString = (ptr_ConfigSetDefaultString) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultString");
544 ConfigGetParamInt = (ptr_ConfigGetParamInt) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamInt");
545 ConfigGetParamFloat = (ptr_ConfigGetParamFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamFloat");
546 ConfigGetParamBool = (ptr_ConfigGetParamBool) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamBool");
547 ConfigGetParamString = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamString");
549 if (!ConfigOpenSection || !ConfigDeleteSection || !ConfigSetParameter || !ConfigGetParameter ||
550 !ConfigSetDefaultInt || !ConfigSetDefaultFloat || !ConfigSetDefaultBool || !ConfigSetDefaultString ||
551 !ConfigGetParamInt || !ConfigGetParamFloat || !ConfigGetParamBool || !ConfigGetParamString)
552 return M64ERR_INCOMPATIBLE;
554 /* ConfigSaveSection was added in Config API v2.1.0 */
555 if (ConfigAPIVersion >= 0x020100 && !ConfigSaveSection)
556 return M64ERR_INCOMPATIBLE;
558 /* get a configuration section handle */
559 if (ConfigOpenSection("Audio-Notaz", &l_ConfigAudio) != M64ERR_SUCCESS)
561 DebugMessage(M64MSG_ERROR, "Couldn't open config section 'Audio-Notaz'");
562 return M64ERR_INPUT_NOT_FOUND;
565 /* check the section version number */
567 if (ConfigGetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fConfigParamsVersion, sizeof(float)) != M64ERR_SUCCESS)
569 DebugMessage(M64MSG_WARNING, "No version number in 'Audio-Notaz' config section. Setting defaults.");
570 ConfigDeleteSection("Audio-Notaz");
571 ConfigOpenSection("Audio-Notaz", &l_ConfigAudio);
574 else if (((int) fConfigParamsVersion) != ((int) CONFIG_PARAM_VERSION))
576 DebugMessage(M64MSG_WARNING, "Incompatible version %.2f in 'Audio-Notaz' config section: current is %.2f. Setting defaults.", fConfigParamsVersion, (float) CONFIG_PARAM_VERSION);
577 ConfigDeleteSection("Audio-Notaz");
578 ConfigOpenSection("Audio-Notaz", &l_ConfigAudio);
581 else if ((CONFIG_PARAM_VERSION - fConfigParamsVersion) >= 0.0001f)
583 /* handle upgrades */
584 float fVersion = CONFIG_PARAM_VERSION;
585 ConfigSetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fVersion);
586 DebugMessage(M64MSG_INFO, "Updating parameter set version in 'Audio-Notaz' config section to %.2f", fVersion);
590 /* set the default values for this plugin */
591 ConfigSetDefaultFloat(l_ConfigAudio, "Version", CONFIG_PARAM_VERSION, "Mupen64Plus Notaz Audio Plugin config parameter version number");
592 ConfigSetDefaultInt(l_ConfigAudio, "MINIMUM_RATE", 44100, "Minimum sample rate to use. Higher values sound better on Pandora's DAC.");
593 ConfigSetDefaultInt(l_ConfigAudio, "PITCH_PERCENT", 100, "sound playback speed compared to normal (10-200%%). this will affect gameplay speed and sound pitch");
595 if (bSaveConfig && ConfigAPIVersion >= 0x020100)
596 ConfigSaveSection("Audio-SDL");
599 return M64ERR_SUCCESS;
602 EXPORT m64p_error CALL PluginShutdown(void)
605 return M64ERR_NOT_INIT;
607 /* reset some local variables */
608 l_DebugCallback = NULL;
609 l_DebugCallContext = NULL;
616 log("Notaz-audio Plugin shutdown\n");
617 return M64ERR_SUCCESS;
621 EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
623 /* set version info */
624 if (PluginType != NULL)
625 *PluginType = M64PLUGIN_AUDIO;
627 if (PluginVersion != NULL)
628 *PluginVersion = NOTAZ_AUDIO_PLUGIN_VERSION;
630 if (APIVersion != NULL)
631 *APIVersion = AUDIO_PLUGIN_API_VERSION;
633 if (PluginNamePtr != NULL)
634 *PluginNamePtr = "Mupen64Plus Notaz Audio Plugin";
636 if (Capabilities != NULL)
641 return M64ERR_SUCCESS;