/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see .
*
*/
// Disable symbol overrides for FILE and fseek as those are used in the
// mikmod headers.
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
// On Windows, unlink and setjmp/longjmp may also be triggered.
#if defined(WIN32)
#define FORBIDDEN_SYMBOL_EXCEPTION_chdir
#define FORBIDDEN_SYMBOL_EXCEPTION_getcwd
#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
#define FORBIDDEN_SYMBOL_EXCEPTION_unlink
#define FORBIDDEN_SYMBOL_EXCEPTION_setjmp
#define FORBIDDEN_SYMBOL_EXCEPTION_longjmp
#endif
#include "audio/mods/impulsetracker.h"
#ifdef USE_MIKMOD
#include
#include "common/inttypes.h"
#include "common/ptr.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
// Start memory wrapper
// Extras, mikmod mreader struct to wrap readstream
typedef struct MikMemoryReader {
MREADER core;
Common::SeekableReadStream *stream;
} MikMemoryReader;
static BOOL memoryReaderEof(MREADER *reader);
static BOOL memoryReaderRead(MREADER *reader, void *ptr, size_t size);
static int memoryReaderGet(MREADER *reader);
static int memoryReaderSeek(MREADER *reader, long offset, int whence);
static long memoryReaderTell(MREADER *reader);
MREADER *createMikMemoryReader(Common::SeekableReadStream *stream) {
MikMemoryReader *reader = (MikMemoryReader *)calloc(1, sizeof(MikMemoryReader));
if (reader) {
reader->core.Eof = &memoryReaderEof;
reader->core.Read = &memoryReaderRead;
reader->core.Get = &memoryReaderGet;
reader->core.Seek = &memoryReaderSeek;
reader->core.Tell = &memoryReaderTell;
reader->stream = stream;
}
return (MREADER *)reader;
}
static BOOL memoryReaderEof(MREADER *reader) {
MikMemoryReader *mr = (MikMemoryReader *)reader;
if (!mr)
return 1;
if (mr->stream && mr->stream->eos() == true)
return 1;
return 0;
}
static BOOL memoryReaderRead(MREADER *reader, void *ptr, size_t size) {
MikMemoryReader *mr;
mr = (MikMemoryReader *)reader;
if (!mr && !mr->stream)
return 0;
uint32 receivedBytes = mr->stream->read(ptr, size);
if (receivedBytes < size)
return 0; // not enough remaining bytes (or error)
return 1;
}
static int memoryReaderGet(MREADER *reader) {
MikMemoryReader *mr;
mr = (MikMemoryReader *)reader;
if (!mr->stream)
return -1;
return mr->stream->readByte();
}
static int memoryReaderSeek(MREADER *reader, long offset, int whence) {
MikMemoryReader *mr;
mr = (MikMemoryReader *)reader;
if (!reader || !mr->stream)
return -1;
return mr->stream->seek(offset, whence);
}
static long memoryReaderTell(MREADER *reader) {
if (reader)
return ((MikMemoryReader *)reader)->stream->pos();
return 0;
}
// End memory wrappper
namespace Audio {
class ImpulseTrackerMod : public RewindableAudioStream {
public:
ImpulseTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
~ImpulseTrackerMod();
// ImpulseTrackerMod functions
bool isLoaded() {
return _mikmod_load_successful;
}
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override {
// Multiplied by 2 as VC_WriteBytes function expects 8 byte integer arrays, whereas buffer needs 16 ones.
VC_WriteBytes((SBYTE *)buffer, numSamples * 2);
return numSamples;
}
bool isStereo() const override {
return true;
}
int getRate() const override {
return 44100;
}
bool endOfData() const override {
return !Player_Active();
}
bool endOfStream() const override {
return endOfData();
}
// RewindableAudioStream API
bool rewind() override {
Player_SetPosition(0);
return true;
}
private:
DisposeAfterUse::Flag _dispose;
bool _mikmod_load_successful = false;
Common::SeekableReadStream *_stream;
MREADER *_reader;
MODULE *_mod;
};
ImpulseTrackerMod::ImpulseTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
if (!stream) {
warning("ImpulseTrackerMod::ImpulseTrackerMod(): Input file/stream is invalid.");
return;
}
MikMod_InitThreads();
MikMod_RegisterDriver(&drv_nos);
// Set flags
md_mode |= DMODE_SOFT_MUSIC | DMODE_NOISEREDUCTION;
md_mixfreq = getRate();
if (MikMod_Init("")) {
warning("ImpulseTrackerMod::ImpulseTrackerMod(): Could not initialize sound, reason: %s",
MikMod_strerror(MikMod_errno));
return;
}
// Loading only impulse tracker loader!
MikMod_RegisterLoader(&load_it);
_stream = stream;
_dispose = disposeAfterUse;
// Load mod using custom loader class!
_reader = createMikMemoryReader(_stream);
_mod = Player_LoadGeneric(_reader, 64, 0);
if (!_mod) {
warning("ImpulseTrackerMod::ImpulseTrackerMod(): Parsing mod error: %s", MikMod_strerror(MikMod_errno));
return;
}
// Start mikmod playing, ie fill VC_Driver buffer with data
Player_Start(_mod);
_mikmod_load_successful = true;
}
ImpulseTrackerMod::~ImpulseTrackerMod() {
Player_Stop();
if (_mod)
Player_Free(_mod);
if (_reader)
free(_reader);
if (_dispose == DisposeAfterUse::Flag::YES)
delete _stream;
MikMod_Exit();
}
RewindableAudioStream *makeImpulseTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
ImpulseTrackerMod *impulseTrackerMod = new ImpulseTrackerMod(stream, disposeAfterUse);
return impulseTrackerMod;
}
bool probeImpulseTracker(Common::SeekableReadStream *st) {
int32 setPos = st->pos();
// xm file
char sigIt[4] = { 0 };
st->read(sigIt, 4);
st->seek(setPos);
if (!memcmp(sigIt, "IMPM", 4)) {
return true;
}
return false;
}
} // End of namespace Audio
#endif // #ifdef USE_MIKMOD