Merge commit '772a572431' into scripting
Conflicts: src/p_pspr.cpp src/thingdef/thingdef_codeptr.cpp
This commit is contained in:
commit
cfcd2668cc
94 changed files with 6366 additions and 964 deletions
|
|
@ -44,7 +44,7 @@ extern HWND Window;
|
|||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#if defined(__FreeBSD__) || defined(__APPLE__)
|
||||
#include <stdlib.h>
|
||||
#elif __sun
|
||||
#include <alloca.h>
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ void I_InitMusic (void)
|
|||
if (!setatterm)
|
||||
{
|
||||
setatterm = true;
|
||||
atterm (I_ShutdownMusic);
|
||||
atterm (I_ShutdownMusicExit);
|
||||
|
||||
#ifndef _WIN32
|
||||
signal (SIGCHLD, ChildSigHandler);
|
||||
|
|
@ -178,7 +178,7 @@ void I_InitMusic (void)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
void I_ShutdownMusic(void)
|
||||
void I_ShutdownMusic(bool onexit)
|
||||
{
|
||||
if (MusicDown)
|
||||
return;
|
||||
|
|
@ -189,11 +189,17 @@ void I_ShutdownMusic(void)
|
|||
assert (currSong == NULL);
|
||||
}
|
||||
Timidity::FreeAll();
|
||||
if (onexit) WildMidi_Shutdown();
|
||||
#ifdef _WIN32
|
||||
I_ShutdownMusicWin32();
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
void I_ShutdownMusicExit()
|
||||
{
|
||||
I_ShutdownMusic(true);
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
|
@ -280,6 +286,10 @@ void MusInfo::FluidSettingStr(const char *, const char *)
|
|||
{
|
||||
}
|
||||
|
||||
void MusInfo::WildMidiSetOption(int opt, int set)
|
||||
{
|
||||
}
|
||||
|
||||
FString MusInfo::GetStats()
|
||||
{
|
||||
return "No stats available for this song";
|
||||
|
|
@ -301,21 +311,21 @@ MusInfo *MusInfo::GetWaveDumper(const char *filename, int rate)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
static MIDIStreamer *CreateMIDIStreamer(FileReader &reader, EMidiDevice devtype, EMIDIType miditype)
|
||||
static MIDIStreamer *CreateMIDIStreamer(FileReader &reader, EMidiDevice devtype, EMIDIType miditype, const char *args)
|
||||
{
|
||||
switch (miditype)
|
||||
{
|
||||
case MIDI_MUS:
|
||||
return new MUSSong2(reader, devtype);
|
||||
return new MUSSong2(reader, devtype, args);
|
||||
|
||||
case MIDI_MIDI:
|
||||
return new MIDISong2(reader, devtype);
|
||||
return new MIDISong2(reader, devtype, args);
|
||||
|
||||
case MIDI_HMI:
|
||||
return new HMISong(reader, devtype);
|
||||
return new HMISong(reader, devtype, args);
|
||||
|
||||
case MIDI_XMI:
|
||||
return new XMISong(reader, devtype);
|
||||
return new XMISong(reader, devtype, args);
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
|
|
@ -377,7 +387,7 @@ static EMIDIType IdentifyMIDIType(DWORD *id, int size)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
MusInfo *I_RegisterSong (FileReader *reader, int device)
|
||||
MusInfo *I_RegisterSong (FileReader *reader, MidiDeviceSetting *device)
|
||||
{
|
||||
MusInfo *info = NULL;
|
||||
const char *fmt;
|
||||
|
|
@ -395,12 +405,6 @@ MusInfo *I_RegisterSong (FileReader *reader, int device)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// non-Windows platforms don't support MDEV_MMAPI so map to MDEV_SNDSYS
|
||||
if (device == MDEV_MMAPI)
|
||||
device = MDEV_SNDSYS;
|
||||
#endif
|
||||
|
||||
// Check for gzip compression. Some formats are expected to have players
|
||||
// that can handle it, so it simplifies things if we make all songs
|
||||
// gzippable.
|
||||
|
|
@ -437,10 +441,15 @@ MusInfo *I_RegisterSong (FileReader *reader, int device)
|
|||
EMIDIType miditype = IdentifyMIDIType(id, sizeof(id));
|
||||
if (miditype != MIDI_NOTMIDI)
|
||||
{
|
||||
EMidiDevice devtype = (EMidiDevice)device;
|
||||
EMidiDevice devtype = device == NULL? MDEV_DEFAULT : (EMidiDevice)device->device;
|
||||
#ifndef _WIN32
|
||||
// non-Windows platforms don't support MDEV_MMAPI so map to MDEV_SNDSYS
|
||||
if (devtype == MDEV_MMAPI)
|
||||
devtype = MDEV_SNDSYS;
|
||||
#endif
|
||||
|
||||
retry_as_sndsys:
|
||||
info = CreateMIDIStreamer(*reader, devtype, miditype);
|
||||
info = CreateMIDIStreamer(*reader, devtype, miditype, device != NULL? device->args.GetChars() : "");
|
||||
if (info != NULL && !info->IsValid())
|
||||
{
|
||||
delete info;
|
||||
|
|
@ -454,7 +463,7 @@ retry_as_sndsys:
|
|||
#ifdef _WIN32
|
||||
if (info == NULL && devtype != MDEV_MMAPI && snd_mididevice >= 0)
|
||||
{
|
||||
info = CreateMIDIStreamer(*reader, MDEV_MMAPI, miditype);
|
||||
info = CreateMIDIStreamer(*reader, MDEV_MMAPI, miditype, "");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -465,7 +474,7 @@ retry_as_sndsys:
|
|||
(id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) || // DosBox Raw OPL
|
||||
(id[0] == MAKE_ID('A','D','L','I') && *((BYTE *)id + 4) == 'B')) // Martin Fernandez's modified IMF
|
||||
{
|
||||
info = new OPLMUSSong (*reader);
|
||||
info = new OPLMUSSong (*reader, device != NULL? device->args.GetChars() : "");
|
||||
}
|
||||
// Check for game music
|
||||
else if ((fmt = GME_CheckFormat(id[0])) != NULL && fmt[0] != '\0')
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ struct FOptionValues;
|
|||
// MUSIC I/O
|
||||
//
|
||||
void I_InitMusic ();
|
||||
void I_ShutdownMusic ();
|
||||
void I_ShutdownMusic (bool onexit = false);
|
||||
void I_ShutdownMusicExit ();
|
||||
void I_BuildMIDIMenuList (FOptionValues *);
|
||||
void I_UpdateMusic ();
|
||||
|
||||
|
|
@ -52,7 +53,8 @@ void I_SetMusicVolume (float volume);
|
|||
|
||||
// Registers a song handle to song data.
|
||||
class MusInfo;
|
||||
MusInfo *I_RegisterSong (FileReader *reader, int device);
|
||||
struct MidiDeviceSetting;
|
||||
MusInfo *I_RegisterSong (FileReader *reader, MidiDeviceSetting *device);
|
||||
MusInfo *I_RegisterCDSong (int track, int cdid = 0);
|
||||
MusInfo *I_RegisterURLSong (const char *url);
|
||||
|
||||
|
|
@ -81,6 +83,7 @@ public:
|
|||
virtual void FluidSettingInt(const char *setting, int value); // FluidSynth settings
|
||||
virtual void FluidSettingNum(const char *setting, double value); // "
|
||||
virtual void FluidSettingStr(const char *setting, const char *value); // "
|
||||
virtual void WildMidiSetOption(int opt, int set);
|
||||
|
||||
void Start(bool loop, float rel_vol = -1.f, int subsong = 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "i_music.h"
|
||||
#include "s_sound.h"
|
||||
#include "files.h"
|
||||
#include "wildmidi/wildmidi_lib.h"
|
||||
|
||||
void I_InitMusicWin32 ();
|
||||
void I_ShutdownMusicWin32 ();
|
||||
|
|
@ -101,6 +102,7 @@ public:
|
|||
virtual void FluidSettingInt(const char *setting, int value);
|
||||
virtual void FluidSettingNum(const char *setting, double value);
|
||||
virtual void FluidSettingStr(const char *setting, const char *value);
|
||||
virtual void WildMidiSetOption(int opt, int set);
|
||||
virtual bool Preprocess(MIDIStreamer *song, bool looping);
|
||||
virtual FString GetStats();
|
||||
};
|
||||
|
|
@ -183,7 +185,7 @@ public:
|
|||
class TimidityPPMIDIDevice : public PseudoMIDIDevice
|
||||
{
|
||||
public:
|
||||
TimidityPPMIDIDevice();
|
||||
TimidityPPMIDIDevice(const char *args);
|
||||
~TimidityPPMIDIDevice();
|
||||
|
||||
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
||||
|
|
@ -218,7 +220,6 @@ protected:
|
|||
#endif
|
||||
};
|
||||
|
||||
|
||||
// Base class for software synthesizer MIDI output devices ------------------
|
||||
|
||||
class SoftSynthMIDIDevice : public MIDIDevice
|
||||
|
|
@ -269,7 +270,7 @@ protected:
|
|||
class OPLMIDIDevice : public SoftSynthMIDIDevice, protected OPLmusicBlock
|
||||
{
|
||||
public:
|
||||
OPLMIDIDevice();
|
||||
OPLMIDIDevice(const char *args);
|
||||
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
||||
void Close();
|
||||
int GetTechnology() const;
|
||||
|
|
@ -302,7 +303,7 @@ namespace Timidity { struct Renderer; }
|
|||
class TimidityMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
public:
|
||||
TimidityMIDIDevice();
|
||||
TimidityMIDIDevice(const char *args);
|
||||
~TimidityMIDIDevice();
|
||||
|
||||
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
||||
|
|
@ -331,6 +332,27 @@ protected:
|
|||
FILE *File;
|
||||
};
|
||||
|
||||
// WildMidi implementation of a MIDI device ---------------------------------
|
||||
|
||||
class WildMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
public:
|
||||
WildMIDIDevice(const char *args);
|
||||
~WildMIDIDevice();
|
||||
|
||||
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
||||
void PrecacheInstruments(const WORD *instruments, int count);
|
||||
FString GetStats();
|
||||
|
||||
protected:
|
||||
WildMidi_Renderer *Renderer;
|
||||
|
||||
void HandleEvent(int status, int parm1, int parm2);
|
||||
void HandleLongEvent(const BYTE *data, int len);
|
||||
void ComputeOutput(float *buffer, int len);
|
||||
void WildMidiSetOption(int opt, int set);
|
||||
};
|
||||
|
||||
// FluidSynth implementation of a MIDI device -------------------------------
|
||||
|
||||
#ifdef HAVE_FLUIDSYNTH
|
||||
|
|
@ -344,7 +366,7 @@ struct fluid_synth_t;
|
|||
class FluidSynthMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
public:
|
||||
FluidSynthMIDIDevice();
|
||||
FluidSynthMIDIDevice(const char *args);
|
||||
~FluidSynthMIDIDevice();
|
||||
|
||||
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
||||
|
|
@ -409,7 +431,7 @@ protected:
|
|||
class MIDIStreamer : public MusInfo
|
||||
{
|
||||
public:
|
||||
MIDIStreamer(EMidiDevice type);
|
||||
MIDIStreamer(EMidiDevice type, const char *args);
|
||||
~MIDIStreamer();
|
||||
|
||||
void MusicVolumeChanged();
|
||||
|
|
@ -427,6 +449,7 @@ public:
|
|||
void FluidSettingInt(const char *setting, int value);
|
||||
void FluidSettingNum(const char *setting, double value);
|
||||
void FluidSettingStr(const char *setting, const char *value);
|
||||
void WildMidiSetOption(int opt, int set);
|
||||
void CreateSMF(TArray<BYTE> &file, int looplimit=0);
|
||||
|
||||
protected:
|
||||
|
|
@ -494,6 +517,7 @@ protected:
|
|||
bool CallbackIsThreaded;
|
||||
int LoopLimit;
|
||||
FString DumpFilename;
|
||||
FString Args;
|
||||
};
|
||||
|
||||
// MUS file played with a MIDI stream ---------------------------------------
|
||||
|
|
@ -501,7 +525,7 @@ protected:
|
|||
class MUSSong2 : public MIDIStreamer
|
||||
{
|
||||
public:
|
||||
MUSSong2(FileReader &reader, EMidiDevice type);
|
||||
MUSSong2(FileReader &reader, EMidiDevice type, const char *args);
|
||||
~MUSSong2();
|
||||
|
||||
MusInfo *GetOPLDumper(const char *filename);
|
||||
|
|
@ -527,7 +551,7 @@ protected:
|
|||
class MIDISong2 : public MIDIStreamer
|
||||
{
|
||||
public:
|
||||
MIDISong2(FileReader &reader, EMidiDevice type);
|
||||
MIDISong2(FileReader &reader, EMidiDevice type, const char *args);
|
||||
~MIDISong2();
|
||||
|
||||
MusInfo *GetOPLDumper(const char *filename);
|
||||
|
|
@ -546,7 +570,7 @@ protected:
|
|||
struct TrackInfo;
|
||||
|
||||
void ProcessInitialMetaEvents ();
|
||||
DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay);
|
||||
DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom);
|
||||
TrackInfo *FindNextDue ();
|
||||
|
||||
BYTE *MusHeader;
|
||||
|
|
@ -584,7 +608,7 @@ protected:
|
|||
class HMISong : public MIDIStreamer
|
||||
{
|
||||
public:
|
||||
HMISong(FileReader &reader, EMidiDevice type);
|
||||
HMISong(FileReader &reader, EMidiDevice type, const char *args);
|
||||
~HMISong();
|
||||
|
||||
MusInfo *GetOPLDumper(const char *filename);
|
||||
|
|
@ -627,7 +651,7 @@ protected:
|
|||
class XMISong : public MIDIStreamer
|
||||
{
|
||||
public:
|
||||
XMISong(FileReader &reader, EMidiDevice type);
|
||||
XMISong(FileReader &reader, EMidiDevice type, const char *args);
|
||||
~XMISong();
|
||||
|
||||
MusInfo *GetOPLDumper(const char *filename);
|
||||
|
|
@ -690,7 +714,7 @@ protected:
|
|||
class OPLMUSSong : public StreamSong
|
||||
{
|
||||
public:
|
||||
OPLMUSSong (FileReader &reader);
|
||||
OPLMUSSong (FileReader &reader, const char *args);
|
||||
~OPLMUSSong ();
|
||||
void Play (bool looping, int subsong);
|
||||
bool IsPlaying ();
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ CUSTOM_CVAR(Int, fluid_chorus_type, FLUID_CHORUS_DEFAULT_TYPE, CVAR_ARCHIVE|CVAR
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
FluidSynthMIDIDevice::FluidSynthMIDIDevice()
|
||||
FluidSynthMIDIDevice::FluidSynthMIDIDevice(const char *args)
|
||||
{
|
||||
FluidSynth = NULL;
|
||||
FluidSettings = NULL;
|
||||
|
|
@ -293,7 +293,15 @@ FluidSynthMIDIDevice::FluidSynthMIDIDevice()
|
|||
fluid_reverb_width, fluid_reverb_level);
|
||||
fluid_synth_set_chorus(FluidSynth, fluid_chorus_voices, fluid_chorus_level,
|
||||
fluid_chorus_speed, fluid_chorus_depth, fluid_chorus_type);
|
||||
if (0 == LoadPatchSets(fluid_patchset))
|
||||
|
||||
// try loading a patch set that got specified with $mididevice.
|
||||
int res = 0;
|
||||
if (args != NULL && *args != 0)
|
||||
{
|
||||
res = LoadPatchSets(args);
|
||||
}
|
||||
|
||||
if (res == 0 && 0 == LoadPatchSets(fluid_patchset))
|
||||
{
|
||||
#ifdef __unix__
|
||||
// This is the standard location on Ubuntu.
|
||||
|
|
|
|||
|
|
@ -128,8 +128,8 @@ extern char MIDI_CommonLengths[15];
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
HMISong::HMISong (FileReader &reader, EMidiDevice type)
|
||||
: MIDIStreamer(type), MusHeader(0), Tracks(0)
|
||||
HMISong::HMISong (FileReader &reader, EMidiDevice type, const char *args)
|
||||
: MIDIStreamer(type, args), MusHeader(0), Tracks(0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (ExitEvent == NULL)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ static DWORD nummididevices;
|
|||
static bool nummididevicesset;
|
||||
|
||||
#ifdef HAVE_FLUIDSYNTH
|
||||
#define NUM_DEF_DEVICES 5
|
||||
#define NUM_DEF_DEVICES 6
|
||||
#else
|
||||
#define NUM_DEF_DEVICES 4
|
||||
#define NUM_DEF_DEVICES 5
|
||||
#endif
|
||||
|
||||
static void AddDefaultMidiDevices(FOptionValues *opt)
|
||||
|
|
@ -33,8 +33,10 @@ static void AddDefaultMidiDevices(FOptionValues *opt)
|
|||
pair[p+1].Value = -3.0;
|
||||
pair[p+2].Text = "TiMidity++";
|
||||
pair[p+2].Value = -2.0;
|
||||
pair[p+3].Text = "Sound System";
|
||||
pair[p+3].Value = -1.0;
|
||||
pair[p+3].Text = "WildMidi";
|
||||
pair[p+3].Value = -6.0;
|
||||
pair[p+4].Text = "Sound System";
|
||||
pair[p+4].Value = -1.0;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|||
if (!nummididevicesset)
|
||||
return;
|
||||
|
||||
if ((self >= (signed)nummididevices) || (self < -5))
|
||||
if ((self >= (signed)nummididevices) || (self < -6))
|
||||
{
|
||||
Printf ("ID out of range. Using default device.\n");
|
||||
self = 0;
|
||||
|
|
@ -166,6 +168,7 @@ CCMD (snd_listmididevices)
|
|||
MIDIOUTCAPS caps;
|
||||
MMRESULT res;
|
||||
|
||||
PrintMidiDevice (-6, "WildMidi", MOD_SWSYNTH, 0);
|
||||
#ifdef HAVE_FLUIDSYNTH
|
||||
PrintMidiDevice (-5, "FluidSynth", MOD_SWSYNTH, 0);
|
||||
#endif
|
||||
|
|
@ -196,8 +199,8 @@ CCMD (snd_listmididevices)
|
|||
|
||||
CUSTOM_CVAR(Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||
{
|
||||
if (self < -5)
|
||||
self = -5;
|
||||
if (self < -6)
|
||||
self = -6;
|
||||
else if (self > -1)
|
||||
self = -1;
|
||||
else
|
||||
|
|
@ -211,6 +214,7 @@ void I_BuildMIDIMenuList (FOptionValues *opt)
|
|||
|
||||
CCMD (snd_listmididevices)
|
||||
{
|
||||
Printf("%s-6. WildMidi\n", -6 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||
#ifdef HAVE_FLUIDSYNTH
|
||||
Printf("%s-5. FluidSynth\n", -5 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ CUSTOM_CVAR (Int, timidity_frequency, 22050, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
TimidityPPMIDIDevice::TimidityPPMIDIDevice()
|
||||
TimidityPPMIDIDevice::TimidityPPMIDIDevice(const char *args)
|
||||
: DiskName("zmid"),
|
||||
#ifdef _WIN32
|
||||
ReadWavePipe(INVALID_HANDLE_VALUE), WriteWavePipe(INVALID_HANDLE_VALUE),
|
||||
|
|
@ -85,7 +85,13 @@ TimidityPPMIDIDevice::TimidityPPMIDIDevice()
|
|||
#ifndef _WIN32
|
||||
WavePipe[0] = WavePipe[1] = -1;
|
||||
#endif
|
||||
|
||||
|
||||
if (args == NULL || *args == 0) args = timidity_exe;
|
||||
|
||||
CommandLine.Format("%s %s -EFchorus=%s -EFreverb=%s -s%d ",
|
||||
args, *timidity_extargs,
|
||||
*timidity_chorus, *timidity_reverb, *timidity_frequency);
|
||||
|
||||
if (DiskName == NULL)
|
||||
{
|
||||
Printf(PRINT_BOLD, "Could not create temp music file\n");
|
||||
|
|
@ -187,10 +193,6 @@ int TimidityPPMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWO
|
|||
Validated = true;
|
||||
#endif // WIN32
|
||||
|
||||
CommandLine.Format("%s %s -EFchorus=%s -EFreverb=%s -s%d ",
|
||||
*timidity_exe, *timidity_extargs,
|
||||
*timidity_chorus, *timidity_reverb, *timidity_frequency);
|
||||
|
||||
pipeSize = (timidity_pipe * timidity_frequency / 1000)
|
||||
<< (timidity_stereo + !timidity_8bit);
|
||||
|
||||
|
|
|
|||
|
|
@ -89,12 +89,12 @@ static const BYTE StaticMIDIhead[] =
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDIStreamer::MIDIStreamer(EMidiDevice type)
|
||||
MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
|
||||
:
|
||||
#ifdef _WIN32
|
||||
PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
|
||||
#endif
|
||||
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type)
|
||||
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), Args(args)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
|
|
@ -240,6 +240,7 @@ EMidiDevice MIDIStreamer::SelectMIDIDevice(EMidiDevice device)
|
|||
#ifdef HAVE_FLUIDSYNTH
|
||||
case -5: return MDEV_FLUIDSYNTH;
|
||||
#endif
|
||||
case -6: return MDEV_WILDMIDI;
|
||||
default:
|
||||
#ifdef _WIN32
|
||||
return MDEV_MMAPI;
|
||||
|
|
@ -268,19 +269,19 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype) const
|
|||
|
||||
#ifdef HAVE_FLUIDSYNTH
|
||||
case MDEV_FLUIDSYNTH:
|
||||
return new FluidSynthMIDIDevice;
|
||||
return new FluidSynthMIDIDevice(Args);
|
||||
#endif
|
||||
|
||||
case MDEV_SNDSYS:
|
||||
return new SndSysMIDIDevice;
|
||||
|
||||
case MDEV_GUS:
|
||||
return new TimidityMIDIDevice;
|
||||
return new TimidityMIDIDevice(Args);
|
||||
|
||||
case MDEV_OPL:
|
||||
try
|
||||
{
|
||||
return new OPLMIDIDevice;
|
||||
return new OPLMIDIDevice(Args);
|
||||
}
|
||||
catch (CRecoverableError &err)
|
||||
{
|
||||
|
|
@ -290,7 +291,10 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype) const
|
|||
}
|
||||
|
||||
case MDEV_TIMIDITY:
|
||||
return new TimidityPPMIDIDevice;
|
||||
return new TimidityPPMIDIDevice(Args);
|
||||
|
||||
case MDEV_WILDMIDI:
|
||||
return new WildMIDIDevice(Args);
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
|
|
@ -622,6 +626,21 @@ void MIDIStreamer::FluidSettingStr(const char *setting, const char *value)
|
|||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDeviceStreamer :: WildMidiSetOption
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIStreamer::WildMidiSetOption(int opt, int set)
|
||||
{
|
||||
if (MIDI != NULL)
|
||||
{
|
||||
MIDI->WildMidiSetOption(opt, set);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIStreamer :: OutputVolume
|
||||
|
|
@ -1192,9 +1211,15 @@ void MIDIStreamer::CreateSMF(TArray<BYTE> &file, int looplimit)
|
|||
len--;
|
||||
file.Push(MIDI_SYSEX);
|
||||
WriteVarLen(file, len);
|
||||
memcpy(&file[file.Reserve(len - 1)], bytes, len);
|
||||
running_status = 255;
|
||||
memcpy(&file[file.Reserve(len)], bytes + 1, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
file.Push(MIDI_SYSEXEND);
|
||||
WriteVarLen(file, len);
|
||||
memcpy(&file[file.Reserve(len)], bytes, len);
|
||||
}
|
||||
running_status = 255;
|
||||
}
|
||||
else if (MEVT_EVENTTYPE(event[2]) == 0)
|
||||
{
|
||||
|
|
@ -1512,6 +1537,16 @@ void MIDIDevice::FluidSettingStr(const char *setting, const char *value)
|
|||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: WildMidiSetOption
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIDevice::WildMidiSetOption(int opt, int set)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: GetStats
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include "doomdef.h"
|
||||
#include "m_swap.h"
|
||||
#include "files.h"
|
||||
#include "s_sound.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
|
|
@ -92,8 +93,8 @@ static const BYTE CtrlTranslate[15] =
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
MUSSong2::MUSSong2 (FileReader &reader, EMidiDevice type)
|
||||
: MIDIStreamer(type), MusHeader(0), MusBuffer(0)
|
||||
MUSSong2::MUSSong2 (FileReader &reader, EMidiDevice type, const char *args)
|
||||
: MIDIStreamer(type, args), MusHeader(0), MusBuffer(0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (ExitEvent == NULL)
|
||||
|
|
@ -210,23 +211,44 @@ bool MUSSong2::CheckDone()
|
|||
|
||||
void MUSSong2::Precache()
|
||||
{
|
||||
WORD *work = (WORD *)alloca(MusHeader->NumInstruments * sizeof(WORD));
|
||||
const WORD *used = (WORD *)MusHeader + sizeof(MUSHeader) / sizeof(WORD);
|
||||
int i, j;
|
||||
TArray<WORD> work(MusHeader->NumInstruments);
|
||||
const BYTE *used = (BYTE *)MusHeader + sizeof(MUSHeader) / sizeof(BYTE);
|
||||
int i, k;
|
||||
|
||||
for (i = j = 0; i < MusHeader->NumInstruments; ++i)
|
||||
for (i = k = 0; i < MusHeader->NumInstruments; ++i)
|
||||
{
|
||||
WORD instr = LittleShort(used[i]);
|
||||
BYTE instr = used[k++];
|
||||
WORD val;
|
||||
if (instr < 128)
|
||||
{
|
||||
work[j++] = instr;
|
||||
val = instr;
|
||||
}
|
||||
else if (used[i] >= 135 && used[i] <= 181)
|
||||
else if (instr >= 135 && instr <= 188)
|
||||
{ // Percussions are 100-based, not 128-based, eh?
|
||||
work[j++] = instr - 100 + (1 << 14);
|
||||
val = instr - 100 + (1 << 14);
|
||||
}
|
||||
else
|
||||
{
|
||||
// skip it.
|
||||
val = used[k++];
|
||||
k += val;
|
||||
continue;
|
||||
}
|
||||
|
||||
int numbanks = used[k++];
|
||||
if (numbanks > 0)
|
||||
{
|
||||
for (int b = 0; b < numbanks; b++)
|
||||
{
|
||||
work.Push(val | (used[k++] << 7));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work.Push(val);
|
||||
}
|
||||
}
|
||||
MIDI->PrecacheInstruments(&work[0], j);
|
||||
MIDI->PrecacheInstruments(&work[0], work.Size());
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
|||
|
|
@ -20,16 +20,25 @@ CUSTOM_CVAR (Int, opl_numchips, 2, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|||
}
|
||||
}
|
||||
|
||||
CVAR(Int, opl_core, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||
CVAR(Int, opl_core, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
int current_opl_core;
|
||||
|
||||
OPLMUSSong::OPLMUSSong (FileReader &reader)
|
||||
// Get OPL core override from $mididevice
|
||||
void OPL_SetCore(const char *args)
|
||||
{
|
||||
current_opl_core = opl_core;
|
||||
if (args != NULL && *args >= '0' && *args < '4') current_opl_core = *args - '0';
|
||||
}
|
||||
|
||||
OPLMUSSong::OPLMUSSong (FileReader &reader, const char *args)
|
||||
{
|
||||
int samples = int(OPL_SAMPLE_RATE / 14);
|
||||
|
||||
OPL_SetCore(args);
|
||||
Music = new OPLmusicFile (&reader);
|
||||
|
||||
m_Stream = GSnd->CreateStream (FillStream, samples*4,
|
||||
(opl_core == 0 ? SoundStream::Mono : 0) | SoundStream::Float, int(OPL_SAMPLE_RATE), this);
|
||||
(current_opl_core == 0 ? SoundStream::Mono : 0) | SoundStream::Float, int(OPL_SAMPLE_RATE), this);
|
||||
if (m_Stream == NULL)
|
||||
{
|
||||
Printf (PRINT_BOLD, "Could not create music stream.\n");
|
||||
|
|
|
|||
|
|
@ -102,8 +102,8 @@ char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDISong2::MIDISong2 (FileReader &reader, EMidiDevice type)
|
||||
: MIDIStreamer(type), MusHeader(0), Tracks(0)
|
||||
MIDISong2::MIDISong2 (FileReader &reader, EMidiDevice type, const char *args)
|
||||
: MIDIStreamer(type, args), MusHeader(0), Tracks(0)
|
||||
{
|
||||
int p;
|
||||
int i;
|
||||
|
|
@ -322,7 +322,12 @@ DWORD *MIDISong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
|
|||
// Play all events for this tick.
|
||||
do
|
||||
{
|
||||
DWORD *new_events = SendCommand(events, TrackDue, time);
|
||||
bool sysex_noroom = false;
|
||||
DWORD *new_events = SendCommand(events, TrackDue, time, max_event_p - events, sysex_noroom);
|
||||
if (sysex_noroom)
|
||||
{
|
||||
return events;
|
||||
}
|
||||
TrackDue = FindNextDue();
|
||||
if (new_events != events)
|
||||
{
|
||||
|
|
@ -366,12 +371,15 @@ void MIDISong2::AdvanceTracks(DWORD time)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
|
||||
DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom)
|
||||
{
|
||||
DWORD len;
|
||||
BYTE event, data1 = 0, data2 = 0;
|
||||
int i;
|
||||
|
||||
sysex_noroom = false;
|
||||
size_t start_p = track->TrackP;
|
||||
|
||||
CHECK_FINISHED
|
||||
event = track->TrackBegin[track->TrackP++];
|
||||
CHECK_FINISHED
|
||||
|
|
@ -588,13 +596,44 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
|
|||
}
|
||||
else
|
||||
{
|
||||
// Skip SysEx events just because I don't want to bother with them.
|
||||
// The old MIDI player ignored them too, so this won't break
|
||||
// anything that played before.
|
||||
// SysEx events could potentially not have enough room in the buffer...
|
||||
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
|
||||
{
|
||||
len = track->ReadVarLen ();
|
||||
track->TrackP += len;
|
||||
len = track->ReadVarLen();
|
||||
if (len >= (MAX_EVENTS-1)*3*4)
|
||||
{ // This message will never fit. Throw it away.
|
||||
track->TrackP += len;
|
||||
}
|
||||
else if (len + 12 >= (size_t)room * 4)
|
||||
{ // Not enough room left in this buffer. Backup and wait for the next one.
|
||||
track->TrackP = start_p;
|
||||
sysex_noroom = true;
|
||||
return events;
|
||||
}
|
||||
else
|
||||
{
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
BYTE *msg = (BYTE *)&events[3];
|
||||
if (event == MIDI_SYSEX)
|
||||
{ // Need to add the SysEx marker to the message.
|
||||
events[2] = (MEVT_LONGMSG << 24) | (len + 1);
|
||||
*msg++ = MIDI_SYSEX;
|
||||
}
|
||||
else
|
||||
{
|
||||
events[2] = (MEVT_LONGMSG << 24) | len;
|
||||
}
|
||||
memcpy(msg, &track->TrackBegin[track->TrackP], len);
|
||||
msg += len;
|
||||
// Must pad with 0
|
||||
while ((size_t)msg & 3)
|
||||
{
|
||||
*msg++ = 0;
|
||||
}
|
||||
events = (DWORD *)msg;
|
||||
track->TrackP += len;
|
||||
}
|
||||
}
|
||||
else if (event == MIDI_META)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -86,10 +86,10 @@ struct FmtChunk
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
TimidityMIDIDevice::TimidityMIDIDevice()
|
||||
TimidityMIDIDevice::TimidityMIDIDevice(const char *args)
|
||||
{
|
||||
Renderer = NULL;
|
||||
Renderer = new Timidity::Renderer((float)SampleRate);
|
||||
Renderer = new Timidity::Renderer((float)SampleRate, args);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
@ -245,6 +245,7 @@ FString TimidityMIDIDevice::GetStats()
|
|||
//==========================================================================
|
||||
|
||||
TimidityWaveWriterMIDIDevice::TimidityWaveWriterMIDIDevice(const char *filename, int rate)
|
||||
:TimidityMIDIDevice(NULL)
|
||||
{
|
||||
File = fopen(filename, "wb");
|
||||
if (File != NULL)
|
||||
|
|
|
|||
234
src/sound/music_wildmidi_mididevice.cpp
Normal file
234
src/sound/music_wildmidi_mididevice.cpp
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
** music_wildmidi_mididevice.cpp
|
||||
** Provides access to WildMidi as a generic MIDI device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2015 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "i_musicinterns.h"
|
||||
#include "templates.h"
|
||||
#include "doomdef.h"
|
||||
#include "m_swap.h"
|
||||
#include "w_wad.h"
|
||||
#include "v_text.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
static FString CurrentConfig;
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
CVAR(String, wildmidi_config, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
CVAR(Int, wildmidi_frequency, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||
CUSTOM_CVAR(Bool, wildmidi_reverb, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL)
|
||||
{
|
||||
if (currSong != NULL)
|
||||
currSong->WildMidiSetOption(WM_MO_REVERB, *self? WM_MO_REVERB:0);
|
||||
}
|
||||
|
||||
CUSTOM_CVAR(Bool, wildmidi_enhanced_resampling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL)
|
||||
{
|
||||
if (currSong != NULL)
|
||||
currSong->WildMidiSetOption(WM_MO_ENHANCED_RESAMPLING, *self? WM_MO_ENHANCED_RESAMPLING:0);
|
||||
}
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
WildMIDIDevice::WildMIDIDevice(const char *args)
|
||||
{
|
||||
Renderer = NULL;
|
||||
|
||||
if (wildmidi_frequency >= 11025 && wildmidi_frequency < 65536)
|
||||
{ // Use our own sample rate instead of the global one
|
||||
SampleRate = wildmidi_frequency;
|
||||
}
|
||||
else
|
||||
{ // Else make sure we're not outside of WildMidi's range
|
||||
SampleRate = clamp(SampleRate, 11025, 65535);
|
||||
}
|
||||
|
||||
if (args == NULL || *args == 0) args = wildmidi_config;
|
||||
|
||||
if (CurrentConfig.CompareNoCase(args) != 0 || SampleRate != WildMidi_GetSampleRate())
|
||||
{
|
||||
if (CurrentConfig.IsNotEmpty())
|
||||
{
|
||||
WildMidi_Shutdown();
|
||||
CurrentConfig = "";
|
||||
}
|
||||
if (!WildMidi_Init(args, SampleRate, 0))
|
||||
{
|
||||
CurrentConfig = args;
|
||||
}
|
||||
}
|
||||
if (CurrentConfig.IsNotEmpty())
|
||||
{
|
||||
Renderer = new WildMidi_Renderer();
|
||||
int flags = 0;
|
||||
if (wildmidi_enhanced_resampling) flags |= WM_MO_ENHANCED_RESAMPLING;
|
||||
if (wildmidi_reverb) flags |= WM_MO_REVERB;
|
||||
Renderer->SetOption(WM_MO_ENHANCED_RESAMPLING | WM_MO_REVERB, flags);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
WildMIDIDevice::~WildMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
if (Renderer != NULL)
|
||||
{
|
||||
delete Renderer;
|
||||
}
|
||||
// Do not shut down the device so that it can be reused for the next song being played.
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WildMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata)
|
||||
{
|
||||
if (Renderer == NULL)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
int ret = OpenStream(2, 0, callback, userdata);
|
||||
if (ret == 0)
|
||||
{
|
||||
// Renderer->Reset();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: PrecacheInstruments
|
||||
//
|
||||
// Each entry is packed as follows:
|
||||
// Bits 0- 6: Instrument number
|
||||
// Bits 7-13: Bank number
|
||||
// Bit 14: Select drum set if 1, tone bank if 0
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::PrecacheInstruments(const WORD *instruments, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Renderer->LoadInstrument((instruments[i] >> 7) & 127, instruments[i] >> 14, instruments[i] & 127);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: HandleEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
Renderer->ShortEvent(status, parm1, parm2);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::HandleLongEvent(const BYTE *data, int len)
|
||||
{
|
||||
Renderer->LongEvent(data, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
Renderer->ComputeOutput(buffer, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FString WildMIDIDevice::GetStats()
|
||||
{
|
||||
FString out;
|
||||
out.Format("%3d voices", Renderer->GetVoiceCount());
|
||||
return out;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::WildMidiSetOption(int opt, int set)
|
||||
{
|
||||
Renderer->SetOption(opt, set);
|
||||
}
|
||||
|
|
@ -108,8 +108,8 @@ extern char MIDI_CommonLengths[15];
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
XMISong::XMISong (FileReader &reader, EMidiDevice type)
|
||||
: MIDIStreamer(type), MusHeader(0), Songs(0)
|
||||
XMISong::XMISong (FileReader &reader, EMidiDevice type, const char *args)
|
||||
: MIDIStreamer(type, args), MusHeader(0), Songs(0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (ExitEvent == NULL)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue