Add a per rom config to Notaz Audio Plugin
[mupen64plus-pandora.git] / source / notaz_audio / main.c
1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2  *   Mupen64plus - main.c                                                  *
3  *   Mupen64Plus homepage: http://code.google.com/p/mupen64plus/           *
4  *   notaz_audio: (c) notaz, 2010                                          *
5  *                                                                         *
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.                                   *
10  *                                                                         *
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.                          *
15  *                                                                         *
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  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <sys/ioctl.h>
30 #include <linux/soundcard.h>
31 #include <stdarg.h>
32 /*
33 #include "../main/winlnxdefs.h"
34
35 #include "Audio_1.2.h"
36 */
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"
43
44 #include "osal_dynamiclib.h"
45
46 /* version info */
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
51
52 #define VERSION_PRINTF_SPLIT(x) (((x) >> 16) & 0xffff), (((x) >> 8) & 0xff), ((x) & 0xff)
53
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;
73
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;
90
91 /* local variables */
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;
97
98 #undef PLUGIN_VERSION
99 #define PLUGIN_VERSION "r2"
100
101 #define PREFIX "[audio] "
102 #define log(f, ...) printf(PREFIX f, ##__VA_ARGS__)
103
104 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
105
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
112
113 #define OSS_FRAGMENT_COUNT 5
114
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;
120
121 /* config stuff */
122 static int minimum_rate = 8000;
123 static int pich_percent = 100;
124
125 static unsigned int sound_out_buff[48000 * 4]; // ~4 sec, enough?
126 static unsigned int sound_silence_buff[48000 / 10];
127
128 static int sound_dev = -1;
129 static int output_freq;
130 static int resample_step;
131 static int silence_bytes;
132 static int fade_len;
133 static int fade_step;
134
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,
139 };
140
141 static int init(int freq)
142 {
143         static int output_freq_old;
144         int stereo, bits, rate;
145         int bsize, frag;
146         int ret, i;
147
148         input_freq = freq;
149
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) {
154                         output_freq = rate;
155                         break;
156                 }
157         }
158
159         if (sound_dev >= 0) {
160                 if (output_freq == output_freq_old)
161                         goto finish;
162                 close(sound_dev);
163         }
164
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\")");
171                         return -1;
172                 }
173         }
174
175         bsize = output_freq / 20 * 4; // ~50ms
176         for (frag = 0; bsize; bsize >>= 1, frag++)
177                 ;
178
179         frag |= OSS_FRAGMENT_COUNT << 16;       // fragment count
180         ret = ioctl(sound_dev, SNDCTL_DSP_SETFRAGMENT, &frag);
181         if (ret < 0)
182                 perror(PREFIX "SNDCTL_DSP_SETFRAGMENT failed");
183
184         silence_bytes = output_freq / 30 * 4; // ~ 25ms
185         memset(sound_silence_buff, 0, sizeof(sound_silence_buff));
186
187         stereo = 1;
188         bits = 16;
189         rate = output_freq;
190         ret = ioctl(sound_dev, SNDCTL_DSP_STEREO, &stereo);
191         if (ret == 0)
192                 ret = ioctl(sound_dev, SNDCTL_DSP_SETFMT, &bits);
193         if (ret == 0)
194                 ret = ioctl(sound_dev, SNDCTL_DSP_SPEED, &rate);
195         if (ret < 0)
196                 perror(PREFIX "failed to set audio format");
197
198         if (rate != output_freq)
199                 log("warning: output rate %d differs from desired %d\n", rate, output_freq);
200
201 finish:
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;
207
208         log("(re)started, rates: %d -> %d\n", freq, output_freq);
209
210         return 0;
211 }
212
213 static unsigned int resample(unsigned int *input, int in_size, unsigned int *output, int out_size)
214 {
215         unsigned int i, j, t;
216         int step = resample_step;
217         int count = 0;
218
219         t = input[0];
220         t = (t << 16) | (t >> 16);
221
222         for (i = j = 0; i < out_size / 4;)
223         {
224                 output[i++] = t;
225
226                 count += step;
227                 while (count >= 1024) {
228                         count -= 1024;
229                         j++;
230                         if (j >= in_size / 4)
231                                 goto out;
232                         t = input[j];
233                         t = (t << 16) | (t >> 16);
234                 }
235         }
236
237 out:
238         return i * 4; // number of bytes to output
239 }
240
241 static void fade(unsigned int *buf, int buf_len, int up)
242 {
243         signed short *sb = (void *)buf;
244         int len = fade_len;
245         int step = fade_step;
246         int counter = 0, mult, mult_step;
247         int i;
248
249         if (up) {
250                 mult = 0;
251                 mult_step = 1;
252         } else {
253                 sb += buf_len / 2 - len * 2;
254                 if (sb < (signed short *)buf)
255                         sb = (void *)buf;
256                 mult = 63;
257                 mult_step = -1;
258         }
259
260         for (i = 0; i < len; i++) {
261                 counter += step;
262                 while (counter >= 1024) {
263                         counter -= 1024;
264                         mult += mult_step;
265                 }
266                 sb[i * 2] = sb[i * 2] / 64 * mult;
267                 sb[i * 2 + 1] = sb[i * 2] / 64 * mult;
268         }
269 }
270
271 EXPORT void CALL AiDacrateChanged( int SystemType )
272 {
273         int f = input_freq;
274         switch (SystemType)
275         {
276         case SYSTEM_NTSC:
277                 f = 48681812 / (*audio_info.AI_DACRATE_REG + 1);
278                 break;
279         case SYSTEM_PAL:
280                 f = 49656530 / (*audio_info.AI_DACRATE_REG + 1);
281                 break;
282         case SYSTEM_MPAL:
283                 f = 48628316 / (*audio_info.AI_DACRATE_REG + 1);
284                 break;
285         }
286         init(f);
287 }
288
289 EXPORT void CALL AiLenChanged(void)
290 {
291         static int had_uflow;
292         unsigned int ret, len, *in, *part2, part2_len;
293         audio_buf_info bi;
294
295         if (sound_dev < 0)
296                 return;
297
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;
301
302         //log("AiLenChanged: %u\n", len);
303
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);
307
308         if (had_uflow)
309                 fade(sound_out_buff, ret / 2, 1);
310
311         write(sound_dev, sound_out_buff, ret / 2);
312         part2 = sound_out_buff + (ret / 4) / 2;
313         part2_len = ret - ret / 2;
314
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);
326                 had_uflow = 1;
327         }
328         else {
329                 write(sound_dev, part2, part2_len);
330                 had_uflow = 0;
331         }
332 }
333
334 /*
335 EXPORT DWORD CALL AiReadLength(void)
336 {
337         return 0;
338 }
339 EXPORT void CALL CloseDLL(void)
340 {
341         if (sound_dev >= 0)
342                 close(sound_dev);
343         sound_dev = -1;
344 }
345
346 static char config_file[512];
347
348 static void read_config(void)
349 {
350         char cfg[512], *p;
351         int val;
352         FILE *f;
353
354         f = fopen(config_file, "r");
355         if (f == NULL) {
356                 perror(PREFIX "can't open config");
357                 return;
358         }
359
360         while (1) {
361                 p = fgets(cfg, sizeof(cfg), f);
362                 if (p == NULL)
363                         break;
364                 if (p[0] == '#')
365                         continue;
366
367                 if (sscanf(p, "pich_percent = %d", &val) == 1 && 10 <= val && val <= 1000) {
368                         pich_percent = val;
369                         break;
370                 }
371                 if (sscanf(p, "minimum_rate = %d", &val) == 1) {
372                         minimum_rate = val;
373                         break;
374                 }
375         }
376         fclose(f);
377 }
378 EXPORT void CALL SetConfigDir(char *configDir)
379 {
380         snprintf(config_file, sizeof(config_file), "%snotaz_audio.conf", configDir);
381         read_config();
382 }
383
384 EXPORT void CALL DllTest(HWND hParent)
385 {
386 }
387
388 EXPORT void CALL GetDllInfo(PLUGIN_INFO *PluginInfo)
389 {
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;
395 }
396
397 EXPORT void CALL DllAbout(HWND hParent)
398 {
399 }
400
401 EXPORT void CALL DllConfig(HWND hParent)
402 {
403         char cmd[512];
404         FILE *f;
405
406         f = fopen(config_file, "r");
407         if (f != NULL)
408                 fclose(f);
409         else {
410                 f = fopen(config_file, "w");
411                 if (f != NULL) {
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);
417                         fclose(f);
418                 }
419         }
420
421         snprintf(cmd, sizeof(cmd), "mousepad \"%s\"", config_file);
422         system(cmd);
423
424         read_config();
425 }
426 */
427 EXPORT int CALL InitiateAudio(AUDIO_INFO Audio_Info)
428 {
429     if (!l_PluginInit)
430         return 0;
431
432         audio_info = Audio_Info;
433         return 1;
434 }
435
436 EXPORT int CALL RomOpen(void)
437 {
438     if (!l_PluginInit)
439         return 0;
440
441         minimum_rate = ConfigGetParamInt(l_ConfigAudio, "MINIMUM_RATE" );
442         pich_percent = ConfigGetParamInt(l_ConfigAudio, "PITCH_PERCENT" );
443         
444         /* get rom crc */
445         char romcrc[50];
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);
449         
450         /* open NotazAudio.ini if any, and look for the rom crc section to have per rom config */
451         FILE *f;
452         f = fopen("NotazAudio.ini", "r");
453         if (f) 
454         {
455                 int section = 0;
456                 int rightsection = 0;
457                 char line[500];
458                 while (!feof(f)) 
459                 {
460                         fgets(line, 500, f);
461                         size_t ln = strlen(line) - 1;
462                         if (line[ln] == '\n')
463                                 line[ln] = '\0';
464                         // *TODO* remove unused whitespaces
465                         if (line[0] == '/')
466                                 continue;
467                         if (!(strcasecmp(line,"")==0))
468                         {
469                                 if (line[0] == '{') //if a section heading
470                                 {
471                                         line[strlen(line)-1]='\0';
472                                         if (strcasecmp(line+1,romcrc)==0)
473                                                 section = 1;    // found !!!
474                                         else
475                                                 section = 0;
476                                 } else if (section==1) 
477                                 {
478                                         if (strncasecmp(line, "MINIMUM_RATE", 12)==0) 
479                                         {
480                                                 minimum_rate = strtol(strchr(line,'=')+1,NULL,10);
481                                         }
482                                         else if (strncasecmp(line, "PITCH_PERCENT", 13)==0) 
483                                         {
484                                                 pich_percent = strtol(strchr(line,'=')+1,NULL,10);
485                                         }
486                                 }
487                         }
488                 }
489                 fclose(f);
490         
491         }
492
493         /* This function is for compatibility with Mupen64. */
494         output_freq = minimum_rate;
495         init(input_freq);
496         return 1;
497 }
498
499 EXPORT void CALL RomClosed(void)
500 {
501 }
502
503 EXPORT void CALL ProcessAList(void)
504 {
505 }
506
507 EXPORT void CALL SetSpeedFactor(int percentage)
508 {
509 }
510
511 EXPORT void CALL VolumeUp(void)
512 {
513 }
514
515 EXPORT void CALL VolumeDown(void)
516 {
517 }
518
519 EXPORT void CALL VolumeMute(void)
520 {
521 }
522 EXPORT int CALL VolumeGetLevel(void)
523 {
524     return 255;
525 }
526
527 EXPORT void CALL VolumeSetLevel(int level)
528 {
529 }
530
531 EXPORT const char * CALL VolumeGetString(void)
532 {
533         return "100%%";
534 }
535
536 /* Global functions */
537 static void DebugMessage(int level, const char *message, ...)
538 {
539   char msgbuf[1024];
540   va_list args;
541
542   if (l_DebugCallback == NULL)
543       return;
544
545   va_start(args, message);
546   vsprintf(msgbuf, message, args);
547
548   (*l_DebugCallback)(l_DebugCallContext, level, msgbuf);
549
550   va_end(args);
551 }
552
553 /* Mupen64Plus plugin functions */
554 EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context,
555                                    void (*DebugCallback)(void *, int, const char *))
556 {
557     ptr_CoreGetAPIVersions CoreAPIVersionFunc;
558     
559     int ConfigAPIVersion, DebugAPIVersion, VidextAPIVersion, bSaveConfig;
560     float fConfigParamsVersion = 0.0f;
561     
562     if (l_PluginInit)
563         return M64ERR_ALREADY_INIT;
564
565     /* first thing is to set the callback function for debug info */
566     l_DebugCallback = DebugCallback;
567     l_DebugCallContext = Context;
568
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)
572     {
573         DebugMessage(M64MSG_ERROR, "Core emulator broken; no CoreAPIVersionFunc() function found.");
574         return M64ERR_INCOMPATIBLE;
575     }
576     
577     (*CoreAPIVersionFunc)(&ConfigAPIVersion, &DebugAPIVersion, &VidextAPIVersion, NULL);
578     if ((ConfigAPIVersion & 0xffff0000) != (CONFIG_API_VERSION & 0xffff0000))
579     {
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;
583     }
584
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");
599
600     CoreDoCommand = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreLibHandle, "CoreDoCommand");
601
602     if (!ConfigOpenSection || !ConfigDeleteSection || !ConfigSetParameter || !ConfigGetParameter ||
603         !ConfigSetDefaultInt || !ConfigSetDefaultFloat || !ConfigSetDefaultBool || !ConfigSetDefaultString ||
604         !ConfigGetParamInt   || !ConfigGetParamFloat   || !ConfigGetParamBool   || !ConfigGetParamString ||
605                 !CoreDoCommand )
606         return M64ERR_INCOMPATIBLE;
607
608     /* ConfigSaveSection was added in Config API v2.1.0 */
609     if (ConfigAPIVersion >= 0x020100 && !ConfigSaveSection)
610         return M64ERR_INCOMPATIBLE;
611
612     /* get a configuration section handle */
613     if (ConfigOpenSection("Audio-Notaz", &l_ConfigAudio) != M64ERR_SUCCESS)
614     {
615         DebugMessage(M64MSG_ERROR, "Couldn't open config section 'Audio-Notaz'");
616         return M64ERR_INPUT_NOT_FOUND;
617     }
618
619     /* check the section version number */
620     bSaveConfig = 0;
621     if (ConfigGetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fConfigParamsVersion, sizeof(float)) != M64ERR_SUCCESS)
622     {
623         DebugMessage(M64MSG_WARNING, "No version number in 'Audio-Notaz' config section. Setting defaults.");
624         ConfigDeleteSection("Audio-Notaz");
625         ConfigOpenSection("Audio-Notaz", &l_ConfigAudio);
626         bSaveConfig = 1;
627     }
628     else if (((int) fConfigParamsVersion) != ((int) CONFIG_PARAM_VERSION))
629     {
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);
633         bSaveConfig = 1;
634     }
635     else if ((CONFIG_PARAM_VERSION - fConfigParamsVersion) >= 0.0001f)
636     {
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);
641         bSaveConfig = 1;
642     }
643
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");
648
649     if (bSaveConfig && ConfigAPIVersion >= 0x020100)
650         ConfigSaveSection("Audio-SDL");
651
652     l_PluginInit = 1;
653     return M64ERR_SUCCESS;
654 }
655
656 EXPORT m64p_error CALL PluginShutdown(void)
657 {
658     if (!l_PluginInit)
659         return M64ERR_NOT_INIT;
660
661     /* reset some local variables */
662     l_DebugCallback = NULL;
663     l_DebugCallContext = NULL;
664
665         if (sound_dev >= 0)
666                 close(sound_dev);
667         sound_dev = -1;
668
669     l_PluginInit = 0;
670         log("Notaz-audio Plugin shutdown\n");
671     return M64ERR_SUCCESS;
672 }
673
674
675 EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
676 {
677     /* set version info */
678     if (PluginType != NULL)
679         *PluginType = M64PLUGIN_AUDIO;
680
681     if (PluginVersion != NULL)
682         *PluginVersion = NOTAZ_AUDIO_PLUGIN_VERSION;
683
684     if (APIVersion != NULL)
685         *APIVersion = AUDIO_PLUGIN_API_VERSION;
686     
687     if (PluginNamePtr != NULL)
688         *PluginNamePtr = "Mupen64Plus Notaz Audio Plugin";
689
690     if (Capabilities != NULL)
691     {
692         *Capabilities = 0;
693     }
694                     
695     return M64ERR_SUCCESS;
696 }