From e434c7c10278a47fc686024fae04d361a0282ab8 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 7 Mar 2007 19:08:27 +0000 Subject: Use native endianness for sound output, rather than always LSB. Add PC speaker code! Subversion-branch: /trunk/chocolate-doom Subversion-revision: 844 --- configure.in | 1 + src/Makefile.am | 5 +- src/i_pcsound.c | 238 ++++++++++++++++++++++++++++++++++++++++++++ src/i_pcsound.h | 40 ++++++++ src/i_sound.c | 111 +++++++++++++++------ src/pcsound/Makefile.am | 10 ++ src/pcsound/pcsound.c | 114 +++++++++++++++++++++ src/pcsound/pcsound.h | 45 +++++++++ src/pcsound/pcsound_sdl.c | 179 +++++++++++++++++++++++++++++++++ src/pcsound/pcsound_win32.c | 113 +++++++++++++++++++++ 10 files changed, 826 insertions(+), 30 deletions(-) create mode 100644 src/i_pcsound.c create mode 100644 src/i_pcsound.h create mode 100644 src/pcsound/Makefile.am create mode 100644 src/pcsound/pcsound.c create mode 100644 src/pcsound/pcsound.h create mode 100644 src/pcsound/pcsound_sdl.c create mode 100644 src/pcsound/pcsound_win32.c diff --git a/configure.in b/configure.in index ed78c4ba..c43ab84c 100644 --- a/configure.in +++ b/configure.in @@ -74,6 +74,7 @@ textscreen/examples/Makefile setup/Makefile man/Makefile src/Makefile +src/pcsound/Makefile src/chocolate-doom-res.rc setup/chocolate-setup-res.rc ]) diff --git a/src/Makefile.am b/src/Makefile.am index d34a5b17..53ef775c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,8 @@ z_native.c z_zone.h chocolate_server_SOURCES=$(DEDSERV_FILES) chocolate_server_LDADD = @LDFLAGS@ @SDL_LIBS@ @SDLNET_LIBS@ +SUBDIRS=pcsound + SOURCE_FILES=\ am_map.c am_map.h \ deh_ammo.c \ @@ -62,6 +64,7 @@ hu_stuff.c hu_stuff.h \ i_main.c \ info.c info.h \ i_scale.c i_scale.h \ +i_pcsound.c i_pcsound.h \ i_sound.c i_sound.h \ i_system.c i_system.h \ i_timer.c i_timer.h \ @@ -138,7 +141,7 @@ else chocolate_doom_SOURCES=$(SOURCE_FILES) endif -chocolate_doom_LDADD = ../textscreen/libtextscreen.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@ @SDLNET_LIBS@ +chocolate_doom_LDADD = ../textscreen/libtextscreen.a pcsound/libpcsound.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@ @SDLNET_LIBS@ EXTRA_DIST = \ chocolate_doom_icon.c \ diff --git a/src/i_pcsound.c b/src/i_pcsound.c new file mode 100644 index 00000000..926e5f1d --- /dev/null +++ b/src/i_pcsound.c @@ -0,0 +1,238 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2007 Simon Howard +// +// 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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// System interface for PC speaker sound. +// +//----------------------------------------------------------------------------- + +#include "SDL.h" + +#include "doomdef.h" +#include "doomtype.h" + +#include "i_pcsound.h" +#include "i_sound.h" +#include "sounds.h" + +#include "w_wad.h" +#include "z_zone.h" + +#include "pcsound/pcsound.h" + +static boolean pcs_initialised = false; + +static SDL_mutex *sound_lock; + +static uint8_t *current_sound_lump = NULL; +static uint8_t *current_sound_pos = NULL; +static unsigned int current_sound_remaining = 0; +static int current_sound_handle = 0; + +static float frequencies[] = { + 0, 175.00, 180.02, 185.01, 190.02, 196.02, 202.02, 208.01, 214.02, 220.02, + 226.02, 233.04, 240.02, 247.03, 254.03, 262.00, 269.03, 277.03, 285.04, + 294.03, 302.07, 311.04, 320.05, 330.06, 339.06, 349.08, 359.06, 370.09, + 381.08, 392.10, 403.10, 415.01, 427.05, 440.12, 453.16, 466.08, 480.15, + 494.07, 508.16, 523.09, 539.16, 554.19, 571.17, 587.19, 604.14, 622.09, + 640.11, 659.21, 679.10, 698.17, 719.21, 740.18, 762.41, 784.47, 807.29, + 831.48, 855.32, 880.57, 906.67, 932.17, 960.69, 988.55, 1017.20, 1046.64, + 1077.85, 1109.93, 1141.79, 1175.54, 1210.12, 1244.19, 1281.61, 1318.43, + 1357.42, 1397.16, 1439.30, 1480.37, 1523.85, 1569.97, 1614.58, 1661.81, + 1711.87, 1762.45, 1813.34, 1864.34, 1921.38, 1975.46, 2036.14, 2093.29, + 2157.64, 2217.80, 2285.78, 2353.41, 2420.24, 2490.98, 2565.97, 2639.77, +}; + +#define NUM_FREQUENCIES (sizeof(frequencies) / sizeof(*frequencies)) + +void PCSCallbackFunc(int *duration, int *freq) +{ + int tone; + + *duration = 1000 / 140; + + if (SDL_LockMutex(sound_lock) < 0) + { + *freq = 0; + return; + } + + if (current_sound_lump != NULL && current_sound_remaining > 0) + { + // Read the next tone + + tone = *current_sound_pos; + + // Use the tone -> frequency lookup table. See pcspkr10.zip + // for a full discussion of this. + // Check we don't overflow the frequency table. + + if (tone < NUM_FREQUENCIES) + { + *freq = (int) frequencies[tone]; + } + else + { + *freq = 0; + } + + ++current_sound_pos; + --current_sound_remaining; + } + else + { + *freq = 0; + } + + SDL_UnlockMutex(sound_lock); +} + +static boolean CachePCSLump(int sound_id) +{ + int lumplen; + int headerlen; + + // Free the current sound lump back to the cache + + if (current_sound_lump != NULL) + { + Z_ChangeTag(current_sound_lump, PU_CACHE); + current_sound_lump = NULL; + } + + // Load from WAD + + current_sound_lump = W_CacheLumpNum(S_sfx[sound_id].lumpnum, PU_STATIC); + lumplen = W_LumpLength(S_sfx[sound_id].lumpnum); + + // Read header + + if (current_sound_lump[0] != 0x00 || current_sound_lump[1] != 0x00) + { + return false; + } + + headerlen = (current_sound_lump[3] << 8) | current_sound_lump[2]; + + if (headerlen > lumplen - 4) + { + return false; + } + + // Header checks out ok + + current_sound_remaining = headerlen; + current_sound_pos = current_sound_lump + 4; + + return true; +} + +int I_PCS_StartSound(int id, + int channel, + int vol, + int sep, + int pitch, + int priority) +{ + int result; + + if (!pcs_initialised) + { + return -1; + } + + // These PC speaker sounds are not played - this can be seen in the + // Heretic source code, where there are remnants of this left over + // from Doom. + + if (id == sfx_posact || id == sfx_bgact || id == sfx_dmact + || id == sfx_dmpain || id == sfx_popain || id == sfx_sawidl) + { + return -1; + } + + if (SDL_LockMutex(sound_lock) < 0) + { + return -1; + } + + result = CachePCSLump(id); + + if (result) + { + current_sound_handle = channel; + } + + SDL_UnlockMutex(sound_lock); + + if (result) + { + return channel; + } + else + { + return -1; + } +} + +void I_PCS_StopSound(int handle) +{ + if (!pcs_initialised) + { + return; + } + + if (SDL_LockMutex(sound_lock) < 0) + { + return; + } + + // If this is the channel currently playing, immediately end it. + + if (current_sound_handle == handle) + { + current_sound_remaining = 0; + } + + SDL_UnlockMutex(sound_lock); +} + +int I_PCS_SoundIsPlaying(int handle) +{ + if (!pcs_initialised) + { + return false; + } + + if (handle != current_sound_handle) + { + return false; + } + + return current_sound_lump != NULL && current_sound_remaining > 0; +} + +void I_PCS_InitSound(void) +{ + pcs_initialised = PCSound_Init(PCSCallbackFunc); + + sound_lock = SDL_CreateMutex(); +} + diff --git a/src/i_pcsound.h b/src/i_pcsound.h new file mode 100644 index 00000000..601f4ee5 --- /dev/null +++ b/src/i_pcsound.h @@ -0,0 +1,40 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2007 Simon Howard +// +// 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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// System interface for PC speaker sound. +// +//----------------------------------------------------------------------------- + +#ifndef __I_PCSOUND_H__ +#define __I_PCSOUND_H__ + +int I_PCS_StartSound(int id, + int channel, + int vol, + int sep, + int pitch, + int priority); +void I_PCS_StopSound(int handle); +int I_PCS_SoundIsPlaying(int handle); +void I_PCS_InitSound(void); + +#endif /* #ifndef __I_PCSOUND_H__ */ + diff --git a/src/i_sound.c b/src/i_sound.c index 15ad27d6..16d8ce7f 100644 --- a/src/i_sound.c +++ b/src/i_sound.c @@ -39,6 +39,7 @@ #include "z_zone.h" #include "i_system.h" +#include "i_pcsound.h" #include "i_sound.h" #include "deh_main.h" #include "m_argv.h" @@ -122,7 +123,7 @@ void ReleaseSoundOnChannel(int channel) static void ExpandSoundData(byte *data, int samplerate, int length, Mix_Chunk *destination) { - byte *expanded = (byte *) destination->abuf; + Sint16 *expanded = (Sint16 *) destination->abuf; int expanded_length; int expand_ratio; int i; @@ -135,28 +136,25 @@ static void ExpandSoundData(byte *data, int samplerate, int length, for (i=0; i> 8) & 0xff; + expanded[i * 4] = expanded[i * 4 + 1] + = expanded[i * 4 + 2] = expanded[i * 4 + 3] = sample; } } else if (samplerate == 22050) { for (i=0; i> 8) & 0xff; + expanded[i * 2] = expanded[i * 2 + 1] = sample; } } else @@ -170,7 +168,7 @@ static void ExpandSoundData(byte *data, int samplerate, int length, for (i=0; i> 8; @@ -180,8 +178,7 @@ static void ExpandSoundData(byte *data, int samplerate, int length, // expand 8->16 bits, mono->stereo - expanded[i * 4] = expanded[i * 4 + 2] = sample & 0xff; - expanded[i * 4 + 1] = expanded[i * 4 + 3] = (sample >> 8) & 0xff; + expanded[i * 2] = expanded[i * 2 + 1] = sample; } } } @@ -200,7 +197,7 @@ static boolean CacheSFX(int sound) // need to load the sound - lumpnum = I_GetSfxLumpNum(&S_sfx[sound]); + lumpnum = S_sfx[sound].lumpnum; data = W_CacheLumpNum(lumpnum, PU_STATIC); lumplen = W_LumpLength(lumpnum); @@ -213,7 +210,7 @@ static boolean CacheSFX(int sound) return false; } - + // 16 bit sample rate field, 32 bit length field samplerate = (data[3] << 8) | data[2]; @@ -288,9 +285,24 @@ void I_SetSfxVolume(int volume) int I_GetSfxLumpNum(sfxinfo_t* sfx) { char namebuf[9]; - sprintf(namebuf, "ds%s", DEH_String(sfx->name)); + char *prefix; + + // Different prefix for PC speaker sound effects. + + if (snd_sfxdevice == SNDDEVICE_PCSPEAKER) + { + prefix = "dp"; + } + else + { + prefix = "ds"; + } + + sprintf(namebuf, "%s%s", prefix, DEH_String(sfx->name)); + return W_GetNumForName(namebuf); } + // // Starting a sound means adding it // to the current list of active sounds @@ -317,6 +329,11 @@ I_StartSound if (!sound_initialised) return 0; + if (snd_sfxdevice == SNDDEVICE_PCSPEAKER) + { + return I_PCS_StartSound(id, channel, vol, sep, pitch, priority); + } + // Release a sound effect if there is already one playing // on this channel @@ -349,6 +366,12 @@ void I_StopSound (int handle) if (!sound_initialised) return; + if (snd_sfxdevice == SNDDEVICE_PCSPEAKER) + { + I_PCS_StopSound(handle); + return; + } + Mix_HaltChannel(handle); // Sound data is no longer needed; release the @@ -366,7 +389,14 @@ int I_SoundIsPlaying(int handle) if (handle < 0) return false; - return Mix_Playing(handle); + if (snd_sfxdevice == SNDDEVICE_PCSPEAKER) + { + return I_PCS_SoundIsPlaying(handle); + } + else + { + return Mix_Playing(handle); + } } @@ -383,6 +413,11 @@ void I_UpdateSound( void ) if (!sound_initialised) return; + if (snd_sfxdevice == SNDDEVICE_PCSPEAKER) + { + return; + } + // Check all channels to see if a sound has finished for (i=0; i 0; - if (snd_sfxdevice < SNDDEVICE_SB) + // If the SFX device is 0 (none), then disable sound effects, + // just like if we specified -nosfx. However, we still continue + // with initialising digital sound output even if we are using + // the PC speaker, because we might be using the SDL PC speaker + // emulation. + + if (snd_sfxdevice == SNDDEVICE_NONE) { nosfxparm = true; } + //! + // Disable sound effects and music. + // + + if (M_CheckParm("-nosound") > 0) + { + nosfxparm = true; + nomusicparm = true; + } + // When trying to run with music enabled on OSX, display // a warning message. @@ -498,16 +554,6 @@ I_InitSound() } #endif - //! - // Disable sound effects and music. - // - - if (M_CheckParm("-nosound") > 0) - { - nosfxparm = true; - nomusicparm = true; - } - // If music or sound is going to play, we need to at least // initialise SDL // No sound in screensaver mode. @@ -521,7 +567,7 @@ I_InitSound() return; } - if (Mix_OpenAudio(22050, AUDIO_S16LSB, 2, 1024) < 0) + if (Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 1024) < 0) { fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError()); return; @@ -531,6 +577,13 @@ I_InitSound() SDL_PauseAudio(0); + // If we are using the PC speaker, we now need to initialise it. + + if (snd_sfxdevice == SNDDEVICE_PCSPEAKER) + { + I_PCS_InitSound(); + } + if (!nomusicparm) music_initialised = true; diff --git a/src/pcsound/Makefile.am b/src/pcsound/Makefile.am new file mode 100644 index 00000000..77957d72 --- /dev/null +++ b/src/pcsound/Makefile.am @@ -0,0 +1,10 @@ + +AM_CFLAGS= @SDL_CFLAGS@ @SDLMIXER_CFLAGS@ + +noinst_LIBRARIES=libpcsound.a + +libpcsound_a_SOURCES = \ + pcsound.c pcsound.h \ + pcsound_sdl.c \ + pcsound_win32.c + diff --git a/src/pcsound/pcsound.c b/src/pcsound/pcsound.c new file mode 100644 index 00000000..9c18eb01 --- /dev/null +++ b/src/pcsound/pcsound.c @@ -0,0 +1,114 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2007 Simon Howard +// +// 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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// PC speaker interface. +// +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include "pcsound.h" + +#ifdef _WIN32 +extern pcsound_driver_t pcsound_win32_driver; +#endif +extern pcsound_driver_t pcsound_sdl_driver; + +static pcsound_driver_t *drivers[] = +{ +#ifdef _WIN32 + &pcsound_win32_driver, +#endif + &pcsound_sdl_driver, + NULL, +}; + +static pcsound_driver_t *pcsound_driver = NULL; + +int PCSound_Init(pcsound_callback_func callback_func) +{ + char *driver_name; + int i; + + if (pcsound_driver != NULL) + { + return 1; + } + + // Check if the environment variable is set + + driver_name = getenv("PCSOUND_DRIVER"); + + if (driver_name != NULL) + { + for (i=0; drivers[i] != NULL; ++i) + { + if (!strcasecmp(drivers[i]->name, driver_name)) + { + // Found the driver! + + if (drivers[i]->init_func(callback_func)) + { + pcsound_driver = drivers[i]; + } + else + { + printf("Failed to initialise PC sound driver: %s\n", + drivers[i]->name); + break; + } + } + } + } + else + { + // Try all drivers until we find a working one + + for (i=0; drivers[i] != NULL; ++i) + { + if (drivers[i]->init_func(callback_func)) + { + pcsound_driver = drivers[i]; + break; + } + } + } + + if (pcsound_driver != NULL) + { + printf("Using PC sound driver: %s\n", pcsound_driver->name); + return 1; + } + else + { + printf("Failed to find a working PC sound driver.\n"); + return 0; + } +} + +void PCSound_Shutdown(void) +{ + pcsound_driver->shutdown_func(); + pcsound_driver = NULL; +} + diff --git a/src/pcsound/pcsound.h b/src/pcsound/pcsound.h new file mode 100644 index 00000000..1adee416 --- /dev/null +++ b/src/pcsound/pcsound.h @@ -0,0 +1,45 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2007 Simon Howard +// +// 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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// PC speaker interface. +// +//----------------------------------------------------------------------------- + +#ifndef PCSOUND_H +#define PCSOUND_H + +typedef struct pcsound_driver_s pcsound_driver_t; +typedef void (*pcsound_callback_func)(int *duration, int *frequency); +typedef int (*pcsound_init_func)(pcsound_callback_func callback); +typedef void (*pcsound_shutdown_func)(void); + +struct pcsound_driver_s +{ + char *name; + pcsound_init_func init_func; + pcsound_shutdown_func shutdown_func; +}; + +int PCSound_Init(pcsound_callback_func callback_func); +void PCSound_Shutdown(void); + +#endif /* #ifndef PCSOUND_H */ + diff --git a/src/pcsound/pcsound_sdl.c b/src/pcsound/pcsound_sdl.c new file mode 100644 index 00000000..50820394 --- /dev/null +++ b/src/pcsound/pcsound_sdl.c @@ -0,0 +1,179 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2007 Simon Howard +// +// 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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// PC speaker interface. +// +//----------------------------------------------------------------------------- + +#include +#include + +#include "SDL.h" +#include "SDL_mixer.h" + +#include "pcsound.h" + +#define SQUARE_WAVE_AMP 0x2000 + +static pcsound_callback_func callback; + +// Output sound format + +static int mixing_freq; +static Uint16 mixing_format; +static int mixing_channels; + +// Currently playing sound +// current_remaining is the number of remaining samples that must be played +// before we invoke the callback to get the next frequency. + +static int current_remaining; +static int current_freq; + +static int phase_offset = 0; + +// Mixer function that does the PC speaker emulation + +static void PCSound_Mix_Callback(void *udata, Uint8 *stream, int len) +{ + Sint16 *leftptr; + Sint16 *rightptr; + Sint16 this_value; + int oldfreq; + int i; + int nsamples; + + // Number of samples is quadrupled, because of 16-bit and stereo + + nsamples = len / 4; + + leftptr = (Sint16 *) stream; + rightptr = ((Sint16 *) stream) + 1; + + // Fill the output buffer + + for (i=0; i +#include + +#include "pcsound.h" + +static SDL_Thread *sound_thread_handle; +static int sound_thread_running; +static pcsound_callback_func callback; + +static void SoundThread(void *unused) +{ + int frequency; + int duration; + + while (sound_thread_running) + { + callback(&duration, &frequency); + + if (frequency != 0) + { + Beep(frequency, duration); + } + else + { + Sleep(duration); + } + } +} + +static int PCSound_Win32_Init(pcsound_callback_func callback_func) +{ + OSVERSIONINFO osvi; + BOOL result; + + // Temporarily disabled - the Windows scheduler is strange and + // stupid. + + return 0; + + // Find the OS version + + osvi.dwOSVersionInfoSize = sizeof(osvi); + + result = GetVersionEx(&osvi); + + if (!result) + { + return 0; + } + + // Beep() ignores its arguments on win9x, so this driver will + // not work there. + + if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT) + { + // TODO: Use _out() to write directly to the PC speaker on + // win9x: See PC/winsound.c in the Python standard library. + + return 0; + } + + // Start a thread to play sound. + + callback = callback_func; + sound_thread_running = 1; + + sound_thread_handle = SDL_CreateThread(SoundThread, NULL); + + return 1; +} + +static void PCSound_Win32_Shutdown(void) +{ + sound_thread_running = 0; + SDL_WaitThread(sound_thread_handle, NULL); +} + +pcsound_driver_t pcsound_win32_driver = +{ + "Windows", + PCSound_Win32_Init, + PCSound_Win32_Shutdown, +}; + +#endif /* #ifdef _WIN32 */ + -- cgit v1.2.3