mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-24 21:21:04 +00:00
- made the sound decoding interface DLL friendly and added compile switches to the MIDI players so that different licenses can be uses as compile target.
This commit is contained in:
parent
77a20ea7d1
commit
f442d2dc54
22 changed files with 197 additions and 46 deletions
|
@ -171,7 +171,7 @@ struct MemoryReader : public FileInterface
|
|||
if (len < 0) len = 0;
|
||||
memcpy(buff, mData + mPos, len);
|
||||
mPos += len;
|
||||
return len / size;
|
||||
return len;
|
||||
}
|
||||
long seek(long offset, int whence) override
|
||||
{
|
||||
|
@ -216,7 +216,7 @@ struct VectorReader : public MemoryReader
|
|||
mLength = (long)mVector.size();
|
||||
mPos = 0;
|
||||
}
|
||||
VectorReader(uint8_t* data, size_t size)
|
||||
VectorReader(const uint8_t* data, size_t size)
|
||||
{
|
||||
mVector.resize(size);
|
||||
memcpy(mVector.data(), data, size);
|
||||
|
|
|
@ -58,6 +58,18 @@ int OPLio::Init(int core, uint32_t numchips, bool stereo, bool initopl3)
|
|||
uint32_t i;
|
||||
IsOPL3 = (core == 1 || core == 2 || core == 3);
|
||||
|
||||
using CoreInit = OPLEmul* (*)(bool);
|
||||
static CoreInit inits[] =
|
||||
{
|
||||
YM3812Create,
|
||||
DBOPLCreate,
|
||||
JavaOPLCreate,
|
||||
NukedOPL3Create,
|
||||
};
|
||||
|
||||
if (core < 0) core = 0;
|
||||
if (core > 3) core = 3;
|
||||
|
||||
memset(chips, 0, sizeof(chips));
|
||||
if (IsOPL3)
|
||||
{
|
||||
|
@ -65,7 +77,7 @@ int OPLio::Init(int core, uint32_t numchips, bool stereo, bool initopl3)
|
|||
}
|
||||
for (i = 0; i < numchips; ++i)
|
||||
{
|
||||
OPLEmul *chip = IsOPL3 ? (core == 1 ? DBOPLCreate(stereo) : (core == 2 ? JavaOPLCreate(stereo) : NukedOPL3Create(stereo))) : YM3812Create(stereo);
|
||||
OPLEmul* chip = inits[core](stereo);
|
||||
if (chip == NULL)
|
||||
{
|
||||
break;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
*/
|
||||
|
||||
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "sndfile_decoder.h"
|
||||
#include "mpg123_decoder.h"
|
||||
|
||||
|
@ -115,3 +116,29 @@ short* dumb_decode_vorbis(int outlen, const void* oggstream, int sizebytes)
|
|||
return samples;
|
||||
}
|
||||
|
||||
DLL_EXPORT struct SoundDecoder* CreateDecoder(const uint8_t* data, size_t size, bool isstatic)
|
||||
{
|
||||
MusicIO::FileInterface* reader;
|
||||
if (isstatic) reader = new MusicIO::MemoryReader(data, (long)size);
|
||||
else reader = new MusicIO::VectorReader(data, size);
|
||||
auto res = SoundDecoder::CreateDecoder(reader);
|
||||
if (!res) reader->close();
|
||||
return res;
|
||||
}
|
||||
|
||||
DLL_EXPORT void SoundDecoder_GetInfo(struct SoundDecoder* decoder, int* samplerate, ChannelConfig* chans, SampleType* type)
|
||||
{
|
||||
if (decoder) decoder->getInfo(samplerate, chans, type);
|
||||
else if (samplerate) *samplerate = 0;
|
||||
}
|
||||
|
||||
DLL_EXPORT size_t SoundDecoder_Read(struct SoundDecoder* decoder, void* buffer, size_t length)
|
||||
{
|
||||
if (decoder) return decoder->read((char*)buffer, length);
|
||||
else return 0;
|
||||
}
|
||||
|
||||
DLL_EXPORT void SoundDecoder_Close(struct SoundDecoder* decoder)
|
||||
{
|
||||
if (decoder) delete decoder;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,10 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "mididevice.h"
|
||||
|
||||
#ifdef HAVE_ADL
|
||||
#include "adlmidi.h"
|
||||
|
||||
ADLConfig adlConfig;
|
||||
|
@ -267,4 +270,10 @@ MIDIDevice *CreateADLMIDIDevice(const char *Args)
|
|||
return new ADLMIDIDevice(&config);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateADLMIDIDevice(const char* Args)
|
||||
{
|
||||
throw std::runtime_error("ADL device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <mutex>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
|
||||
|
@ -687,5 +688,12 @@ MIDIDevice *CreateFluidSynthMIDIDevice(int samplerate, const char *Args)
|
|||
Fluid_SetupConfig(Args, fluid_patchset, true);
|
||||
return new FluidSynthMIDIDevice(samplerate, fluid_patchset, musicCallbacks.Fluid_MessageFunc);
|
||||
}
|
||||
#else
|
||||
|
||||
MIDIDevice* CreateFluidSynthMIDIDevice(int samplerate, const char* Args)
|
||||
{
|
||||
throw std::runtime_error("FlidSynth device not supported in this configuration");
|
||||
}
|
||||
|
||||
|
||||
#endif // HAVE_FLUIDSYNTH
|
||||
|
|
|
@ -35,8 +35,11 @@
|
|||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
|
||||
#ifdef HAVE_OPL
|
||||
#include "oplsynth/opl.h"
|
||||
#include "oplsynth/opl_mus_player.h"
|
||||
|
||||
|
@ -324,3 +327,10 @@ MIDIDevice* CreateOplMIDIDevice(const char *Args)
|
|||
if (Args != NULL && *Args >= '0' && *Args < '4') core = *Args - '0';
|
||||
return new OPLMIDIDevice(core);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateOplMIDIDevice(const char* Args)
|
||||
{
|
||||
throw std::runtime_error("OPL device not supported in this configuration");
|
||||
}
|
||||
#endif
|
|
@ -35,6 +35,9 @@
|
|||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_OPN
|
||||
#include "opnmidi.h"
|
||||
|
||||
OpnConfig opnConfig;
|
||||
|
@ -245,4 +248,9 @@ MIDIDevice *CreateOPNMIDIDevice(const char *Args)
|
|||
return new OPNMIDIDevice(bank);
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateOPNMIDIDevice(const char* Args)
|
||||
{
|
||||
throw std::runtime_error("OPN device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -35,8 +35,11 @@
|
|||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_GUS
|
||||
|
||||
#include "timidity/timidity.h"
|
||||
#include "timidity/playmidi.h"
|
||||
#include "timidity/instrum.h"
|
||||
|
@ -294,10 +297,16 @@ bool GUS_SetupConfig(const char* args)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
MIDIDevice *CreateTimidityMIDIDevice(const char *Args, int samplerate)
|
||||
#
|
||||
MIDIDevice* CreateTimidityMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
GUS_SetupConfig(Args);
|
||||
return new TimidityMIDIDevice(samplerate);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateTimidityMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
throw std::runtime_error("GUS device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -33,6 +33,9 @@
|
|||
*/
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_TIMIDITY
|
||||
|
||||
#include "timiditypp/timidity.h"
|
||||
#include "timiditypp/instrum.h"
|
||||
|
@ -231,3 +234,9 @@ MIDIDevice *CreateTimidityPPMIDIDevice(const char *Args, int samplerate)
|
|||
return new TimidityPPMIDIDevice(samplerate);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateTimidityPPMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
throw std::runtime_error("Timidity++ device not supported in this configuration");
|
||||
}
|
||||
#endif
|
|
@ -35,6 +35,10 @@
|
|||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_WILDMIDI
|
||||
|
||||
#include "wildmidi/wildmidi_lib.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
@ -275,3 +279,9 @@ MIDIDevice *CreateWildMIDIDevice(const char *Args, int samplerate)
|
|||
return new WildMIDIDevice(samplerate);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateWildMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
throw std::runtime_error("WildMidi device not supported in this configuration");
|
||||
}
|
||||
#endif
|
|
@ -287,11 +287,9 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
#endif
|
||||
// Intentional fall-through for systems without standard midi support
|
||||
|
||||
#ifdef HAVE_FLUIDSYNTH
|
||||
case MDEV_FLUIDSYNTH:
|
||||
dev = CreateFluidSynthMIDIDevice(samplerate, Args.c_str());
|
||||
break;
|
||||
#endif // HAVE_FLUIDSYNTH
|
||||
|
||||
case MDEV_OPL:
|
||||
dev = CreateOplMIDIDevice(Args.c_str());
|
||||
|
@ -322,6 +320,8 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
#ifdef HAVE_SYSTEM_MIDI
|
||||
else if (!checked[MDEV_STANDARD]) devtype = MDEV_STANDARD;
|
||||
#endif
|
||||
else if (!checked[MDEV_ADL]) devtype = MDEV_ADL;
|
||||
else if (!checked[MDEV_OPN]) devtype = MDEV_OPN;
|
||||
else if (!checked[MDEV_OPL]) devtype = MDEV_OPL;
|
||||
|
||||
if (devtype == MDEV_DEFAULT)
|
||||
|
@ -334,13 +334,15 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
if (selectedDevice != requestedDevice && (selectedDevice != lastSelectedDevice || requestedDevice != lastRequestedDevice))
|
||||
{
|
||||
static const char *devnames[] = {
|
||||
"Windows Default",
|
||||
"System Default",
|
||||
"OPL",
|
||||
"",
|
||||
"Timidity++",
|
||||
"FluidSynth",
|
||||
"GUS",
|
||||
"WildMidi"
|
||||
"WildMidi",
|
||||
"ADL",
|
||||
"OPN",
|
||||
};
|
||||
|
||||
lastRequestedDevice = requestedDevice;
|
||||
|
|
|
@ -338,6 +338,12 @@ void FindLoopTags(MusicIO::FileInterface *fr, uint32_t *start, bool *startass, u
|
|||
FindOggComments(fr, start, startass, end, endass);
|
||||
}
|
||||
|
||||
DLL_EXPORT void FindLoopTags(const uint8_t* data, size_t size, uint32_t* start, bool* startass, uint32_t* end, bool* endass)
|
||||
{
|
||||
MusicIO::FileInterface* reader = new MusicIO::MemoryReader(data, (long)size);
|
||||
FindLoopTags(reader, start, startass, end, endass);
|
||||
reader->close();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "zmusic.h"
|
||||
#include "zmusic_internal.h"
|
||||
#include "../../libraries/music_common/fileio.h"
|
||||
|
||||
// Note: Bools here are stored as ints to allow having a simpler interface.
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../music_common/fileio.h"
|
||||
#include "zmusic_internal.h"
|
||||
#include <vector>
|
||||
|
||||
enum SampleType
|
||||
{
|
||||
SampleType_UInt8,
|
||||
SampleType_Int16
|
||||
};
|
||||
enum ChannelConfig
|
||||
{
|
||||
ChannelConfig_Mono,
|
||||
ChannelConfig_Stereo
|
||||
};
|
||||
|
||||
struct SoundDecoder
|
||||
{
|
||||
static SoundDecoder* CreateDecoder(MusicIO::FileInterface* reader);
|
||||
|
@ -37,4 +26,3 @@ protected:
|
|||
SoundDecoder(const SoundDecoder &rhs) = delete;
|
||||
SoundDecoder& operator=(const SoundDecoder &rhs) = delete;
|
||||
};
|
||||
|
||||
|
|
|
@ -59,6 +59,17 @@ struct SoundStreamInfo
|
|||
int mNumChannels; // If mNumChannels is negative, 16 bit integer format is used instead of floating point.
|
||||
};
|
||||
|
||||
enum SampleType
|
||||
{
|
||||
SampleType_UInt8,
|
||||
SampleType_Int16
|
||||
};
|
||||
enum ChannelConfig
|
||||
{
|
||||
ChannelConfig_Mono,
|
||||
ChannelConfig_Stereo
|
||||
};
|
||||
|
||||
enum EIntConfigKey
|
||||
{
|
||||
zmusic_adl_chips_count,
|
||||
|
@ -217,8 +228,10 @@ struct Callbacks
|
|||
|
||||
#ifndef ZMUSIC_INTERNAL
|
||||
#define DLL_IMPORT // _declspec(dllimport)
|
||||
// Note that the internal 'class' definitions are not C compatible!
|
||||
typedef struct { int zm1; } *ZMusic_MidiSource;
|
||||
typedef struct { int zm2; } *ZMusic_MusicStream;
|
||||
struct SoundDecoder;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -262,11 +275,21 @@ extern "C"
|
|||
DLL_IMPORT void ZMusic_GetStreamInfo(ZMusic_MusicStream song, SoundStreamInfo *info);
|
||||
// Configuration interface. The return value specifies if a music restart is needed.
|
||||
// RealValue should be written back to the CVAR or whatever other method the client uses to store configuration state.
|
||||
DLL_IMPORT bool ChangeMusicSettingInt(EIntConfigKey key, ZMusic_MusicStream song, int value, int* pRealValue = nullptr);
|
||||
DLL_IMPORT bool ChangeMusicSettingFloat(EFloatConfigKey key, ZMusic_MusicStream song, float value, float* pRealValue = nullptr);
|
||||
DLL_IMPORT bool ChangeMusicSettingInt(EIntConfigKey key, ZMusic_MusicStream song, int value, int* pRealValue);
|
||||
DLL_IMPORT bool ChangeMusicSettingFloat(EFloatConfigKey key, ZMusic_MusicStream song, float value, float* pRealValue);
|
||||
DLL_IMPORT bool ChangeMusicSettingString(EStringConfigKey key, ZMusic_MusicStream song, const char* value);
|
||||
DLL_IMPORT const char *ZMusic_GetStats(ZMusic_MusicStream song);
|
||||
|
||||
|
||||
DLL_IMPORT struct SoundDecoder* CreateDecoder(const uint8_t* data, size_t size, bool isstatic);
|
||||
DLL_IMPORT void SoundDecoder_GetInfo(struct SoundDecoder* decoder, int* samplerate, ChannelConfig* chans, SampleType* type);
|
||||
DLL_IMPORT size_t SoundDecoder_Read(struct SoundDecoder* decoder, void* buffer, size_t length);
|
||||
DLL_IMPORT void SoundDecoder_Close(struct SoundDecoder* decoder);
|
||||
DLL_IMPORT void FindLoopTags(const uint8_t* data, size_t size, uint32_t* start, bool* startass, uint32_t* end, bool* endass);
|
||||
// The rest of the decoder interface is only useful for streaming music.
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,36 @@
|
|||
typedef class MIDISource *ZMusic_MidiSource;
|
||||
typedef class MusInfo *ZMusic_MusicStream;
|
||||
|
||||
// Some MIDI backends are not compatible with LGPLv2, so with this #define the support level can be set.
|
||||
// License can be:
|
||||
// 0: LGPLv2. FluidSynth and GUS only.
|
||||
// 1: GPLv2. Adds Timidity++ and OPL.
|
||||
// 2: GPLv3. Adds ADL, OPN and WildMidi.
|
||||
//
|
||||
|
||||
// Default is GPLv3. Most Open Source games can comply with this.
|
||||
// The notable exceptions are the Build engine games and Descent which are under a no profit clause.
|
||||
// For these games LGPLv2 is the only viable choice.
|
||||
#ifndef LICENSE
|
||||
#define LICENSE 2
|
||||
#endif
|
||||
|
||||
// Devices under a permissive license, or at most LGPLv2+
|
||||
#define HAVE_GUS
|
||||
// FluidSynth is a configuration option
|
||||
|
||||
#if LICENSE >= 1
|
||||
#define HAVE_TIMIDITY
|
||||
#define HAVE_OPL
|
||||
#endif
|
||||
|
||||
#if LICENSE >= 2
|
||||
// MIDI Devices that cannot be licensed as LGPLv2.
|
||||
#define HAVE_ADL
|
||||
#define HAVE_OPN
|
||||
#define HAVE_WILDMIDI
|
||||
#endif
|
||||
|
||||
#include "zmusic.h"
|
||||
#include "../../music_common/fileio.h"
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
|
||||
#include "i_module.h"
|
||||
#include "cmdlib.h"
|
||||
#include "zmusic/sounddecoder.h"
|
||||
|
||||
#include "c_dispatch.h"
|
||||
#include "i_music.h"
|
||||
|
|
|
@ -35,8 +35,10 @@
|
|||
#ifndef __I_SOUND__
|
||||
#define __I_SOUND__
|
||||
|
||||
#include <vector>
|
||||
#include "i_soundinternal.h"
|
||||
#include "utility/zstring.h"
|
||||
#include "zmusic/zmusic.h"
|
||||
|
||||
class FileReader;
|
||||
struct FSoundChan;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "vectors.h"
|
||||
#include "tarray.h"
|
||||
#include "zmusic/sounddecoder.h"
|
||||
#include "tflags.h"
|
||||
|
||||
enum EChanFlag
|
||||
|
@ -139,9 +138,6 @@ struct FISoundChannel
|
|||
EChanFlags ChanFlags;
|
||||
};
|
||||
|
||||
|
||||
void FindLoopTags(MusicIO::FileInterface *fr, uint32_t *start, bool *startass, uint32_t *end, bool *endass);
|
||||
|
||||
class SoundStream;
|
||||
|
||||
|
||||
|
|
|
@ -43,8 +43,6 @@
|
|||
#include "i_module.h"
|
||||
#include "cmdlib.h"
|
||||
#include "m_fixed.h"
|
||||
#include "zmusic/sounddecoder.h"
|
||||
#include "filereadermusicinterface.h"
|
||||
|
||||
|
||||
const char *GetSampleTypeName(SampleType type);
|
||||
|
@ -1099,18 +1097,14 @@ std::pair<SoundHandle,bool> OpenALSoundRenderer::LoadSound(uint8_t *sfxdata, int
|
|||
/* Only downmix to mono if we can't spatialize multi-channel sounds. */
|
||||
monoize = monoize && !AL.SOFT_source_spatialize;
|
||||
|
||||
auto mreader = new MusicIO::MemoryReader(sfxdata, length);
|
||||
FindLoopTags(mreader, &loop_start, &startass, &loop_end, &endass);
|
||||
mreader->seek(0, SEEK_SET);
|
||||
std::unique_ptr<SoundDecoder> decoder(SoundDecoder::CreateDecoder(mreader));
|
||||
FindLoopTags(sfxdata, length, &loop_start, &startass, &loop_end, &endass);
|
||||
auto decoder = CreateDecoder(sfxdata, length, true);
|
||||
if (!decoder)
|
||||
{
|
||||
delete mreader;
|
||||
return std::make_pair(retval, true);
|
||||
}
|
||||
// the decode will take ownership of the reader here.
|
||||
|
||||
decoder->getInfo(&srate, &chans, &type);
|
||||
SoundDecoder_GetInfo(decoder, &srate, &chans, &type);
|
||||
int samplesize = 1;
|
||||
if (chans == ChannelConfig_Mono || monoize)
|
||||
{
|
||||
|
@ -1125,12 +1119,24 @@ std::pair<SoundHandle,bool> OpenALSoundRenderer::LoadSound(uint8_t *sfxdata, int
|
|||
|
||||
if (format == AL_NONE)
|
||||
{
|
||||
SoundDecoder_Close(decoder);
|
||||
Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
|
||||
GetSampleTypeName(type));
|
||||
return std::make_pair(retval, true);
|
||||
}
|
||||
|
||||
auto data = decoder->readAll();
|
||||
std::vector<uint8_t> data;
|
||||
unsigned total = 0;
|
||||
unsigned got;
|
||||
|
||||
data.resize(total + 32768);
|
||||
while ((got = (unsigned)SoundDecoder_Read(decoder, (char*)&data[total], data.size() - total)) > 0)
|
||||
{
|
||||
total += got;
|
||||
data.resize(total * 2);
|
||||
}
|
||||
data.resize(total);
|
||||
SoundDecoder_Close(decoder);
|
||||
|
||||
if(chans != ChannelConfig_Mono && monoize)
|
||||
{
|
||||
|
|
|
@ -53,9 +53,7 @@
|
|||
#include "i_soundfont.h"
|
||||
#include "s_music.h"
|
||||
#include "doomstat.h"
|
||||
#include "streamsources/streamsource.h"
|
||||
#include "filereadermusicinterface.h"
|
||||
#include "../libraries/zmusic/midisources/midisource.h"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
#include "vm.h"
|
||||
#include "i_system.h"
|
||||
#include "s_music.h"
|
||||
#include "mididevices/mididevice.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
|
|
Loading…
Reference in a new issue