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"
42 #include "m64p_frontend.h"
44 #include "osal_dynamiclib.h"
47 #define NOTAZ_AUDIO_PLUGIN_VERSION 0x020000
48 #define AUDIO_PLUGIN_API_VERSION 0x020000
49 #define CONFIG_API_VERSION 0x020100
50 #define CONFIG_PARAM_VERSION 1.00
52 #define VERSION_PRINTF_SPLIT(x) (((x) >> 16) & 0xffff), (((x) >> 8) & 0xff), ((x) & 0xff)
54 /* declarations of pointers to Core config functions */
55 extern ptr_ConfigListSections ConfigListSections;
56 extern ptr_ConfigOpenSection ConfigOpenSection;
57 extern ptr_ConfigDeleteSection ConfigDeleteSection;
58 extern ptr_ConfigSaveSection ConfigSaveSection;
59 extern ptr_ConfigListParameters ConfigListParameters;
60 extern ptr_ConfigSaveFile ConfigSaveFile;
61 extern ptr_ConfigSetParameter ConfigSetParameter;
62 extern ptr_ConfigGetParameter ConfigGetParameter;
63 extern ptr_ConfigGetParameterHelp ConfigGetParameterHelp;
64 extern ptr_ConfigSetDefaultInt ConfigSetDefaultInt;
65 extern ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat;
66 extern ptr_ConfigSetDefaultBool ConfigSetDefaultBool;
67 extern ptr_ConfigSetDefaultString ConfigSetDefaultString;
68 extern ptr_ConfigGetParamInt ConfigGetParamInt;
69 extern ptr_ConfigGetParamFloat ConfigGetParamFloat;
70 extern ptr_ConfigGetParamBool ConfigGetParamBool;
71 extern ptr_ConfigGetParamString ConfigGetParamString;
72 extern ptr_CoreDoCommand CoreDoCommand;
74 /* definitions of pointers to Core config functions */
75 ptr_ConfigOpenSection ConfigOpenSection = NULL;
76 ptr_ConfigDeleteSection ConfigDeleteSection = NULL;
77 ptr_ConfigSaveSection ConfigSaveSection = NULL;
78 ptr_ConfigSetParameter ConfigSetParameter = NULL;
79 ptr_ConfigGetParameter ConfigGetParameter = NULL;
80 ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL;
81 ptr_ConfigSetDefaultInt ConfigSetDefaultInt = NULL;
82 ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat = NULL;
83 ptr_ConfigSetDefaultBool ConfigSetDefaultBool = NULL;
84 ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL;
85 ptr_ConfigGetParamInt ConfigGetParamInt = NULL;
86 ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL;
87 ptr_ConfigGetParamBool ConfigGetParamBool = NULL;
88 ptr_ConfigGetParamString ConfigGetParamString = NULL;
89 ptr_CoreDoCommand CoreDoCommand = NULL;
92 static void (*l_DebugCallback)(void *, int, const char *) = NULL;
93 static void *l_DebugCallContext = NULL;
94 static int l_PluginInit = 0;
95 static int l_PausedForSync = 1; /* Audio is started in paused state after SDL initialization */
96 static m64p_handle l_ConfigAudio;
99 #define PLUGIN_VERSION "r2"
101 #define PREFIX "[audio] "
102 #define log(f, ...) printf(PREFIX f, ##__VA_ARGS__)
104 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
106 /* comment from jttl_audio:
107 * This sets default frequency what is used if rom doesn't want to change it.
108 * Popably only game that needs this is Zelda: Ocarina Of Time Master Quest
109 * *NOTICE* We should try to find out why Demos' frequencies are always wrong
110 * They tend to rely on a default frequency, apparently, never the same one ;)*/
111 #define DEFAULT_FREQUENCY 33600
113 #define OSS_FRAGMENT_COUNT 5
115 /* Read header for type definition */
116 static AUDIO_INFO audio_info;
117 /* Audio frequency, this is usually obtained from the game,
118 * but for compatibility we set default value */
119 static int input_freq = DEFAULT_FREQUENCY;
122 static int minimum_rate = 8000;
123 static int pich_percent = 100;
125 static unsigned int sound_out_buff[48000 * 4]; // ~4 sec, enough?
126 static unsigned int sound_silence_buff[48000 / 10];
128 static int sound_dev = -1;
129 static int output_freq;
130 static int resample_step;
131 static int silence_bytes;
133 static int fade_step;
135 /* rates Pandora supports natively, we'll need to do
136 * less resampling if we stick close to these */
137 static const int supported_rates[] = {
138 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
141 static int init(int freq)
143 static int output_freq_old;
144 int stereo, bits, rate;
150 // find lowest alowed rate that is higher than game's output
151 for (i = 0; i < ARRAY_SIZE(supported_rates); i++) {
152 int rate = supported_rates[i];
153 if (freq <= rate + 100 && rate >= minimum_rate) {
159 if (sound_dev >= 0) {
160 if (output_freq == output_freq_old)
165 sound_dev = open("/dev/dsp", O_WRONLY);
166 if (sound_dev == -1) {
167 perror(PREFIX "open(\"/dev/dsp\")");
168 sound_dev = open("/dev/dsp1", O_WRONLY);
169 if (sound_dev == -1) {
170 perror(PREFIX "open(\"/dev/dsp1\")");
175 bsize = output_freq / 20 * 4; // ~50ms
176 for (frag = 0; bsize; bsize >>= 1, frag++)
179 frag |= OSS_FRAGMENT_COUNT << 16; // fragment count
180 ret = ioctl(sound_dev, SNDCTL_DSP_SETFRAGMENT, &frag);
182 perror(PREFIX "SNDCTL_DSP_SETFRAGMENT failed");
184 silence_bytes = output_freq / 30 * 4; // ~ 25ms
185 memset(sound_silence_buff, 0, sizeof(sound_silence_buff));
190 ret = ioctl(sound_dev, SNDCTL_DSP_STEREO, &stereo);
192 ret = ioctl(sound_dev, SNDCTL_DSP_SETFMT, &bits);
194 ret = ioctl(sound_dev, SNDCTL_DSP_SPEED, &rate);
196 perror(PREFIX "failed to set audio format");
198 if (rate != output_freq)
199 log("warning: output rate %d differs from desired %d\n", rate, output_freq);
202 resample_step = freq * 1024 / output_freq;
203 if (pich_percent != 100)
204 resample_step = resample_step * pich_percent / 100;
205 fade_len = output_freq / 1000;
206 fade_step = 64 * 1024 / fade_len;
208 log("(re)started, rates: %d -> %d\n", freq, output_freq);
213 static unsigned int resample(unsigned int *input, int in_size, unsigned int *output, int out_size)
215 unsigned int i, j, t;
216 int step = resample_step;
220 t = (t << 16) | (t >> 16);
222 for (i = j = 0; i < out_size / 4;)
227 while (count >= 1024) {
230 if (j >= in_size / 4)
233 t = (t << 16) | (t >> 16);
238 return i * 4; // number of bytes to output
241 static void fade(unsigned int *buf, int buf_len, int up)
243 signed short *sb = (void *)buf;
245 int step = fade_step;
246 int counter = 0, mult, mult_step;
253 sb += buf_len / 2 - len * 2;
254 if (sb < (signed short *)buf)
260 for (i = 0; i < len; i++) {
262 while (counter >= 1024) {
266 sb[i * 2] = sb[i * 2] / 64 * mult;
267 sb[i * 2 + 1] = sb[i * 2] / 64 * mult;
271 EXPORT void CALL AiDacrateChanged( int SystemType )
277 f = 48681812 / (*audio_info.AI_DACRATE_REG + 1);
280 f = 49656530 / (*audio_info.AI_DACRATE_REG + 1);
283 f = 48628316 / (*audio_info.AI_DACRATE_REG + 1);
289 EXPORT void CALL AiLenChanged(void)
291 static int had_uflow;
292 unsigned int ret, len, *in, *part2, part2_len;
298 // XXX: what about alignment? len limit? rdram overflow?
299 in = (unsigned int *)(audio_info.RDRAM + (*audio_info.AI_DRAM_ADDR_REG & 0xFFFFFC));
300 len = *audio_info.AI_LEN_REG;
302 //log("AiLenChanged: %u\n", len);
304 ret = resample(in, len, sound_out_buff, sizeof(sound_out_buff));
305 if (ret >= sizeof(sound_out_buff))
306 log("overflow, in_len=%d\n", len);
309 fade(sound_out_buff, ret / 2, 1);
311 write(sound_dev, sound_out_buff, ret / 2);
312 part2 = sound_out_buff + (ret / 4) / 2;
313 part2_len = ret - ret / 2;
315 // try to keep at most 2 free fragments to avoid
316 // hardware underflows that cause crackling on pandora
317 // XXX: for some reason GETOSPACE only works after write?
318 // XXX: .fragments sometimes overflows? ALSA OSS emu bugs?
319 ret = ioctl(sound_dev, SNDCTL_DSP_GETOSPACE, &bi);
320 if (ret == 0 && 2 < bi.fragments && bi.fragments <= OSS_FRAGMENT_COUNT) {
321 fade(part2, part2_len, 0);
322 write(sound_dev, part2, part2_len);
323 write(sound_dev, sound_silence_buff, silence_bytes);
324 if (bi.fragments == 4)
325 write(sound_dev, sound_silence_buff, silence_bytes);
329 write(sound_dev, part2, part2_len);
335 EXPORT DWORD CALL AiReadLength(void)
339 EXPORT void CALL CloseDLL(void)
346 static char config_file[512];
348 static void read_config(void)
354 f = fopen(config_file, "r");
356 perror(PREFIX "can't open config");
361 p = fgets(cfg, sizeof(cfg), f);
367 if (sscanf(p, "pich_percent = %d", &val) == 1 && 10 <= val && val <= 1000) {
371 if (sscanf(p, "minimum_rate = %d", &val) == 1) {
378 EXPORT void CALL SetConfigDir(char *configDir)
380 snprintf(config_file, sizeof(config_file), "%snotaz_audio.conf", configDir);
384 EXPORT void CALL DllTest(HWND hParent)
388 EXPORT void CALL GetDllInfo(PLUGIN_INFO *PluginInfo)
390 PluginInfo->Version = 0x0101;
391 PluginInfo->Type = PLUGIN_TYPE_AUDIO;
392 strcpy(PluginInfo->Name, "notaz's OSS audio " PLUGIN_VERSION);
393 PluginInfo->NormalMemory = TRUE;
394 PluginInfo->MemoryBswaped = TRUE;
397 EXPORT void CALL DllAbout(HWND hParent)
401 EXPORT void CALL DllConfig(HWND hParent)
406 f = fopen(config_file, "r");
410 f = fopen(config_file, "w");
412 fprintf(f, "# minimum sample rate to use. Higher values sound better on Pandora's DAC.\n");
413 fprintf(f, "minimum_rate = %d\n\n", minimum_rate);
414 fprintf(f, "# sound playback speed compared to normal (10-200%%)\n");
415 fprintf(f, "# this will affect gameplay speed and sound pitch\n");
416 fprintf(f, "pich_percent = %d\n\n", pich_percent);
421 snprintf(cmd, sizeof(cmd), "mousepad \"%s\"", config_file);
427 EXPORT int CALL InitiateAudio(AUDIO_INFO Audio_Info)
432 audio_info = Audio_Info;
436 EXPORT int CALL RomOpen(void)
441 minimum_rate = ConfigGetParamInt(l_ConfigAudio, "MINIMUM_RATE" );
442 pich_percent = ConfigGetParamInt(l_ConfigAudio, "PITCH_PERCENT" );
446 m64p_rom_header romheader;
447 CoreDoCommand(M64CMD_ROM_GET_HEADER, sizeof(romheader), &romheader); // is that the right way to get rom header and crc ?
448 sprintf((char*)romcrc, "%08x%08x", (unsigned int)romheader.CRC1, (unsigned int)romheader.CRC2);
450 /* open NotazAudio.ini if any, and look for the rom crc section to have per rom config */
452 f = fopen("NotazAudio.ini", "r");
456 int rightsection = 0;
461 size_t ln = strlen(line) - 1;
462 if (line[ln] == '\n')
464 // *TODO* remove unused whitespaces
467 if (!(strcasecmp(line,"")==0))
469 if (line[0] == '{') //if a section heading
471 line[strlen(line)-1]='\0';
472 if (strcasecmp(line+1,romcrc)==0)
473 section = 1; // found !!!
476 } else if (section==1)
478 if (strncasecmp(line, "MINIMUM_RATE", 12)==0)
480 minimum_rate = strtol(strchr(line,'=')+1,NULL,10);
482 else if (strncasecmp(line, "PITCH_PERCENT", 13)==0)
484 pich_percent = strtol(strchr(line,'=')+1,NULL,10);
493 /* This function is for compatibility with Mupen64. */
494 output_freq = minimum_rate;
499 EXPORT void CALL RomClosed(void)
503 EXPORT void CALL ProcessAList(void)
507 EXPORT void CALL SetSpeedFactor(int percentage)
511 EXPORT void CALL VolumeUp(void)
515 EXPORT void CALL VolumeDown(void)
519 EXPORT void CALL VolumeMute(void)
522 EXPORT int CALL VolumeGetLevel(void)
527 EXPORT void CALL VolumeSetLevel(int level)
531 EXPORT const char * CALL VolumeGetString(void)
536 /* Global functions */
537 static void DebugMessage(int level, const char *message, ...)
542 if (l_DebugCallback == NULL)
545 va_start(args, message);
546 vsprintf(msgbuf, message, args);
548 (*l_DebugCallback)(l_DebugCallContext, level, msgbuf);
553 /* Mupen64Plus plugin functions */
554 EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context,
555 void (*DebugCallback)(void *, int, const char *))
557 ptr_CoreGetAPIVersions CoreAPIVersionFunc;
559 int ConfigAPIVersion, DebugAPIVersion, VidextAPIVersion, bSaveConfig;
560 float fConfigParamsVersion = 0.0f;
563 return M64ERR_ALREADY_INIT;
565 /* first thing is to set the callback function for debug info */
566 l_DebugCallback = DebugCallback;
567 l_DebugCallContext = Context;
569 /* attach and call the CoreGetAPIVersions function, check Config API version for compatibility */
570 CoreAPIVersionFunc = (ptr_CoreGetAPIVersions) osal_dynlib_getproc(CoreLibHandle, "CoreGetAPIVersions");
571 if (CoreAPIVersionFunc == NULL)
573 DebugMessage(M64MSG_ERROR, "Core emulator broken; no CoreAPIVersionFunc() function found.");
574 return M64ERR_INCOMPATIBLE;
577 (*CoreAPIVersionFunc)(&ConfigAPIVersion, &DebugAPIVersion, &VidextAPIVersion, NULL);
578 if ((ConfigAPIVersion & 0xffff0000) != (CONFIG_API_VERSION & 0xffff0000))
580 DebugMessage(M64MSG_ERROR, "Emulator core Config API (v%i.%i.%i) incompatible with plugin (v%i.%i.%i)",
581 VERSION_PRINTF_SPLIT(ConfigAPIVersion), VERSION_PRINTF_SPLIT(CONFIG_API_VERSION));
582 return M64ERR_INCOMPATIBLE;
585 /* Get the core config function pointers from the library handle */
586 ConfigOpenSection = (ptr_ConfigOpenSection) osal_dynlib_getproc(CoreLibHandle, "ConfigOpenSection");
587 ConfigDeleteSection = (ptr_ConfigDeleteSection) osal_dynlib_getproc(CoreLibHandle, "ConfigDeleteSection");
588 ConfigSaveSection = (ptr_ConfigSaveSection) osal_dynlib_getproc(CoreLibHandle, "ConfigSaveSection");
589 ConfigSetParameter = (ptr_ConfigSetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigSetParameter");
590 ConfigGetParameter = (ptr_ConfigGetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParameter");
591 ConfigSetDefaultInt = (ptr_ConfigSetDefaultInt) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultInt");
592 ConfigSetDefaultFloat = (ptr_ConfigSetDefaultFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultFloat");
593 ConfigSetDefaultBool = (ptr_ConfigSetDefaultBool) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultBool");
594 ConfigSetDefaultString = (ptr_ConfigSetDefaultString) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultString");
595 ConfigGetParamInt = (ptr_ConfigGetParamInt) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamInt");
596 ConfigGetParamFloat = (ptr_ConfigGetParamFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamFloat");
597 ConfigGetParamBool = (ptr_ConfigGetParamBool) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamBool");
598 ConfigGetParamString = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamString");
600 CoreDoCommand = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreLibHandle, "CoreDoCommand");
602 if (!ConfigOpenSection || !ConfigDeleteSection || !ConfigSetParameter || !ConfigGetParameter ||
603 !ConfigSetDefaultInt || !ConfigSetDefaultFloat || !ConfigSetDefaultBool || !ConfigSetDefaultString ||
604 !ConfigGetParamInt || !ConfigGetParamFloat || !ConfigGetParamBool || !ConfigGetParamString ||
606 return M64ERR_INCOMPATIBLE;
608 /* ConfigSaveSection was added in Config API v2.1.0 */
609 if (ConfigAPIVersion >= 0x020100 && !ConfigSaveSection)
610 return M64ERR_INCOMPATIBLE;
612 /* get a configuration section handle */
613 if (ConfigOpenSection("Audio-Notaz", &l_ConfigAudio) != M64ERR_SUCCESS)
615 DebugMessage(M64MSG_ERROR, "Couldn't open config section 'Audio-Notaz'");
616 return M64ERR_INPUT_NOT_FOUND;
619 /* check the section version number */
621 if (ConfigGetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fConfigParamsVersion, sizeof(float)) != M64ERR_SUCCESS)
623 DebugMessage(M64MSG_WARNING, "No version number in 'Audio-Notaz' config section. Setting defaults.");
624 ConfigDeleteSection("Audio-Notaz");
625 ConfigOpenSection("Audio-Notaz", &l_ConfigAudio);
628 else if (((int) fConfigParamsVersion) != ((int) CONFIG_PARAM_VERSION))
630 DebugMessage(M64MSG_WARNING, "Incompatible version %.2f in 'Audio-Notaz' config section: current is %.2f. Setting defaults.", fConfigParamsVersion, (float) CONFIG_PARAM_VERSION);
631 ConfigDeleteSection("Audio-Notaz");
632 ConfigOpenSection("Audio-Notaz", &l_ConfigAudio);
635 else if ((CONFIG_PARAM_VERSION - fConfigParamsVersion) >= 0.0001f)
637 /* handle upgrades */
638 float fVersion = CONFIG_PARAM_VERSION;
639 ConfigSetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fVersion);
640 DebugMessage(M64MSG_INFO, "Updating parameter set version in 'Audio-Notaz' config section to %.2f", fVersion);
644 /* set the default values for this plugin */
645 ConfigSetDefaultFloat(l_ConfigAudio, "Version", CONFIG_PARAM_VERSION, "Mupen64Plus Notaz Audio Plugin config parameter version number");
646 ConfigSetDefaultInt(l_ConfigAudio, "MINIMUM_RATE", 44100, "Minimum sample rate to use. Higher values sound better on Pandora's DAC.");
647 ConfigSetDefaultInt(l_ConfigAudio, "PITCH_PERCENT", 100, "sound playback speed compared to normal (10-200%%). this will affect gameplay speed and sound pitch");
649 if (bSaveConfig && ConfigAPIVersion >= 0x020100)
650 ConfigSaveSection("Audio-SDL");
653 return M64ERR_SUCCESS;
656 EXPORT m64p_error CALL PluginShutdown(void)
659 return M64ERR_NOT_INIT;
661 /* reset some local variables */
662 l_DebugCallback = NULL;
663 l_DebugCallContext = NULL;
670 log("Notaz-audio Plugin shutdown\n");
671 return M64ERR_SUCCESS;
675 EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
677 /* set version info */
678 if (PluginType != NULL)
679 *PluginType = M64PLUGIN_AUDIO;
681 if (PluginVersion != NULL)
682 *PluginVersion = NOTAZ_AUDIO_PLUGIN_VERSION;
684 if (APIVersion != NULL)
685 *APIVersion = AUDIO_PLUGIN_API_VERSION;
687 if (PluginNamePtr != NULL)
688 *PluginNamePtr = "Mupen64Plus Notaz Audio Plugin";
690 if (Capabilities != NULL)
695 return M64ERR_SUCCESS;