ALL: synced with ScummVM
This commit is contained in:
parent
1f3ad19a38
commit
068ff94d20
228 changed files with 11689 additions and 4232 deletions
9
Makefile
9
Makefile
|
@ -32,8 +32,10 @@ ifeq "$(HAVE_GCC)" "1"
|
||||||
# being helpful.
|
# being helpful.
|
||||||
#CXXFLAGS+= -Wmissing-format-attribute
|
#CXXFLAGS+= -Wmissing-format-attribute
|
||||||
|
|
||||||
# Disable RTTI and exceptions
|
ifneq "$(BACKEND)" "tizen"
|
||||||
|
# Disable RTTI and exceptions. These settings cause tizen apps to crash
|
||||||
CXXFLAGS+= -fno-rtti -fno-exceptions
|
CXXFLAGS+= -fno-rtti -fno-exceptions
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq "$(HAVE_CLANG)" "1"
|
ifneq "$(HAVE_CLANG)" "1"
|
||||||
# enable checking of pointers returned by "new", but only when we do not
|
# enable checking of pointers returned by "new", but only when we do not
|
||||||
|
@ -44,6 +46,11 @@ endif
|
||||||
|
|
||||||
ifeq "$(HAVE_CLANG)" "1"
|
ifeq "$(HAVE_CLANG)" "1"
|
||||||
CXXFLAGS+= -Wno-conversion -Wno-shorten-64-to-32 -Wno-sign-compare -Wno-four-char-constants
|
CXXFLAGS+= -Wno-conversion -Wno-shorten-64-to-32 -Wno-sign-compare -Wno-four-char-constants
|
||||||
|
# We use a anonymous nested type declaration in an anonymous union in
|
||||||
|
# common/str.h. This is no standard construct and clang warns about it.
|
||||||
|
# It works for all our target systems though, thus we simply disable that
|
||||||
|
# warning.
|
||||||
|
CXXFLAGS+= -Wno-nested-anon-types
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq "$(HAVE_ICC)" "1"
|
ifeq "$(HAVE_ICC)" "1"
|
||||||
|
|
|
@ -80,7 +80,7 @@ endif
|
||||||
$(EXECUTABLE): $(OBJS)
|
$(EXECUTABLE): $(OBJS)
|
||||||
$(QUIET_LINK)$(LD) $(LDFLAGS) $(PRE_OBJS_FLAGS) $+ $(POST_OBJS_FLAGS) $(LIBS) -o $@
|
$(QUIET_LINK)$(LD) $(LDFLAGS) $(PRE_OBJS_FLAGS) $+ $(POST_OBJS_FLAGS) $(LIBS) -o $@
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean clean-devtools
|
||||||
$(RM) config.h config.mk config.log
|
$(RM) config.h config.mk config.log
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -174,7 +174,7 @@ ifeq ($(origin VER_REV), undefined)
|
||||||
VER_DIRTY := $(shell cd $(srcdir); git update-index --refresh --unmerged 1>/dev/null 2>&1; git diff-index --quiet HEAD || echo "-dirty")
|
VER_DIRTY := $(shell cd $(srcdir); git update-index --refresh --unmerged 1>/dev/null 2>&1; git diff-index --quiet HEAD || echo "-dirty")
|
||||||
# Get the working copy base revision
|
# Get the working copy base revision
|
||||||
#ResidualVM: --always
|
#ResidualVM: --always
|
||||||
VER_REV := $(shell cd $(srcdir); git describe --always --match desc/\* | cut -d '-' -f 2-)$(VER_DIRTY)
|
VER_REV := $(shell cd $(srcdir); git describe --always --long --match desc/\* | cut -d '-' -f 2-)$(VER_DIRTY)
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
GITROOT := git://github.com/residualvm/residualvm.git
|
GITROOT := git://github.com/residualvm/residualvm.git
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
#include "audio/audiostream.h"
|
#include "audio/audiostream.h"
|
||||||
#include "audio/decoders/flac.h"
|
#include "audio/decoders/flac.h"
|
||||||
#include "audio/decoders/mp3.h"
|
#include "audio/decoders/mp3.h"
|
||||||
//#include "audio/decoders/quicktime.h"
|
//#include "audio/decoders/quicktime.h" do not include in REsidualVM
|
||||||
#include "audio/decoders/raw.h"
|
#include "audio/decoders/raw.h"
|
||||||
#include "audio/decoders/vorbis.h"
|
#include "audio/decoders/vorbis.h"
|
||||||
|
|
||||||
|
@ -99,6 +99,10 @@ LoopingAudioStream::LoopingAudioStream(RewindableAudioStream *stream, uint loops
|
||||||
// TODO: Properly indicate error
|
// TODO: Properly indicate error
|
||||||
_loops = _completeIterations = 1;
|
_loops = _completeIterations = 1;
|
||||||
}
|
}
|
||||||
|
if (stream->endOfData()) {
|
||||||
|
// Apparently this is an empty stream
|
||||||
|
_loops = _completeIterations = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
|
int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||||
|
@ -119,6 +123,10 @@ int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||||
_loops = _completeIterations = 1;
|
_loops = _completeIterations = 1;
|
||||||
return samplesRead;
|
return samplesRead;
|
||||||
}
|
}
|
||||||
|
if (_parent->endOfData()) {
|
||||||
|
// Apparently this is an empty stream
|
||||||
|
_loops = _completeIterations = 1;
|
||||||
|
}
|
||||||
|
|
||||||
return samplesRead + readBuffer(buffer + samplesRead, remainingSamples);
|
return samplesRead + readBuffer(buffer + samplesRead, remainingSamples);
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,7 +268,6 @@ static const int MSADPCMAdaptationTable[] = {
|
||||||
768, 614, 512, 409, 307, 230, 230, 230
|
768, 614, 512, 409, 307, 230, 230, 230
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
|
int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
|
||||||
int32 predictor;
|
int32 predictor;
|
||||||
|
|
||||||
|
@ -290,40 +289,42 @@ int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
|
||||||
int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||||
int samples;
|
int samples;
|
||||||
byte data;
|
byte data;
|
||||||
int i = 0;
|
int i;
|
||||||
|
|
||||||
samples = 0;
|
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
|
||||||
|
if (_decodedSampleCount == 0) {
|
||||||
|
if (_blockPos[0] == _blockAlign) {
|
||||||
|
// read block header
|
||||||
|
for (i = 0; i < _channels; i++) {
|
||||||
|
_status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
|
||||||
|
_status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
|
||||||
|
_status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
|
||||||
|
}
|
||||||
|
|
||||||
while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
|
for (i = 0; i < _channels; i++)
|
||||||
if (_blockPos[0] == _blockAlign) {
|
_status.ch[i].delta = _stream->readSint16LE();
|
||||||
// read block header
|
|
||||||
for (i = 0; i < _channels; i++) {
|
for (i = 0; i < _channels; i++)
|
||||||
_status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
|
_status.ch[i].sample1 = _stream->readSint16LE();
|
||||||
_status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
|
|
||||||
_status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
|
for (i = 0; i < _channels; i++)
|
||||||
|
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample2 = _stream->readSint16LE();
|
||||||
|
|
||||||
|
for (i = 0; i < _channels; i++)
|
||||||
|
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample1;
|
||||||
|
|
||||||
|
_blockPos[0] = _channels * 7;
|
||||||
|
} else {
|
||||||
|
data = _stream->readByte();
|
||||||
|
_blockPos[0]++;
|
||||||
|
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
|
||||||
|
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < _channels; i++)
|
|
||||||
_status.ch[i].delta = _stream->readSint16LE();
|
|
||||||
|
|
||||||
for (i = 0; i < _channels; i++)
|
|
||||||
_status.ch[i].sample1 = _stream->readSint16LE();
|
|
||||||
|
|
||||||
for (i = 0; i < _channels; i++)
|
|
||||||
buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE();
|
|
||||||
|
|
||||||
for (i = 0; i < _channels; i++)
|
|
||||||
buffer[samples++] = _status.ch[i].sample1;
|
|
||||||
|
|
||||||
_blockPos[0] = _channels * 7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
|
// (1 - (count - 1)) ensures that _decodedSamples acts as a FIFO of depth 2
|
||||||
data = _stream->readByte();
|
buffer[samples] = _decodedSamples[1 - (_decodedSampleCount - 1)];
|
||||||
_blockPos[0]++;
|
_decodedSampleCount--;
|
||||||
buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
|
|
||||||
buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return samples;
|
return samples;
|
||||||
|
@ -432,7 +433,7 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
|
||||||
return samp;
|
return samp;
|
||||||
}
|
}
|
||||||
|
|
||||||
RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) {
|
RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, ADPCMType type, int rate, int channels, uint32 blockAlign) {
|
||||||
// If size is 0, report the entire size of the stream
|
// If size is 0, report the entire size of the stream
|
||||||
if (!size)
|
if (!size)
|
||||||
size = stream->size();
|
size = stream->size();
|
||||||
|
|
|
@ -51,7 +51,7 @@ class RewindableAudioStream;
|
||||||
// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs
|
// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs
|
||||||
// Usually, if the audio stream we're trying to play has the FourCC header
|
// Usually, if the audio stream we're trying to play has the FourCC header
|
||||||
// string intact, it's easy to discern which encoding is used
|
// string intact, it's easy to discern which encoding is used
|
||||||
enum typesADPCM {
|
enum ADPCMType {
|
||||||
kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
|
kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
|
||||||
kADPCMMSIma, // Microsoft IMA ADPCM
|
kADPCMMSIma, // Microsoft IMA ADPCM
|
||||||
kADPCMMS, // Microsoft ADPCM
|
kADPCMMS, // Microsoft ADPCM
|
||||||
|
@ -76,9 +76,9 @@ enum typesADPCM {
|
||||||
RewindableAudioStream *makeADPCMStream(
|
RewindableAudioStream *makeADPCMStream(
|
||||||
Common::SeekableReadStream *stream,
|
Common::SeekableReadStream *stream,
|
||||||
DisposeAfterUse::Flag disposeAfterUse,
|
DisposeAfterUse::Flag disposeAfterUse,
|
||||||
uint32 size, typesADPCM type,
|
uint32 size, ADPCMType type,
|
||||||
int rate = 22050,
|
int rate,
|
||||||
int channels = 2,
|
int channels,
|
||||||
uint32 blockAlign = 0);
|
uint32 blockAlign = 0);
|
||||||
|
|
||||||
} // End of namespace Audio
|
} // End of namespace Audio
|
||||||
|
|
|
@ -206,12 +206,19 @@ public:
|
||||||
if (blockAlign == 0)
|
if (blockAlign == 0)
|
||||||
error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
|
error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
|
||||||
memset(&_status, 0, sizeof(_status));
|
memset(&_status, 0, sizeof(_status));
|
||||||
|
_decodedSampleCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); }
|
||||||
|
|
||||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int16 decodeMS(ADPCMChannelStatus *c, byte);
|
int16 decodeMS(ADPCMChannelStatus *c, byte);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8 _decodedSampleCount;
|
||||||
|
int16 _decodedSamples[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Duck DK3 IMA ADPCM Decoder
|
// Duck DK3 IMA ADPCM Decoder
|
||||||
|
|
|
@ -48,7 +48,7 @@ class SeekableAudioStream;
|
||||||
* successful. In that case, the stream's seek position will be set to the
|
* successful. In that case, the stream's seek position will be set to the
|
||||||
* start of the audio data, and size, rate and flags contain information
|
* start of the audio data, and size, rate and flags contain information
|
||||||
* necessary for playback. Currently this function only supports uncompressed
|
* necessary for playback. Currently this function only supports uncompressed
|
||||||
* raw PCM data as well as IMA ADPCM.
|
* raw PCM.
|
||||||
*/
|
*/
|
||||||
extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);
|
extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@ OPL *Config::create(DriverId driver, OplType type) {
|
||||||
return new MAME::OPL();
|
return new MAME::OPL();
|
||||||
else
|
else
|
||||||
warning("MAME OPL emulator only supports OPL2 emulation");
|
warning("MAME OPL emulator only supports OPL2 emulation");
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
#ifndef DISABLE_DOSBOX_OPL
|
#ifndef DISABLE_DOSBOX_OPL
|
||||||
case kDOSBox:
|
case kDOSBox:
|
||||||
|
|
|
@ -129,7 +129,9 @@ public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to directly write to a specific OPL register.
|
* Function to directly write to a specific OPL register.
|
||||||
* This writes to *both* chips for a Dual OPL2.
|
* This writes to *both* chips for a Dual OPL2. We allow
|
||||||
|
* writing to secondary OPL registers by using register
|
||||||
|
* values >= 0x100.
|
||||||
*
|
*
|
||||||
* @param r hardware register number to write to
|
* @param r hardware register number to write to
|
||||||
* @param v value, which will be written
|
* @param v value, which will be written
|
||||||
|
|
|
@ -194,7 +194,9 @@ public:
|
||||||
enum {
|
enum {
|
||||||
// PROP_TIMEDIV = 1,
|
// PROP_TIMEDIV = 1,
|
||||||
PROP_OLD_ADLIB = 2,
|
PROP_OLD_ADLIB = 2,
|
||||||
PROP_CHANNEL_MASK = 3
|
PROP_CHANNEL_MASK = 3,
|
||||||
|
// HACK: Not so nice, but our SCUMM AdLib code is in audio/
|
||||||
|
PROP_SCUMM_OPL3 = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,6 +46,7 @@ _numTracks(0),
|
||||||
_activeTrack(255),
|
_activeTrack(255),
|
||||||
_abortParse(0) {
|
_abortParse(0) {
|
||||||
memset(_activeNotes, 0, sizeof(_activeNotes));
|
memset(_activeNotes, 0, sizeof(_activeNotes));
|
||||||
|
memset(_tracks, 0, sizeof(_tracks));
|
||||||
_nextEvent.start = NULL;
|
_nextEvent.start = NULL;
|
||||||
_nextEvent.delta = 0;
|
_nextEvent.delta = 0;
|
||||||
_nextEvent.event = 0;
|
_nextEvent.event = 0;
|
||||||
|
|
|
@ -394,6 +394,7 @@ public:
|
||||||
|
|
||||||
static MidiParser *createParser_SMF();
|
static MidiParser *createParser_SMF();
|
||||||
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
|
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
|
||||||
|
static MidiParser *createParser_QT();
|
||||||
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
|
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "gui/EventRecorder.h"
|
||||||
|
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "common/system.h"
|
#include "common/system.h"
|
||||||
#include "common/textconsole.h"
|
#include "common/textconsole.h"
|
||||||
|
@ -171,9 +173,9 @@ private:
|
||||||
#pragma mark --- Mixer ---
|
#pragma mark --- Mixer ---
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
|
// TODO: parameter "system" is unused
|
||||||
MixerImpl::MixerImpl(OSystem *system, uint sampleRate)
|
MixerImpl::MixerImpl(OSystem *system, uint sampleRate)
|
||||||
: _syst(system), _mutex(), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0), _soundTypeSettings() {
|
: _mutex(), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0), _soundTypeSettings() {
|
||||||
|
|
||||||
assert(sampleRate > 0);
|
assert(sampleRate > 0);
|
||||||
|
|
||||||
|
@ -427,6 +429,7 @@ void MixerImpl::pauseHandle(SoundHandle handle, bool paused) {
|
||||||
|
|
||||||
bool MixerImpl::isSoundIDActive(int id) {
|
bool MixerImpl::isSoundIDActive(int id) {
|
||||||
Common::StackLock lock(_mutex);
|
Common::StackLock lock(_mutex);
|
||||||
|
g_eventRec.updateSubsystems();
|
||||||
for (int i = 0; i != NUM_CHANNELS; i++)
|
for (int i = 0; i != NUM_CHANNELS; i++)
|
||||||
if (_channels[i] && _channels[i]->getId() == id)
|
if (_channels[i] && _channels[i]->getId() == id)
|
||||||
return true;
|
return true;
|
||||||
|
@ -443,6 +446,7 @@ int MixerImpl::getSoundID(SoundHandle handle) {
|
||||||
|
|
||||||
bool MixerImpl::isSoundHandleActive(SoundHandle handle) {
|
bool MixerImpl::isSoundHandleActive(SoundHandle handle) {
|
||||||
Common::StackLock lock(_mutex);
|
Common::StackLock lock(_mutex);
|
||||||
|
g_eventRec.updateSubsystems();
|
||||||
const int index = handle._val % NUM_CHANNELS;
|
const int index = handle._val % NUM_CHANNELS;
|
||||||
return _channels[index] && _channels[index]->getHandle()._val == handle._val;
|
return _channels[index] && _channels[index]->getHandle()._val == handle._val;
|
||||||
}
|
}
|
||||||
|
@ -491,7 +495,7 @@ Channel::Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream,
|
||||||
DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent)
|
DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent)
|
||||||
: _type(type), _mixer(mixer), _id(id), _permanent(permanent), _volume(Mixer::kMaxChannelVolume),
|
: _type(type), _mixer(mixer), _id(id), _permanent(permanent), _volume(Mixer::kMaxChannelVolume),
|
||||||
_balance(0), _pauseLevel(0), _samplesConsumed(0), _samplesDecoded(0), _mixerTimeStamp(0),
|
_balance(0), _pauseLevel(0), _samplesConsumed(0), _samplesDecoded(0), _mixerTimeStamp(0),
|
||||||
_pauseStartTime(0), _pauseTime(0), _converter(0),
|
_pauseStartTime(0), _pauseTime(0), _converter(0), _volL(0), _volR(0),
|
||||||
_stream(stream, autofreeStream) {
|
_stream(stream, autofreeStream) {
|
||||||
assert(mixer);
|
assert(mixer);
|
||||||
assert(stream);
|
assert(stream);
|
||||||
|
@ -556,12 +560,12 @@ void Channel::pause(bool paused) {
|
||||||
_pauseLevel++;
|
_pauseLevel++;
|
||||||
|
|
||||||
if (_pauseLevel == 1)
|
if (_pauseLevel == 1)
|
||||||
_pauseStartTime = g_system->getMillis();
|
_pauseStartTime = g_system->getMillis(true);
|
||||||
} else if (_pauseLevel > 0) {
|
} else if (_pauseLevel > 0) {
|
||||||
_pauseLevel--;
|
_pauseLevel--;
|
||||||
|
|
||||||
if (!_pauseLevel) {
|
if (!_pauseLevel) {
|
||||||
_pauseTime = (g_system->getMillis() - _pauseStartTime);
|
_pauseTime = (g_system->getMillis(true) - _pauseStartTime);
|
||||||
_pauseStartTime = 0;
|
_pauseStartTime = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,7 +583,7 @@ Timestamp Channel::getElapsedTime() {
|
||||||
if (isPaused())
|
if (isPaused())
|
||||||
delta = _pauseStartTime - _mixerTimeStamp;
|
delta = _pauseStartTime - _mixerTimeStamp;
|
||||||
else
|
else
|
||||||
delta = g_system->getMillis() - _mixerTimeStamp - _pauseTime;
|
delta = g_system->getMillis(true) - _mixerTimeStamp - _pauseTime;
|
||||||
|
|
||||||
// Convert the number of samples into a time duration.
|
// Convert the number of samples into a time duration.
|
||||||
|
|
||||||
|
@ -599,13 +603,12 @@ int Channel::mix(int16 *data, uint len) {
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
|
|
||||||
int res = 0;
|
int res = 0;
|
||||||
|
|
||||||
if (_stream->endOfData()) {
|
if (_stream->endOfData()) {
|
||||||
// TODO: call drain method
|
// TODO: call drain method
|
||||||
} else {
|
} else {
|
||||||
assert(_converter);
|
assert(_converter);
|
||||||
_samplesConsumed = _samplesDecoded;
|
_samplesConsumed = _samplesDecoded;
|
||||||
_mixerTimeStamp = g_system->getMillis();
|
_mixerTimeStamp = g_system->getMillis(true);
|
||||||
_pauseTime = 0;
|
_pauseTime = 0;
|
||||||
res = _converter->flow(*_stream, data, len, _volL, _volR);
|
res = _converter->flow(*_stream, data, len, _volL, _volR);
|
||||||
_samplesDecoded += res;
|
_samplesDecoded += res;
|
||||||
|
|
|
@ -51,10 +51,9 @@ namespace Audio {
|
||||||
class MixerImpl : public Mixer {
|
class MixerImpl : public Mixer {
|
||||||
private:
|
private:
|
||||||
enum {
|
enum {
|
||||||
NUM_CHANNELS = 32
|
NUM_CHANNELS = 32 // ResidualVM specific
|
||||||
};
|
};
|
||||||
|
|
||||||
OSystem *_syst;
|
|
||||||
Common::Mutex _mutex;
|
Common::Mutex _mutex;
|
||||||
|
|
||||||
const uint _sampleRate;
|
const uint _sampleRate;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -127,12 +127,54 @@ int MidiDriver_FluidSynth::open() {
|
||||||
|
|
||||||
_synth = new_fluid_synth(_settings);
|
_synth = new_fluid_synth(_settings);
|
||||||
|
|
||||||
// In theory, this ought to reduce CPU load... but it doesn't make any
|
if (ConfMan.getBool("fluidsynth_chorus_activate")) {
|
||||||
// noticeable difference for me, so disable it for now.
|
fluid_synth_set_chorus_on(_synth, 1);
|
||||||
|
|
||||||
// fluid_synth_set_interp_method(_synth, -1, FLUID_INTERP_LINEAR);
|
int chorusNr = ConfMan.getInt("fluidsynth_chorus_nr");
|
||||||
// fluid_synth_set_reverb_on(_synth, 0);
|
double chorusLevel = (double)ConfMan.getInt("fluidsynth_chorus_level") / 100.0;
|
||||||
// fluid_synth_set_chorus_on(_synth, 0);
|
double chorusSpeed = (double)ConfMan.getInt("fluidsynth_chorus_speed") / 100.0;
|
||||||
|
double chorusDepthMs = (double)ConfMan.getInt("fluidsynth_chorus_depth") / 10.0;
|
||||||
|
|
||||||
|
Common::String chorusWaveForm = ConfMan.get("fluidsynth_chorus_waveform");
|
||||||
|
int chorusType = FLUID_CHORUS_MOD_SINE;
|
||||||
|
if (chorusWaveForm == "sine") {
|
||||||
|
chorusType = FLUID_CHORUS_MOD_SINE;
|
||||||
|
} else {
|
||||||
|
chorusType = FLUID_CHORUS_MOD_TRIANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluid_synth_set_chorus(_synth, chorusNr, chorusLevel, chorusSpeed, chorusDepthMs, chorusType);
|
||||||
|
} else {
|
||||||
|
fluid_synth_set_chorus_on(_synth, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ConfMan.getBool("fluidsynth_reverb_activate")) {
|
||||||
|
fluid_synth_set_reverb_on(_synth, 1);
|
||||||
|
|
||||||
|
double reverbRoomSize = (double)ConfMan.getInt("fluidsynth_reverb_roomsize") / 100.0;
|
||||||
|
double reverbDamping = (double)ConfMan.getInt("fluidsynth_reverb_damping") / 100.0;
|
||||||
|
int reverbWidth = ConfMan.getInt("fluidsynth_reverb_width");
|
||||||
|
double reverbLevel = (double)ConfMan.getInt("fluidsynth_reverb_level") / 100.0;
|
||||||
|
|
||||||
|
fluid_synth_set_reverb(_synth, reverbRoomSize, reverbDamping, reverbWidth, reverbLevel);
|
||||||
|
} else {
|
||||||
|
fluid_synth_set_reverb_on(_synth, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::String interpolation = ConfMan.get("fluidsynth_misc_interpolation");
|
||||||
|
int interpMethod = FLUID_INTERP_4THORDER;
|
||||||
|
|
||||||
|
if (interpolation == "none") {
|
||||||
|
interpMethod = FLUID_INTERP_NONE;
|
||||||
|
} else if (interpolation == "linear") {
|
||||||
|
interpMethod = FLUID_INTERP_LINEAR;
|
||||||
|
} else if (interpolation == "4th") {
|
||||||
|
interpMethod = FLUID_INTERP_4THORDER;
|
||||||
|
} else if (interpolation == "7th") {
|
||||||
|
interpMethod = FLUID_INTERP_7THORDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluid_synth_set_interp_method(_synth, -1, interpMethod);
|
||||||
|
|
||||||
const char *soundfont = ConfMan.get("soundfont").c_str();
|
const char *soundfont = ConfMan.get("soundfont").c_str();
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#ifdef USE_MT32EMU
|
#ifdef USE_MT32EMU
|
||||||
|
|
||||||
#include "audio/softsynth/mt32/mt32emu.h"
|
#include "audio/softsynth/mt32/mt32emu.h"
|
||||||
|
#include "audio/softsynth/mt32/ROMInfo.h"
|
||||||
|
|
||||||
#include "audio/softsynth/emumidi.h"
|
#include "audio/softsynth/emumidi.h"
|
||||||
#include "audio/musicplugin.h"
|
#include "audio/musicplugin.h"
|
||||||
|
@ -47,6 +48,51 @@
|
||||||
#include "graphics/palette.h"
|
#include "graphics/palette.h"
|
||||||
#include "graphics/font.h"
|
#include "graphics/font.h"
|
||||||
|
|
||||||
|
#include "gui/message.h"
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
class ReportHandlerScummVM : public ReportHandler {
|
||||||
|
friend class Synth;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~ReportHandlerScummVM() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// Callback for debug messages, in vprintf() format
|
||||||
|
void printDebug(const char *fmt, va_list list) {
|
||||||
|
Common::String out = Common::String::vformat(fmt, list);
|
||||||
|
debug(4, "%s", out.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callbacks for reporting various errors and information
|
||||||
|
void onErrorControlROM() {
|
||||||
|
GUI::MessageDialog dialog("MT32emu: Init Error - Missing or invalid Control ROM image", "OK");
|
||||||
|
dialog.runModal();
|
||||||
|
error("MT32emu: Init Error - Missing or invalid Control ROM image");
|
||||||
|
}
|
||||||
|
void onErrorPCMROM() {
|
||||||
|
GUI::MessageDialog dialog("MT32emu: Init Error - Missing PCM ROM image", "OK");
|
||||||
|
dialog.runModal();
|
||||||
|
error("MT32emu: Init Error - Missing PCM ROM image");
|
||||||
|
}
|
||||||
|
void showLCDMessage(const char *message) {
|
||||||
|
g_system->displayMessageOnOSD(message);
|
||||||
|
}
|
||||||
|
void onDeviceReset() {}
|
||||||
|
void onDeviceReconfig() {}
|
||||||
|
void onNewReverbMode(Bit8u /* mode */) {}
|
||||||
|
void onNewReverbTime(Bit8u /* time */) {}
|
||||||
|
void onNewReverbLevel(Bit8u /* level */) {}
|
||||||
|
void onPartStateChanged(int /* partNum */, bool /* isActive */) {}
|
||||||
|
void onPolyStateChanged(int /* partNum */) {}
|
||||||
|
void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {}
|
||||||
|
void onProgramChanged(int /* partNum */, char * /* patchName */) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // end of namespace MT32Emu
|
||||||
|
|
||||||
class MidiChannel_MT32 : public MidiChannel_MPU401 {
|
class MidiChannel_MT32 : public MidiChannel_MPU401 {
|
||||||
void effectLevel(byte value) { }
|
void effectLevel(byte value) { }
|
||||||
void chorusLevel(byte value) { }
|
void chorusLevel(byte value) { }
|
||||||
|
@ -57,6 +103,10 @@ private:
|
||||||
MidiChannel_MT32 _midiChannels[16];
|
MidiChannel_MT32 _midiChannels[16];
|
||||||
uint16 _channelMask;
|
uint16 _channelMask;
|
||||||
MT32Emu::Synth *_synth;
|
MT32Emu::Synth *_synth;
|
||||||
|
MT32Emu::ReportHandlerScummVM *_reportHandler;
|
||||||
|
const MT32Emu::ROMImage *_controlROM, *_pcmROM;
|
||||||
|
Common::File *_controlFile, *_pcmFile;
|
||||||
|
void deleteMuntStructures();
|
||||||
|
|
||||||
int _outputRate;
|
int _outputRate;
|
||||||
|
|
||||||
|
@ -84,149 +134,6 @@ public:
|
||||||
int getRate() const { return _outputRate; }
|
int getRate() const { return _outputRate; }
|
||||||
};
|
};
|
||||||
|
|
||||||
static int eatSystemEvents() {
|
|
||||||
Common::Event event;
|
|
||||||
Common::EventManager *eventMan = g_system->getEventManager();
|
|
||||||
while (eventMan->pollEvent(event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case Common::EVENT_QUIT:
|
|
||||||
return 1;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void drawProgress(float progress) {
|
|
||||||
const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kGUIFont));
|
|
||||||
Graphics::Surface *screen = g_system->lockScreen();
|
|
||||||
|
|
||||||
assert(screen);
|
|
||||||
assert(screen->pixels);
|
|
||||||
|
|
||||||
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
|
||||||
|
|
||||||
int16 w = g_system->getWidth() / 7 * 5;
|
|
||||||
int16 h = font.getFontHeight();
|
|
||||||
int16 x = g_system->getWidth() / 7;
|
|
||||||
int16 y = g_system->getHeight() / 2 - h / 2;
|
|
||||||
|
|
||||||
Common::Rect r(x, y, x + w, y + h);
|
|
||||||
|
|
||||||
uint32 col;
|
|
||||||
|
|
||||||
if (screenFormat.bytesPerPixel > 1)
|
|
||||||
col = screenFormat.RGBToColor(0, 171, 0);
|
|
||||||
else
|
|
||||||
col = 1;
|
|
||||||
|
|
||||||
screen->frameRect(r, col);
|
|
||||||
|
|
||||||
r.grow(-1);
|
|
||||||
r.setWidth(uint16(progress * w));
|
|
||||||
|
|
||||||
if (screenFormat.bytesPerPixel > 1)
|
|
||||||
col = screenFormat.RGBToColor(171, 0, 0);
|
|
||||||
else
|
|
||||||
col = 2;
|
|
||||||
|
|
||||||
screen->fillRect(r, col);
|
|
||||||
|
|
||||||
g_system->unlockScreen();
|
|
||||||
g_system->updateScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void drawMessage(int offset, const Common::String &text) {
|
|
||||||
const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kGUIFont));
|
|
||||||
Graphics::Surface *screen = g_system->lockScreen();
|
|
||||||
|
|
||||||
assert(screen);
|
|
||||||
assert(screen->pixels);
|
|
||||||
|
|
||||||
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
|
||||||
|
|
||||||
uint16 h = font.getFontHeight();
|
|
||||||
uint16 y = g_system->getHeight() / 2 - h / 2 + offset * (h + 1);
|
|
||||||
|
|
||||||
uint32 col;
|
|
||||||
|
|
||||||
if (screenFormat.bytesPerPixel > 1)
|
|
||||||
col = screenFormat.RGBToColor(0, 0, 0);
|
|
||||||
else
|
|
||||||
col = 0;
|
|
||||||
|
|
||||||
Common::Rect r(0, y, screen->w, y + h);
|
|
||||||
screen->fillRect(r, col);
|
|
||||||
|
|
||||||
if (screenFormat.bytesPerPixel > 1)
|
|
||||||
col = screenFormat.RGBToColor(0, 171, 0);
|
|
||||||
else
|
|
||||||
col = 1;
|
|
||||||
|
|
||||||
font.drawString(screen, text, 0, y, screen->w, col, Graphics::kTextAlignCenter);
|
|
||||||
|
|
||||||
g_system->unlockScreen();
|
|
||||||
g_system->updateScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Common::File *MT32_OpenFile(void *userData, const char *filename) {
|
|
||||||
Common::File *file = new Common::File();
|
|
||||||
if (!file->open(filename)) {
|
|
||||||
delete file;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void MT32_PrintDebug(void *userData, const char *fmt, va_list list) {
|
|
||||||
if (((MidiDriver_MT32 *)userData)->_initializing) {
|
|
||||||
char buf[512];
|
|
||||||
|
|
||||||
vsnprintf(buf, 512, fmt, list);
|
|
||||||
buf[70] = 0; // Truncate to a reasonable length
|
|
||||||
|
|
||||||
drawMessage(1, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
//vdebug(0, fmt, list); // FIXME: Use a higher debug level
|
|
||||||
}
|
|
||||||
|
|
||||||
static int MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) {
|
|
||||||
switch (type) {
|
|
||||||
case MT32Emu::ReportType_lcdMessage:
|
|
||||||
g_system->displayMessageOnOSD((const char *)reportData);
|
|
||||||
break;
|
|
||||||
case MT32Emu::ReportType_errorControlROM:
|
|
||||||
error("Failed to load MT32_CONTROL.ROM");
|
|
||||||
break;
|
|
||||||
case MT32Emu::ReportType_errorPCMROM:
|
|
||||||
error("Failed to load MT32_PCM.ROM");
|
|
||||||
break;
|
|
||||||
case MT32Emu::ReportType_progressInit:
|
|
||||||
if (((MidiDriver_MT32 *)userData)->_initializing) {
|
|
||||||
drawProgress(*((const float *)reportData));
|
|
||||||
return eatSystemEvents();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MT32Emu::ReportType_availableSSE:
|
|
||||||
debug(1, "MT32emu: SSE is available");
|
|
||||||
break;
|
|
||||||
case MT32Emu::ReportType_usingSSE:
|
|
||||||
debug(1, "MT32emu: using SSE");
|
|
||||||
break;
|
|
||||||
case MT32Emu::ReportType_available3DNow:
|
|
||||||
debug(1, "MT32emu: 3DNow! is available");
|
|
||||||
break;
|
|
||||||
case MT32Emu::ReportType_using3DNow:
|
|
||||||
debug(1, "MT32emu: using 3DNow!");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// MidiDriver_MT32
|
// MidiDriver_MT32
|
||||||
|
@ -239,43 +146,51 @@ MidiDriver_MT32::MidiDriver_MT32(Audio::Mixer *mixer) : MidiDriver_Emulated(mixe
|
||||||
for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) {
|
for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) {
|
||||||
_midiChannels[i].init(this, i);
|
_midiChannels[i].init(this, i);
|
||||||
}
|
}
|
||||||
|
_reportHandler = NULL;
|
||||||
_synth = NULL;
|
_synth = NULL;
|
||||||
// A higher baseFreq reduces the length used in generateSamples(),
|
|
||||||
// and means that the timer callback will be called more often.
|
|
||||||
// That results in more accurate timing.
|
|
||||||
_baseFreq = 10000;
|
|
||||||
// Unfortunately bugs in the emulator cause inaccurate tuning
|
// Unfortunately bugs in the emulator cause inaccurate tuning
|
||||||
// at rates other than 32KHz, thus we produce data at 32KHz and
|
// at rates other than 32KHz, thus we produce data at 32KHz and
|
||||||
// rely on Mixer to convert.
|
// rely on Mixer to convert.
|
||||||
_outputRate = 32000; //_mixer->getOutputRate();
|
_outputRate = 32000; //_mixer->getOutputRate();
|
||||||
_initializing = false;
|
_initializing = false;
|
||||||
|
|
||||||
|
// Initialized in open()
|
||||||
|
_controlROM = NULL;
|
||||||
|
_pcmROM = NULL;
|
||||||
|
_controlFile = NULL;
|
||||||
|
_pcmFile = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
MidiDriver_MT32::~MidiDriver_MT32() {
|
MidiDriver_MT32::~MidiDriver_MT32() {
|
||||||
|
deleteMuntStructures();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver_MT32::deleteMuntStructures() {
|
||||||
delete _synth;
|
delete _synth;
|
||||||
|
_synth = NULL;
|
||||||
|
delete _reportHandler;
|
||||||
|
_reportHandler = NULL;
|
||||||
|
|
||||||
|
if (_controlROM)
|
||||||
|
MT32Emu::ROMImage::freeROMImage(_controlROM);
|
||||||
|
_controlROM = NULL;
|
||||||
|
if (_pcmROM)
|
||||||
|
MT32Emu::ROMImage::freeROMImage(_pcmROM);
|
||||||
|
_pcmROM = NULL;
|
||||||
|
|
||||||
|
delete _controlFile;
|
||||||
|
_controlFile = NULL;
|
||||||
|
delete _pcmFile;
|
||||||
|
_pcmFile = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MidiDriver_MT32::open() {
|
int MidiDriver_MT32::open() {
|
||||||
MT32Emu::SynthProperties prop;
|
|
||||||
|
|
||||||
if (_isOpen)
|
if (_isOpen)
|
||||||
return MERR_ALREADY_OPEN;
|
return MERR_ALREADY_OPEN;
|
||||||
|
|
||||||
MidiDriver_Emulated::open();
|
MidiDriver_Emulated::open();
|
||||||
|
_reportHandler = new MT32Emu::ReportHandlerScummVM();
|
||||||
memset(&prop, 0, sizeof(prop));
|
_synth = new MT32Emu::Synth(_reportHandler);
|
||||||
prop.sampleRate = getRate();
|
|
||||||
prop.useReverb = true;
|
|
||||||
prop.useDefaultReverb = false;
|
|
||||||
prop.reverbType = 0;
|
|
||||||
prop.reverbTime = 5;
|
|
||||||
prop.reverbLevel = 3;
|
|
||||||
prop.userData = this;
|
|
||||||
prop.printDebug = MT32_PrintDebug;
|
|
||||||
prop.report = MT32_Report;
|
|
||||||
prop.openFile = MT32_OpenFile;
|
|
||||||
|
|
||||||
_synth = new MT32Emu::Synth();
|
|
||||||
|
|
||||||
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
||||||
|
|
||||||
|
@ -290,8 +205,16 @@ int MidiDriver_MT32::open() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_initializing = true;
|
_initializing = true;
|
||||||
drawMessage(-1, _s("Initializing MT-32 Emulator"));
|
debug(4, _s("Initializing MT-32 Emulator"));
|
||||||
if (!_synth->open(prop))
|
_controlFile = new Common::File();
|
||||||
|
if (!_controlFile->open("MT32_CONTROL.ROM") && !_controlFile->open("CM32L_CONTROL.ROM"))
|
||||||
|
error("Error opening MT32_CONTROL.ROM / CM32L_CONTROL.ROM");
|
||||||
|
_pcmFile = new Common::File();
|
||||||
|
if (!_pcmFile->open("MT32_PCM.ROM") && !_pcmFile->open("CM32L_PCM.ROM"))
|
||||||
|
error("Error opening MT32_PCM.ROM / CM32L_PCM.ROM");
|
||||||
|
_controlROM = MT32Emu::ROMImage::makeROMImage(_controlFile);
|
||||||
|
_pcmROM = MT32Emu::ROMImage::makeROMImage(_pcmFile);
|
||||||
|
if (!_synth->open(*_controlROM, *_pcmROM))
|
||||||
return MERR_DEVICE_NOT_AVAILABLE;
|
return MERR_DEVICE_NOT_AVAILABLE;
|
||||||
|
|
||||||
double gain = (double)ConfMan.getInt("midi_gain") / 100.0;
|
double gain = (double)ConfMan.getInt("midi_gain") / 100.0;
|
||||||
|
@ -352,8 +275,7 @@ void MidiDriver_MT32::close() {
|
||||||
_mixer->stopHandle(_mixerSoundHandle);
|
_mixer->stopHandle(_mixerSoundHandle);
|
||||||
|
|
||||||
_synth->close();
|
_synth->close();
|
||||||
delete _synth;
|
deleteMuntStructures();
|
||||||
_synth = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MidiDriver_MT32::generateSamples(int16 *data, int len) {
|
void MidiDriver_MT32::generateSamples(int16 *data, int len) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -16,64 +16,97 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "mt32emu.h"
|
#include "mt32emu.h"
|
||||||
|
|
||||||
|
#if MT32EMU_USE_REVERBMODEL == 1
|
||||||
|
|
||||||
#include "AReverbModel.h"
|
#include "AReverbModel.h"
|
||||||
|
|
||||||
|
// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
|
||||||
|
// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
|
||||||
|
// and followed by three parallel comb filters
|
||||||
|
|
||||||
namespace MT32Emu {
|
namespace MT32Emu {
|
||||||
|
|
||||||
// Default reverb settings for modes 0-2
|
// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
|
||||||
|
// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
|
||||||
|
// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
|
||||||
|
// so we can simply increase the input buffer size.
|
||||||
|
static const Bit32u PROCESS_DELAY = 1;
|
||||||
|
|
||||||
static const unsigned int NUM_ALLPASSES = 6;
|
// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
|
||||||
static const unsigned int NUM_DELAYS = 5;
|
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
|
||||||
|
|
||||||
static const Bit32u MODE_0_ALLPASSES[] = {729, 78, 394, 994, 1250, 1889};
|
static const Bit32u NUM_ALLPASSES = 3;
|
||||||
static const Bit32u MODE_0_DELAYS[] = {846, 4, 1819, 778, 346};
|
static const Bit32u NUM_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be perfectly processed via a comb here.
|
||||||
static const float MODE_0_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.9f};
|
|
||||||
static const float MODE_0_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
|
|
||||||
|
|
||||||
static const Bit32u MODE_1_ALLPASSES[] = {176, 809, 1324, 1258};
|
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
|
||||||
static const Bit32u MODE_1_DELAYS[] = {2262, 124, 974, 2516, 356};
|
static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
|
||||||
static const float MODE_1_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.95f};
|
static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
|
||||||
static const float MODE_1_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
|
static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
|
||||||
|
static const Bit32u MODE_0_COMB_FACTOR[] = {0x3C, 0x60, 0x60, 0x60};
|
||||||
|
static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||||
|
static const Bit32u MODE_0_LEVELS[] = {10*1, 10*3, 10*5, 10*7, 11*9, 11*12, 11*15, 13*15};
|
||||||
|
static const Bit32u MODE_0_LPF_AMP = 6;
|
||||||
|
|
||||||
static const Bit32u MODE_2_ALLPASSES[] = {78, 729, 994, 389};
|
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
|
||||||
static const Bit32u MODE_2_DELAYS[] = {846, 4, 1819, 778, 346};
|
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
|
||||||
static const float MODE_2_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
|
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
|
||||||
static const float MODE_2_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
|
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
|
||||||
|
static const Bit32u MODE_1_COMB_FACTOR[] = {0x30, 0x60, 0x60, 0x60};
|
||||||
|
static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||||
|
static const Bit32u MODE_1_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 11*15, 14*15};
|
||||||
|
static const Bit32u MODE_1_LPF_AMP = 6;
|
||||||
|
|
||||||
const AReverbSettings AReverbModel::REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_DELAYS, MODE_0_TIMES, MODE_0_LEVELS, 0.687770909f, 0.5f, 0.5f};
|
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
|
||||||
const AReverbSettings AReverbModel::REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_DELAYS, MODE_1_TIMES, MODE_1_LEVELS, 0.712025098f, 0.375f, 0.625f};
|
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
|
||||||
const AReverbSettings AReverbModel::REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_DELAYS, MODE_2_TIMES, MODE_2_LEVELS, 0.939522749f, 0.0f, 0.0f};
|
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
|
||||||
|
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
|
||||||
|
static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
|
||||||
|
static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||||
|
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||||
|
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
|
||||||
|
static const Bit32u MODE_2_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 12*15, 14*15};
|
||||||
|
static const Bit32u MODE_2_LPF_AMP = 8;
|
||||||
|
|
||||||
RingBuffer::RingBuffer(Bit32u newsize) {
|
static const AReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_LEVELS, MODE_0_LPF_AMP};
|
||||||
index = 0;
|
static const AReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_LEVELS, MODE_1_LPF_AMP};
|
||||||
size = newsize;
|
static const AReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_LEVELS, MODE_2_LPF_AMP};
|
||||||
|
|
||||||
|
static const AReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_0_SETTINGS};
|
||||||
|
|
||||||
|
RingBuffer::RingBuffer(const Bit32u newsize) : size(newsize), index(0) {
|
||||||
buffer = new float[size];
|
buffer = new float[size];
|
||||||
}
|
}
|
||||||
|
|
||||||
RingBuffer::~RingBuffer() {
|
RingBuffer::~RingBuffer() {
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
buffer = NULL;
|
buffer = NULL;
|
||||||
size = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float RingBuffer::next() {
|
float RingBuffer::next() {
|
||||||
index++;
|
if (++index >= size) {
|
||||||
if (index >= size) {
|
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
return buffer[index];
|
return buffer[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RingBuffer::isEmpty() {
|
bool RingBuffer::isEmpty() const {
|
||||||
if (buffer == NULL) return true;
|
if (buffer == NULL) return true;
|
||||||
|
|
||||||
float *buf = buffer;
|
float *buf = buffer;
|
||||||
float total = 0;
|
float max = 0.001f;
|
||||||
for (Bit32u i = 0; i < size; i++) {
|
for (Bit32u i = 0; i < size; i++) {
|
||||||
total += (*buf < 0 ? -*buf : *buf);
|
if ((*buf < -max) || (*buf > max)) return false;
|
||||||
buf++;
|
buf++;
|
||||||
}
|
}
|
||||||
return ((total / size) < .0002 ? true : false);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RingBuffer::mute() {
|
void RingBuffer::mute() {
|
||||||
|
@ -83,59 +116,66 @@ void RingBuffer::mute() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AllpassFilter::AllpassFilter(Bit32u useSize) : RingBuffer(useSize) {
|
AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
|
||||||
}
|
|
||||||
|
|
||||||
Delay::Delay(Bit32u useSize) : RingBuffer(useSize) {
|
float AllpassFilter::process(const float in) {
|
||||||
}
|
// This model corresponds to the allpass filter implementation of the real CM-32L device
|
||||||
|
|
||||||
float AllpassFilter::process(float in) {
|
|
||||||
// This model corresponds to the allpass filter implementation in the real CM-32L device
|
|
||||||
// found from sample analysis
|
// found from sample analysis
|
||||||
|
|
||||||
float out;
|
const float bufferOut = next();
|
||||||
|
|
||||||
out = next();
|
|
||||||
|
|
||||||
// store input - feedback / 2
|
// store input - feedback / 2
|
||||||
buffer[index] = in - 0.5f * out;
|
buffer[index] = in - 0.5f * bufferOut;
|
||||||
|
|
||||||
// return buffer output + feedforward / 2
|
// return buffer output + feedforward / 2
|
||||||
return out + 0.5f * buffer[index];
|
return bufferOut + 0.5f * buffer[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
float Delay::process(float in) {
|
CombFilter::CombFilter(const Bit32u useSize) : RingBuffer(useSize) {}
|
||||||
// Implements a very simple delay
|
|
||||||
|
|
||||||
float out;
|
void CombFilter::process(const float in) {
|
||||||
|
// This model corresponds to the comb filter implementation of the real CM-32L device
|
||||||
|
// found from sample analysis
|
||||||
|
|
||||||
out = next();
|
// the previously stored value
|
||||||
|
float last = buffer[index];
|
||||||
|
|
||||||
// store input
|
// prepare input + feedback
|
||||||
buffer[index] = in;
|
float filterIn = in + next() * feedbackFactor;
|
||||||
|
|
||||||
// return buffer output
|
// store input + feedback processed by a low-pass filter
|
||||||
return out;
|
buffer[index] = filterFactor * last - filterIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
AReverbModel::AReverbModel(const AReverbSettings *useSettings) : allpasses(NULL), delays(NULL), currentSettings(useSettings) {
|
float CombFilter::getOutputAt(const Bit32u outIndex) const {
|
||||||
|
return buffer[(size + index - outIndex) % size];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CombFilter::setFeedbackFactor(const float useFeedbackFactor) {
|
||||||
|
feedbackFactor = useFeedbackFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombFilter::setFilterFactor(const float useFilterFactor) {
|
||||||
|
filterFactor = useFilterFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
AReverbModel::AReverbModel(const ReverbMode mode) : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]) {}
|
||||||
|
|
||||||
AReverbModel::~AReverbModel() {
|
AReverbModel::~AReverbModel() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AReverbModel::open(unsigned int /*sampleRate*/) {
|
void AReverbModel::open() {
|
||||||
// FIXME: filter sizes must be multiplied by sample rate to 32000Hz ratio
|
|
||||||
// IIR filter values depend on sample rate as well
|
|
||||||
allpasses = new AllpassFilter*[NUM_ALLPASSES];
|
allpasses = new AllpassFilter*[NUM_ALLPASSES];
|
||||||
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
||||||
allpasses[i] = new AllpassFilter(currentSettings->allpassSizes[i]);
|
allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
|
||||||
}
|
}
|
||||||
delays = new Delay*[NUM_DELAYS];
|
combs = new CombFilter*[NUM_COMBS];
|
||||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
for (Bit32u i = 0; i < NUM_COMBS; i++) {
|
||||||
delays[i] = new Delay(currentSettings->delaySizes[i]);
|
combs[i] = new CombFilter(currentSettings.combSizes[i]);
|
||||||
|
combs[i]->setFilterFactor(currentSettings.filterFactor[i] / 256.0f);
|
||||||
}
|
}
|
||||||
|
lpfAmp = currentSettings.lpfAmp / 16.0f;
|
||||||
mute();
|
mute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,84 +190,80 @@ void AReverbModel::close() {
|
||||||
delete[] allpasses;
|
delete[] allpasses;
|
||||||
allpasses = NULL;
|
allpasses = NULL;
|
||||||
}
|
}
|
||||||
if (delays != NULL) {
|
if (combs != NULL) {
|
||||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
for (Bit32u i = 0; i < NUM_COMBS; i++) {
|
||||||
if (delays[i] != NULL) {
|
if (combs[i] != NULL) {
|
||||||
delete delays[i];
|
delete combs[i];
|
||||||
delays[i] = NULL;
|
combs[i] = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete[] delays;
|
delete[] combs;
|
||||||
delays = NULL;
|
combs = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AReverbModel::mute() {
|
void AReverbModel::mute() {
|
||||||
|
if (allpasses == NULL || combs == NULL) return;
|
||||||
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
||||||
allpasses[i]->mute();
|
allpasses[i]->mute();
|
||||||
}
|
}
|
||||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
for (Bit32u i = 0; i < NUM_COMBS; i++) {
|
||||||
delays[i]->mute();
|
combs[i]->mute();
|
||||||
}
|
}
|
||||||
filterhist1 = 0;
|
|
||||||
filterhist2 = 0;
|
|
||||||
combhist = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AReverbModel::setParameters(Bit8u time, Bit8u level) {
|
void AReverbModel::setParameters(Bit8u time, Bit8u level) {
|
||||||
// FIXME: wetLevel definitely needs ramping when changed
|
// FIXME: wetLevel definitely needs ramping when changed
|
||||||
// Although, most games don't set reverb level during MIDI playback
|
// Although, most games don't set reverb level during MIDI playback
|
||||||
decayTime = currentSettings->decayTimes[time];
|
if (combs == NULL) return;
|
||||||
wetLevel = currentSettings->wetLevels[level];
|
level &= 7;
|
||||||
|
time &= 7;
|
||||||
|
for (Bit32u i = 0; i < NUM_COMBS; i++) {
|
||||||
|
combs[i]->setFeedbackFactor(currentSettings.decayTimes[(i << 3) + time] / 256.0f);
|
||||||
|
}
|
||||||
|
wetLevel = (level == 0 && time == 0) ? 0.0f : 0.5f * lpfAmp * currentSettings.wetLevels[level] / 256.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AReverbModel::isActive() const {
|
bool AReverbModel::isActive() const {
|
||||||
bool bActive = false;
|
|
||||||
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
||||||
bActive |= !allpasses[i]->isEmpty();
|
if (!allpasses[i]->isEmpty()) return true;
|
||||||
}
|
}
|
||||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
for (Bit32u i = 0; i < NUM_COMBS; i++) {
|
||||||
bActive |= !delays[i]->isEmpty();
|
if (!combs[i]->isEmpty()) return true;
|
||||||
}
|
}
|
||||||
return bActive;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||||
// Three series allpass filters followed by a delay, fourth allpass filter and another delay
|
float dry, link, outL1;
|
||||||
float dry, link, outL1, outL2, outR1, outR2;
|
|
||||||
|
|
||||||
for (unsigned long i = 0; i < numSamples; i++) {
|
for (unsigned long i = 0; i < numSamples; i++) {
|
||||||
dry = *inLeft + *inRight;
|
dry = wetLevel * (*inLeft + *inRight);
|
||||||
|
|
||||||
// Implementation of 2-stage IIR single-pole low-pass filter
|
// Get the last stored sample before processing in order not to loose it
|
||||||
// found at the entrance of reverb processing on real devices
|
link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
|
||||||
filterhist1 += (dry - filterhist1) * currentSettings->filtVal;
|
|
||||||
filterhist2 += (filterhist1 - filterhist2) * currentSettings->filtVal;
|
|
||||||
|
|
||||||
link = allpasses[0]->process(-filterhist2);
|
combs[0]->process(-dry);
|
||||||
|
|
||||||
|
link = allpasses[0]->process(link);
|
||||||
link = allpasses[1]->process(link);
|
link = allpasses[1]->process(link);
|
||||||
|
|
||||||
// this implements a comb filter cross-linked with the fourth allpass filter
|
|
||||||
link += combhist * decayTime;
|
|
||||||
link = allpasses[2]->process(link);
|
link = allpasses[2]->process(link);
|
||||||
link = delays[0]->process(link);
|
|
||||||
outL1 = link;
|
|
||||||
link = allpasses[3]->process(link);
|
|
||||||
link = delays[1]->process(link);
|
|
||||||
outR1 = link;
|
|
||||||
link = allpasses[4]->process(link);
|
|
||||||
link = delays[2]->process(link);
|
|
||||||
outL2 = link;
|
|
||||||
link = allpasses[5]->process(link);
|
|
||||||
link = delays[3]->process(link);
|
|
||||||
outR2 = link;
|
|
||||||
link = delays[4]->process(link);
|
|
||||||
|
|
||||||
// comb filter end point
|
// If the output position is equal to the comb size, get it now in order not to loose it
|
||||||
combhist = combhist * currentSettings->damp1 + link * currentSettings->damp2;
|
outL1 = 1.5f * combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
|
||||||
|
|
||||||
*outLeft = (outL1 + outL2) * wetLevel;
|
combs[1]->process(link);
|
||||||
*outRight = (outR1 + outR2) * wetLevel;
|
combs[2]->process(link);
|
||||||
|
combs[3]->process(link);
|
||||||
|
|
||||||
|
link = outL1 + 1.5f * combs[2]->getOutputAt(currentSettings.outLPositions[1]);
|
||||||
|
link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
|
||||||
|
*outLeft = link;
|
||||||
|
|
||||||
|
link = 1.5f * combs[1]->getOutputAt(currentSettings.outRPositions[0]);
|
||||||
|
link += 1.5f * combs[2]->getOutputAt(currentSettings.outRPositions[1]);
|
||||||
|
link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
|
||||||
|
*outRight = link;
|
||||||
|
|
||||||
inLeft++;
|
inLeft++;
|
||||||
inRight++;
|
inRight++;
|
||||||
|
@ -237,3 +273,5 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -21,66 +21,67 @@
|
||||||
namespace MT32Emu {
|
namespace MT32Emu {
|
||||||
|
|
||||||
struct AReverbSettings {
|
struct AReverbSettings {
|
||||||
const Bit32u *allpassSizes;
|
const Bit32u * const allpassSizes;
|
||||||
const Bit32u *delaySizes;
|
const Bit32u * const combSizes;
|
||||||
const float *decayTimes;
|
const Bit32u * const outLPositions;
|
||||||
const float *wetLevels;
|
const Bit32u * const outRPositions;
|
||||||
float filtVal;
|
const Bit32u * const filterFactor;
|
||||||
float damp1;
|
const Bit32u * const decayTimes;
|
||||||
float damp2;
|
const Bit32u * const wetLevels;
|
||||||
|
const Bit32u lpfAmp;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RingBuffer {
|
class RingBuffer {
|
||||||
protected:
|
protected:
|
||||||
float *buffer;
|
float *buffer;
|
||||||
Bit32u size;
|
const Bit32u size;
|
||||||
Bit32u index;
|
Bit32u index;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RingBuffer(Bit32u size);
|
RingBuffer(const Bit32u size);
|
||||||
virtual ~RingBuffer();
|
virtual ~RingBuffer();
|
||||||
float next();
|
float next();
|
||||||
bool isEmpty();
|
bool isEmpty() const;
|
||||||
void mute();
|
void mute();
|
||||||
};
|
};
|
||||||
|
|
||||||
class AllpassFilter : public RingBuffer {
|
class AllpassFilter : public RingBuffer {
|
||||||
public:
|
public:
|
||||||
AllpassFilter(Bit32u size);
|
AllpassFilter(const Bit32u size);
|
||||||
float process(float in);
|
float process(const float in);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Delay : public RingBuffer {
|
class CombFilter : public RingBuffer {
|
||||||
|
float feedbackFactor;
|
||||||
|
float filterFactor;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Delay(Bit32u size);
|
CombFilter(const Bit32u size);
|
||||||
float process(float in);
|
void process(const float in);
|
||||||
|
float getOutputAt(const Bit32u outIndex) const;
|
||||||
|
void setFeedbackFactor(const float useFeedbackFactor);
|
||||||
|
void setFilterFactor(const float useFilterFactor);
|
||||||
};
|
};
|
||||||
|
|
||||||
class AReverbModel : public ReverbModel {
|
class AReverbModel : public ReverbModel {
|
||||||
AllpassFilter **allpasses;
|
AllpassFilter **allpasses;
|
||||||
Delay **delays;
|
CombFilter **combs;
|
||||||
|
|
||||||
const AReverbSettings *currentSettings;
|
const AReverbSettings ¤tSettings;
|
||||||
float decayTime;
|
float lpfAmp;
|
||||||
float wetLevel;
|
float wetLevel;
|
||||||
float filterhist1, filterhist2;
|
|
||||||
float combhist;
|
|
||||||
void mute();
|
void mute();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AReverbModel(const AReverbSettings *newSettings);
|
AReverbModel(const ReverbMode mode);
|
||||||
~AReverbModel();
|
~AReverbModel();
|
||||||
void open(unsigned int sampleRate);
|
void open();
|
||||||
void close();
|
void close();
|
||||||
void setParameters(Bit8u time, Bit8u level);
|
void setParameters(Bit8u time, Bit8u level);
|
||||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||||
bool isActive() const;
|
bool isActive() const;
|
||||||
|
|
||||||
static const AReverbSettings REVERB_MODE_0_SETTINGS;
|
|
||||||
static const AReverbSettings REVERB_MODE_1_SETTINGS;
|
|
||||||
static const AReverbSettings REVERB_MODE_2_SETTINGS;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default reverb settings for modes 0-2
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
393
audio/softsynth/mt32/BReverbModel.cpp
Normal file
393
audio/softsynth/mt32/BReverbModel.cpp
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mt32emu.h"
|
||||||
|
|
||||||
|
#if MT32EMU_USE_REVERBMODEL == 2
|
||||||
|
|
||||||
|
#include "BReverbModel.h"
|
||||||
|
|
||||||
|
// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
|
||||||
|
// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
|
||||||
|
// and followed by three parallel comb filters
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
|
||||||
|
// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
|
||||||
|
// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
|
||||||
|
// so we can simply increase the input buffer size.
|
||||||
|
static const Bit32u PROCESS_DELAY = 1;
|
||||||
|
|
||||||
|
static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
|
||||||
|
static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
|
||||||
|
|
||||||
|
// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
|
||||||
|
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
|
||||||
|
|
||||||
|
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
|
||||||
|
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
|
||||||
|
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb.
|
||||||
|
static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
|
||||||
|
static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
|
||||||
|
static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
|
||||||
|
static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
|
||||||
|
static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||||
|
static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
|
||||||
|
static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||||
|
static const Bit32u MODE_0_LPF_AMP = 0x60;
|
||||||
|
|
||||||
|
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
|
||||||
|
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
|
||||||
|
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||||
|
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
|
||||||
|
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
|
||||||
|
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
|
||||||
|
static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
|
||||||
|
static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||||
|
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||||
|
static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
|
||||||
|
static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||||
|
static const Bit32u MODE_1_LPF_AMP = 0x60;
|
||||||
|
|
||||||
|
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
|
||||||
|
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
|
||||||
|
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||||
|
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
|
||||||
|
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
|
||||||
|
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
|
||||||
|
static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
|
||||||
|
static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||||
|
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||||
|
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
|
||||||
|
static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
|
||||||
|
static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||||
|
static const Bit32u MODE_2_LPF_AMP = 0x80;
|
||||||
|
|
||||||
|
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
|
||||||
|
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
|
||||||
|
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
|
||||||
|
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
|
||||||
|
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
|
||||||
|
static const Bit32u MODE_3_COMB_FACTOR[] = {0x68};
|
||||||
|
static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
|
||||||
|
static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
|
||||||
|
static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
|
||||||
|
|
||||||
|
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
|
||||||
|
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
|
||||||
|
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
|
||||||
|
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
|
||||||
|
|
||||||
|
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
|
||||||
|
|
||||||
|
// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
|
||||||
|
// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation.
|
||||||
|
static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) {
|
||||||
|
Bit8u mask = 0x80;
|
||||||
|
Bit32s res = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0;
|
||||||
|
a >>= 1;
|
||||||
|
res += (mask & addMask) > 0 ? a + carry : 0;
|
||||||
|
mask >>= 1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) {
|
||||||
|
buffer = new Bit16s[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
RingBuffer::~RingBuffer() {
|
||||||
|
delete[] buffer;
|
||||||
|
buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32s RingBuffer::next() {
|
||||||
|
if (++index >= size) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
return buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RingBuffer::isEmpty() const {
|
||||||
|
if (buffer == NULL) return true;
|
||||||
|
|
||||||
|
Bit16s *buf = buffer;
|
||||||
|
for (Bit32u i = 0; i < size; i++) {
|
||||||
|
if (*buf < -8 || *buf > 8) return false;
|
||||||
|
buf++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RingBuffer::mute() {
|
||||||
|
Bit16s *buf = buffer;
|
||||||
|
for (Bit32u i = 0; i < size; i++) {
|
||||||
|
*buf++ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
|
||||||
|
|
||||||
|
Bit32s AllpassFilter::process(const Bit32s in) {
|
||||||
|
// This model corresponds to the allpass filter implementation of the real CM-32L device
|
||||||
|
// found from sample analysis
|
||||||
|
|
||||||
|
Bit16s bufferOut = next();
|
||||||
|
|
||||||
|
// store input - feedback / 2
|
||||||
|
buffer[index] = in - (bufferOut >> 1);
|
||||||
|
|
||||||
|
// return buffer output + feedforward / 2
|
||||||
|
return bufferOut + (buffer[index] >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {}
|
||||||
|
|
||||||
|
void CombFilter::process(const Bit32s in) {
|
||||||
|
// This model corresponds to the comb filter implementation of the real CM-32L device
|
||||||
|
|
||||||
|
// the previously stored value
|
||||||
|
Bit32s last = buffer[index];
|
||||||
|
|
||||||
|
// prepare input + feedback
|
||||||
|
Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */);
|
||||||
|
|
||||||
|
// store input + feedback processed by a low-pass filter
|
||||||
|
buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const {
|
||||||
|
return buffer[(size + index - outIndex) % size];
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) {
|
||||||
|
feedbackFactor = useFeedbackFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp)
|
||||||
|
: CombFilter(useSize, useFilterFactor), amp(useAmp) {}
|
||||||
|
|
||||||
|
void DelayWithLowPassFilter::process(const Bit32s in) {
|
||||||
|
// the previously stored value
|
||||||
|
Bit32s last = buffer[index];
|
||||||
|
|
||||||
|
// move to the next index
|
||||||
|
next();
|
||||||
|
|
||||||
|
// low-pass filter process
|
||||||
|
Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in;
|
||||||
|
|
||||||
|
// store lpfOut multiplied by LPF amp factor
|
||||||
|
buffer[index] = weirdMul(lpfOut, amp, 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {}
|
||||||
|
|
||||||
|
void TapDelayCombFilter::process(const Bit32s in) {
|
||||||
|
// the previously stored value
|
||||||
|
Bit32s last = buffer[index];
|
||||||
|
|
||||||
|
// move to the next index
|
||||||
|
next();
|
||||||
|
|
||||||
|
// prepare input + feedback
|
||||||
|
// Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
|
||||||
|
Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0);
|
||||||
|
|
||||||
|
// store input + feedback processed by a low-pass filter
|
||||||
|
buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32s TapDelayCombFilter::getLeftOutput() const {
|
||||||
|
return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32s TapDelayCombFilter::getRightOutput() const {
|
||||||
|
return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
|
||||||
|
outL = useOutL;
|
||||||
|
outR = useOutR;
|
||||||
|
}
|
||||||
|
|
||||||
|
BReverbModel::BReverbModel(const ReverbMode mode)
|
||||||
|
: allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {}
|
||||||
|
|
||||||
|
BReverbModel::~BReverbModel() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BReverbModel::open() {
|
||||||
|
if (currentSettings.numberOfAllpasses > 0) {
|
||||||
|
allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses];
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||||
|
allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combs = new CombFilter*[currentSettings.numberOfCombs];
|
||||||
|
if (tapDelayMode) {
|
||||||
|
*combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors);
|
||||||
|
} else {
|
||||||
|
combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
|
||||||
|
for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
|
||||||
|
combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BReverbModel::close() {
|
||||||
|
if (allpasses != NULL) {
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||||
|
if (allpasses[i] != NULL) {
|
||||||
|
delete allpasses[i];
|
||||||
|
allpasses[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete[] allpasses;
|
||||||
|
allpasses = NULL;
|
||||||
|
}
|
||||||
|
if (combs != NULL) {
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||||
|
if (combs[i] != NULL) {
|
||||||
|
delete combs[i];
|
||||||
|
combs[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete[] combs;
|
||||||
|
combs = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BReverbModel::mute() {
|
||||||
|
if (allpasses != NULL) {
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||||
|
allpasses[i]->mute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (combs != NULL) {
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||||
|
combs[i]->mute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BReverbModel::setParameters(Bit8u time, Bit8u level) {
|
||||||
|
if (combs == NULL) return;
|
||||||
|
level &= 7;
|
||||||
|
time &= 7;
|
||||||
|
if (tapDelayMode) {
|
||||||
|
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||||
|
comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
|
||||||
|
comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
|
||||||
|
} else {
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||||
|
combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (time == 0 && level == 0) {
|
||||||
|
dryAmp = wetLevel = 0;
|
||||||
|
} else {
|
||||||
|
dryAmp = currentSettings.dryAmps[level];
|
||||||
|
wetLevel = currentSettings.wetLevels[level];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BReverbModel::isActive() const {
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||||
|
if (!allpasses[i]->isEmpty()) return true;
|
||||||
|
}
|
||||||
|
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||||
|
if (!combs[i]->isEmpty()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||||
|
Bit32s dry, link, outL1, outR1;
|
||||||
|
|
||||||
|
for (unsigned long i = 0; i < numSamples; i++) {
|
||||||
|
if (tapDelayMode) {
|
||||||
|
dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f);
|
||||||
|
} else {
|
||||||
|
dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
|
||||||
|
dry = weirdMul(dry, dryAmp, 0xFF);
|
||||||
|
|
||||||
|
if (tapDelayMode) {
|
||||||
|
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||||
|
comb->process(dry);
|
||||||
|
*outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f;
|
||||||
|
*outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f;
|
||||||
|
} else {
|
||||||
|
// Get the last stored sample before processing in order not to loose it
|
||||||
|
link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
|
||||||
|
|
||||||
|
// Entrance LPF. Note, comb.process() differs a bit here.
|
||||||
|
combs[0]->process(dry);
|
||||||
|
|
||||||
|
// This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
|
||||||
|
link = link - 1;
|
||||||
|
link = allpasses[0]->process(link);
|
||||||
|
link = allpasses[1]->process(link);
|
||||||
|
link = allpasses[2]->process(link);
|
||||||
|
|
||||||
|
// If the output position is equal to the comb size, get it now in order not to loose it
|
||||||
|
outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
|
||||||
|
outL1 += outL1 >> 1;
|
||||||
|
|
||||||
|
combs[1]->process(link);
|
||||||
|
combs[2]->process(link);
|
||||||
|
combs[3]->process(link);
|
||||||
|
|
||||||
|
link = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
|
||||||
|
link += link >> 1;
|
||||||
|
link += outL1;
|
||||||
|
link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
|
||||||
|
*outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
|
||||||
|
|
||||||
|
outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
|
||||||
|
outR1 += outR1 >> 1;
|
||||||
|
link = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
|
||||||
|
link += link >> 1;
|
||||||
|
link += outR1;
|
||||||
|
link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
|
||||||
|
*outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inLeft++;
|
||||||
|
inRight++;
|
||||||
|
outLeft++;
|
||||||
|
outRight++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
112
audio/softsynth/mt32/BReverbModel.h
Normal file
112
audio/softsynth/mt32/BReverbModel.h
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MT32EMU_B_REVERB_MODEL_H
|
||||||
|
#define MT32EMU_B_REVERB_MODEL_H
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
struct BReverbSettings {
|
||||||
|
const Bit32u numberOfAllpasses;
|
||||||
|
const Bit32u * const allpassSizes;
|
||||||
|
const Bit32u numberOfCombs;
|
||||||
|
const Bit32u * const combSizes;
|
||||||
|
const Bit32u * const outLPositions;
|
||||||
|
const Bit32u * const outRPositions;
|
||||||
|
const Bit32u * const filterFactors;
|
||||||
|
const Bit32u * const feedbackFactors;
|
||||||
|
const Bit32u * const dryAmps;
|
||||||
|
const Bit32u * const wetLevels;
|
||||||
|
const Bit32u lpfAmp;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RingBuffer {
|
||||||
|
protected:
|
||||||
|
Bit16s *buffer;
|
||||||
|
const Bit32u size;
|
||||||
|
Bit32u index;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RingBuffer(const Bit32u size);
|
||||||
|
virtual ~RingBuffer();
|
||||||
|
Bit32s next();
|
||||||
|
bool isEmpty() const;
|
||||||
|
void mute();
|
||||||
|
};
|
||||||
|
|
||||||
|
class AllpassFilter : public RingBuffer {
|
||||||
|
public:
|
||||||
|
AllpassFilter(const Bit32u size);
|
||||||
|
Bit32s process(const Bit32s in);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CombFilter : public RingBuffer {
|
||||||
|
protected:
|
||||||
|
const Bit32u filterFactor;
|
||||||
|
Bit32u feedbackFactor;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CombFilter(const Bit32u size, const Bit32u useFilterFactor);
|
||||||
|
virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure
|
||||||
|
Bit32s getOutputAt(const Bit32u outIndex) const;
|
||||||
|
void setFeedbackFactor(const Bit32u useFeedbackFactor);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DelayWithLowPassFilter : public CombFilter {
|
||||||
|
Bit32u amp;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp);
|
||||||
|
void process(const Bit32s in);
|
||||||
|
void setFeedbackFactor(const Bit32u) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TapDelayCombFilter : public CombFilter {
|
||||||
|
Bit32u outL;
|
||||||
|
Bit32u outR;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor);
|
||||||
|
void process(const Bit32s in);
|
||||||
|
Bit32s getLeftOutput() const;
|
||||||
|
Bit32s getRightOutput() const;
|
||||||
|
void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR);
|
||||||
|
};
|
||||||
|
|
||||||
|
class BReverbModel : public ReverbModel {
|
||||||
|
AllpassFilter **allpasses;
|
||||||
|
CombFilter **combs;
|
||||||
|
|
||||||
|
const BReverbSettings ¤tSettings;
|
||||||
|
const bool tapDelayMode;
|
||||||
|
Bit32u dryAmp;
|
||||||
|
Bit32u wetLevel;
|
||||||
|
void mute();
|
||||||
|
|
||||||
|
public:
|
||||||
|
BReverbModel(const ReverbMode mode);
|
||||||
|
~BReverbModel();
|
||||||
|
void open();
|
||||||
|
void close();
|
||||||
|
void setParameters(Bit8u time, Bit8u level);
|
||||||
|
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||||
|
bool isActive() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
namespace MT32Emu {
|
namespace MT32Emu {
|
||||||
|
|
||||||
// CONFIRMED: The values below are found via analysis of digital samples. Checked with all time and level combinations.
|
// CONFIRMED: The values below are found via analysis of digital samples and tracing reverb RAM address / data lines. Checked with all time and level combinations.
|
||||||
// Obviously:
|
// Obviously:
|
||||||
// rightDelay = (leftDelay - 2) * 2 + 2
|
// rightDelay = (leftDelay - 2) * 2 + 2
|
||||||
// echoDelay = rightDelay - 1
|
// echoDelay = rightDelay - 1
|
||||||
|
@ -39,14 +39,16 @@ static const Bit32u REVERB_TIMINGS[8][3]= {
|
||||||
{8002, 16002, 16001}
|
{8002, 16002, 16001}
|
||||||
};
|
};
|
||||||
|
|
||||||
static const float REVERB_FADE[8] = {0.0f, -0.049400051f, -0.08220577f, -0.131861118f, -0.197344907f, -0.262956344f, -0.345162114f, -0.509508615f};
|
// Reverb amp is found as dryAmp * wetAmp
|
||||||
const float REVERB_FEEDBACK67 = -0.629960524947437f; // = -EXP2F(-2 / 3)
|
static const Bit32u REVERB_AMP[8] = {0x20*0x18, 0x50*0x18, 0x50*0x28, 0x50*0x40, 0x50*0x60, 0x50*0x80, 0x50*0xA8, 0x50*0xF8};
|
||||||
const float REVERB_FEEDBACK = -0.682034520443118f; // = -EXP2F(-53 / 96)
|
static const Bit32u REVERB_FEEDBACK67 = 0x60;
|
||||||
const float LPF_VALUE = 0.594603558f; // = EXP2F(-0.75f)
|
static const Bit32u REVERB_FEEDBACK = 0x68;
|
||||||
|
static const float LPF_VALUE = 0x68 / 256.0f;
|
||||||
|
|
||||||
|
static const Bit32u BUFFER_SIZE = 16384;
|
||||||
|
|
||||||
DelayReverb::DelayReverb() {
|
DelayReverb::DelayReverb() {
|
||||||
buf = NULL;
|
buf = NULL;
|
||||||
sampleRate = 0;
|
|
||||||
setParameters(0, 0);
|
setParameters(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,27 +56,22 @@ DelayReverb::~DelayReverb() {
|
||||||
delete[] buf;
|
delete[] buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DelayReverb::open(unsigned int newSampleRate) {
|
void DelayReverb::open() {
|
||||||
if (newSampleRate != sampleRate || buf == NULL) {
|
if (buf == NULL) {
|
||||||
sampleRate = newSampleRate;
|
|
||||||
|
|
||||||
delete[] buf;
|
delete[] buf;
|
||||||
|
|
||||||
// If we ever need a speedup, set bufSize to EXP2F(ceil(log2(bufSize))) and use & instead of % to find buf indexes
|
buf = new float[BUFFER_SIZE];
|
||||||
bufSize = 16384 * sampleRate / 32000;
|
|
||||||
buf = new float[bufSize];
|
|
||||||
|
|
||||||
recalcParameters();
|
recalcParameters();
|
||||||
|
|
||||||
// mute buffer
|
// mute buffer
|
||||||
bufIx = 0;
|
bufIx = 0;
|
||||||
if (buf != NULL) {
|
if (buf != NULL) {
|
||||||
for (unsigned int i = 0; i < bufSize; i++) {
|
for (unsigned int i = 0; i < BUFFER_SIZE; i++) {
|
||||||
buf[i] = 0.0f;
|
buf[i] = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// FIXME: IIR filter value depends on sample rate as well
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DelayReverb::close() {
|
void DelayReverb::close() {
|
||||||
|
@ -91,59 +88,53 @@ void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) {
|
||||||
|
|
||||||
void DelayReverb::recalcParameters() {
|
void DelayReverb::recalcParameters() {
|
||||||
// Number of samples between impulse and eventual appearance on the left channel
|
// Number of samples between impulse and eventual appearance on the left channel
|
||||||
delayLeft = REVERB_TIMINGS[time][0] * sampleRate / 32000;
|
delayLeft = REVERB_TIMINGS[time][0];
|
||||||
// Number of samples between impulse and eventual appearance on the right channel
|
// Number of samples between impulse and eventual appearance on the right channel
|
||||||
delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000;
|
delayRight = REVERB_TIMINGS[time][1];
|
||||||
// Number of samples between a response and that response feeding back/echoing
|
// Number of samples between a response and that response feeding back/echoing
|
||||||
delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000;
|
delayFeedback = REVERB_TIMINGS[time][2];
|
||||||
|
|
||||||
if (time < 6) {
|
if (level < 3 || time < 6) {
|
||||||
feedback = REVERB_FEEDBACK;
|
feedback = REVERB_FEEDBACK / 256.0f;
|
||||||
} else {
|
} else {
|
||||||
feedback = REVERB_FEEDBACK67;
|
feedback = REVERB_FEEDBACK67 / 256.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fading speed, i.e. amplitude ratio of neighbor responses
|
// Overall output amp
|
||||||
fade = REVERB_FADE[level];
|
amp = (level == 0 && time == 0) ? 0.0f : REVERB_AMP[level] / 65536.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||||
if (buf == NULL) {
|
if (buf == NULL) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) {
|
for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) {
|
||||||
// The ring buffer write index moves backwards; reads are all done with positive offsets.
|
// The ring buffer write index moves backwards; reads are all done with positive offsets.
|
||||||
Bit32u bufIxPrev = (bufIx + 1) % bufSize;
|
Bit32u bufIxPrev = (bufIx + 1) % BUFFER_SIZE;
|
||||||
Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize;
|
Bit32u bufIxLeft = (bufIx + delayLeft) % BUFFER_SIZE;
|
||||||
Bit32u bufIxRight = (bufIx + delayRight) % bufSize;
|
Bit32u bufIxRight = (bufIx + delayRight) % BUFFER_SIZE;
|
||||||
Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize;
|
Bit32u bufIxFeedback = (bufIx + delayFeedback) % BUFFER_SIZE;
|
||||||
|
|
||||||
// Attenuated input samples and feedback response are directly added to the current ring buffer location
|
// Attenuated input samples and feedback response are directly added to the current ring buffer location
|
||||||
float sample = fade * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
|
float lpfIn = amp * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
|
||||||
|
|
||||||
// Single-pole IIR filter found on real devices
|
// Single-pole IIR filter found on real devices
|
||||||
buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE;
|
buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn;
|
||||||
|
|
||||||
outLeft[sampleIx] = buf[bufIxLeft];
|
outLeft[sampleIx] = buf[bufIxLeft];
|
||||||
outRight[sampleIx] = buf[bufIxRight];
|
outRight[sampleIx] = buf[bufIxRight];
|
||||||
|
|
||||||
bufIx = (bufSize + bufIx - 1) % bufSize;
|
bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DelayReverb::isActive() const {
|
bool DelayReverb::isActive() const {
|
||||||
// Quick hack: Return true iff all samples in the left buffer are the same and
|
if (buf == NULL) return false;
|
||||||
// all samples in the right buffers are the same (within the sample output threshold).
|
|
||||||
if (buf == NULL) {
|
float *b = buf;
|
||||||
return false;
|
float max = 0.001f;
|
||||||
}
|
for (Bit32u i = 0; i < BUFFER_SIZE; i++) {
|
||||||
float last = buf[0] * 8192.0f;
|
if ((*b < -max) || (*b > max)) return true;
|
||||||
for (unsigned int i = 1; i < bufSize; i++) {
|
b++;
|
||||||
float s = (buf[i] * 8192.0f);
|
|
||||||
if (fabs(s - last) > 1.0f) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -25,17 +25,14 @@ private:
|
||||||
Bit8u time;
|
Bit8u time;
|
||||||
Bit8u level;
|
Bit8u level;
|
||||||
|
|
||||||
unsigned int sampleRate;
|
|
||||||
Bit32u bufSize;
|
|
||||||
Bit32u bufIx;
|
Bit32u bufIx;
|
||||||
|
|
||||||
float *buf;
|
float *buf;
|
||||||
|
|
||||||
Bit32u delayLeft;
|
Bit32u delayLeft;
|
||||||
Bit32u delayRight;
|
Bit32u delayRight;
|
||||||
Bit32u delayFeedback;
|
Bit32u delayFeedback;
|
||||||
|
|
||||||
float fade;
|
float amp;
|
||||||
float feedback;
|
float feedback;
|
||||||
|
|
||||||
void recalcParameters();
|
void recalcParameters();
|
||||||
|
@ -43,7 +40,7 @@ private:
|
||||||
public:
|
public:
|
||||||
DelayReverb();
|
DelayReverb();
|
||||||
~DelayReverb();
|
~DelayReverb();
|
||||||
void open(unsigned int sampleRate);
|
void open();
|
||||||
void close();
|
void close();
|
||||||
void setParameters(Bit8u time, Bit8u level);
|
void setParameters(Bit8u time, Bit8u level);
|
||||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -35,9 +35,7 @@ FreeverbModel::~FreeverbModel() {
|
||||||
delete freeverb;
|
delete freeverb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreeverbModel::open(unsigned int /*sampleRate*/) {
|
void FreeverbModel::open() {
|
||||||
// FIXME: scaleTuning must be multiplied by sample rate to 32000Hz ratio
|
|
||||||
// IIR filter values depend on sample rate as well
|
|
||||||
if (freeverb == NULL) {
|
if (freeverb == NULL) {
|
||||||
freeverb = new revmodel(scaleTuning);
|
freeverb = new revmodel(scaleTuning);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -32,7 +32,7 @@ class FreeverbModel : public ReverbModel {
|
||||||
public:
|
public:
|
||||||
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
|
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
|
||||||
~FreeverbModel();
|
~FreeverbModel();
|
||||||
void open(unsigned int sampleRate);
|
void open();
|
||||||
void close();
|
void close();
|
||||||
void setParameters(Bit8u time, Bit8u level);
|
void setParameters(Bit8u time, Bit8u level);
|
||||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -82,9 +82,13 @@ void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
|
||||||
if (increment == 0) {
|
if (increment == 0) {
|
||||||
largeIncrement = 0;
|
largeIncrement = 0;
|
||||||
} else {
|
} else {
|
||||||
// Using integer argument here, no precision loss:
|
// Three bits in the fractional part, no need to interpolate
|
||||||
// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
|
// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
|
||||||
largeIncrement = (unsigned int)(EXP2I(((increment & 0x7F) + 24) << 9) + 0.125f);
|
Bit32u expArg = increment & 0x7F;
|
||||||
|
largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511];
|
||||||
|
largeIncrement <<= expArg >> 3;
|
||||||
|
largeIncrement += 64;
|
||||||
|
largeIncrement >>= 9;
|
||||||
}
|
}
|
||||||
descending = (increment & 0x80) != 0;
|
descending = (increment & 0x80) != 0;
|
||||||
if (descending) {
|
if (descending) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
418
audio/softsynth/mt32/LA32WaveGenerator.cpp
Normal file
418
audio/softsynth/mt32/LA32WaveGenerator.cpp
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#include <cmath>
|
||||||
|
#include "mt32emu.h"
|
||||||
|
#include "mmath.h"
|
||||||
|
#include "LA32WaveGenerator.h"
|
||||||
|
|
||||||
|
#if MT32EMU_ACCURATE_WG == 0
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;
|
||||||
|
static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18;
|
||||||
|
static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18;
|
||||||
|
static const Bit32u MAX_CUTOFF_VALUE = 240 << 18;
|
||||||
|
static const LogSample SILENCE = {65535, LogSample::POSITIVE};
|
||||||
|
|
||||||
|
Bit16u LA32Utilites::interpolateExp(const Bit16u fract) {
|
||||||
|
Bit16u expTabIndex = fract >> 3;
|
||||||
|
Bit16u extraBits = ~fract & 7;
|
||||||
|
Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex];
|
||||||
|
Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]);
|
||||||
|
return expTabEntry2 + (((expTabEntry1 - expTabEntry2) * extraBits) >> 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit16s LA32Utilites::unlog(const LogSample &logSample) {
|
||||||
|
//Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f);
|
||||||
|
Bit32u intLogValue = logSample.logValue >> 12;
|
||||||
|
Bit16u fracLogValue = logSample.logValue & 4095;
|
||||||
|
Bit16s sample = interpolateExp(fracLogValue) >> intLogValue;
|
||||||
|
return logSample.sign == LogSample::POSITIVE ? sample : -sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) {
|
||||||
|
Bit32u logSampleValue = logSample1.logValue + logSample2.logValue;
|
||||||
|
logSample1.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||||
|
logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32u LA32WaveGenerator::getSampleStep() {
|
||||||
|
// sampleStep = EXP2F(pitch / 4096.0f + 4.0f)
|
||||||
|
Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||||
|
sampleStep <<= pitch >> 12;
|
||||||
|
sampleStep >>= 8;
|
||||||
|
sampleStep &= ~1;
|
||||||
|
return sampleStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) {
|
||||||
|
// resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f);
|
||||||
|
Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095);
|
||||||
|
resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12;
|
||||||
|
return resonanceWaveLengthFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) {
|
||||||
|
// Ratio of positive segment to wave length
|
||||||
|
Bit32u effectivePulseWidthValue = 0;
|
||||||
|
if (pulseWidth > 128) {
|
||||||
|
effectivePulseWidthValue = (pulseWidth - 128) << 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32u highLinearLength = 0;
|
||||||
|
// highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||||
|
if (effectivePulseWidthValue < effectiveCutoffValue) {
|
||||||
|
Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue;
|
||||||
|
highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095);
|
||||||
|
highLinearLength <<= 7 + (expArg >> 12);
|
||||||
|
highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||||
|
}
|
||||||
|
return highLinearLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) {
|
||||||
|
// Assuming 12-bit multiplication used here
|
||||||
|
squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4);
|
||||||
|
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||||
|
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||||
|
if (squareWavePosition < highLinearLength) {
|
||||||
|
phase = POSITIVE_LINEAR_SEGMENT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
squareWavePosition -= highLinearLength;
|
||||||
|
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||||
|
phase = POSITIVE_FALLING_SINE_SEGMENT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||||
|
resonanceSinePosition = squareWavePosition;
|
||||||
|
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||||
|
phase = NEGATIVE_FALLING_SINE_SEGMENT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||||
|
if (squareWavePosition < lowLinearLength) {
|
||||||
|
phase = NEGATIVE_LINEAR_SEGMENT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
squareWavePosition -= lowLinearLength;
|
||||||
|
phase = NEGATIVE_RISING_SINE_SEGMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::advancePosition() {
|
||||||
|
wavePosition += getSampleStep();
|
||||||
|
wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||||
|
|
||||||
|
Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0;
|
||||||
|
Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue);
|
||||||
|
Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue);
|
||||||
|
Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength;
|
||||||
|
computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor);
|
||||||
|
|
||||||
|
// resonancePhase computation hack
|
||||||
|
*(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::generateNextSquareWaveLogSample() {
|
||||||
|
Bit32u logSampleValue;
|
||||||
|
switch (phase) {
|
||||||
|
case POSITIVE_RISING_SINE_SEGMENT:
|
||||||
|
case NEGATIVE_FALLING_SINE_SEGMENT:
|
||||||
|
logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511];
|
||||||
|
break;
|
||||||
|
case POSITIVE_FALLING_SINE_SEGMENT:
|
||||||
|
case NEGATIVE_RISING_SINE_SEGMENT:
|
||||||
|
logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511];
|
||||||
|
break;
|
||||||
|
case POSITIVE_LINEAR_SEGMENT:
|
||||||
|
case NEGATIVE_LINEAR_SEGMENT:
|
||||||
|
default:
|
||||||
|
logSampleValue = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logSampleValue <<= 2;
|
||||||
|
logSampleValue += amp >> 10;
|
||||||
|
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||||
|
logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
squareLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||||
|
squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
|
||||||
|
Bit32u logSampleValue;
|
||||||
|
if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) {
|
||||||
|
logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511];
|
||||||
|
} else {
|
||||||
|
logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511];
|
||||||
|
}
|
||||||
|
logSampleValue <<= 2;
|
||||||
|
logSampleValue += amp >> 10;
|
||||||
|
|
||||||
|
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||||
|
Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1;
|
||||||
|
// Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
|
||||||
|
logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
|
||||||
|
|
||||||
|
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||||
|
if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
|
||||||
|
// The window is synchronous sine here
|
||||||
|
logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
|
||||||
|
} else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) {
|
||||||
|
// The window is synchronous square sine here
|
||||||
|
logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||||
|
// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
|
||||||
|
logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
|
||||||
|
} else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
|
||||||
|
// For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
|
||||||
|
Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13;
|
||||||
|
logSampleValue += Tables::getInstance().logsin9[sineIx] << 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures
|
||||||
|
logSampleValue -= 1 << 12;
|
||||||
|
|
||||||
|
resonanceLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||||
|
resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const {
|
||||||
|
Bit32u sawtoothCosinePosition = wavePosition + (1 << 18);
|
||||||
|
if ((sawtoothCosinePosition & (1 << 18)) > 0) {
|
||||||
|
logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511];
|
||||||
|
} else {
|
||||||
|
logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511];
|
||||||
|
}
|
||||||
|
logSample.logValue <<= 2;
|
||||||
|
logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const {
|
||||||
|
Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1;
|
||||||
|
logSampleValue += amp >> 10;
|
||||||
|
logSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||||
|
logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::generateNextPCMWaveLogSamples() {
|
||||||
|
// This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc.
|
||||||
|
// The most probable cause is the factor in the interpolation formula is one bit less
|
||||||
|
// accurate than the sample position counter
|
||||||
|
pcmInterpolationFactor = (wavePosition & 255) >> 1;
|
||||||
|
Bit32u pcmWaveTableIx = wavePosition >> 8;
|
||||||
|
pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||||
|
if (pcmWaveInterpolated) {
|
||||||
|
pcmWaveTableIx++;
|
||||||
|
if (pcmWaveTableIx < pcmWaveLength) {
|
||||||
|
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||||
|
} else {
|
||||||
|
if (pcmWaveLooped) {
|
||||||
|
pcmWaveTableIx -= pcmWaveLength;
|
||||||
|
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||||
|
} else {
|
||||||
|
secondPCMLogSample = SILENCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
secondPCMLogSample = SILENCE;
|
||||||
|
}
|
||||||
|
// pcmSampleStep = (Bit32u)EXP2F(pitch / 4096.0f + 3.0f);
|
||||||
|
Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||||
|
pcmSampleStep <<= pitch >> 12;
|
||||||
|
// Seeing the actual lengths of the PCM wave for pitches 00..12,
|
||||||
|
// the pcmPosition counter can be assumed to have 8-bit fractions
|
||||||
|
pcmSampleStep >>= 9;
|
||||||
|
wavePosition += pcmSampleStep;
|
||||||
|
if (wavePosition >= (pcmWaveLength << 8)) {
|
||||||
|
if (pcmWaveLooped) {
|
||||||
|
wavePosition -= pcmWaveLength << 8;
|
||||||
|
} else {
|
||||||
|
deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
|
||||||
|
sawtoothWaveform = useSawtoothWaveform;
|
||||||
|
pulseWidth = usePulseWidth;
|
||||||
|
resonance = useResonance;
|
||||||
|
|
||||||
|
wavePosition = 0;
|
||||||
|
|
||||||
|
squareWavePosition = 0;
|
||||||
|
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||||
|
|
||||||
|
resonanceSinePosition = 0;
|
||||||
|
resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT;
|
||||||
|
resonanceAmpSubtraction = (32 - resonance) << 10;
|
||||||
|
resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
|
||||||
|
|
||||||
|
pcmWaveAddress = NULL;
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
|
||||||
|
pcmWaveAddress = usePCMWaveAddress;
|
||||||
|
pcmWaveLength = usePCMWaveLength;
|
||||||
|
pcmWaveLooped = usePCMWaveLooped;
|
||||||
|
pcmWaveInterpolated = usePCMWaveInterpolated;
|
||||||
|
|
||||||
|
wavePosition = 0;
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
amp = useAmp;
|
||||||
|
pitch = usePitch;
|
||||||
|
|
||||||
|
if (isPCMWave()) {
|
||||||
|
generateNextPCMWaveLogSamples();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||||
|
// More research is needed to be sure that this is correct, however.
|
||||||
|
cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal;
|
||||||
|
|
||||||
|
generateNextSquareWaveLogSample();
|
||||||
|
generateNextResonanceWaveLogSample();
|
||||||
|
if (sawtoothWaveform) {
|
||||||
|
LogSample cosineLogSample;
|
||||||
|
generateNextSawtoothCosineLogSample(cosineLogSample);
|
||||||
|
LA32Utilites::addLogSamples(squareLogSample, cosineLogSample);
|
||||||
|
LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample);
|
||||||
|
}
|
||||||
|
advancePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const {
|
||||||
|
if (!isActive()) {
|
||||||
|
return SILENCE;
|
||||||
|
}
|
||||||
|
if (isPCMWave()) {
|
||||||
|
return first ? firstPCMLogSample : secondPCMLogSample;
|
||||||
|
}
|
||||||
|
return first ? squareLogSample : resonanceLogSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::deactivate() {
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LA32WaveGenerator::isActive() const {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LA32WaveGenerator::isPCMWave() const {
|
||||||
|
return pcmWaveAddress != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
|
||||||
|
return pcmInterpolationFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) {
|
||||||
|
ringModulated = useRingModulated;
|
||||||
|
mixed = useMixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||||
|
} else {
|
||||||
|
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||||
|
} else {
|
||||||
|
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
master.generateNextSample(amp, pitch, cutoff);
|
||||||
|
} else {
|
||||||
|
slave.generateNextSample(amp, pitch, cutoff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) {
|
||||||
|
if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LogSample firstLogSample = wg.getOutputLogSample(true);
|
||||||
|
LogSample secondLogSample = wg.getOutputLogSample(false);
|
||||||
|
if (ringModulatingLogSample != NULL) {
|
||||||
|
LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample);
|
||||||
|
LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample);
|
||||||
|
}
|
||||||
|
Bit16s firstSample = LA32Utilites::unlog(firstLogSample);
|
||||||
|
Bit16s secondSample = LA32Utilites::unlog(secondLogSample);
|
||||||
|
if (wg.isPCMWave()) {
|
||||||
|
return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7));
|
||||||
|
}
|
||||||
|
return firstSample + secondSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit16s LA32PartialPair::nextOutSample() {
|
||||||
|
if (ringModulated) {
|
||||||
|
LogSample slaveFirstLogSample = slave.getOutputLogSample(true);
|
||||||
|
LogSample slaveSecondLogSample = slave.getOutputLogSample(false);
|
||||||
|
Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample);
|
||||||
|
if (!slave.isPCMWave()) {
|
||||||
|
sample += unlogAndMixWGOutput(master, &slaveSecondLogSample);
|
||||||
|
}
|
||||||
|
if (mixed) {
|
||||||
|
sample += unlogAndMixWGOutput(master, NULL);
|
||||||
|
}
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
master.deactivate();
|
||||||
|
} else {
|
||||||
|
slave.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||||
|
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #if MT32EMU_ACCURATE_WG == 0
|
246
audio/softsynth/mt32/LA32WaveGenerator.h
Normal file
246
audio/softsynth/mt32/LA32WaveGenerator.h
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if MT32EMU_ACCURATE_WG == 0
|
||||||
|
|
||||||
|
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||||
|
#define MT32EMU_LA32_WAVE_GENERATOR_H
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions
|
||||||
|
* It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these:
|
||||||
|
* - interpolation of exponent table (obvious, a delta value has 4 bits)
|
||||||
|
* - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion)
|
||||||
|
* - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip)
|
||||||
|
* and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss
|
||||||
|
* Subtraction is supposed to be replaced by simple inversion
|
||||||
|
* As the logarithmic sine is always negative, all the logarithmic values are treated as decrements
|
||||||
|
*/
|
||||||
|
struct LogSample {
|
||||||
|
// 16-bit fixed point value, includes 12-bit fractional part
|
||||||
|
// 4-bit integer part allows to present any 16-bit sample in the log-space
|
||||||
|
// Obviously, the log value doesn't contain the sign of the resulting sample
|
||||||
|
Bit16u logValue;
|
||||||
|
enum {
|
||||||
|
POSITIVE,
|
||||||
|
NEGATIVE
|
||||||
|
} sign;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LA32Utilites {
|
||||||
|
public:
|
||||||
|
static Bit16u interpolateExp(const Bit16u fract);
|
||||||
|
static Bit16s unlog(const LogSample &logSample);
|
||||||
|
static void addLogSamples(LogSample &logSample1, const LogSample &logSample2);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||||
|
* The output square wave is created by adding high / low linear segments in-between
|
||||||
|
* the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
|
||||||
|
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||||
|
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||||
|
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||||
|
*/
|
||||||
|
class LA32WaveGenerator {
|
||||||
|
//***************************************************************************
|
||||||
|
// The local copy of partial parameters below
|
||||||
|
//***************************************************************************
|
||||||
|
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||||
|
bool sawtoothWaveform;
|
||||||
|
|
||||||
|
// Logarithmic amp of the wave generator
|
||||||
|
Bit32u amp;
|
||||||
|
|
||||||
|
// Logarithmic frequency of the resulting wave
|
||||||
|
Bit16u pitch;
|
||||||
|
|
||||||
|
// Values in range [1..31]
|
||||||
|
// Value 1 correspong to the minimum resonance
|
||||||
|
Bit8u resonance;
|
||||||
|
|
||||||
|
// Processed value in range [0..255]
|
||||||
|
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||||
|
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||||
|
Bit8u pulseWidth;
|
||||||
|
|
||||||
|
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||||
|
Bit32u cutoffVal;
|
||||||
|
|
||||||
|
// Logarithmic PCM sample start address
|
||||||
|
const Bit16s *pcmWaveAddress;
|
||||||
|
|
||||||
|
// Logarithmic PCM sample length
|
||||||
|
Bit32u pcmWaveLength;
|
||||||
|
|
||||||
|
// true for looped logarithmic PCM samples
|
||||||
|
bool pcmWaveLooped;
|
||||||
|
|
||||||
|
// false for slave PCM partials in the structures with the ring modulation
|
||||||
|
bool pcmWaveInterpolated;
|
||||||
|
|
||||||
|
//***************************************************************************
|
||||||
|
// Internal variables below
|
||||||
|
//***************************************************************************
|
||||||
|
|
||||||
|
// Relative position within either the synth wave or the PCM sampled wave
|
||||||
|
// 0 - start of the positive rising sine segment of the square wave or start of the PCM sample
|
||||||
|
// 1048576 (2^20) - end of the negative rising sine segment of the square wave
|
||||||
|
// For PCM waves, the address of the currently playing sample equals (wavePosition / 256)
|
||||||
|
Bit32u wavePosition;
|
||||||
|
|
||||||
|
// Relative position within a square wave phase:
|
||||||
|
// 0 - start of the phase
|
||||||
|
// 262144 (2^18) - end of a sine phase in the square wave
|
||||||
|
Bit32u squareWavePosition;
|
||||||
|
|
||||||
|
// Relative position within the positive or negative wave segment:
|
||||||
|
// 0 - start of the corresponding positive or negative segment of the square wave
|
||||||
|
// 262144 (2^18) - corresponds to end of the first sine phase in the square wave
|
||||||
|
// The same increment sampleStep is used to indicate the current position
|
||||||
|
// since the length of the resonance wave is always equal to four square wave sine segments.
|
||||||
|
Bit32u resonanceSinePosition;
|
||||||
|
|
||||||
|
// The amp of the resonance sine wave grows with the resonance value
|
||||||
|
// As the resonance value cannot change while the partial is active, it is initialised once
|
||||||
|
Bit32u resonanceAmpSubtraction;
|
||||||
|
|
||||||
|
// The decay speed of resonance sine wave, depends on the resonance value
|
||||||
|
Bit32u resAmpDecayFactor;
|
||||||
|
|
||||||
|
// Fractional part of the pcmPosition
|
||||||
|
Bit32u pcmInterpolationFactor;
|
||||||
|
|
||||||
|
// Current phase of the square wave
|
||||||
|
enum {
|
||||||
|
POSITIVE_RISING_SINE_SEGMENT,
|
||||||
|
POSITIVE_LINEAR_SEGMENT,
|
||||||
|
POSITIVE_FALLING_SINE_SEGMENT,
|
||||||
|
NEGATIVE_FALLING_SINE_SEGMENT,
|
||||||
|
NEGATIVE_LINEAR_SEGMENT,
|
||||||
|
NEGATIVE_RISING_SINE_SEGMENT
|
||||||
|
} phase;
|
||||||
|
|
||||||
|
// Current phase of the resonance wave
|
||||||
|
enum {
|
||||||
|
POSITIVE_RISING_RESONANCE_SINE_SEGMENT,
|
||||||
|
POSITIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||||
|
NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||||
|
NEGATIVE_RISING_RESONANCE_SINE_SEGMENT
|
||||||
|
} resonancePhase;
|
||||||
|
|
||||||
|
// Resulting log-space samples of the square and resonance waves
|
||||||
|
LogSample squareLogSample;
|
||||||
|
LogSample resonanceLogSample;
|
||||||
|
|
||||||
|
// Processed neighbour log-space samples of the PCM wave
|
||||||
|
LogSample firstPCMLogSample;
|
||||||
|
LogSample secondPCMLogSample;
|
||||||
|
|
||||||
|
//***************************************************************************
|
||||||
|
// Internal methods below
|
||||||
|
//***************************************************************************
|
||||||
|
|
||||||
|
Bit32u getSampleStep();
|
||||||
|
Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue);
|
||||||
|
Bit32u getHighLinearLength(Bit32u effectiveCutoffValue);
|
||||||
|
|
||||||
|
void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor);
|
||||||
|
void advancePosition();
|
||||||
|
|
||||||
|
void generateNextSquareWaveLogSample();
|
||||||
|
void generateNextResonanceWaveLogSample();
|
||||||
|
void generateNextSawtoothCosineLogSample(LogSample &logSample) const;
|
||||||
|
|
||||||
|
void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const;
|
||||||
|
void generateNextPCMWaveLogSamples();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||||
|
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||||
|
|
||||||
|
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||||
|
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||||
|
|
||||||
|
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||||
|
void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||||
|
|
||||||
|
// WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards
|
||||||
|
LogSample getOutputLogSample(const bool first) const;
|
||||||
|
|
||||||
|
// Deactivate the WG engine
|
||||||
|
void deactivate();
|
||||||
|
|
||||||
|
// Return active state of the WG engine
|
||||||
|
bool isActive() const;
|
||||||
|
|
||||||
|
// Return true if the WG engine generates PCM wave samples
|
||||||
|
bool isPCMWave() const;
|
||||||
|
|
||||||
|
// Return current PCM interpolation factor
|
||||||
|
Bit32u getPCMInterpolationFactor() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||||
|
class LA32PartialPair {
|
||||||
|
LA32WaveGenerator master;
|
||||||
|
LA32WaveGenerator slave;
|
||||||
|
bool ringModulated;
|
||||||
|
bool mixed;
|
||||||
|
|
||||||
|
static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample);
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum PairType {
|
||||||
|
MASTER,
|
||||||
|
SLAVE
|
||||||
|
};
|
||||||
|
|
||||||
|
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||||
|
// ringModulated should be set to true for the structures with ring modulation
|
||||||
|
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||||
|
void init(const bool ringModulated, const bool mixed);
|
||||||
|
|
||||||
|
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||||
|
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||||
|
|
||||||
|
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||||
|
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||||
|
|
||||||
|
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||||
|
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||||
|
|
||||||
|
// Perform mixing / ring modulation and return the result
|
||||||
|
Bit16s nextOutSample();
|
||||||
|
|
||||||
|
// Deactivate the WG engine
|
||||||
|
void deactivate(const PairType master);
|
||||||
|
|
||||||
|
// Return active state of the WG engine
|
||||||
|
bool isActive(const PairType master) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MT32Emu
|
||||||
|
|
||||||
|
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||||
|
|
||||||
|
#endif // #if MT32EMU_ACCURATE_WG == 0
|
347
audio/softsynth/mt32/LegacyWaveGenerator.cpp
Normal file
347
audio/softsynth/mt32/LegacyWaveGenerator.cpp
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#include <cmath>
|
||||||
|
#include "mt32emu.h"
|
||||||
|
#include "mmath.h"
|
||||||
|
#include "LegacyWaveGenerator.h"
|
||||||
|
|
||||||
|
#if MT32EMU_ACCURATE_WG == 1
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
static const float MIDDLE_CUTOFF_VALUE = 128.0f;
|
||||||
|
static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f;
|
||||||
|
static const float MAX_CUTOFF_VALUE = 240.0f;
|
||||||
|
|
||||||
|
float LA32WaveGenerator::getPCMSample(unsigned int position) {
|
||||||
|
if (position >= pcmWaveLength) {
|
||||||
|
if (!pcmWaveLooped) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
position = position % pcmWaveLength;
|
||||||
|
}
|
||||||
|
Bit16s pcmSample = pcmWaveAddress[position];
|
||||||
|
float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f);
|
||||||
|
return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||||
|
this->sawtoothWaveform = sawtoothWaveform;
|
||||||
|
this->pulseWidth = pulseWidth;
|
||||||
|
this->resonance = resonance;
|
||||||
|
|
||||||
|
wavePos = 0.0f;
|
||||||
|
lastFreq = 0.0f;
|
||||||
|
|
||||||
|
pcmWaveAddress = NULL;
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) {
|
||||||
|
this->pcmWaveAddress = pcmWaveAddress;
|
||||||
|
this->pcmWaveLength = pcmWaveLength;
|
||||||
|
this->pcmWaveLooped = pcmWaveLooped;
|
||||||
|
this->pcmWaveInterpolated = pcmWaveInterpolated;
|
||||||
|
|
||||||
|
pcmPosition = 0.0f;
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) {
|
||||||
|
if (!active) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->amp = amp;
|
||||||
|
this->pitch = pitch;
|
||||||
|
|
||||||
|
float sample = 0.0f;
|
||||||
|
|
||||||
|
// SEMI-CONFIRMED: From sample analysis:
|
||||||
|
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
|
||||||
|
// This gives results within +/- 2 at the output (before any DAC bitshifting)
|
||||||
|
// when sustaining at levels 156 - 255 with no modifiers.
|
||||||
|
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
|
||||||
|
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
|
||||||
|
// positive amps, so negative still needs to be explored, as well as lower levels.
|
||||||
|
//
|
||||||
|
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
|
||||||
|
|
||||||
|
float amp = EXP2F(ampVal / -1024.0f / 4096.0f);
|
||||||
|
float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
|
||||||
|
|
||||||
|
if (isPCMWave()) {
|
||||||
|
// Render PCM waveform
|
||||||
|
int len = pcmWaveLength;
|
||||||
|
int intPCMPosition = (int)pcmPosition;
|
||||||
|
if (intPCMPosition >= len && !pcmWaveLooped) {
|
||||||
|
// We're now past the end of a non-looping PCM waveform so it's time to die.
|
||||||
|
deactivate();
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
float positionDelta = freq * 2048.0f / SAMPLE_RATE;
|
||||||
|
|
||||||
|
// Linear interpolation
|
||||||
|
float firstSample = getPCMSample(intPCMPosition);
|
||||||
|
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
|
||||||
|
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
|
||||||
|
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
|
||||||
|
if (pcmWaveInterpolated) {
|
||||||
|
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
|
||||||
|
} else {
|
||||||
|
sample = firstSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
float newPCMPosition = pcmPosition + positionDelta;
|
||||||
|
if (pcmWaveLooped) {
|
||||||
|
newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength);
|
||||||
|
}
|
||||||
|
pcmPosition = newPCMPosition;
|
||||||
|
} else {
|
||||||
|
// Render synthesised waveform
|
||||||
|
wavePos *= lastFreq / freq;
|
||||||
|
lastFreq = freq;
|
||||||
|
|
||||||
|
float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f);
|
||||||
|
{
|
||||||
|
//static const float resAmpFactor = EXP2F(-7);
|
||||||
|
//resAmp = EXP2I(resonance << 10) * resAmpFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cutoffModifier may not be supposed to be directly added to the cutoff -
|
||||||
|
// it may for example need to be multiplied in some way.
|
||||||
|
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||||
|
// More research is needed to be sure that this is correct, however.
|
||||||
|
float cutoffVal = cutoffRampVal / 262144.0f;
|
||||||
|
if (cutoffVal > MAX_CUTOFF_VALUE) {
|
||||||
|
cutoffVal = MAX_CUTOFF_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wave length in samples
|
||||||
|
float waveLen = SAMPLE_RATE / freq;
|
||||||
|
|
||||||
|
// Init cosineLen
|
||||||
|
float cosineLen = 0.5f * waveLen;
|
||||||
|
if (cutoffVal > MIDDLE_CUTOFF_VALUE) {
|
||||||
|
cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start playing in center of first cosine segment
|
||||||
|
// relWavePos is shifted by a half of cosineLen
|
||||||
|
float relWavePos = wavePos + 0.5f * cosineLen;
|
||||||
|
if (relWavePos > waveLen) {
|
||||||
|
relWavePos -= waveLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ratio of positive segment to wave length
|
||||||
|
float pulseLen = 0.5f;
|
||||||
|
if (pulseWidth > 128) {
|
||||||
|
pulseLen = EXP2F((64 - pulseWidth) / 64.0f);
|
||||||
|
//static const float pulseLenFactor = EXP2F(-192 / 64);
|
||||||
|
//pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
|
||||||
|
}
|
||||||
|
pulseLen *= waveLen;
|
||||||
|
|
||||||
|
float hLen = pulseLen - cosineLen;
|
||||||
|
|
||||||
|
// Ignore pulsewidths too high for given freq
|
||||||
|
if (hLen < 0.0f) {
|
||||||
|
hLen = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore pulsewidths too high for given freq and cutoff
|
||||||
|
float lLen = waveLen - hLen - 2 * cosineLen;
|
||||||
|
if (lLen < 0.0f) {
|
||||||
|
lLen = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct resAmp for cutoff in range 50..66
|
||||||
|
if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
|
||||||
|
resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce filtered square wave with 2 cosine waves on slopes
|
||||||
|
|
||||||
|
// 1st cosine segment
|
||||||
|
if (relWavePos < cosineLen) {
|
||||||
|
sample = -cos(FLOAT_PI * relWavePos / cosineLen);
|
||||||
|
} else
|
||||||
|
|
||||||
|
// high linear segment
|
||||||
|
if (relWavePos < (cosineLen + hLen)) {
|
||||||
|
sample = 1.f;
|
||||||
|
} else
|
||||||
|
|
||||||
|
// 2nd cosine segment
|
||||||
|
if (relWavePos < (2 * cosineLen + hLen)) {
|
||||||
|
sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// low linear segment
|
||||||
|
sample = -1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cutoffVal < 128.0f) {
|
||||||
|
|
||||||
|
// Attenuate samples below cutoff 50
|
||||||
|
// Found by sample analysis
|
||||||
|
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Add resonance sine. Effective for cutoff > 50 only
|
||||||
|
float resSample = 1.0f;
|
||||||
|
|
||||||
|
// Resonance decay speed factor
|
||||||
|
float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2];
|
||||||
|
|
||||||
|
// Now relWavePos counts from the middle of first cosine
|
||||||
|
relWavePos = wavePos;
|
||||||
|
|
||||||
|
// negative segments
|
||||||
|
if (!(relWavePos < (cosineLen + hLen))) {
|
||||||
|
resSample = -resSample;
|
||||||
|
relWavePos -= cosineLen + hLen;
|
||||||
|
|
||||||
|
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||||
|
resAmpDecayFactor += 0.25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resonance sine WG
|
||||||
|
resSample *= sin(FLOAT_PI * relWavePos / cosineLen);
|
||||||
|
|
||||||
|
// Resonance sine amp
|
||||||
|
float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact
|
||||||
|
float resAmpFade = EXP2F(resAmpFadeLog2);
|
||||||
|
|
||||||
|
// Now relWavePos set negative to the left from center of any cosine
|
||||||
|
relWavePos = wavePos;
|
||||||
|
|
||||||
|
// negative segment
|
||||||
|
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
|
||||||
|
relWavePos -= waveLen;
|
||||||
|
} else
|
||||||
|
|
||||||
|
// positive segment
|
||||||
|
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
|
||||||
|
relWavePos -= cosineLen + hLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||||
|
if (relWavePos < 0.5f * cosineLen) {
|
||||||
|
float syncSine = sin(FLOAT_PI * relWavePos / cosineLen);
|
||||||
|
if (relWavePos < 0.0f) {
|
||||||
|
// The window is synchronous square sine here
|
||||||
|
resAmpFade *= syncSine * syncSine;
|
||||||
|
} else {
|
||||||
|
// The window is synchronous sine here
|
||||||
|
resAmpFade *= syncSine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sample += resSample * resAmp * resAmpFade;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sawtooth waves
|
||||||
|
if (sawtoothWaveform) {
|
||||||
|
sample *= cos(FLOAT_2PI * wavePos / waveLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
wavePos++;
|
||||||
|
|
||||||
|
// wavePos isn't supposed to be > waveLen
|
||||||
|
if (wavePos > waveLen) {
|
||||||
|
wavePos -= waveLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply sample with current TVA value
|
||||||
|
sample *= amp;
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32WaveGenerator::deactivate() {
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LA32WaveGenerator::isActive() const {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LA32WaveGenerator::isPCMWave() const {
|
||||||
|
return pcmWaveAddress != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::init(const bool ringModulated, const bool mixed) {
|
||||||
|
this->ringModulated = ringModulated;
|
||||||
|
this->mixed = mixed;
|
||||||
|
masterOutputSample = 0.0f;
|
||||||
|
slaveOutputSample = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||||
|
} else {
|
||||||
|
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||||
|
} else {
|
||||||
|
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
masterOutputSample = master.generateNextSample(amp, pitch, cutoff);
|
||||||
|
} else {
|
||||||
|
slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit16s LA32PartialPair::nextOutSample() {
|
||||||
|
float outputSample;
|
||||||
|
if (ringModulated) {
|
||||||
|
float ringModulatedSample = masterOutputSample * slaveOutputSample;
|
||||||
|
outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
|
||||||
|
} else {
|
||||||
|
outputSample = masterOutputSample + slaveOutputSample;
|
||||||
|
}
|
||||||
|
return Bit16s(outputSample * 8192.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||||
|
if (useMaster == MASTER) {
|
||||||
|
master.deactivate();
|
||||||
|
masterOutputSample = 0.0f;
|
||||||
|
} else {
|
||||||
|
slave.deactivate();
|
||||||
|
slaveOutputSample = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||||
|
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #if MT32EMU_ACCURATE_WG == 1
|
146
audio/softsynth/mt32/LegacyWaveGenerator.h
Normal file
146
audio/softsynth/mt32/LegacyWaveGenerator.h
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if MT32EMU_ACCURATE_WG == 1
|
||||||
|
|
||||||
|
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||||
|
#define MT32EMU_LA32_WAVE_GENERATOR_H
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||||
|
* The output square wave is created by adding high / low linear segments in-between
|
||||||
|
* the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
|
||||||
|
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||||
|
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||||
|
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||||
|
*/
|
||||||
|
class LA32WaveGenerator {
|
||||||
|
//***************************************************************************
|
||||||
|
// The local copy of partial parameters below
|
||||||
|
//***************************************************************************
|
||||||
|
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||||
|
bool sawtoothWaveform;
|
||||||
|
|
||||||
|
// Logarithmic amp of the wave generator
|
||||||
|
Bit32u amp;
|
||||||
|
|
||||||
|
// Logarithmic frequency of the resulting wave
|
||||||
|
Bit16u pitch;
|
||||||
|
|
||||||
|
// Values in range [1..31]
|
||||||
|
// Value 1 correspong to the minimum resonance
|
||||||
|
Bit8u resonance;
|
||||||
|
|
||||||
|
// Processed value in range [0..255]
|
||||||
|
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||||
|
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||||
|
Bit8u pulseWidth;
|
||||||
|
|
||||||
|
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||||
|
Bit32u cutoffVal;
|
||||||
|
|
||||||
|
// Logarithmic PCM sample start address
|
||||||
|
const Bit16s *pcmWaveAddress;
|
||||||
|
|
||||||
|
// Logarithmic PCM sample length
|
||||||
|
Bit32u pcmWaveLength;
|
||||||
|
|
||||||
|
// true for looped logarithmic PCM samples
|
||||||
|
bool pcmWaveLooped;
|
||||||
|
|
||||||
|
// false for slave PCM partials in the structures with the ring modulation
|
||||||
|
bool pcmWaveInterpolated;
|
||||||
|
|
||||||
|
//***************************************************************************
|
||||||
|
// Internal variables below
|
||||||
|
//***************************************************************************
|
||||||
|
|
||||||
|
float wavePos;
|
||||||
|
float lastFreq;
|
||||||
|
float pcmPosition;
|
||||||
|
|
||||||
|
float getPCMSample(unsigned int position);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||||
|
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||||
|
|
||||||
|
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||||
|
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||||
|
|
||||||
|
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||||
|
float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||||
|
|
||||||
|
// Deactivate the WG engine
|
||||||
|
void deactivate();
|
||||||
|
|
||||||
|
// Return active state of the WG engine
|
||||||
|
bool isActive() const;
|
||||||
|
|
||||||
|
// Return true if the WG engine generates PCM wave samples
|
||||||
|
bool isPCMWave() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||||
|
class LA32PartialPair {
|
||||||
|
LA32WaveGenerator master;
|
||||||
|
LA32WaveGenerator slave;
|
||||||
|
bool ringModulated;
|
||||||
|
bool mixed;
|
||||||
|
float masterOutputSample;
|
||||||
|
float slaveOutputSample;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum PairType {
|
||||||
|
MASTER,
|
||||||
|
SLAVE
|
||||||
|
};
|
||||||
|
|
||||||
|
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||||
|
// ringModulated should be set to true for the structures with ring modulation
|
||||||
|
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||||
|
void init(const bool ringModulated, const bool mixed);
|
||||||
|
|
||||||
|
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||||
|
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||||
|
|
||||||
|
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||||
|
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||||
|
|
||||||
|
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||||
|
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||||
|
|
||||||
|
// Perform mixing / ring modulation and return the result
|
||||||
|
Bit16s nextOutSample();
|
||||||
|
|
||||||
|
// Deactivate the WG engine
|
||||||
|
void deactivate(const PairType master);
|
||||||
|
|
||||||
|
// Return active state of the WG engine
|
||||||
|
bool isActive(const PairType master) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MT32Emu
|
||||||
|
|
||||||
|
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||||
|
|
||||||
|
#endif // #if MT32EMU_ACCURATE_WG == 1
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -68,18 +68,16 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) {
|
||||||
activePartialCount = 0;
|
activePartialCount = 0;
|
||||||
memset(patchCache, 0, sizeof(patchCache));
|
memset(patchCache, 0, sizeof(patchCache));
|
||||||
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
|
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
|
||||||
freePolys.push_front(new Poly(this));
|
freePolys.prepend(new Poly(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Part::~Part() {
|
Part::~Part() {
|
||||||
while (!activePolys.empty()) {
|
while (!activePolys.isEmpty()) {
|
||||||
delete activePolys.front();
|
delete activePolys.takeFirst();
|
||||||
activePolys.pop_front();
|
|
||||||
}
|
}
|
||||||
while (!freePolys.empty()) {
|
while (!freePolys.isEmpty()) {
|
||||||
delete freePolys.front();
|
delete freePolys.takeFirst();
|
||||||
freePolys.pop_front();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +175,7 @@ void Part::refresh() {
|
||||||
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
|
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
|
||||||
}
|
}
|
||||||
memcpy(currentInstr, timbreTemp->common.name, 10);
|
memcpy(currentInstr, timbreTemp->common.name, 10);
|
||||||
|
synth->newTimbreSet(partNum, patchTemp->patch.timbreGroup, currentInstr);
|
||||||
updatePitchBenderRange();
|
updatePitchBenderRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,8 +244,8 @@ void Part::backupCacheToPartials(PatchCache cache[4]) {
|
||||||
// if so then duplicate the cached data from the part to the partial so that
|
// if so then duplicate the cached data from the part to the partial so that
|
||||||
// we can change the part's cache without affecting the partial.
|
// we can change the part's cache without affecting the partial.
|
||||||
// We delay this until now to avoid a copy operation with every note played
|
// We delay this until now to avoid a copy operation with every note played
|
||||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
(*polyIt)->backupCacheToPartials(cache);
|
poly->backupCacheToPartials(cache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,8 +444,7 @@ void Part::abortPoly(Poly *poly) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Part::abortFirstPoly(unsigned int key) {
|
bool Part::abortFirstPoly(unsigned int key) {
|
||||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
Poly *poly = *polyIt;
|
|
||||||
if (poly->getKey() == key) {
|
if (poly->getKey() == key) {
|
||||||
abortPoly(poly);
|
abortPoly(poly);
|
||||||
return true;
|
return true;
|
||||||
|
@ -456,8 +454,7 @@ bool Part::abortFirstPoly(unsigned int key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Part::abortFirstPoly(PolyState polyState) {
|
bool Part::abortFirstPoly(PolyState polyState) {
|
||||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
Poly *poly = *polyIt;
|
|
||||||
if (poly->getState() == polyState) {
|
if (poly->getState() == polyState) {
|
||||||
abortPoly(poly);
|
abortPoly(poly);
|
||||||
return true;
|
return true;
|
||||||
|
@ -474,10 +471,10 @@ bool Part::abortFirstPolyPreferHeld() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Part::abortFirstPoly() {
|
bool Part::abortFirstPoly() {
|
||||||
if (activePolys.empty()) {
|
if (activePolys.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
abortPoly(activePolys.front());
|
abortPoly(activePolys.getFirst());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,17 +499,16 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (freePolys.empty()) {
|
if (freePolys.isEmpty()) {
|
||||||
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
|
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Poly *poly = freePolys.front();
|
Poly *poly = freePolys.takeFirst();
|
||||||
freePolys.pop_front();
|
|
||||||
if (patchTemp->patch.assignMode & 1) {
|
if (patchTemp->patch.assignMode & 1) {
|
||||||
// Priority to data first received
|
// Priority to data first received
|
||||||
activePolys.push_front(poly);
|
activePolys.prepend(poly);
|
||||||
} else {
|
} else {
|
||||||
activePolys.push_back(poly);
|
activePolys.append(poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
Partial *partials[4];
|
Partial *partials[4];
|
||||||
|
@ -537,13 +533,13 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
|
||||||
#if MT32EMU_MONITOR_PARTIALS > 1
|
#if MT32EMU_MONITOR_PARTIALS > 1
|
||||||
synth->printPartialUsage();
|
synth->printPartialUsage();
|
||||||
#endif
|
#endif
|
||||||
|
synth->polyStateChanged(partNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Part::allNotesOff() {
|
void Part::allNotesOff() {
|
||||||
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
|
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
|
||||||
// should treat the hold pedal as usual.
|
// should treat the hold pedal as usual.
|
||||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
Poly *poly = *polyIt;
|
|
||||||
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff.
|
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff.
|
||||||
// if (poly->canSustain() || poly->getKey() == 0) {
|
// if (poly->canSustain() || poly->getKey() == 0) {
|
||||||
// FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed.
|
// FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed.
|
||||||
|
@ -557,15 +553,13 @@ void Part::allSoundOff() {
|
||||||
// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
|
// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
|
||||||
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
|
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
|
||||||
// we're only using this method internally.
|
// we're only using this method internally.
|
||||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
Poly *poly = *polyIt;
|
|
||||||
poly->startDecay();
|
poly->startDecay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Part::stopPedalHold() {
|
void Part::stopPedalHold() {
|
||||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
Poly *poly = *polyIt;
|
|
||||||
poly->stopPedalHold();
|
poly->stopPedalHold();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -583,8 +577,7 @@ void Part::stopNote(unsigned int key) {
|
||||||
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
|
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
Poly *poly = *polyIt;
|
|
||||||
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
|
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
|
||||||
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
|
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
|
||||||
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
|
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
|
||||||
|
@ -605,8 +598,7 @@ unsigned int Part::getActivePartialCount() const {
|
||||||
|
|
||||||
unsigned int Part::getActiveNonReleasingPartialCount() const {
|
unsigned int Part::getActiveNonReleasingPartialCount() const {
|
||||||
unsigned int activeNonReleasingPartialCount = 0;
|
unsigned int activeNonReleasingPartialCount = 0;
|
||||||
for (Common::List<Poly *>::const_iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||||
Poly *poly = *polyIt;
|
|
||||||
if (poly->getState() != POLY_Releasing) {
|
if (poly->getState() != POLY_Releasing) {
|
||||||
activeNonReleasingPartialCount += poly->getActivePartialCount();
|
activeNonReleasingPartialCount += poly->getActivePartialCount();
|
||||||
}
|
}
|
||||||
|
@ -618,7 +610,100 @@ void Part::partialDeactivated(Poly *poly) {
|
||||||
activePartialCount--;
|
activePartialCount--;
|
||||||
if (!poly->isActive()) {
|
if (!poly->isActive()) {
|
||||||
activePolys.remove(poly);
|
activePolys.remove(poly);
|
||||||
freePolys.push_front(poly);
|
freePolys.prepend(poly);
|
||||||
|
synth->polyStateChanged(partNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#define POLY_LIST_DEBUG
|
||||||
|
|
||||||
|
PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}
|
||||||
|
|
||||||
|
bool PolyList::isEmpty() const {
|
||||||
|
#ifdef POLY_LIST_DEBUG
|
||||||
|
if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) {
|
||||||
|
printf("PolyList: desynchronised firstPoly & lastPoly pointers\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return firstPoly == NULL && lastPoly == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Poly *PolyList::getFirst() const {
|
||||||
|
return firstPoly;
|
||||||
|
}
|
||||||
|
|
||||||
|
Poly *PolyList::getLast() const {
|
||||||
|
return lastPoly;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PolyList::prepend(Poly *poly) {
|
||||||
|
#ifdef POLY_LIST_DEBUG
|
||||||
|
if (poly->getNext() != NULL) {
|
||||||
|
printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
poly->setNext(firstPoly);
|
||||||
|
firstPoly = poly;
|
||||||
|
if (lastPoly == NULL) {
|
||||||
|
lastPoly = poly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PolyList::append(Poly *poly) {
|
||||||
|
#ifdef POLY_LIST_DEBUG
|
||||||
|
if (poly->getNext() != NULL) {
|
||||||
|
printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
poly->setNext(NULL);
|
||||||
|
if (lastPoly != NULL) {
|
||||||
|
#ifdef POLY_LIST_DEBUG
|
||||||
|
if (lastPoly->getNext() != NULL) {
|
||||||
|
printf("PolyList: Non-NULL next field in the lastPoly\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
lastPoly->setNext(poly);
|
||||||
|
}
|
||||||
|
lastPoly = poly;
|
||||||
|
if (firstPoly == NULL) {
|
||||||
|
firstPoly = poly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Poly *PolyList::takeFirst() {
|
||||||
|
Poly *oldFirst = firstPoly;
|
||||||
|
firstPoly = oldFirst->getNext();
|
||||||
|
if (firstPoly == NULL) {
|
||||||
|
#ifdef POLY_LIST_DEBUG
|
||||||
|
if (lastPoly != oldFirst) {
|
||||||
|
printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
lastPoly = NULL;
|
||||||
|
}
|
||||||
|
oldFirst->setNext(NULL);
|
||||||
|
return oldFirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PolyList::remove(Poly * const polyToRemove) {
|
||||||
|
if (polyToRemove == firstPoly) {
|
||||||
|
takeFirst();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) {
|
||||||
|
if (poly->getNext() == polyToRemove) {
|
||||||
|
if (polyToRemove == lastPoly) {
|
||||||
|
#ifdef POLY_LIST_DEBUG
|
||||||
|
if (lastPoly->getNext() != NULL) {
|
||||||
|
printf("PolyList: Non-NULL next field in the lastPoly\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
lastPoly = poly;
|
||||||
|
}
|
||||||
|
poly->setNext(polyToRemove->getNext());
|
||||||
|
polyToRemove->setNext(NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -18,13 +18,27 @@
|
||||||
#ifndef MT32EMU_PART_H
|
#ifndef MT32EMU_PART_H
|
||||||
#define MT32EMU_PART_H
|
#define MT32EMU_PART_H
|
||||||
|
|
||||||
#include <common/list.h>
|
|
||||||
|
|
||||||
namespace MT32Emu {
|
namespace MT32Emu {
|
||||||
|
|
||||||
class PartialManager;
|
class PartialManager;
|
||||||
class Synth;
|
class Synth;
|
||||||
|
|
||||||
|
class PolyList {
|
||||||
|
private:
|
||||||
|
Poly *firstPoly;
|
||||||
|
Poly *lastPoly;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PolyList();
|
||||||
|
bool isEmpty() const;
|
||||||
|
Poly *getFirst() const;
|
||||||
|
Poly *getLast() const;
|
||||||
|
void prepend(Poly *poly);
|
||||||
|
void append(Poly *poly);
|
||||||
|
Poly *takeFirst();
|
||||||
|
void remove(Poly * const poly);
|
||||||
|
};
|
||||||
|
|
||||||
class Part {
|
class Part {
|
||||||
private:
|
private:
|
||||||
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
|
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
|
||||||
|
@ -37,8 +51,8 @@ private:
|
||||||
|
|
||||||
unsigned int activePartialCount;
|
unsigned int activePartialCount;
|
||||||
PatchCache patchCache[4];
|
PatchCache patchCache[4];
|
||||||
Common::List<Poly *> freePolys;
|
PolyList freePolys;
|
||||||
Common::List<Poly *> activePolys;
|
PolyList activePolys;
|
||||||
|
|
||||||
void setPatch(const PatchParam *patch);
|
void setPatch(const PatchParam *patch);
|
||||||
unsigned int midiKeyToKey(unsigned int midiKey);
|
unsigned int midiKeyToKey(unsigned int midiKey);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -35,7 +35,12 @@ static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||||
static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f};
|
static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f};
|
||||||
|
|
||||||
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
|
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
|
||||||
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0), tva(new TVA(this, &Ramp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) {
|
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) {
|
||||||
|
// Initialisation of tva, tvp and tvf uses 'this' pointer
|
||||||
|
// and thus should not be in the initializer list to avoid a compiler warning
|
||||||
|
tva = new TVA(this, &Ramp);
|
||||||
|
tvp = new TVP(this);
|
||||||
|
tvf = new TVF(this, &cutoffModifierRamp);
|
||||||
ownerPart = -1;
|
ownerPart = -1;
|
||||||
poly = NULL;
|
poly = NULL;
|
||||||
pair = NULL;
|
pair = NULL;
|
||||||
|
@ -81,26 +86,22 @@ void Partial::deactivate() {
|
||||||
ownerPart = -1;
|
ownerPart = -1;
|
||||||
if (poly != NULL) {
|
if (poly != NULL) {
|
||||||
poly->partialDeactivated(this);
|
poly->partialDeactivated(this);
|
||||||
if (pair != NULL) {
|
|
||||||
pair->pair = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#if MT32EMU_MONITOR_PARTIALS > 2
|
#if MT32EMU_MONITOR_PARTIALS > 2
|
||||||
synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum);
|
synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum);
|
||||||
synth->printPartialUsage(sampleNum);
|
synth->printPartialUsage(sampleNum);
|
||||||
#endif
|
#endif
|
||||||
}
|
if (isRingModulatingSlave()) {
|
||||||
|
pair->la32Pair.deactivate(LA32PartialPair::SLAVE);
|
||||||
// DEPRECATED: This should probably go away eventually, it's currently only used as a kludge to protect our old assumptions that
|
|
||||||
// rhythm part notes were always played as key MIDDLEC.
|
|
||||||
int Partial::getKey() const {
|
|
||||||
if (poly == NULL) {
|
|
||||||
return -1;
|
|
||||||
} else if (ownerPart == 8) {
|
|
||||||
// FIXME: Hack, should go away after new pitch stuff is committed (and possibly some TVF changes)
|
|
||||||
return MIDDLEC;
|
|
||||||
} else {
|
} else {
|
||||||
return poly->getKey();
|
la32Pair.deactivate(LA32PartialPair::MASTER);
|
||||||
|
if (hasRingModulatingSlave()) {
|
||||||
|
pair->deactivate();
|
||||||
|
pair = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pair != NULL) {
|
||||||
|
pair->pair = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +164,6 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
|
||||||
pcmWave = &synth->pcmWaves[pcmNum];
|
pcmWave = &synth->pcmWaves[pcmNum];
|
||||||
} else {
|
} else {
|
||||||
pcmWave = NULL;
|
pcmWave = NULL;
|
||||||
wavePos = 0.0f;
|
|
||||||
lastFreq = 0.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
|
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
|
||||||
|
@ -175,26 +174,65 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
|
||||||
pulseWidthVal = 255;
|
pulseWidthVal = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
pcmPosition = 0.0f;
|
|
||||||
pair = pairPartial;
|
pair = pairPartial;
|
||||||
alreadyOutputed = false;
|
alreadyOutputed = false;
|
||||||
tva->reset(part, patchCache->partialParam, rhythmTemp);
|
tva->reset(part, patchCache->partialParam, rhythmTemp);
|
||||||
tvp->reset(part, patchCache->partialParam);
|
tvp->reset(part, patchCache->partialParam);
|
||||||
tvf->reset(patchCache->partialParam, tvp->getBasePitch());
|
tvf->reset(patchCache->partialParam, tvp->getBasePitch());
|
||||||
}
|
|
||||||
|
|
||||||
float Partial::getPCMSample(unsigned int position) {
|
LA32PartialPair::PairType pairType;
|
||||||
if (position >= pcmWave->len) {
|
LA32PartialPair *useLA32Pair;
|
||||||
if (!pcmWave->loop) {
|
if (isRingModulatingSlave()) {
|
||||||
return 0;
|
pairType = LA32PartialPair::SLAVE;
|
||||||
}
|
useLA32Pair = &pair->la32Pair;
|
||||||
position = position % pcmWave->len;
|
} else {
|
||||||
|
pairType = LA32PartialPair::MASTER;
|
||||||
|
la32Pair.init(hasRingModulatingSlave(), mixType == 1);
|
||||||
|
useLA32Pair = &la32Pair;
|
||||||
}
|
}
|
||||||
return synth->pcmROMData[pcmWave->addr + position];
|
if (isPCM()) {
|
||||||
|
useLA32Pair->initPCM(pairType, &synth->pcmROMData[pcmWave->addr], pcmWave->len, pcmWave->loop);
|
||||||
|
} else {
|
||||||
|
useLA32Pair->initSynth(pairType, (patchCache->waveform & 1) != 0, pulseWidthVal, patchCache->srcPartial.tvf.resonance + 1);
|
||||||
|
}
|
||||||
|
if (!hasRingModulatingSlave()) {
|
||||||
|
la32Pair.deactivate(LA32PartialPair::SLAVE);
|
||||||
|
}
|
||||||
|
// Temporary integration hack
|
||||||
|
stereoVolume.leftVol /= 8192.0f;
|
||||||
|
stereoVolume.rightVol /= 8192.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) {
|
Bit32u Partial::getAmpValue() {
|
||||||
const Tables &tables = Tables::getInstance();
|
// SEMI-CONFIRMED: From sample analysis:
|
||||||
|
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
|
||||||
|
// This gives results within +/- 2 at the output (before any DAC bitshifting)
|
||||||
|
// when sustaining at levels 156 - 255 with no modifiers.
|
||||||
|
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
|
||||||
|
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
|
||||||
|
// positive amps, so negative still needs to be explored, as well as lower levels.
|
||||||
|
//
|
||||||
|
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
|
||||||
|
// TODO: The tests above were performed using the float model, to be refined
|
||||||
|
Bit32u ampRampVal = 67117056 - ampRamp.nextValue();
|
||||||
|
if (ampRamp.checkInterrupt()) {
|
||||||
|
tva->handleInterrupt();
|
||||||
|
}
|
||||||
|
return ampRampVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bit32u Partial::getCutoffValue() {
|
||||||
|
if (isPCM()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue();
|
||||||
|
if (cutoffModifierRamp.checkInterrupt()) {
|
||||||
|
tvf->handleInterrupt();
|
||||||
|
}
|
||||||
|
return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long Partial::generateSamples(Bit16s *partialBuf, unsigned long length) {
|
||||||
if (!isActive() || alreadyOutputed) {
|
if (!isActive() || alreadyOutputed) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -202,303 +240,31 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
|
||||||
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum);
|
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
alreadyOutputed = true;
|
alreadyOutputed = true;
|
||||||
|
|
||||||
// Generate samples
|
|
||||||
|
|
||||||
for (sampleNum = 0; sampleNum < length; sampleNum++) {
|
for (sampleNum = 0; sampleNum < length; sampleNum++) {
|
||||||
float sample = 0;
|
if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) {
|
||||||
Bit32u ampRampVal = ampRamp.nextValue();
|
|
||||||
if (ampRamp.checkInterrupt()) {
|
|
||||||
tva->handleInterrupt();
|
|
||||||
}
|
|
||||||
if (!tva->isPlaying()) {
|
|
||||||
deactivate();
|
deactivate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue());
|
||||||
Bit16u pitch = tvp->nextPitch();
|
if (hasRingModulatingSlave()) {
|
||||||
|
la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue());
|
||||||
// SEMI-CONFIRMED: From sample analysis:
|
if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) {
|
||||||
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
|
pair->deactivate();
|
||||||
// This gives results within +/- 2 at the output (before any DAC bitshifting)
|
if (mixType == 2) {
|
||||||
// when sustaining at levels 156 - 255 with no modifiers.
|
deactivate();
|
||||||
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
|
break;
|
||||||
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
|
|
||||||
// positive amps, so negative still needs to be explored, as well as lower levels.
|
|
||||||
//
|
|
||||||
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
|
|
||||||
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f);
|
|
||||||
float freq = EXP2F(pitch / 4096.0f - 16.0f) * 32000.0f;
|
|
||||||
#else
|
|
||||||
static const float ampFactor = EXP2F(32772 / -2048.0f);
|
|
||||||
float amp = EXP2I(ampRampVal >> 10) * ampFactor;
|
|
||||||
|
|
||||||
static const float freqFactor = EXP2F(-16.0f) * 32000.0f;
|
|
||||||
float freq = EXP2I(pitch) * freqFactor;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (patchCache->PCMPartial) {
|
|
||||||
// Render PCM waveform
|
|
||||||
int len = pcmWave->len;
|
|
||||||
int intPCMPosition = (int)pcmPosition;
|
|
||||||
if (intPCMPosition >= len && !pcmWave->loop) {
|
|
||||||
// We're now past the end of a non-looping PCM waveform so it's time to die.
|
|
||||||
deactivate();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Bit32u pcmAddr = pcmWave->addr;
|
|
||||||
float positionDelta = freq * 2048.0f / synth->myProp.sampleRate;
|
|
||||||
|
|
||||||
// Linear interpolation
|
|
||||||
float firstSample = synth->pcmROMData[pcmAddr + intPCMPosition];
|
|
||||||
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
|
|
||||||
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
|
|
||||||
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
|
|
||||||
if (pair == NULL || mixType == 0 || structurePosition == 0) {
|
|
||||||
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
|
|
||||||
} else {
|
|
||||||
sample = firstSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
float newPCMPosition = pcmPosition + positionDelta;
|
|
||||||
if (pcmWave->loop) {
|
|
||||||
newPCMPosition = fmod(newPCMPosition, (float)pcmWave->len);
|
|
||||||
}
|
|
||||||
pcmPosition = newPCMPosition;
|
|
||||||
} else {
|
|
||||||
// Render synthesised waveform
|
|
||||||
wavePos *= lastFreq / freq;
|
|
||||||
lastFreq = freq;
|
|
||||||
|
|
||||||
Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue();
|
|
||||||
if (cutoffModifierRamp.checkInterrupt()) {
|
|
||||||
tvf->handleInterrupt();
|
|
||||||
}
|
|
||||||
float cutoffModifier = cutoffModifierRampVal / 262144.0f;
|
|
||||||
|
|
||||||
// res corresponds to a value set in an LA32 register
|
|
||||||
Bit8u res = patchCache->srcPartial.tvf.resonance + 1;
|
|
||||||
|
|
||||||
// Using tiny exact table for computation of EXP2F(1.0f - (32 - res) / 4.0f)
|
|
||||||
float resAmp = tables.resAmpMax[res];
|
|
||||||
|
|
||||||
// The cutoffModifier may not be supposed to be directly added to the cutoff -
|
|
||||||
// it may for example need to be multiplied in some way.
|
|
||||||
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
|
||||||
// More research is needed to be sure that this is correct, however.
|
|
||||||
float cutoffVal = tvf->getBaseCutoff() + cutoffModifier;
|
|
||||||
if (cutoffVal > 240.0f) {
|
|
||||||
cutoffVal = 240.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wave length in samples
|
|
||||||
float waveLen = synth->myProp.sampleRate / freq;
|
|
||||||
|
|
||||||
// Init cosineLen
|
|
||||||
float cosineLen = 0.5f * waveLen;
|
|
||||||
if (cutoffVal > 128.0f) {
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
cosineLen *= EXP2F((cutoffVal - 128.0f) / -16.0f); // found from sample analysis
|
|
||||||
#else
|
|
||||||
static const float cosineLenFactor = EXP2F(128.0f / -16.0f);
|
|
||||||
cosineLen *= EXP2I(Bit32u((256.0f - cutoffVal) * 256.0f)) * cosineLenFactor;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start playing in center of first cosine segment
|
|
||||||
// relWavePos is shifted by a half of cosineLen
|
|
||||||
float relWavePos = wavePos + 0.5f * cosineLen;
|
|
||||||
if (relWavePos > waveLen) {
|
|
||||||
relWavePos -= waveLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
float pulseLen = 0.5f;
|
|
||||||
if (pulseWidthVal > 128) {
|
|
||||||
pulseLen += tables.pulseLenFactor[pulseWidthVal - 128];
|
|
||||||
}
|
|
||||||
pulseLen *= waveLen;
|
|
||||||
|
|
||||||
float lLen = pulseLen - cosineLen;
|
|
||||||
|
|
||||||
// Ignore pulsewidths too high for given freq
|
|
||||||
if (lLen < 0.0f) {
|
|
||||||
lLen = 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore pulsewidths too high for given freq and cutoff
|
|
||||||
float hLen = waveLen - lLen - 2 * cosineLen;
|
|
||||||
if (hLen < 0.0f) {
|
|
||||||
hLen = 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Correct resAmp for cutoff in range 50..66
|
|
||||||
if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
|
|
||||||
#else
|
|
||||||
resAmp *= tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Produce filtered square wave with 2 cosine waves on slopes
|
|
||||||
|
|
||||||
// 1st cosine segment
|
|
||||||
if (relWavePos < cosineLen) {
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
sample = -cosf(FLOAT_PI * relWavePos / cosineLen);
|
|
||||||
#else
|
|
||||||
sample = -tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024];
|
|
||||||
#endif
|
|
||||||
} else
|
|
||||||
|
|
||||||
// high linear segment
|
|
||||||
if (relWavePos < (cosineLen + hLen)) {
|
|
||||||
sample = 1.f;
|
|
||||||
} else
|
|
||||||
|
|
||||||
// 2nd cosine segment
|
|
||||||
if (relWavePos < (2 * cosineLen + hLen)) {
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
|
|
||||||
#else
|
|
||||||
sample = tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024];
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// low linear segment
|
|
||||||
sample = -1.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cutoffVal < 128.0f) {
|
|
||||||
|
|
||||||
// Attenuate samples below cutoff 50
|
|
||||||
// Found by sample analysis
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
|
|
||||||
#else
|
|
||||||
static const float cutoffAttenuationFactor = EXP2F(-0.125f * 128.0f);
|
|
||||||
sample *= EXP2I(Bit32u(512.0f * cutoffVal)) * cutoffAttenuationFactor;
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Add resonance sine. Effective for cutoff > 50 only
|
|
||||||
float resSample = 1.0f;
|
|
||||||
|
|
||||||
// Now relWavePos counts from the middle of first cosine
|
|
||||||
relWavePos = wavePos;
|
|
||||||
|
|
||||||
// negative segments
|
|
||||||
if (!(relWavePos < (cosineLen + hLen))) {
|
|
||||||
resSample = -resSample;
|
|
||||||
relWavePos -= cosineLen + hLen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resonance sine WG
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
resSample *= sinf(FLOAT_PI * relWavePos / cosineLen);
|
|
||||||
#else
|
|
||||||
resSample *= tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Resonance sine amp
|
|
||||||
float resAmpFadeLog2 = -tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen); // seems to be exact
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
float resAmpFade = EXP2F(resAmpFadeLog2);
|
|
||||||
#else
|
|
||||||
static const float resAmpFadeFactor = EXP2F(-30.0f);
|
|
||||||
float resAmpFade = (resAmpFadeLog2 < -30.0f) ? 0.0f : EXP2I(Bit32u((30.0f + resAmpFadeLog2) * 4096.0f)) * resAmpFadeFactor;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Now relWavePos set negative to the left from center of any cosine
|
|
||||||
relWavePos = wavePos;
|
|
||||||
|
|
||||||
// negative segment
|
|
||||||
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
|
|
||||||
relWavePos -= waveLen;
|
|
||||||
} else
|
|
||||||
|
|
||||||
// positive segment
|
|
||||||
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
|
|
||||||
relWavePos -= cosineLen + hLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fading to zero while within cosine segments to avoid jumps in the wave
|
|
||||||
// Sample analysis suggests that this window is very close to cosine
|
|
||||||
if (relWavePos < 0.5f * cosineLen) {
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
resAmpFade *= 0.5f * (1.0f - cosf(FLOAT_PI * relWavePos / (0.5f * cosineLen)));
|
|
||||||
#else
|
|
||||||
resAmpFade *= 0.5f * (1.0f + tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
sample += resSample * resAmp * resAmpFade;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sawtooth waves
|
|
||||||
if ((patchCache->waveform & 1) != 0) {
|
|
||||||
#if MT32EMU_ACCURATE_WG == 1
|
|
||||||
sample *= cosf(FLOAT_2PI * wavePos / waveLen);
|
|
||||||
#else
|
|
||||||
sample *= tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
wavePos++;
|
|
||||||
|
|
||||||
// wavePos isn't supposed to be > waveLen
|
|
||||||
if (wavePos > waveLen) {
|
|
||||||
wavePos -= waveLen;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*partialBuf++ = la32Pair.nextOutSample();
|
||||||
// Multiply sample with current TVA value
|
|
||||||
sample *= amp;
|
|
||||||
*partialBuf++ = sample;
|
|
||||||
}
|
}
|
||||||
unsigned long renderedSamples = sampleNum;
|
unsigned long renderedSamples = sampleNum;
|
||||||
sampleNum = 0;
|
sampleNum = 0;
|
||||||
return renderedSamples;
|
return renderedSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
float *Partial::mixBuffersRingMix(float *buf1, float *buf2, unsigned long len) {
|
|
||||||
if (buf1 == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (buf2 == NULL) {
|
|
||||||
return buf1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len--) {
|
|
||||||
// FIXME: At this point we have no idea whether this is remotely correct...
|
|
||||||
*buf1 = *buf1 * *buf2 + *buf1;
|
|
||||||
buf1++;
|
|
||||||
buf2++;
|
|
||||||
}
|
|
||||||
return buf1;
|
|
||||||
}
|
|
||||||
|
|
||||||
float *Partial::mixBuffersRing(float *buf1, float *buf2, unsigned long len) {
|
|
||||||
if (buf1 == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (buf2 == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len--) {
|
|
||||||
// FIXME: At this point we have no idea whether this is remotely correct...
|
|
||||||
*buf1 = *buf1 * *buf2;
|
|
||||||
buf1++;
|
|
||||||
buf2++;
|
|
||||||
}
|
|
||||||
return buf1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Partial::hasRingModulatingSlave() const {
|
bool Partial::hasRingModulatingSlave() const {
|
||||||
return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2);
|
return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2);
|
||||||
}
|
}
|
||||||
|
@ -530,53 +296,14 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt
|
||||||
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum);
|
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
unsigned long numGenerated = generateSamples(myBuffer, length);
|
||||||
float *partialBuf = &myBuffer[0];
|
|
||||||
unsigned long numGenerated = generateSamples(partialBuf, length);
|
|
||||||
if (mixType == 1 || mixType == 2) {
|
|
||||||
float *pairBuf;
|
|
||||||
unsigned long pairNumGenerated;
|
|
||||||
if (pair == NULL) {
|
|
||||||
pairBuf = NULL;
|
|
||||||
pairNumGenerated = 0;
|
|
||||||
} else {
|
|
||||||
pairBuf = &pair->myBuffer[0];
|
|
||||||
pairNumGenerated = pair->generateSamples(pairBuf, numGenerated);
|
|
||||||
// pair will have been set to NULL if it deactivated within generateSamples()
|
|
||||||
if (pair != NULL) {
|
|
||||||
if (!isActive()) {
|
|
||||||
pair->deactivate();
|
|
||||||
pair = NULL;
|
|
||||||
} else if (!pair->isActive()) {
|
|
||||||
pair = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pairNumGenerated > 0) {
|
|
||||||
if (mixType == 1) {
|
|
||||||
mixBuffersRingMix(partialBuf, pairBuf, pairNumGenerated);
|
|
||||||
} else {
|
|
||||||
mixBuffersRing(partialBuf, pairBuf, pairNumGenerated);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numGenerated > pairNumGenerated) {
|
|
||||||
if (mixType == 2) {
|
|
||||||
numGenerated = pairNumGenerated;
|
|
||||||
deactivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < numGenerated; i++) {
|
for (unsigned int i = 0; i < numGenerated; i++) {
|
||||||
*leftBuf++ = partialBuf[i] * stereoVolume.leftVol;
|
*leftBuf++ = myBuffer[i] * stereoVolume.leftVol;
|
||||||
|
*rightBuf++ = myBuffer[i] * stereoVolume.rightVol;
|
||||||
}
|
}
|
||||||
for (unsigned int i = 0; i < numGenerated; i++) {
|
for (; numGenerated < length; numGenerated++) {
|
||||||
*rightBuf++ = partialBuf[i] * stereoVolume.rightVol;
|
|
||||||
}
|
|
||||||
while (numGenerated < length) {
|
|
||||||
*leftBuf++ = 0.0f;
|
*leftBuf++ = 0.0f;
|
||||||
*rightBuf++ = 0.0f;
|
*rightBuf++ = 0.0f;
|
||||||
numGenerated++;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -44,12 +44,7 @@ private:
|
||||||
int structurePosition; // 0 or 1 of a structure pair
|
int structurePosition; // 0 or 1 of a structure pair
|
||||||
StereoVolume stereoVolume;
|
StereoVolume stereoVolume;
|
||||||
|
|
||||||
// Distance in (possibly fractional) samples from the start of the current pulse
|
Bit16s myBuffer[MAX_SAMPLES_PER_RUN];
|
||||||
float wavePos;
|
|
||||||
|
|
||||||
float lastFreq;
|
|
||||||
|
|
||||||
float myBuffer[MAX_SAMPLES_PER_RUN];
|
|
||||||
|
|
||||||
// Only used for PCM partials
|
// Only used for PCM partials
|
||||||
int pcmNum;
|
int pcmNum;
|
||||||
|
@ -60,17 +55,16 @@ private:
|
||||||
// Range: 0-255
|
// Range: 0-255
|
||||||
int pulseWidthVal;
|
int pulseWidthVal;
|
||||||
|
|
||||||
float pcmPosition;
|
|
||||||
|
|
||||||
Poly *poly;
|
Poly *poly;
|
||||||
|
|
||||||
LA32Ramp ampRamp;
|
LA32Ramp ampRamp;
|
||||||
LA32Ramp cutoffModifierRamp;
|
LA32Ramp cutoffModifierRamp;
|
||||||
|
|
||||||
float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len);
|
// TODO: This should be owned by PartialPair
|
||||||
float *mixBuffersRing(float *buf1, float *buf2, unsigned long len);
|
LA32PartialPair la32Pair;
|
||||||
|
|
||||||
float getPCMSample(unsigned int position);
|
Bit32u getAmpValue();
|
||||||
|
Bit32u getCutoffValue();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const PatchCache *patchCache;
|
const PatchCache *patchCache;
|
||||||
|
@ -90,7 +84,6 @@ public:
|
||||||
unsigned long debugGetSampleNum() const;
|
unsigned long debugGetSampleNum() const;
|
||||||
|
|
||||||
int getOwnerPart() const;
|
int getOwnerPart() const;
|
||||||
int getKey() const;
|
|
||||||
const Poly *getPoly() const;
|
const Poly *getPoly() const;
|
||||||
bool isActive() const;
|
bool isActive() const;
|
||||||
void activate(int part);
|
void activate(int part);
|
||||||
|
@ -111,7 +104,7 @@ public:
|
||||||
bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length);
|
bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length);
|
||||||
|
|
||||||
// This function writes mono sample output to the provided buffer, and returns the number of samples written
|
// This function writes mono sample output to the provided buffer, and returns the number of samples written
|
||||||
unsigned long generateSamples(float *partialBuf, unsigned long length);
|
unsigned long generateSamples(Bit16s *partialBuf, unsigned long length);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -29,6 +29,7 @@ Poly::Poly(Part *usePart) {
|
||||||
partials[i] = NULL;
|
partials[i] = NULL;
|
||||||
}
|
}
|
||||||
state = POLY_Inactive;
|
state = POLY_Inactive;
|
||||||
|
next = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
|
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
|
||||||
|
@ -174,4 +175,12 @@ void Poly::partialDeactivated(Partial *partial) {
|
||||||
part->partialDeactivated(this);
|
part->partialDeactivated(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Poly *Poly::getNext() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Poly::setNext(Poly *poly) {
|
||||||
|
next = poly;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -41,6 +41,8 @@ private:
|
||||||
|
|
||||||
Partial *partials[4];
|
Partial *partials[4];
|
||||||
|
|
||||||
|
Poly *next;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Poly(Part *part);
|
Poly(Part *part);
|
||||||
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
|
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
|
||||||
|
@ -60,6 +62,9 @@ public:
|
||||||
bool isActive() const;
|
bool isActive() const;
|
||||||
|
|
||||||
void partialDeactivated(Partial *partial);
|
void partialDeactivated(Partial *partial);
|
||||||
|
|
||||||
|
Poly *getNext();
|
||||||
|
void setNext(Poly *poly);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
111
audio/softsynth/mt32/ROMInfo.cpp
Normal file
111
audio/softsynth/mt32/ROMInfo.cpp
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#include <cstring>
|
||||||
|
#include "ROMInfo.h"
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
// Known ROMs
|
||||||
|
static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, NULL};
|
||||||
|
static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL, NULL};
|
||||||
|
static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL, NULL};
|
||||||
|
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL, NULL};
|
||||||
|
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL, NULL};
|
||||||
|
|
||||||
|
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL, NULL};
|
||||||
|
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL, NULL};
|
||||||
|
|
||||||
|
static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL, NULL};
|
||||||
|
static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL, NULL};
|
||||||
|
|
||||||
|
static const ROMInfo * const ROM_INFOS[] = {
|
||||||
|
&CTRL_MT32_V1_04,
|
||||||
|
&CTRL_MT32_V1_05,
|
||||||
|
&CTRL_MT32_V1_06,
|
||||||
|
&CTRL_MT32_V1_07,
|
||||||
|
&CTRL_MT32_BLUER,
|
||||||
|
&CTRL_CM32L_V1_00,
|
||||||
|
&CTRL_CM32L_V1_02,
|
||||||
|
&PCM_MT32,
|
||||||
|
&PCM_CM32L,
|
||||||
|
NULL};
|
||||||
|
|
||||||
|
const ROMInfo* ROMInfo::getROMInfo(Common::File *file) {
|
||||||
|
size_t fileSize = file->size();
|
||||||
|
// We haven't added the SHA1 checksum code in ScummVM, as the file size
|
||||||
|
// suffices for our needs for now.
|
||||||
|
//const char *fileDigest = file->getSHA1();
|
||||||
|
for (int i = 0; ROM_INFOS[i] != NULL; i++) {
|
||||||
|
const ROMInfo *romInfo = ROM_INFOS[i];
|
||||||
|
if (fileSize == romInfo->fileSize /*&& !strcmp(fileDigest, romInfo->sha1Digest)*/) {
|
||||||
|
return romInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
|
||||||
|
(void) romInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getROMCount() {
|
||||||
|
int count;
|
||||||
|
for(count = 0; ROM_INFOS[count] != NULL; count++) {
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROMInfo** ROMInfo::getROMInfoList(unsigned int types, unsigned int pairTypes) {
|
||||||
|
const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1];
|
||||||
|
const ROMInfo **currentROMInList = romInfoList;
|
||||||
|
for(int i = 0; ROM_INFOS[i] != NULL; i++) {
|
||||||
|
const ROMInfo *romInfo = ROM_INFOS[i];
|
||||||
|
if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) {
|
||||||
|
*currentROMInList++ = romInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*currentROMInList = NULL;
|
||||||
|
return romInfoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
|
||||||
|
delete[] romInfoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROMImage* ROMImage::makeROMImage(Common::File *file) {
|
||||||
|
ROMImage *romImage = new ROMImage;
|
||||||
|
romImage->file = file;
|
||||||
|
romImage->romInfo = ROMInfo::getROMInfo(romImage->file);
|
||||||
|
return romImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ROMImage::freeROMImage(const ROMImage *romImage) {
|
||||||
|
ROMInfo::freeROMInfo(romImage->romInfo);
|
||||||
|
delete romImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Common::File* ROMImage::getFile() const {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROMInfo* ROMImage::getROMInfo() const {
|
||||||
|
return romInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
77
audio/softsynth/mt32/ROMInfo.h
Normal file
77
audio/softsynth/mt32/ROMInfo.h
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MT32EMU_ROMINFO_H
|
||||||
|
#define MT32EMU_ROMINFO_H
|
||||||
|
|
||||||
|
//#include <cstddef>
|
||||||
|
#include "common/file.h"
|
||||||
|
|
||||||
|
namespace MT32Emu {
|
||||||
|
|
||||||
|
// Defines vital info about ROM file to be used by synth and applications
|
||||||
|
|
||||||
|
struct ROMInfo {
|
||||||
|
public:
|
||||||
|
size_t fileSize;
|
||||||
|
const char *sha1Digest;
|
||||||
|
enum Type {PCM, Control, Reverb} type;
|
||||||
|
const char *shortName;
|
||||||
|
const char *description;
|
||||||
|
enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType;
|
||||||
|
ROMInfo *pairROMInfo;
|
||||||
|
void *controlROMInfo;
|
||||||
|
|
||||||
|
// Returns a ROMInfo struct by inspecting the size and the SHA1 hash
|
||||||
|
static const ROMInfo* getROMInfo(Common::File *file);
|
||||||
|
|
||||||
|
// Currently no-op
|
||||||
|
static void freeROMInfo(const ROMInfo *romInfo);
|
||||||
|
|
||||||
|
// Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes
|
||||||
|
// (specified by bitmasks)
|
||||||
|
// Useful for GUI/console app to output information on what ROMs it supports
|
||||||
|
static const ROMInfo** getROMInfoList(unsigned int types, unsigned int pairTypes);
|
||||||
|
|
||||||
|
// Frees the list of ROMInfos given
|
||||||
|
static void freeROMInfoList(const ROMInfo **romInfos);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work
|
||||||
|
|
||||||
|
class ROMImage {
|
||||||
|
private:
|
||||||
|
Common::File *file;
|
||||||
|
const ROMInfo *romInfo;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Creates a ROMImage object given a ROMInfo and a File. Keeps a reference
|
||||||
|
// to the File and ROMInfo given, which must be freed separately by the user
|
||||||
|
// after the ROMImage is freed
|
||||||
|
static const ROMImage* makeROMImage(Common::File *file);
|
||||||
|
|
||||||
|
// Must only be done after all Synths using the ROMImage are deleted
|
||||||
|
static void freeROMImage(const ROMImage *romImage);
|
||||||
|
|
||||||
|
Common::File *getFile() const;
|
||||||
|
const ROMInfo *getROMInfo() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -27,8 +27,10 @@
|
||||||
#include "mmath.h"
|
#include "mmath.h"
|
||||||
#include "PartialManager.h"
|
#include "PartialManager.h"
|
||||||
|
|
||||||
#if MT32EMU_USE_AREVERBMODEL == 1
|
#if MT32EMU_USE_REVERBMODEL == 1
|
||||||
#include "AReverbModel.h"
|
#include "AReverbModel.h"
|
||||||
|
#elif MT32EMU_USE_REVERBMODEL == 2
|
||||||
|
#include "BReverbModel.h"
|
||||||
#else
|
#else
|
||||||
#include "FreeverbModel.h"
|
#include "FreeverbModel.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -140,22 +142,36 @@ Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) {
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
Synth::Synth() {
|
Synth::Synth(ReportHandler *useReportHandler) {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
reverbEnabled = true;
|
reverbEnabled = true;
|
||||||
reverbOverridden = false;
|
reverbOverridden = false;
|
||||||
|
|
||||||
#if MT32EMU_USE_AREVERBMODEL == 1
|
if (useReportHandler == NULL) {
|
||||||
reverbModels[0] = new AReverbModel(&AReverbModel::REVERB_MODE_0_SETTINGS);
|
reportHandler = new ReportHandler;
|
||||||
reverbModels[1] = new AReverbModel(&AReverbModel::REVERB_MODE_1_SETTINGS);
|
isDefaultReportHandler = true;
|
||||||
reverbModels[2] = new AReverbModel(&AReverbModel::REVERB_MODE_2_SETTINGS);
|
} else {
|
||||||
|
reportHandler = useReportHandler;
|
||||||
|
isDefaultReportHandler = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MT32EMU_USE_REVERBMODEL == 1
|
||||||
|
reverbModels[REVERB_MODE_ROOM] = new AReverbModel(REVERB_MODE_ROOM);
|
||||||
|
reverbModels[REVERB_MODE_HALL] = new AReverbModel(REVERB_MODE_HALL);
|
||||||
|
reverbModels[REVERB_MODE_PLATE] = new AReverbModel(REVERB_MODE_PLATE);
|
||||||
|
reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb();
|
||||||
|
#elif MT32EMU_USE_REVERBMODEL == 2
|
||||||
|
reverbModels[REVERB_MODE_ROOM] = new BReverbModel(REVERB_MODE_ROOM);
|
||||||
|
reverbModels[REVERB_MODE_HALL] = new BReverbModel(REVERB_MODE_HALL);
|
||||||
|
reverbModels[REVERB_MODE_PLATE] = new BReverbModel(REVERB_MODE_PLATE);
|
||||||
|
reverbModels[REVERB_MODE_TAP_DELAY] = new BReverbModel(REVERB_MODE_TAP_DELAY);
|
||||||
#else
|
#else
|
||||||
reverbModels[0] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f);
|
reverbModels[REVERB_MODE_ROOM] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f);
|
||||||
reverbModels[1] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f);
|
reverbModels[REVERB_MODE_HALL] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f);
|
||||||
reverbModels[2] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f);
|
reverbModels[REVERB_MODE_PLATE] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f);
|
||||||
|
reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
reverbModels[3] = new DelayReverb();
|
|
||||||
reverbModel = NULL;
|
reverbModel = NULL;
|
||||||
setDACInputMode(DACInputMode_NICE);
|
setDACInputMode(DACInputMode_NICE);
|
||||||
setOutputGain(1.0f);
|
setOutputGain(1.0f);
|
||||||
|
@ -170,31 +186,36 @@ Synth::~Synth() {
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
delete reverbModels[i];
|
delete reverbModels[i];
|
||||||
}
|
}
|
||||||
}
|
if (isDefaultReportHandler) {
|
||||||
|
delete reportHandler;
|
||||||
int Synth::report(ReportType type, const void *data) {
|
|
||||||
if (myProp.report != NULL) {
|
|
||||||
return myProp.report(myProp.userData, type, data);
|
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Synth::getSampleRate() const {
|
void ReportHandler::showLCDMessage(const char *data) {
|
||||||
return myProp.sampleRate;
|
printf("WRITE-LCD: %s", data);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportHandler::printDebug(const char *fmt, va_list list) {
|
||||||
|
vprintf(fmt, list);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::polyStateChanged(int partNum) {
|
||||||
|
reportHandler->onPolyStateChanged(partNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]) {
|
||||||
|
reportHandler->onProgramChanged(partNum, timbreGroup, patchName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::printDebug(const char *fmt, ...) {
|
void Synth::printDebug(const char *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
if (myProp.printDebug != NULL) {
|
|
||||||
myProp.printDebug(myProp.userData, fmt, ap);
|
|
||||||
} else {
|
|
||||||
#if MT32EMU_DEBUG_SAMPLESTAMPS > 0
|
#if MT32EMU_DEBUG_SAMPLESTAMPS > 0
|
||||||
printf("[%u] ", renderedSampleCount);
|
reportHandler->printDebug("[%u] ", renderedSampleCount);
|
||||||
#endif
|
#endif
|
||||||
vprintf(fmt, ap);
|
reportHandler->printDebug(fmt, ap);
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,80 +265,60 @@ void Synth::setReverbOutputGain(float newReverbOutputGain) {
|
||||||
reverbOutputGain = newReverbOutputGain;
|
reverbOutputGain = newReverbOutputGain;
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::File *Synth::openFile(const char *filename) {
|
bool Synth::loadControlROM(const ROMImage &controlROMImage) {
|
||||||
if (myProp.openFile != NULL) {
|
if (&controlROMImage == NULL) return false;
|
||||||
return myProp.openFile(myProp.userData, filename);
|
Common::File *file = controlROMImage.getFile();
|
||||||
}
|
const ROMInfo *controlROMInfo = controlROMImage.getROMInfo();
|
||||||
char pathBuf[2048];
|
if ((controlROMInfo == NULL)
|
||||||
if (myProp.baseDir != NULL) {
|
|| (controlROMInfo->type != ROMInfo::Control)
|
||||||
strcpy(&pathBuf[0], myProp.baseDir);
|
|| (controlROMInfo->pairType != ROMInfo::Full)) {
|
||||||
strcat(&pathBuf[0], filename);
|
return false;
|
||||||
filename = pathBuf;
|
|
||||||
}
|
|
||||||
Common::File *file = new Common::File();
|
|
||||||
if (!file->open(filename)) {
|
|
||||||
delete file;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Synth::closeFile(Common::File *file) {
|
|
||||||
if (myProp.closeFile != NULL) {
|
|
||||||
myProp.closeFile(myProp.userData, file);
|
|
||||||
} else {
|
|
||||||
file->close();
|
|
||||||
delete file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadResult Synth::loadControlROM(const char *filename) {
|
|
||||||
Common::File *file = openFile(filename); // ROM File
|
|
||||||
if (file == NULL) {
|
|
||||||
return LoadResult_NotFound;
|
|
||||||
}
|
|
||||||
size_t fileSize = file->size();
|
|
||||||
if (fileSize != CONTROL_ROM_SIZE) {
|
|
||||||
printDebug("Control ROM file %s size mismatch: %i", filename, fileSize);
|
|
||||||
}
|
}
|
||||||
|
#if MT32EMU_MONITOR_INIT
|
||||||
|
printDebug("Found Control ROM: %s, %s", controlROMInfo->shortName, controlROMInfo->description);
|
||||||
|
#endif
|
||||||
file->read(controlROMData, CONTROL_ROM_SIZE);
|
file->read(controlROMData, CONTROL_ROM_SIZE);
|
||||||
if (file->err()) {
|
|
||||||
closeFile(file);
|
|
||||||
return LoadResult_Unreadable;
|
|
||||||
}
|
|
||||||
closeFile(file);
|
|
||||||
|
|
||||||
// Control ROM successfully loaded, now check whether it's a known type
|
// Control ROM successfully loaded, now check whether it's a known type
|
||||||
controlROMMap = NULL;
|
controlROMMap = NULL;
|
||||||
for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) {
|
for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) {
|
||||||
if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) {
|
if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) {
|
||||||
controlROMMap = &ControlROMMaps[i];
|
controlROMMap = &ControlROMMaps[i];
|
||||||
return LoadResult_OK;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printDebug("%s does not match a known control ROM type", filename);
|
#if MT32EMU_MONITOR_INIT
|
||||||
return LoadResult_Invalid;
|
printDebug("Control ROM failed to load");
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadResult Synth::loadPCMROM(const char *filename) {
|
bool Synth::loadPCMROM(const ROMImage &pcmROMImage) {
|
||||||
Common::File *file = openFile(filename); // ROM File
|
if (&pcmROMImage == NULL) return false;
|
||||||
if (file == NULL) {
|
Common::File *file = pcmROMImage.getFile();
|
||||||
return LoadResult_NotFound;
|
const ROMInfo *pcmROMInfo = pcmROMImage.getROMInfo();
|
||||||
|
if ((pcmROMInfo == NULL)
|
||||||
|
|| (pcmROMInfo->type != ROMInfo::PCM)
|
||||||
|
|| (pcmROMInfo->pairType != ROMInfo::Full)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
#if MT32EMU_MONITOR_INIT
|
||||||
|
printDebug("Found PCM ROM: %s, %s", pcmROMInfo->shortName, pcmROMInfo->description);
|
||||||
|
#endif
|
||||||
size_t fileSize = file->size();
|
size_t fileSize = file->size();
|
||||||
if (fileSize < (size_t)(2 * pcmROMSize)) {
|
if (fileSize != (2 * pcmROMSize)) {
|
||||||
printDebug("PCM ROM file is too short (expected %d, got %d)", 2 * pcmROMSize, fileSize);
|
#if MT32EMU_MONITOR_INIT
|
||||||
closeFile(file);
|
printDebug("PCM ROM file has wrong size (expected %d, got %d)", 2 * pcmROMSize, fileSize);
|
||||||
return LoadResult_Invalid;
|
#endif
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (file->err()) {
|
|
||||||
closeFile(file);
|
byte *buffer = new byte[file->size()];
|
||||||
return LoadResult_Unreadable;
|
file->read(buffer, file->size());
|
||||||
}
|
const byte *fileData = buffer;
|
||||||
LoadResult rc = LoadResult_OK;
|
for (size_t i = 0; i < pcmROMSize; i++) {
|
||||||
for (int i = 0; i < pcmROMSize; i++) {
|
Bit8u s = *(fileData++);
|
||||||
Bit8u s = file->readByte();
|
Bit8u c = *(fileData++);
|
||||||
Bit8u c = file->readByte();
|
|
||||||
|
|
||||||
int order[16] = {0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8};
|
int order[16] = {0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8};
|
||||||
|
|
||||||
|
@ -331,28 +332,20 @@ LoadResult Synth::loadPCMROM(const char *filename) {
|
||||||
}
|
}
|
||||||
log = log | (short)(bit << (15 - u));
|
log = log | (short)(bit << (15 - u));
|
||||||
}
|
}
|
||||||
bool negative = log < 0;
|
pcmROMData[i] = log;
|
||||||
log &= 0x7FFF;
|
|
||||||
|
|
||||||
// CONFIRMED from sample analysis to be 99.99%+ accurate with current TVA multiplier
|
|
||||||
float lin = EXP2F((32787 - log) / -2048.0f);
|
|
||||||
|
|
||||||
if (negative) {
|
|
||||||
lin = -lin;
|
|
||||||
}
|
|
||||||
|
|
||||||
pcmROMData[i] = lin;
|
|
||||||
}
|
}
|
||||||
closeFile(file);
|
|
||||||
return rc;
|
delete[] buffer;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) {
|
bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) {
|
||||||
ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress];
|
ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress];
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
int rAddr = tps[i].pos * 0x800;
|
size_t rAddr = tps[i].pos * 0x800;
|
||||||
int rLenExp = (tps[i].len & 0x70) >> 4;
|
size_t rLenExp = (tps[i].len & 0x70) >> 4;
|
||||||
int rLen = 0x800 << rLenExp;
|
size_t rLen = 0x800 << rLenExp;
|
||||||
if (rAddr + rLen > pcmROMSize) {
|
if (rAddr + rLen > pcmROMSize) {
|
||||||
printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen);
|
printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen);
|
||||||
return false;
|
return false;
|
||||||
|
@ -414,12 +407,11 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Synth::open(SynthProperties &useProp) {
|
bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
prerenderReadIx = prerenderWriteIx = 0;
|
prerenderReadIx = prerenderWriteIx = 0;
|
||||||
myProp = useProp;
|
|
||||||
#if MT32EMU_MONITOR_INIT
|
#if MT32EMU_MONITOR_INIT
|
||||||
printDebug("Initialising Constant Tables");
|
printDebug("Initialising Constant Tables");
|
||||||
#endif
|
#endif
|
||||||
|
@ -428,11 +420,6 @@ bool Synth::open(SynthProperties &useProp) {
|
||||||
reverbModels[i]->open(useProp.sampleRate);
|
reverbModels[i]->open(useProp.sampleRate);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (useProp.baseDir != NULL) {
|
|
||||||
char *baseDirCopy = new char[strlen(useProp.baseDir) + 1];
|
|
||||||
strcpy(baseDirCopy, useProp.baseDir);
|
|
||||||
myProp.baseDir = baseDirCopy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is to help detect bugs
|
// This is to help detect bugs
|
||||||
memset(&mt32ram, '?', sizeof(mt32ram));
|
memset(&mt32ram, '?', sizeof(mt32ram));
|
||||||
|
@ -440,12 +427,10 @@ bool Synth::open(SynthProperties &useProp) {
|
||||||
#if MT32EMU_MONITOR_INIT
|
#if MT32EMU_MONITOR_INIT
|
||||||
printDebug("Loading Control ROM");
|
printDebug("Loading Control ROM");
|
||||||
#endif
|
#endif
|
||||||
if (loadControlROM("CM32L_CONTROL.ROM") != LoadResult_OK) {
|
if (!loadControlROM(controlROMImage)) {
|
||||||
if (loadControlROM("MT32_CONTROL.ROM") != LoadResult_OK) {
|
printDebug("Init Error - Missing or invalid Control ROM image");
|
||||||
printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM");
|
reportHandler->onErrorControlROM();
|
||||||
//report(ReportType_errorControlROM, &errno);
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initMemoryRegions();
|
initMemoryRegions();
|
||||||
|
@ -454,17 +439,15 @@ bool Synth::open(SynthProperties &useProp) {
|
||||||
// 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500
|
// 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500
|
||||||
// Note that the size below is given in samples (16-bit), not bytes
|
// Note that the size below is given in samples (16-bit), not bytes
|
||||||
pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024;
|
pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024;
|
||||||
pcmROMData = new float[pcmROMSize];
|
pcmROMData = new Bit16s[pcmROMSize];
|
||||||
|
|
||||||
#if MT32EMU_MONITOR_INIT
|
#if MT32EMU_MONITOR_INIT
|
||||||
printDebug("Loading PCM ROM");
|
printDebug("Loading PCM ROM");
|
||||||
#endif
|
#endif
|
||||||
if (loadPCMROM("CM32L_PCM.ROM") != LoadResult_OK) {
|
if (!loadPCMROM(pcmROMImage)) {
|
||||||
if (loadPCMROM("MT32_PCM.ROM") != LoadResult_OK) {
|
printDebug("Init Error - Missing PCM ROM image");
|
||||||
printDebug("Init Error - Missing MT32_PCM.ROM");
|
reportHandler->onErrorPCMROM();
|
||||||
//report(ReportType_errorPCMROM, &errno);
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MT32EMU_MONITOR_INIT
|
#if MT32EMU_MONITOR_INIT
|
||||||
|
@ -593,9 +576,6 @@ void Synth::close() {
|
||||||
parts[i] = NULL;
|
parts[i] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete[] myProp.baseDir;
|
|
||||||
myProp.baseDir = NULL;
|
|
||||||
|
|
||||||
delete[] pcmWaves;
|
delete[] pcmWaves;
|
||||||
delete[] pcmROMData;
|
delete[] pcmROMData;
|
||||||
|
|
||||||
|
@ -1182,7 +1162,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
|
||||||
case MR_System:
|
case MR_System:
|
||||||
region->write(0, off, data, len);
|
region->write(0, off, data, len);
|
||||||
|
|
||||||
report(ReportType_devReconfig, NULL);
|
reportHandler->onDeviceReconfig();
|
||||||
// FIXME: We haven't properly confirmed any of this behaviour
|
// FIXME: We haven't properly confirmed any of this behaviour
|
||||||
// In particular, we tend to reset things such as reverb even if the write contained
|
// In particular, we tend to reset things such as reverb even if the write contained
|
||||||
// the same parameters as were already set, which may be wrong.
|
// the same parameters as were already set, which may be wrong.
|
||||||
|
@ -1220,7 +1200,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
|
||||||
#if MT32EMU_MONITOR_SYSEX > 0
|
#if MT32EMU_MONITOR_SYSEX > 0
|
||||||
printDebug("WRITE-LCD: %s", buf);
|
printDebug("WRITE-LCD: %s", buf);
|
||||||
#endif
|
#endif
|
||||||
report(ReportType_lcdMessage, buf);
|
reportHandler->showLCDMessage(buf);
|
||||||
break;
|
break;
|
||||||
case MR_Reset:
|
case MR_Reset:
|
||||||
reset();
|
reset();
|
||||||
|
@ -1248,9 +1228,9 @@ void Synth::refreshSystemReverbParameters() {
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
report(ReportType_newReverbMode, &mt32ram.system.reverbMode);
|
reportHandler->onNewReverbMode(mt32ram.system.reverbMode);
|
||||||
report(ReportType_newReverbTime, &mt32ram.system.reverbTime);
|
reportHandler->onNewReverbTime(mt32ram.system.reverbTime);
|
||||||
report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel);
|
reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel);
|
||||||
|
|
||||||
ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode];
|
ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode];
|
||||||
#if MT32EMU_REDUCE_REVERB_MEMORY
|
#if MT32EMU_REDUCE_REVERB_MEMORY
|
||||||
|
@ -1258,7 +1238,7 @@ void Synth::refreshSystemReverbParameters() {
|
||||||
if (reverbModel != NULL) {
|
if (reverbModel != NULL) {
|
||||||
reverbModel->close();
|
reverbModel->close();
|
||||||
}
|
}
|
||||||
newReverbModel->open(myProp.sampleRate);
|
newReverbModel->open();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
reverbModel = newReverbModel;
|
reverbModel = newReverbModel;
|
||||||
|
@ -1313,7 +1293,7 @@ void Synth::reset() {
|
||||||
#if MT32EMU_MONITOR_SYSEX > 0
|
#if MT32EMU_MONITOR_SYSEX > 0
|
||||||
printDebug("RESET");
|
printDebug("RESET");
|
||||||
#endif
|
#endif
|
||||||
report(ReportType_devReset, NULL);
|
reportHandler->onDeviceReset();
|
||||||
partialManager->deactivateAll();
|
partialManager->deactivateAll();
|
||||||
mt32ram = mt32default;
|
mt32ram = mt32default;
|
||||||
for (int i = 0; i < 9; i++) {
|
for (int i = 0; i < 9; i++) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -26,6 +26,7 @@ class TableInitialiser;
|
||||||
class Partial;
|
class Partial;
|
||||||
class PartialManager;
|
class PartialManager;
|
||||||
class Part;
|
class Part;
|
||||||
|
class ROMImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Methods for emulating the connection between the LA32 and the DAC, which involves
|
* Methods for emulating the connection between the LA32 and the DAC, which involves
|
||||||
|
@ -57,71 +58,6 @@ enum DACInputMode {
|
||||||
DACInputMode_GENERATION2
|
DACInputMode_GENERATION2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ReportType {
|
|
||||||
// Errors
|
|
||||||
ReportType_errorControlROM = 1,
|
|
||||||
ReportType_errorPCMROM,
|
|
||||||
ReportType_errorSampleRate,
|
|
||||||
|
|
||||||
// Progress
|
|
||||||
ReportType_progressInit,
|
|
||||||
|
|
||||||
// HW spec
|
|
||||||
ReportType_availableSSE,
|
|
||||||
ReportType_available3DNow,
|
|
||||||
ReportType_usingSSE,
|
|
||||||
ReportType_using3DNow,
|
|
||||||
|
|
||||||
// General info
|
|
||||||
ReportType_lcdMessage,
|
|
||||||
ReportType_devReset,
|
|
||||||
ReportType_devReconfig,
|
|
||||||
ReportType_newReverbMode,
|
|
||||||
ReportType_newReverbTime,
|
|
||||||
ReportType_newReverbLevel
|
|
||||||
};
|
|
||||||
|
|
||||||
enum LoadResult {
|
|
||||||
LoadResult_OK,
|
|
||||||
LoadResult_NotFound,
|
|
||||||
LoadResult_Unreadable,
|
|
||||||
LoadResult_Invalid
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SynthProperties {
|
|
||||||
// Sample rate to use in mixing
|
|
||||||
unsigned int sampleRate;
|
|
||||||
|
|
||||||
// Deprecated - ignored. Use Synth::setReverbEnabled() instead.
|
|
||||||
bool useReverb;
|
|
||||||
// Deprecated - ignored. Use Synth::setReverbOverridden() instead.
|
|
||||||
bool useDefaultReverb;
|
|
||||||
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
|
|
||||||
unsigned char reverbType;
|
|
||||||
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
|
|
||||||
unsigned char reverbTime;
|
|
||||||
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
|
|
||||||
unsigned char reverbLevel;
|
|
||||||
// The name of the directory in which the ROM and data files are stored (with trailing slash/backslash)
|
|
||||||
// Not used if "openFile" is set. May be NULL in any case.
|
|
||||||
const char *baseDir;
|
|
||||||
// This is used as the first argument to all callbacks
|
|
||||||
void *userData;
|
|
||||||
// Callback for reporting various errors and information. May be NULL
|
|
||||||
int (*report)(void *userData, ReportType type, const void *reportData);
|
|
||||||
// Callback for debug messages, in vprintf() format
|
|
||||||
void (*printDebug)(void *userData, const char *fmt, va_list list);
|
|
||||||
// Callback for providing an implementation of File, opened and ready for use
|
|
||||||
// May be NULL, in which case a default implementation will be used.
|
|
||||||
Common::File *(*openFile)(void *userData, const char *filename);
|
|
||||||
// Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted.
|
|
||||||
void (*closeFile)(void *userData, Common::File *file);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is the specification of the Callback routine used when calling the RecalcWaveforms
|
|
||||||
// function
|
|
||||||
typedef void (*recalcStatusCallback)(int percDone);
|
|
||||||
|
|
||||||
typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain);
|
typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain);
|
||||||
|
|
||||||
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
|
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
|
||||||
|
@ -179,6 +115,13 @@ enum MemoryRegionType {
|
||||||
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
|
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ReverbMode {
|
||||||
|
REVERB_MODE_ROOM,
|
||||||
|
REVERB_MODE_HALL,
|
||||||
|
REVERB_MODE_PLATE,
|
||||||
|
REVERB_MODE_TAP_DELAY
|
||||||
|
};
|
||||||
|
|
||||||
class MemoryRegion {
|
class MemoryRegion {
|
||||||
private:
|
private:
|
||||||
Synth *synth;
|
Synth *synth;
|
||||||
|
@ -278,7 +221,7 @@ class ReverbModel {
|
||||||
public:
|
public:
|
||||||
virtual ~ReverbModel() {}
|
virtual ~ReverbModel() {}
|
||||||
// After construction or a close(), open() will be called at least once before any other call (with the exception of close()).
|
// After construction or a close(), open() will be called at least once before any other call (with the exception of close()).
|
||||||
virtual void open(unsigned int sampleRate) = 0;
|
virtual void open() = 0;
|
||||||
// May be called multiple times without an open() in between.
|
// May be called multiple times without an open() in between.
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual void setParameters(Bit8u time, Bit8u level) = 0;
|
virtual void setParameters(Bit8u time, Bit8u level) = 0;
|
||||||
|
@ -286,6 +229,32 @@ public:
|
||||||
virtual bool isActive() const = 0;
|
virtual bool isActive() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ReportHandler {
|
||||||
|
friend class Synth;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~ReportHandler() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// Callback for debug messages, in vprintf() format
|
||||||
|
virtual void printDebug(const char *fmt, va_list list);
|
||||||
|
|
||||||
|
// Callbacks for reporting various errors and information
|
||||||
|
virtual void onErrorControlROM() {}
|
||||||
|
virtual void onErrorPCMROM() {}
|
||||||
|
virtual void showLCDMessage(const char *message);
|
||||||
|
virtual void onDeviceReset() {}
|
||||||
|
virtual void onDeviceReconfig() {}
|
||||||
|
virtual void onNewReverbMode(Bit8u /* mode */) {}
|
||||||
|
virtual void onNewReverbTime(Bit8u /* time */) {}
|
||||||
|
virtual void onNewReverbLevel(Bit8u /* level */) {}
|
||||||
|
virtual void onPartStateChanged(int /* partNum */, bool /* hasActiveNonReleasingPolys */) {}
|
||||||
|
virtual void onPolyStateChanged(int /* partNum */) {}
|
||||||
|
virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {}
|
||||||
|
virtual void onProgramChanged(int /* partNum */, int /* bankNum */, const char * /* patchName */) {}
|
||||||
|
};
|
||||||
|
|
||||||
class Synth {
|
class Synth {
|
||||||
friend class Part;
|
friend class Part;
|
||||||
friend class RhythmPart;
|
friend class RhythmPart;
|
||||||
|
@ -314,8 +283,8 @@ private:
|
||||||
|
|
||||||
const ControlROMMap *controlROMMap;
|
const ControlROMMap *controlROMMap;
|
||||||
Bit8u controlROMData[CONTROL_ROM_SIZE];
|
Bit8u controlROMData[CONTROL_ROM_SIZE];
|
||||||
float *pcmROMData;
|
Bit16s *pcmROMData;
|
||||||
int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
|
size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
|
||||||
|
|
||||||
Bit8s chantable[32];
|
Bit8s chantable[32];
|
||||||
|
|
||||||
|
@ -336,6 +305,9 @@ private:
|
||||||
|
|
||||||
bool isOpen;
|
bool isOpen;
|
||||||
|
|
||||||
|
bool isDefaultReportHandler;
|
||||||
|
ReportHandler *reportHandler;
|
||||||
|
|
||||||
PartialManager *partialManager;
|
PartialManager *partialManager;
|
||||||
Part *parts[9];
|
Part *parts[9];
|
||||||
|
|
||||||
|
@ -368,8 +340,6 @@ private:
|
||||||
int prerenderReadIx;
|
int prerenderReadIx;
|
||||||
int prerenderWriteIx;
|
int prerenderWriteIx;
|
||||||
|
|
||||||
SynthProperties myProp;
|
|
||||||
|
|
||||||
bool prerender();
|
bool prerender();
|
||||||
void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len);
|
void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len);
|
||||||
void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len);
|
void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len);
|
||||||
|
@ -383,8 +353,8 @@ private:
|
||||||
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
|
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
|
||||||
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
|
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
|
||||||
|
|
||||||
LoadResult loadControlROM(const char *filename);
|
bool loadControlROM(const ROMImage &controlROMImage);
|
||||||
LoadResult loadPCMROM(const char *filename);
|
bool loadPCMROM(const ROMImage &pcmROMImage);
|
||||||
|
|
||||||
bool initPCMList(Bit16u mapAddress, Bit16u count);
|
bool initPCMList(Bit16u mapAddress, Bit16u count);
|
||||||
bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed);
|
bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed);
|
||||||
|
@ -398,24 +368,23 @@ private:
|
||||||
void refreshSystem();
|
void refreshSystem();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
unsigned int getSampleRate() const;
|
|
||||||
|
|
||||||
void printPartialUsage(unsigned long sampleOffset = 0);
|
void printPartialUsage(unsigned long sampleOffset = 0);
|
||||||
protected:
|
|
||||||
int report(ReportType type, const void *reportData);
|
void polyStateChanged(int partNum);
|
||||||
Common::File *openFile(const char *filename);
|
void newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]);
|
||||||
void closeFile(Common::File *file);
|
|
||||||
void printDebug(const char *fmt, ...);
|
void printDebug(const char *fmt, ...);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
|
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
|
||||||
|
|
||||||
Synth();
|
// Optionally sets callbacks for reporting various errors, information and debug messages
|
||||||
|
Synth(ReportHandler *useReportHandler = NULL);
|
||||||
~Synth();
|
~Synth();
|
||||||
|
|
||||||
// Used to initialise the MT-32. Must be called before any other function.
|
// Used to initialise the MT-32. Must be called before any other function.
|
||||||
// Returns true if initialization was sucessful, otherwise returns false.
|
// Returns true if initialization was sucessful, otherwise returns false.
|
||||||
bool open(SynthProperties &useProp);
|
// controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth.
|
||||||
|
bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage);
|
||||||
|
|
||||||
// Closes the MT-32 and deallocates any memory used by the synthesizer
|
// Closes the MT-32 and deallocates any memory used by the synthesizer
|
||||||
void close(void);
|
void close(void);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -30,7 +30,7 @@ namespace MT32Emu {
|
||||||
static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0};
|
static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0};
|
||||||
|
|
||||||
TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) :
|
TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) :
|
||||||
partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system) {
|
partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system), phase(TVA_PHASE_DEAD) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
||||||
|
@ -274,7 +274,7 @@ void TVA::nextPhase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int newTarget;
|
int newTarget;
|
||||||
int newIncrement = 0;
|
int newIncrement = 0; // Initialised to please compilers
|
||||||
int envPointIndex = phase;
|
int envPointIndex = phase;
|
||||||
|
|
||||||
if (!allLevelsZeroFromNowOn) {
|
if (!allLevelsZeroFromNowOn) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -47,12 +47,11 @@ static Bit16u keyToPitchTable[] = {
|
||||||
|
|
||||||
TVP::TVP(const Partial *usePartial) :
|
TVP::TVP(const Partial *usePartial) :
|
||||||
partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) {
|
partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) {
|
||||||
unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate;
|
|
||||||
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
|
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
|
||||||
maxCounter = sampleRate / 4000;
|
maxCounter = SAMPLE_RATE / 4000;
|
||||||
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
|
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
|
||||||
// This is how much to increment it by every maxCounter samples.
|
// This is how much to increment it by every maxCounter samples.
|
||||||
processTimerIncrement = 500000 * maxCounter / sampleRate;
|
processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bit16s keyToPitch(unsigned int key) {
|
static Bit16s keyToPitch(unsigned int key) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -72,36 +72,25 @@ Tables::Tables() {
|
||||||
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
|
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ratio of negative segment to wave length
|
// The LA32 chip contains an exponent table inside. The table contains 12-bit integer values.
|
||||||
for (int i = 0; i < 128; i++) {
|
// The actual table size is 512 rows. The 9 higher bits of the fractional part of the argument are used as a lookup address.
|
||||||
// Formula determined from sample analysis.
|
// To improve the precision of computations, the lower bits are supposed to be used for interpolation as the LA32 chip also
|
||||||
float pt = 0.5f / 127.0f * i;
|
// contains another 512-row table with inverted differences between the main table values.
|
||||||
pulseLenFactor[i] = (1.241857812f - pt) * pt; // seems to be 2 ^ (5 / 16) = 1.241857812f
|
for (int i = 0; i < 512; i++) {
|
||||||
|
exp9[i] = Bit16u(8191.5f - EXP2F(13.0f + ~i / 512.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The LA32 chip presumably has such a table inside as the internal computaions seem to be performed using fixed point math with 12-bit fractions
|
// There is a logarithmic sine table inside the LA32 chip. The table contains 13-bit integer values.
|
||||||
for (int i = 0; i < 4096; i++) {
|
for (int i = 1; i < 512; i++) {
|
||||||
exp2[i] = EXP2F(i / 4096.0f);
|
logsin9[i] = Bit16u(0.5f - LOG2F(sin((i + 0.5f) / 1024.0f * FLOAT_PI)) * 1024.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The very first value is clamped to the maximum possible 13-bit integer
|
||||||
|
logsin9[0] = 8191;
|
||||||
|
|
||||||
// found from sample analysis
|
// found from sample analysis
|
||||||
for (int i = 0; i < 32; i++) {
|
static const Bit8u resAmpDecayFactorTable[] = {31, 16, 12, 8, 5, 3, 2, 1};
|
||||||
resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.0f);
|
resAmpDecayFactor = resAmpDecayFactorTable;
|
||||||
}
|
|
||||||
|
|
||||||
// found from sample analysis
|
|
||||||
resAmpFadeFactor[7] = 1.0f / 8.0f;
|
|
||||||
resAmpFadeFactor[6] = 2.0f / 8.0f;
|
|
||||||
resAmpFadeFactor[5] = 3.0f / 8.0f;
|
|
||||||
resAmpFadeFactor[4] = 5.0f / 8.0f;
|
|
||||||
resAmpFadeFactor[3] = 8.0f / 8.0f;
|
|
||||||
resAmpFadeFactor[2] = 12.0f / 8.0f;
|
|
||||||
resAmpFadeFactor[1] = 16.0f / 8.0f;
|
|
||||||
resAmpFadeFactor[0] = 31.0f / 8.0f;
|
|
||||||
|
|
||||||
for (int i = 0; i < 5120; i++) {
|
|
||||||
sinf10[i] = sin(FLOAT_PI * i / 2048.0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -20,7 +20,9 @@
|
||||||
|
|
||||||
namespace MT32Emu {
|
namespace MT32Emu {
|
||||||
|
|
||||||
// Sample rate to use in mixing
|
// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent.
|
||||||
|
// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator.
|
||||||
|
// The output from the synth is supposed to be resampled to convert the sample rate.
|
||||||
const unsigned int SAMPLE_RATE = 32000;
|
const unsigned int SAMPLE_RATE = 32000;
|
||||||
|
|
||||||
const int MIDDLEC = 60;
|
const int MIDDLEC = 60;
|
||||||
|
@ -54,11 +56,10 @@ public:
|
||||||
// CONFIRMED:
|
// CONFIRMED:
|
||||||
Bit8u pulseWidth100To255[101];
|
Bit8u pulseWidth100To255[101];
|
||||||
|
|
||||||
float exp2[4096];
|
Bit16u exp9[512];
|
||||||
float pulseLenFactor[128];
|
Bit16u logsin9[512];
|
||||||
float resAmpMax[32];
|
|
||||||
float resAmpFadeFactor[8];
|
const Bit8u *resAmpDecayFactor;
|
||||||
float sinf10[5120];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -52,10 +52,6 @@ static inline float EXP2F(float x) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline float EXP2I(unsigned int i) {
|
|
||||||
return float(1 << (i >> 12)) * Tables::getInstance().exp2[i & 0x0FFF];
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline float EXP10F(float x) {
|
static inline float EXP10F(float x) {
|
||||||
return exp(FLOAT_LN_10 * x);
|
return exp(FLOAT_LN_10 * x);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,17 @@ MODULE := audio/softsynth/mt32
|
||||||
|
|
||||||
MODULE_OBJS := \
|
MODULE_OBJS := \
|
||||||
AReverbModel.o \
|
AReverbModel.o \
|
||||||
|
BReverbModel.o \
|
||||||
DelayReverb.o \
|
DelayReverb.o \
|
||||||
FreeverbModel.o \
|
FreeverbModel.o \
|
||||||
LA32Ramp.o \
|
LA32Ramp.o \
|
||||||
|
LA32WaveGenerator.o \
|
||||||
|
LegacyWaveGenerator.o \
|
||||||
Part.o \
|
Part.o \
|
||||||
Partial.o \
|
Partial.o \
|
||||||
PartialManager.o \
|
PartialManager.o \
|
||||||
Poly.o \
|
Poly.o \
|
||||||
|
ROMInfo.o \
|
||||||
Synth.o \
|
Synth.o \
|
||||||
TVA.o \
|
TVA.o \
|
||||||
TVF.o \
|
TVF.o \
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -59,20 +59,6 @@
|
||||||
#define MT32EMU_MONITOR_TVA 0
|
#define MT32EMU_MONITOR_TVA 0
|
||||||
#define MT32EMU_MONITOR_TVF 0
|
#define MT32EMU_MONITOR_TVF 0
|
||||||
|
|
||||||
// The WG algorithm involves dozens of transcendent maths, e.g. exponents and trigonometry.
|
|
||||||
// Unfortunately, the majority of systems perform such computations inefficiently,
|
|
||||||
// standard math libs and FPUs make no optimisations for single precision floats,
|
|
||||||
// and use no LUTs to speedup computing internal taylor series. Though, there're rare exceptions,
|
|
||||||
// and there's a hope it will become common soon.
|
|
||||||
// So, this is the crucial point of speed optimisations. We have now eliminated all the transcendent maths
|
|
||||||
// within the critical path and use LUTs instead.
|
|
||||||
// Besides, since the LA32 chip is assumed to use similar LUTs inside, the overall emulation accuracy should be better.
|
|
||||||
// 0: Use LUTs to speedup WG. Most common setting. You can expect about 50% performance boost.
|
|
||||||
// 1: Use precise float math. Use this setting to achieve more accurate wave generator. If your system performs better with this setting, it is really notable. :)
|
|
||||||
#define MT32EMU_ACCURATE_WG 0
|
|
||||||
|
|
||||||
#define MT32EMU_USE_EXTINT 0
|
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
// The maximum number of partials playing simultaneously
|
// The maximum number of partials playing simultaneously
|
||||||
#define MT32EMU_MAX_PARTIALS 32
|
#define MT32EMU_MAX_PARTIALS 32
|
||||||
|
@ -84,9 +70,14 @@
|
||||||
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
|
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
|
||||||
#define MT32EMU_REDUCE_REVERB_MEMORY 1
|
#define MT32EMU_REDUCE_REVERB_MEMORY 1
|
||||||
|
|
||||||
// 0: Use standard Freeverb
|
// 0: Use legacy Freeverb
|
||||||
// 1: Use AReverb (currently not properly tuned)
|
// 1: Use Accurate Reverb model aka AReverb
|
||||||
#define MT32EMU_USE_AREVERBMODEL 0
|
// 2: Use Bit-perfect Boss Reverb model aka BReverb (for developers, not much practical use)
|
||||||
|
#define MT32EMU_USE_REVERBMODEL 1
|
||||||
|
|
||||||
|
// 0: Use refined wave generator based on logarithmic fixed-point computations and LUTs
|
||||||
|
// 1: Use legacy accurate wave generator based on float computations
|
||||||
|
#define MT32EMU_ACCURATE_WG 0
|
||||||
|
|
||||||
namespace MT32Emu
|
namespace MT32Emu
|
||||||
{
|
{
|
||||||
|
@ -111,11 +102,14 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024;
|
||||||
#include "Tables.h"
|
#include "Tables.h"
|
||||||
#include "Poly.h"
|
#include "Poly.h"
|
||||||
#include "LA32Ramp.h"
|
#include "LA32Ramp.h"
|
||||||
|
#include "LA32WaveGenerator.h"
|
||||||
|
#include "LegacyWaveGenerator.h"
|
||||||
#include "TVA.h"
|
#include "TVA.h"
|
||||||
#include "TVP.h"
|
#include "TVP.h"
|
||||||
#include "TVF.h"
|
#include "TVF.h"
|
||||||
#include "Partial.h"
|
#include "Partial.h"
|
||||||
#include "Part.h"
|
#include "Part.h"
|
||||||
|
#include "ROMInfo.h"
|
||||||
#include "Synth.h"
|
#include "Synth.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -247,7 +247,7 @@ byte OPL::read(int port) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OPL::writeReg(int r, int v) {
|
void OPL::writeReg(int r, int v) {
|
||||||
byte tempReg = 0;
|
int tempReg = 0;
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case Config::kOpl2:
|
case Config::kOpl2:
|
||||||
case Config::kDualOpl2:
|
case Config::kDualOpl2:
|
||||||
|
@ -257,12 +257,27 @@ void OPL::writeReg(int r, int v) {
|
||||||
// Backup old setup register
|
// Backup old setup register
|
||||||
tempReg = _reg.normal;
|
tempReg = _reg.normal;
|
||||||
|
|
||||||
// We need to set the register we want to write to via port 0x388
|
// We directly allow writing to secondary OPL3 registers by using
|
||||||
write(0x388, r);
|
// register values >= 0x100.
|
||||||
// Do the real writing to the register
|
if (_type == Config::kOpl3 && r >= 0x100) {
|
||||||
write(0x389, v);
|
// We need to set the register we want to write to via port 0x222,
|
||||||
|
// since we want to write to the secondary register set.
|
||||||
|
write(0x222, r);
|
||||||
|
// Do the real writing to the register
|
||||||
|
write(0x223, v);
|
||||||
|
} else {
|
||||||
|
// We need to set the register we want to write to via port 0x388
|
||||||
|
write(0x388, r);
|
||||||
|
// Do the real writing to the register
|
||||||
|
write(0x389, v);
|
||||||
|
}
|
||||||
|
|
||||||
// Restore the old register
|
// Restore the old register
|
||||||
write(0x388, tempReg);
|
if (_type == Config::kOpl3 && tempReg >= 0x100) {
|
||||||
|
write(0x222, tempReg & ~0x100);
|
||||||
|
} else {
|
||||||
|
write(0x388, tempReg);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,7 +223,7 @@ static int *ENV_CURVE;
|
||||||
|
|
||||||
|
|
||||||
/* multiple table */
|
/* multiple table */
|
||||||
#define ML(a) (int)(a * 2)
|
#define ML(a) (uint)(a * 2)
|
||||||
static const uint MUL_TABLE[16]= {
|
static const uint MUL_TABLE[16]= {
|
||||||
/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */
|
/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */
|
||||||
ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00),
|
ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00),
|
||||||
|
|
|
@ -51,6 +51,8 @@ DefaultEventManager::DefaultEventManager(Common::EventSource *boss) :
|
||||||
|
|
||||||
// Reset key repeat
|
// Reset key repeat
|
||||||
_currentKeyDown.keycode = 0;
|
_currentKeyDown.keycode = 0;
|
||||||
|
_currentKeyDown.ascii = 0;
|
||||||
|
_currentKeyDown.flags = 0;
|
||||||
|
|
||||||
#ifdef ENABLE_VKEYBD
|
#ifdef ENABLE_VKEYBD
|
||||||
_vk = new Common::VirtualKeyboard();
|
_vk = new Common::VirtualKeyboard();
|
||||||
|
@ -82,7 +84,8 @@ void DefaultEventManager::init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DefaultEventManager::pollEvent(Common::Event &event) {
|
bool DefaultEventManager::pollEvent(Common::Event &event) {
|
||||||
uint32 time = g_system->getMillis();
|
// Skip recording of these events
|
||||||
|
uint32 time = g_system->getMillis(true);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
_dispatcher.dispatch();
|
_dispatcher.dispatch();
|
||||||
|
|
|
@ -101,6 +101,7 @@ int SdlEventSource::mapKey(SDLKey key, SDLMod mod, Uint16 unicode) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResidualVM specific relMouse x,y
|
||||||
void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y, int relx, int rely) {
|
void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y, int relx, int rely) {
|
||||||
event.mouse.x = x;
|
event.mouse.x = x;
|
||||||
event.mouse.y = y;
|
event.mouse.y = y;
|
||||||
|
@ -118,7 +119,9 @@ void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y, int r
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlEventSource::handleKbdMouse() {
|
void SdlEventSource::handleKbdMouse() {
|
||||||
uint32 curTime = g_system->getMillis();
|
// Skip recording of these events
|
||||||
|
uint32 curTime = g_system->getMillis(true);
|
||||||
|
|
||||||
if (curTime >= _km.last_time + _km.delay_time) {
|
if (curTime >= _km.last_time + _km.delay_time) {
|
||||||
_km.last_time = curTime;
|
_km.last_time = curTime;
|
||||||
if (_km.x_down_count == 1) {
|
if (_km.x_down_count == 1) {
|
||||||
|
@ -530,7 +533,7 @@ bool SdlEventSource::handleKeyUp(SDL_Event &ev, Common::Event &event) {
|
||||||
|
|
||||||
bool SdlEventSource::handleMouseMotion(SDL_Event &ev, Common::Event &event) {
|
bool SdlEventSource::handleMouseMotion(SDL_Event &ev, Common::Event &event) {
|
||||||
event.type = Common::EVENT_MOUSEMOVE;
|
event.type = Common::EVENT_MOUSEMOVE;
|
||||||
processMouseEvent(event, ev.motion.x, ev.motion.y, ev.motion.xrel, ev.motion.yrel);
|
processMouseEvent(event, ev.motion.x, ev.motion.y, ev.motion.xrel, ev.motion.yrel); // ResidualVM xrel,yrel
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ public:
|
||||||
* @param mode Mode to use while listing the directory.
|
* @param mode Mode to use while listing the directory.
|
||||||
* @param hidden Whether to include hidden files or not in the results.
|
* @param hidden Whether to include hidden files or not in the results.
|
||||||
*
|
*
|
||||||
* @return true if succesful, false otherwise (e.g. when the directory does not exist).
|
* @return true if successful, false otherwise (e.g. when the directory does not exist).
|
||||||
*/
|
*/
|
||||||
virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const = 0;
|
virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const = 0;
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
#include "graphics/scaler.h"
|
#include "graphics/scaler.h"
|
||||||
#include "graphics/surface.h"
|
#include "graphics/surface.h"
|
||||||
#include "graphics/pixelbuffer.h"
|
#include "graphics/pixelbuffer.h"
|
||||||
|
#include "gui/EventRecorder.h"
|
||||||
|
|
||||||
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
|
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
|
||||||
{0, 0, 0}
|
{0, 0, 0}
|
||||||
};
|
};
|
||||||
|
|
|
@ -69,8 +69,8 @@ public:
|
||||||
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
|
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
|
||||||
#endif
|
#endif
|
||||||
virtual void initSize(uint w, uint h, const Graphics::PixelFormat *format = NULL);
|
virtual void initSize(uint w, uint h, const Graphics::PixelFormat *format = NULL);
|
||||||
virtual void launcherInitSize(uint w, uint h);
|
virtual void launcherInitSize(uint w, uint h); // ResidualVM specific method
|
||||||
Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d);
|
Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d); // ResidualVM specific method
|
||||||
virtual int getScreenChangeID() const { return _screenChangeCount; }
|
virtual int getScreenChangeID() const { return _screenChangeCount; }
|
||||||
|
|
||||||
virtual void beginGFXTransaction();
|
virtual void beginGFXTransaction();
|
||||||
|
@ -100,17 +100,17 @@ public:
|
||||||
virtual void clearOverlay();
|
virtual void clearOverlay();
|
||||||
virtual void grabOverlay(void *buf, int pitch);
|
virtual void grabOverlay(void *buf, int pitch);
|
||||||
virtual void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h);
|
virtual void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h);
|
||||||
//ResidualVM specific implemention:
|
|
||||||
|
//ResidualVM specific implementions:
|
||||||
virtual int16 getOverlayHeight() { return _overlayHeight; }
|
virtual int16 getOverlayHeight() { return _overlayHeight; }
|
||||||
virtual int16 getOverlayWidth() { return _overlayWidth; }
|
virtual int16 getOverlayWidth() { return _overlayWidth; }
|
||||||
void closeOverlay();
|
void closeOverlay(); // ResidualVM specific method
|
||||||
|
|
||||||
virtual bool showMouse(bool visible);
|
virtual bool showMouse(bool visible);
|
||||||
virtual void warpMouse(int x, int y);
|
virtual void warpMouse(int x, int y);
|
||||||
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
|
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
|
||||||
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
||||||
// ResidualVM specific method
|
virtual bool lockMouse(bool lock); // ResidualVM specific method
|
||||||
virtual bool lockMouse(bool lock);
|
|
||||||
|
|
||||||
#ifdef USE_OSD
|
#ifdef USE_OSD
|
||||||
virtual void displayMessageOnOSD(const char *msg);
|
virtual void displayMessageOnOSD(const char *msg);
|
||||||
|
|
|
@ -128,7 +128,7 @@ public:
|
||||||
* @param name name of the keymap to push
|
* @param name name of the keymap to push
|
||||||
* @param transparent if true keymapper will iterate down the
|
* @param transparent if true keymapper will iterate down the
|
||||||
* stack if it cannot find a key in the new map
|
* stack if it cannot find a key in the new map
|
||||||
* @return true if succesful
|
* @return true if successful
|
||||||
*/
|
*/
|
||||||
bool pushKeymap(const String& name, bool transparent = false);
|
bool pushKeymap(const String& name, bool transparent = false);
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ public:
|
||||||
void sysEx(const byte *msg, uint16 length);
|
void sysEx(const byte *msg, uint16 length);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void loadSoundFont(const char *soundfont);
|
||||||
AUGraph _auGraph;
|
AUGraph _auGraph;
|
||||||
AudioUnit _synth;
|
AudioUnit _synth;
|
||||||
};
|
};
|
||||||
|
@ -171,52 +172,8 @@ int MidiDriver_CORE::open() {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Load custom soundfont, if specified
|
// Load custom soundfont, if specified
|
||||||
if (ConfMan.hasKey("soundfont")) {
|
if (ConfMan.hasKey("soundfont"))
|
||||||
const char *soundfont = ConfMan.get("soundfont").c_str();
|
loadSoundFont(ConfMan.get("soundfont").c_str());
|
||||||
|
|
||||||
// TODO: We should really check whether the file contains an
|
|
||||||
// actual soundfont...
|
|
||||||
|
|
||||||
#if USE_DEPRECATED_COREAUDIO_API
|
|
||||||
// Before 10.5, we need to use kMusicDeviceProperty_SoundBankFSSpec
|
|
||||||
FSRef fsref;
|
|
||||||
FSSpec fsSpec;
|
|
||||||
err = FSPathMakeRef ((const byte *)soundfont, &fsref, NULL);
|
|
||||||
|
|
||||||
if (err == noErr) {
|
|
||||||
err = FSGetCatalogInfo (&fsref, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == noErr) {
|
|
||||||
err = AudioUnitSetProperty (
|
|
||||||
_synth,
|
|
||||||
kMusicDeviceProperty_SoundBankFSSpec, kAudioUnitScope_Global,
|
|
||||||
0,
|
|
||||||
&fsSpec, sizeof(fsSpec)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// kMusicDeviceProperty_SoundBankFSSpec is present on 10.6+, but broken
|
|
||||||
// kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement
|
|
||||||
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)soundfont, strlen(soundfont), false);
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
err = AudioUnitSetProperty (
|
|
||||||
_synth,
|
|
||||||
kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global,
|
|
||||||
0,
|
|
||||||
&url, sizeof(url)
|
|
||||||
);
|
|
||||||
|
|
||||||
CFRelease(url);
|
|
||||||
} else {
|
|
||||||
warning("Failed to allocate CFURLRef from '%s'", soundfont);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (err != noErr)
|
|
||||||
error("Failed loading custom sound font '%s' (error %ld)", soundfont, (long)err);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef COREAUDIO_DISABLE_REVERB
|
#ifdef COREAUDIO_DISABLE_REVERB
|
||||||
// Disable reverb mode, as that sucks up a lot of CPU power, which can
|
// Disable reverb mode, as that sucks up a lot of CPU power, which can
|
||||||
|
@ -242,6 +199,74 @@ bail:
|
||||||
return MERR_CANNOT_CONNECT;
|
return MERR_CANNOT_CONNECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MidiDriver_CORE::loadSoundFont(const char *soundfont) {
|
||||||
|
// TODO: We should really check whether the file contains an
|
||||||
|
// actual soundfont...
|
||||||
|
|
||||||
|
OSStatus err = 0;
|
||||||
|
|
||||||
|
#if USE_DEPRECATED_COREAUDIO_API
|
||||||
|
FSRef fsref;
|
||||||
|
err = FSPathMakeRef((const byte *)soundfont, &fsref, NULL);
|
||||||
|
|
||||||
|
SInt32 version;
|
||||||
|
err = Gestalt(gestaltSystemVersion, &version);
|
||||||
|
|
||||||
|
if (err == noErr) {
|
||||||
|
if (version >= 0x1030) {
|
||||||
|
// Use kMusicDeviceProperty_SoundBankFSRef in >= 10.3
|
||||||
|
|
||||||
|
// HACK HACK HACK HACK SUPER HACK: Using the value of 1012 instead of
|
||||||
|
// kMusicDeviceProperty_SoundBankFSRef so this compiles with the 10.2
|
||||||
|
// SDK (which does not have that symbol).
|
||||||
|
if (err == noErr) {
|
||||||
|
err = AudioUnitSetProperty(
|
||||||
|
_synth,
|
||||||
|
/*kMusicDeviceProperty_SoundBankFSRef*/ 1012, kAudioUnitScope_Global,
|
||||||
|
0,
|
||||||
|
&fsref, sizeof(fsref)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In 10.2, only kMusicDeviceProperty_SoundBankFSSpec is available
|
||||||
|
FSSpec fsSpec;
|
||||||
|
|
||||||
|
if (err == noErr)
|
||||||
|
err = FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
|
||||||
|
|
||||||
|
if (err == noErr) {
|
||||||
|
err = AudioUnitSetProperty(
|
||||||
|
_synth,
|
||||||
|
kMusicDeviceProperty_SoundBankFSSpec, kAudioUnitScope_Global,
|
||||||
|
0,
|
||||||
|
&fsSpec, sizeof(fsSpec)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement
|
||||||
|
// In addition, the File Manager API became deprecated starting in 10.8
|
||||||
|
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)soundfont, strlen(soundfont), false);
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
err = AudioUnitSetProperty(
|
||||||
|
_synth,
|
||||||
|
kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global,
|
||||||
|
0,
|
||||||
|
&url, sizeof(url)
|
||||||
|
);
|
||||||
|
|
||||||
|
CFRelease(url);
|
||||||
|
} else {
|
||||||
|
warning("Failed to allocate CFURLRef from '%s'", soundfont);
|
||||||
|
}
|
||||||
|
#endif // USE_DEPRECATED_COREAUDIO_API
|
||||||
|
|
||||||
|
if (err != noErr)
|
||||||
|
error("Failed loading custom sound font '%s' (error %ld)", soundfont, (long)err);
|
||||||
|
}
|
||||||
|
|
||||||
void MidiDriver_CORE::close() {
|
void MidiDriver_CORE::close() {
|
||||||
MidiDriver_MPU401::close();
|
MidiDriver_MPU401::close();
|
||||||
if (_auGraph) {
|
if (_auGraph) {
|
||||||
|
|
|
@ -88,12 +88,9 @@ int MidiDriver_SEQ::open() {
|
||||||
|
|
||||||
device = ::open((device_name), O_RDWR, 0);
|
device = ::open((device_name), O_RDWR, 0);
|
||||||
|
|
||||||
if ((device_name == NULL) || (device < 0)) {
|
if (device < 0) {
|
||||||
if (device_name == NULL)
|
warning("Cannot open rawmidi device %s - using /dev/null (no music will be heard)",
|
||||||
warning("Opening /dev/null (no music will be heard) ");
|
device_name);
|
||||||
else
|
|
||||||
warning("Cannot open rawmidi device %s - using /dev/null (no music will be heard) ",
|
|
||||||
device_name);
|
|
||||||
device = (::open(("/dev/null"), O_RDWR, 0));
|
device = (::open(("/dev/null"), O_RDWR, 0));
|
||||||
if (device < 0)
|
if (device < 0)
|
||||||
error("Cannot open /dev/null to dump midi output");
|
error("Cannot open /dev/null to dump midi output");
|
||||||
|
@ -145,7 +142,7 @@ void MidiDriver_SEQ::send(uint32 b) {
|
||||||
buf[position++] = 0;
|
buf[position++] = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
warning("MidiDriver_SEQ::send: unknown : %08x", (int)b);
|
warning("MidiDriver_SEQ::send: unknown: %08x", (int)b);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (write(device, buf, position) == -1)
|
if (write(device, buf, position) == -1)
|
||||||
|
|
|
@ -153,7 +153,7 @@ void SdlMixerManager::suspendAudio() {
|
||||||
int SdlMixerManager::resumeAudio() {
|
int SdlMixerManager::resumeAudio() {
|
||||||
if (!_audioSuspended)
|
if (!_audioSuspended)
|
||||||
return -2;
|
return -2;
|
||||||
if (SDL_OpenAudio(&_obtained, NULL) < 0){
|
if (SDL_OpenAudio(&_obtained, NULL) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
SDL_PauseAudio(0);
|
SDL_PauseAudio(0);
|
||||||
|
|
|
@ -26,11 +26,11 @@
|
||||||
|
|
||||||
#include "backends/graphics/graphics.h"
|
#include "backends/graphics/graphics.h"
|
||||||
#include "backends/mutex/mutex.h"
|
#include "backends/mutex/mutex.h"
|
||||||
|
#include "gui/EventRecorder.h"
|
||||||
|
|
||||||
#include "audio/mixer.h"
|
#include "audio/mixer.h"
|
||||||
#include "graphics/pixelformat.h"
|
#include "graphics/pixelformat.h"
|
||||||
// ResidualVM specific:
|
#include "graphics/pixelbuffer.h" // ResidualVM specific:
|
||||||
#include "graphics/pixelbuffer.h"
|
|
||||||
|
|
||||||
ModularBackend::ModularBackend()
|
ModularBackend::ModularBackend()
|
||||||
:
|
:
|
||||||
|
@ -54,7 +54,7 @@ bool ModularBackend::hasFeature(Feature f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModularBackend::setFeatureState(Feature f, bool enable) {
|
void ModularBackend::setFeatureState(Feature f, bool enable) {
|
||||||
return _graphicsManager->setFeatureState(f, enable);
|
_graphicsManager->setFeatureState(f, enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModularBackend::getFeatureState(Feature f) {
|
bool ModularBackend::getFeatureState(Feature f) {
|
||||||
|
@ -153,7 +153,9 @@ void ModularBackend::fillScreen(uint32 col) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModularBackend::updateScreen() {
|
void ModularBackend::updateScreen() {
|
||||||
|
g_eventRec.preDrawOverlayGui();
|
||||||
_graphicsManager->updateScreen();
|
_graphicsManager->updateScreen();
|
||||||
|
g_eventRec.postDrawOverlayGui();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModularBackend::setShakePos(int shakeOffset) {
|
void ModularBackend::setShakePos(int shakeOffset) {
|
||||||
|
|
|
@ -72,10 +72,8 @@ public:
|
||||||
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
|
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
|
||||||
#endif
|
#endif
|
||||||
virtual void initSize(uint width, uint height, const Graphics::PixelFormat *format = NULL);
|
virtual void initSize(uint width, uint height, const Graphics::PixelFormat *format = NULL);
|
||||||
// ResidualVM specific method
|
virtual void launcherInitSize(uint w, uint h); // ResidualVM specific method
|
||||||
virtual void launcherInitSize(uint w, uint h);
|
virtual Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d); // ResidualVM specific method
|
||||||
// ResidualVM specific method
|
|
||||||
virtual Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d);
|
|
||||||
virtual int getScreenChangeID() const;
|
virtual int getScreenChangeID() const;
|
||||||
|
|
||||||
virtual void beginGFXTransaction();
|
virtual void beginGFXTransaction();
|
||||||
|
@ -106,8 +104,7 @@ public:
|
||||||
virtual void warpMouse(int x, int y);
|
virtual void warpMouse(int x, int y);
|
||||||
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
|
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
|
||||||
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
||||||
// ResidualVM specific method
|
virtual bool lockMouse(bool lock); // ResidualVM specific method
|
||||||
virtual bool lockMouse(bool lock);
|
|
||||||
|
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
|
|
|
@ -112,9 +112,9 @@ MODULE_OBJS += \
|
||||||
mixer/sdl13/sdl13-mixer.o
|
mixer/sdl13/sdl13-mixer.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(BACKEND),bada)
|
ifeq ($(BACKEND),tizen)
|
||||||
MODULE_OBJS += \
|
MODULE_OBJS += \
|
||||||
timer/bada/timer.o
|
timer/tizen/timer.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(BACKEND),ds)
|
ifeq ($(BACKEND),ds)
|
||||||
|
@ -206,5 +206,11 @@ MODULE_OBJS += \
|
||||||
plugins/wii/wii-provider.o
|
plugins/wii/wii-provider.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifdef ENABLE_EVENTRECORDER
|
||||||
|
MODULE_OBJS += \
|
||||||
|
mixer/nullmixer/nullsdl-mixer.o \
|
||||||
|
saves/recorder/recorder-saves.o
|
||||||
|
endif
|
||||||
|
|
||||||
# Include common rules
|
# Include common rules
|
||||||
include $(srcdir)/rules.mk
|
include $(srcdir)/rules.mk
|
||||||
|
|
|
@ -33,15 +33,15 @@ OSystem::MutexRef SdlMutexManager::createMutex() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlMutexManager::lockMutex(OSystem::MutexRef mutex) {
|
void SdlMutexManager::lockMutex(OSystem::MutexRef mutex) {
|
||||||
SDL_mutexP((SDL_mutex *) mutex);
|
SDL_mutexP((SDL_mutex *)mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlMutexManager::unlockMutex(OSystem::MutexRef mutex) {
|
void SdlMutexManager::unlockMutex(OSystem::MutexRef mutex) {
|
||||||
SDL_mutexV((SDL_mutex *) mutex);
|
SDL_mutexV((SDL_mutex *)mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlMutexManager::deleteMutex(OSystem::MutexRef mutex) {
|
void SdlMutexManager::deleteMutex(OSystem::MutexRef mutex) {
|
||||||
SDL_DestroyMutex((SDL_mutex *) mutex);
|
SDL_DestroyMutex((SDL_mutex *)mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -116,7 +116,7 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
|
||||||
_screen_changeid(0),
|
_screen_changeid(0),
|
||||||
_egl_surface_width(0),
|
_egl_surface_width(0),
|
||||||
_egl_surface_height(0),
|
_egl_surface_height(0),
|
||||||
_htc_fail(false),
|
_htc_fail(true),
|
||||||
_force_redraw(false),
|
_force_redraw(false),
|
||||||
_game_texture(0),
|
_game_texture(0),
|
||||||
_game_pbuf(),
|
_game_pbuf(),
|
||||||
|
@ -149,8 +149,7 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
|
||||||
_touchpad_scale(66),
|
_touchpad_scale(66),
|
||||||
_dpad_scale(4),
|
_dpad_scale(4),
|
||||||
_fingersDown(0),
|
_fingersDown(0),
|
||||||
_trackball_scale(2)
|
_trackball_scale(2) {
|
||||||
{
|
|
||||||
|
|
||||||
_fsFactory = new POSIXFilesystemFactory();
|
_fsFactory = new POSIXFilesystemFactory();
|
||||||
|
|
||||||
|
@ -166,10 +165,10 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
|
||||||
getSystemProperty("ro.product.cpu.abi").c_str());
|
getSystemProperty("ro.product.cpu.abi").c_str());
|
||||||
|
|
||||||
mf.toLowercase();
|
mf.toLowercase();
|
||||||
_htc_fail = mf.contains("htc");
|
/*_htc_fail = mf.contains("htc");
|
||||||
|
|
||||||
if (_htc_fail)
|
if (_htc_fail)
|
||||||
LOGI("Enabling HTC workaround");
|
LOGI("Enabling HTC workaround");*/
|
||||||
}
|
}
|
||||||
|
|
||||||
OSystem_Android::~OSystem_Android() {
|
OSystem_Android::~OSystem_Android() {
|
||||||
|
@ -463,7 +462,7 @@ bool OSystem_Android::getFeatureState(Feature f) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 OSystem_Android::getMillis() {
|
uint32 OSystem_Android::getMillis(bool skipRecord) {
|
||||||
timeval curTime;
|
timeval curTime;
|
||||||
|
|
||||||
gettimeofday(&curTime, 0);
|
gettimeofday(&curTime, 0);
|
||||||
|
|
|
@ -288,7 +288,7 @@ public:
|
||||||
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
||||||
|
|
||||||
virtual bool pollEvent(Common::Event &event);
|
virtual bool pollEvent(Common::Event &event);
|
||||||
virtual uint32 getMillis();
|
virtual uint32 getMillis(bool skipRecord = false);
|
||||||
virtual void delayMillis(uint msecs);
|
virtual void delayMillis(uint msecs);
|
||||||
|
|
||||||
virtual MutexRef createMutex(void);
|
virtual MutexRef createMutex(void);
|
||||||
|
|
|
@ -50,7 +50,7 @@ JAVACFLAGS = -source 1.5 -target 1.5
|
||||||
|
|
||||||
ANDROID_JAR = $(ANDROID_SDK)/platforms/android-8/android.jar
|
ANDROID_JAR = $(ANDROID_SDK)/platforms/android-8/android.jar
|
||||||
|
|
||||||
PATH_BUILD = build.tmp
|
PATH_BUILD = ./build.tmp
|
||||||
PATH_BUILD_ASSETS = $(PATH_BUILD)/assets
|
PATH_BUILD_ASSETS = $(PATH_BUILD)/assets
|
||||||
PATH_BUILD_CLASSES_MAIN_TOP = $(PATH_BUILD)/classes.main
|
PATH_BUILD_CLASSES_MAIN_TOP = $(PATH_BUILD)/classes.main
|
||||||
PATH_BUILD_CLASSES_PLUGIN_TOP = $(PATH_BUILD)/classes.plugin
|
PATH_BUILD_CLASSES_PLUGIN_TOP = $(PATH_BUILD)/classes.plugin
|
||||||
|
@ -128,7 +128,7 @@ $(FILE_RESOURCES_MAIN): $(FILE_MANIFEST) $(RESOURCES) $(ANDROID_JAR) $(DIST_FILE
|
||||||
work_dir=`pwd`; \
|
work_dir=`pwd`; \
|
||||||
for i in $(PATH_BUILD_ASSETS)/*.zip; do \
|
for i in $(PATH_BUILD_ASSETS)/*.zip; do \
|
||||||
echo "recompress $$i"; \
|
echo "recompress $$i"; \
|
||||||
cd $$work_dir; \
|
cd "$$work_dir"; \
|
||||||
$(RM) -rf $(PATH_BUILD_ASSETS)/tmp; \
|
$(RM) -rf $(PATH_BUILD_ASSETS)/tmp; \
|
||||||
$(MKDIR) $(PATH_BUILD_ASSETS)/tmp; \
|
$(MKDIR) $(PATH_BUILD_ASSETS)/tmp; \
|
||||||
unzip -q $$i -d $(PATH_BUILD_ASSETS)/tmp; \
|
unzip -q $$i -d $(PATH_BUILD_ASSETS)/tmp; \
|
||||||
|
|
|
@ -62,8 +62,7 @@ void OSystem_PS3::initBackend() {
|
||||||
ConfMan.set("joystick_num", 0);
|
ConfMan.set("joystick_num", 0);
|
||||||
ConfMan.set("vkeybdpath", PREFIX "/data");
|
ConfMan.set("vkeybdpath", PREFIX "/data");
|
||||||
ConfMan.registerDefault("fullscreen", true);
|
ConfMan.registerDefault("fullscreen", true);
|
||||||
//ResidualVM: not used
|
//ConfMan.registerDefault("aspect_ratio", true); //ResidualVM: not used
|
||||||
//ConfMan.registerDefault("aspect_ratio", true);
|
|
||||||
|
|
||||||
// Create the savefile manager
|
// Create the savefile manager
|
||||||
if (_savefileManager == 0)
|
if (_savefileManager == 0)
|
||||||
|
|
|
@ -35,8 +35,11 @@
|
||||||
// it with an alternate slightly less unfriendly override.
|
// it with an alternate slightly less unfriendly override.
|
||||||
#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_FILE)
|
#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_FILE)
|
||||||
#undef FILE
|
#undef FILE
|
||||||
|
// Solaris has typedef __FILE FILE in several places already
|
||||||
|
#if !defined(__sun)
|
||||||
typedef struct { int FAKE; } FAKE_FILE;
|
typedef struct { int FAKE; } FAKE_FILE;
|
||||||
#define FILE FAKE_FILE
|
#define FILE FAKE_FILE
|
||||||
|
#endif // (__sun)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_strcasecmp)
|
#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_strcasecmp)
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
#include "backends/platform/sdl/sdl.h"
|
#include "backends/platform/sdl/sdl.h"
|
||||||
#include "common/config-manager.h"
|
#include "common/config-manager.h"
|
||||||
#include "common/EventRecorder.h"
|
#include "gui/EventRecorder.h"
|
||||||
#include "common/taskbar.h"
|
#include "common/taskbar.h"
|
||||||
#include "common/textconsole.h"
|
#include "common/textconsole.h"
|
||||||
|
|
||||||
|
@ -87,7 +87,9 @@ OSystem_SDL::~OSystem_SDL() {
|
||||||
_audiocdManager = 0;
|
_audiocdManager = 0;
|
||||||
delete _mixerManager;
|
delete _mixerManager;
|
||||||
_mixerManager = 0;
|
_mixerManager = 0;
|
||||||
delete _timerManager;
|
|
||||||
|
delete g_eventRec.getTimerManager();
|
||||||
|
|
||||||
_timerManager = 0;
|
_timerManager = 0;
|
||||||
delete _mutexManager;
|
delete _mutexManager;
|
||||||
_mutexManager = 0;
|
_mutexManager = 0;
|
||||||
|
@ -117,9 +119,6 @@ void OSystem_SDL::init() {
|
||||||
if (_mutexManager == 0)
|
if (_mutexManager == 0)
|
||||||
_mutexManager = new SdlMutexManager();
|
_mutexManager = new SdlMutexManager();
|
||||||
|
|
||||||
if (_timerManager == 0)
|
|
||||||
_timerManager = new SdlTimerManager();
|
|
||||||
|
|
||||||
#if defined(USE_TASKBAR)
|
#if defined(USE_TASKBAR)
|
||||||
if (_taskbarManager == 0)
|
if (_taskbarManager == 0)
|
||||||
_taskbarManager = new Common::TaskbarManager();
|
_taskbarManager = new Common::TaskbarManager();
|
||||||
|
@ -149,10 +148,12 @@ void OSystem_SDL::initBackend() {
|
||||||
|
|
||||||
if (_mixerManager == 0) {
|
if (_mixerManager == 0) {
|
||||||
_mixerManager = new SdlMixerManager();
|
_mixerManager = new SdlMixerManager();
|
||||||
|
|
||||||
// Setup and start mixer
|
// Setup and start mixer
|
||||||
_mixerManager->init();
|
_mixerManager->init();
|
||||||
}
|
}
|
||||||
|
g_eventRec.registerMixerManager(_mixerManager);
|
||||||
|
|
||||||
|
g_eventRec.registerTimerManager(new SdlTimerManager());
|
||||||
|
|
||||||
if (_audiocdManager == 0) {
|
if (_audiocdManager == 0) {
|
||||||
// Audio CD support was removed with SDL 1.3
|
// Audio CD support was removed with SDL 1.3
|
||||||
|
@ -419,14 +420,15 @@ void OSystem_SDL::setupIcon() {
|
||||||
free(icon);
|
free(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 OSystem_SDL::getMillis() {
|
|
||||||
|
uint32 OSystem_SDL::getMillis(bool skipRecord) {
|
||||||
uint32 millis = SDL_GetTicks();
|
uint32 millis = SDL_GetTicks();
|
||||||
g_eventRec.processMillis(millis);
|
g_eventRec.processMillis(millis, skipRecord);
|
||||||
return millis;
|
return millis;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OSystem_SDL::delayMillis(uint msecs) {
|
void OSystem_SDL::delayMillis(uint msecs) {
|
||||||
if (!g_eventRec.processDelayMillis(msecs))
|
if (!g_eventRec.processDelayMillis())
|
||||||
SDL_Delay(msecs);
|
SDL_Delay(msecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,11 +446,15 @@ void OSystem_SDL::getTimeAndDate(TimeDate &td) const {
|
||||||
|
|
||||||
Audio::Mixer *OSystem_SDL::getMixer() {
|
Audio::Mixer *OSystem_SDL::getMixer() {
|
||||||
assert(_mixerManager);
|
assert(_mixerManager);
|
||||||
return _mixerManager->getMixer();
|
return getMixerManager()->getMixer();
|
||||||
}
|
}
|
||||||
|
|
||||||
SdlMixerManager *OSystem_SDL::getMixerManager() {
|
SdlMixerManager *OSystem_SDL::getMixerManager() {
|
||||||
assert(_mixerManager);
|
assert(_mixerManager);
|
||||||
return _mixerManager;
|
return g_eventRec.getMixerManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::TimerManager *OSystem_SDL::getTimerManager() {
|
||||||
|
return g_eventRec.getTimerManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#define PLATFORM_SDL_H
|
#define PLATFORM_SDL_H
|
||||||
|
|
||||||
#include "backends/platform/sdl/sdl-sys.h"
|
#include "backends/platform/sdl/sdl-sys.h"
|
||||||
|
// ResidualVM specific:
|
||||||
#ifdef USE_OPENGL
|
#ifdef USE_OPENGL
|
||||||
#include <SDL_opengl.h>
|
#include <SDL_opengl.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -72,10 +73,11 @@ public:
|
||||||
|
|
||||||
virtual void setWindowCaption(const char *caption);
|
virtual void setWindowCaption(const char *caption);
|
||||||
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
|
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
|
||||||
virtual uint32 getMillis();
|
virtual uint32 getMillis(bool skipRecord = false);
|
||||||
virtual void delayMillis(uint msecs);
|
virtual void delayMillis(uint msecs);
|
||||||
virtual void getTimeAndDate(TimeDate &td) const;
|
virtual void getTimeAndDate(TimeDate &td) const;
|
||||||
virtual Audio::Mixer *getMixer();
|
virtual Audio::Mixer *getMixer();
|
||||||
|
virtual Common::TimerManager *getTimerManager();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool _inited;
|
bool _inited;
|
||||||
|
|
35
backends/saves/recorder/recorder-saves.cpp
Normal file
35
backends/saves/recorder/recorder-saves.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "backends/saves/recorder/recorder-saves.h"
|
||||||
|
#include "gui/EventRecorder.h"
|
||||||
|
#include "common/savefile.h"
|
||||||
|
|
||||||
|
Common::InSaveFile *RecorderSaveFileManager::openForLoading(const Common::String &filename) {
|
||||||
|
Common::InSaveFile *result = g_eventRec.processSaveStream(filename);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::StringArray RecorderSaveFileManager::listSaveFiles(const Common::String &pattern) {
|
||||||
|
return g_eventRec.listSaveFiles(pattern);
|
||||||
|
}
|
||||||
|
|
36
backends/saves/recorder/recorder-saves.h
Normal file
36
backends/saves/recorder/recorder-saves.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BACKEND_SAVES_RECORDER_H
|
||||||
|
#define BACKEND_SAVES_RECORDER_H
|
||||||
|
|
||||||
|
#include "backends/saves/default/default-saves.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a savefile manager implementation for event recorder.
|
||||||
|
*/
|
||||||
|
class RecorderSaveFileManager : public DefaultSaveFileManager {
|
||||||
|
virtual Common::StringArray listSaveFiles(const Common::String &pattern);
|
||||||
|
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -80,7 +80,7 @@ DefaultTimerManager::~DefaultTimerManager() {
|
||||||
void DefaultTimerManager::handler() {
|
void DefaultTimerManager::handler() {
|
||||||
Common::StackLock lock(_mutex);
|
Common::StackLock lock(_mutex);
|
||||||
|
|
||||||
const uint32 curTime = g_system->getMillis();
|
uint32 curTime = g_system->getMillis(true);
|
||||||
|
|
||||||
// Repeat as long as there is a TimerSlot that is scheduled to fire.
|
// Repeat as long as there is a TimerSlot that is scheduled to fire.
|
||||||
TimerSlot *slot = _head->next;
|
TimerSlot *slot = _head->next;
|
||||||
|
|
|
@ -117,6 +117,13 @@ static const char HELP_STRING[] =
|
||||||
" --no-show-fps Set the turn off display FPS info\n"
|
" --no-show-fps Set the turn off display FPS info\n"
|
||||||
" --soft-renderer Switch to 3D software renderer\n"
|
" --soft-renderer Switch to 3D software renderer\n"
|
||||||
" --no-soft-renderer Switch to 3D hardware renderer\n"
|
" --no-soft-renderer Switch to 3D hardware renderer\n"
|
||||||
|
#ifdef ENABLE_EVENTRECORDER
|
||||||
|
" --record-mode=MODE Specify record mode for event recorder (record, playback,\n"
|
||||||
|
" passthrough [default])\n"
|
||||||
|
" --record-file-name=FILE Specify record file name\n"
|
||||||
|
" --disable-display Disable any gfx output. Used for headless events\n"
|
||||||
|
" playback by Event Recorder\n"
|
||||||
|
#endif
|
||||||
"\n"
|
"\n"
|
||||||
#ifdef ENABLE_GRIM
|
#ifdef ENABLE_GRIM
|
||||||
" --dimuse-tempo=NUM Set internal Digital iMuse tempo (10 - 100) per second\n"
|
" --dimuse-tempo=NUM Set internal Digital iMuse tempo (10 - 100) per second\n"
|
||||||
|
@ -127,7 +134,7 @@ static const char HELP_STRING[] =
|
||||||
|
|
||||||
static const char *s_appName = "residualvm";
|
static const char *s_appName = "residualvm";
|
||||||
|
|
||||||
static void usage(const char *s, ...) GCC_PRINTF(1, 2);
|
static void NORETURN_PRE usage(const char *s, ...) GCC_PRINTF(1, 2) NORETURN_POST;
|
||||||
|
|
||||||
static void usage(const char *s, ...) {
|
static void usage(const char *s, ...) {
|
||||||
char buf[STRINGBUFLEN];
|
char buf[STRINGBUFLEN];
|
||||||
|
@ -178,7 +185,7 @@ void registerDefaults() {
|
||||||
|
|
||||||
// Game specific
|
// Game specific
|
||||||
ConfMan.registerDefault("path", "");
|
ConfMan.registerDefault("path", "");
|
||||||
ConfMan.registerDefault("platform", Common::kPlatformPC);
|
ConfMan.registerDefault("platform", Common::kPlatformDOS);
|
||||||
ConfMan.registerDefault("language", "en");
|
ConfMan.registerDefault("language", "en");
|
||||||
ConfMan.registerDefault("subtitles", false);
|
ConfMan.registerDefault("subtitles", false);
|
||||||
ConfMan.registerDefault("boot_param", 0);
|
ConfMan.registerDefault("boot_param", 0);
|
||||||
|
@ -197,11 +204,34 @@ void registerDefaults() {
|
||||||
ConfMan.registerDefault("confirm_exit", false);
|
ConfMan.registerDefault("confirm_exit", false);
|
||||||
ConfMan.registerDefault("disable_sdl_parachute", false);
|
ConfMan.registerDefault("disable_sdl_parachute", false);
|
||||||
|
|
||||||
|
ConfMan.registerDefault("disable_display", false);
|
||||||
ConfMan.registerDefault("record_mode", "none");
|
ConfMan.registerDefault("record_mode", "none");
|
||||||
ConfMan.registerDefault("record_file_name", "record.bin");
|
ConfMan.registerDefault("record_file_name", "record.bin");
|
||||||
ConfMan.registerDefault("record_temp_file_name", "record.tmp");
|
|
||||||
ConfMan.registerDefault("record_time_file_name", "record.time");
|
|
||||||
|
|
||||||
|
ConfMan.registerDefault("gui_saveload_chooser", "grid");
|
||||||
|
ConfMan.registerDefault("gui_saveload_last_pos", "0");
|
||||||
|
|
||||||
|
ConfMan.registerDefault("gui_browser_show_hidden", false);
|
||||||
|
|
||||||
|
#ifdef USE_FLUIDSYNTH
|
||||||
|
// The settings are deliberately stored the same way as in Qsynth. The
|
||||||
|
// FluidSynth music driver is responsible for transforming them into
|
||||||
|
// their appropriate values.
|
||||||
|
ConfMan.registerDefault("fluidsynth_chorus_activate", true);
|
||||||
|
ConfMan.registerDefault("fluidsynth_chorus_nr", 3);
|
||||||
|
ConfMan.registerDefault("fluidsynth_chorus_level", 100);
|
||||||
|
ConfMan.registerDefault("fluidsynth_chorus_speed", 30);
|
||||||
|
ConfMan.registerDefault("fluidsynth_chorus_depth", 80);
|
||||||
|
ConfMan.registerDefault("fluidsynth_chorus_waveform", "sine");
|
||||||
|
|
||||||
|
ConfMan.registerDefault("fluidsynth_reverb_activate", true);
|
||||||
|
ConfMan.registerDefault("fluidsynth_reverb_roomsize", 20);
|
||||||
|
ConfMan.registerDefault("fluidsynth_reverb_damping", 0);
|
||||||
|
ConfMan.registerDefault("fluidsynth_reverb_width", 1);
|
||||||
|
ConfMan.registerDefault("fluidsynth_reverb_level", 90);
|
||||||
|
|
||||||
|
ConfMan.registerDefault("fluidsynth_misc_interpolation", "4th");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -273,12 +303,19 @@ void registerDefaults() {
|
||||||
continue; \
|
continue; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End an option handler
|
||||||
|
#define END_COMMAND \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Common::String parseCommandLine(Common::StringMap &settings, int argc, const char * const *argv) {
|
Common::String parseCommandLine(Common::StringMap &settings, int argc, const char * const *argv) {
|
||||||
const char *s, *s2;
|
const char *s, *s2;
|
||||||
|
|
||||||
|
if (!argv)
|
||||||
|
return Common::String();
|
||||||
|
|
||||||
// argv[0] contains the name of the executable.
|
// argv[0] contains the name of the executable.
|
||||||
if (argv && argv[0]) {
|
if (argv[0]) {
|
||||||
s = strrchr(argv[0], '/');
|
s = strrchr(argv[0], '/');
|
||||||
s_appName = s ? (s+1) : argv[0];
|
s_appName = s ? (s+1) : argv[0];
|
||||||
}
|
}
|
||||||
|
@ -304,27 +341,27 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
||||||
bool isLongCmd = (s[0] == '-' && s[1] == '-');
|
bool isLongCmd = (s[0] == '-' && s[1] == '-');
|
||||||
|
|
||||||
DO_COMMAND('h', "help")
|
DO_COMMAND('h', "help")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
|
|
||||||
DO_COMMAND('v', "version")
|
DO_COMMAND('v', "version")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
|
|
||||||
DO_COMMAND('t', "list-targets")
|
DO_COMMAND('t', "list-targets")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
|
|
||||||
DO_COMMAND('z', "list-games")
|
DO_COMMAND('z', "list-games")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
|
|
||||||
#ifdef DETECTOR_TESTING_HACK
|
#ifdef DETECTOR_TESTING_HACK
|
||||||
// HACK FIXME TODO: This command is intentionally *not* documented!
|
// HACK FIXME TODO: This command is intentionally *not* documented!
|
||||||
DO_LONG_COMMAND("test-detector")
|
DO_LONG_COMMAND("test-detector")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef UPGRADE_ALL_TARGETS_HACK
|
#ifdef UPGRADE_ALL_TARGETS_HACK
|
||||||
// HACK FIXME TODO: This command is intentionally *not* documented!
|
// HACK FIXME TODO: This command is intentionally *not* documented!
|
||||||
DO_LONG_COMMAND("upgrade-targets")
|
DO_LONG_COMMAND("upgrade-targets")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DO_LONG_OPTION("list-saves")
|
DO_LONG_OPTION("list-saves")
|
||||||
|
@ -350,7 +387,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
||||||
END_OPTION
|
END_OPTION
|
||||||
|
|
||||||
DO_LONG_COMMAND("list-audio-devices")
|
DO_LONG_COMMAND("list-audio-devices")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
|
|
||||||
DO_LONG_OPTION_INT("output-rate")
|
DO_LONG_OPTION_INT("output-rate")
|
||||||
END_OPTION
|
END_OPTION
|
||||||
|
@ -358,6 +395,17 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
||||||
DO_OPTION_BOOL('f', "fullscreen")
|
DO_OPTION_BOOL('f', "fullscreen")
|
||||||
END_OPTION
|
END_OPTION
|
||||||
|
|
||||||
|
#ifdef ENABLE_EVENTRECORDER
|
||||||
|
DO_LONG_OPTION_INT("disable-display")
|
||||||
|
END_OPTION
|
||||||
|
|
||||||
|
DO_LONG_OPTION("record-mode")
|
||||||
|
END_OPTION
|
||||||
|
|
||||||
|
DO_LONG_OPTION("record-file-name")
|
||||||
|
END_OPTION
|
||||||
|
#endif
|
||||||
|
|
||||||
DO_LONG_OPTION("opl-driver")
|
DO_LONG_OPTION("opl-driver")
|
||||||
END_OPTION
|
END_OPTION
|
||||||
|
|
||||||
|
@ -480,7 +528,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
||||||
END_OPTION
|
END_OPTION
|
||||||
|
|
||||||
DO_LONG_COMMAND("list-themes")
|
DO_LONG_COMMAND("list-themes")
|
||||||
END_OPTION
|
END_COMMAND
|
||||||
|
|
||||||
DO_LONG_OPTION("target-md5")
|
DO_LONG_OPTION("target-md5")
|
||||||
END_OPTION
|
END_OPTION
|
||||||
|
@ -494,18 +542,6 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
||||||
DO_LONG_OPTION("speech-mode")
|
DO_LONG_OPTION("speech-mode")
|
||||||
END_OPTION
|
END_OPTION
|
||||||
|
|
||||||
DO_LONG_OPTION("record-mode")
|
|
||||||
END_OPTION
|
|
||||||
|
|
||||||
DO_LONG_OPTION("record-file-name")
|
|
||||||
END_OPTION
|
|
||||||
|
|
||||||
DO_LONG_OPTION("record-temp-file-name")
|
|
||||||
END_OPTION
|
|
||||||
|
|
||||||
DO_LONG_OPTION("record-time-file-name")
|
|
||||||
END_OPTION
|
|
||||||
|
|
||||||
#ifdef IPHONE
|
#ifdef IPHONE
|
||||||
// This is automatically set when launched from the Springboard.
|
// This is automatically set when launched from the Springboard.
|
||||||
DO_LONG_OPTION_OPT("launchedFromSB", 0)
|
DO_LONG_OPTION_OPT("launchedFromSB", 0)
|
||||||
|
@ -533,8 +569,7 @@ static void listGames() {
|
||||||
"-------------------- ------------------------------------------------------\n");
|
"-------------------- ------------------------------------------------------\n");
|
||||||
|
|
||||||
const EnginePlugin::List &plugins = EngineMan.getPlugins();
|
const EnginePlugin::List &plugins = EngineMan.getPlugins();
|
||||||
EnginePlugin::List::const_iterator iter = plugins.begin();
|
for (EnginePlugin::List::const_iterator iter = plugins.begin(); iter != plugins.end(); ++iter) {
|
||||||
for (iter = plugins.begin(); iter != plugins.end(); ++iter) {
|
|
||||||
GameList list = (**iter)->getSupportedGames();
|
GameList list = (**iter)->getSupportedGames();
|
||||||
for (GameList::iterator v = list.begin(); v != list.end(); ++v) {
|
for (GameList::iterator v = list.begin(); v != list.end(); ++v) {
|
||||||
printf("%-20s %s\n", v->gameid().c_str(), v->description().c_str());
|
printf("%-20s %s\n", v->gameid().c_str(), v->description().c_str());
|
||||||
|
|
|
@ -42,8 +42,11 @@
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/debug-channels.h" /* for debug manager */
|
#include "common/debug-channels.h" /* for debug manager */
|
||||||
#include "common/events.h"
|
#include "common/events.h"
|
||||||
#include "common/EventRecorder.h"
|
#include "gui/EventRecorder.h"
|
||||||
#include "common/fs.h"
|
#include "common/fs.h"
|
||||||
|
#ifdef ENABLE_EVENTRECORDER
|
||||||
|
#include "common/recorderfile.h"
|
||||||
|
#endif
|
||||||
#include "common/system.h"
|
#include "common/system.h"
|
||||||
#include "common/textconsole.h"
|
#include "common/textconsole.h"
|
||||||
#include "common/tokenizer.h"
|
#include "common/tokenizer.h"
|
||||||
|
@ -251,7 +254,7 @@ static void setupGraphics(OSystem &system) {
|
||||||
system.setGraphicsMode(ConfMan.get("gfx_mode").c_str());
|
system.setGraphicsMode(ConfMan.get("gfx_mode").c_str());
|
||||||
|
|
||||||
system.initSize(320, 200);
|
system.initSize(320, 200);
|
||||||
system.launcherInitSize(640, 400);//ResidualVM specific
|
system.launcherInitSize(640, 400); //ResidualVM specific
|
||||||
|
|
||||||
if (ConfMan.hasKey("aspect_ratio"))
|
if (ConfMan.hasKey("aspect_ratio"))
|
||||||
system.setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio"));
|
system.setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio"));
|
||||||
|
@ -410,7 +413,9 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
||||||
settings["gfx-mode"] = "default";
|
settings["gfx-mode"] = "default";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (settings.contains("disable-display")) {
|
||||||
|
ConfMan.setInt("disable-display", 1, Common::ConfigManager::kTransientDomain);
|
||||||
|
}
|
||||||
setupGraphics(system);
|
setupGraphics(system);
|
||||||
|
|
||||||
// Init the different managers that are used by the engines.
|
// Init the different managers that are used by the engines.
|
||||||
|
@ -429,7 +434,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
||||||
// TODO: This is just to match the current behavior, when we further extend
|
// TODO: This is just to match the current behavior, when we further extend
|
||||||
// our event recorder, we might do this at another place. Or even change
|
// our event recorder, we might do this at another place. Or even change
|
||||||
// the whole API for that ;-).
|
// the whole API for that ;-).
|
||||||
g_eventRec.init();
|
g_eventRec.RegisterEventSource();
|
||||||
|
|
||||||
// Now as the event manager is created, setup the keymapper
|
// Now as the event manager is created, setup the keymapper
|
||||||
setupKeymapper(system);
|
setupKeymapper(system);
|
||||||
|
@ -449,6 +454,21 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
||||||
// to save memory
|
// to save memory
|
||||||
PluginManager::instance().unloadPluginsExcept(PLUGIN_TYPE_ENGINE, plugin);
|
PluginManager::instance().unloadPluginsExcept(PLUGIN_TYPE_ENGINE, plugin);
|
||||||
|
|
||||||
|
#ifdef ENABLE_EVENTRECORDER
|
||||||
|
Common::String recordMode = ConfMan.get("record_mode");
|
||||||
|
Common::String recordFileName = ConfMan.get("record_file_name");
|
||||||
|
|
||||||
|
if (recordMode == "record") {
|
||||||
|
g_eventRec.init(g_eventRec.generateRecordFileName(ConfMan.getActiveDomainName()), GUI::EventRecorder::kRecorderRecord);
|
||||||
|
} else if (recordMode == "playback") {
|
||||||
|
g_eventRec.init(recordFileName, GUI::EventRecorder::kRecorderPlayback);
|
||||||
|
} else if ((recordMode == "info") && (!recordFileName.empty())) {
|
||||||
|
Common::PlaybackFile record;
|
||||||
|
record.openRead(recordFileName);
|
||||||
|
debug("info:author=%s name=%s description=%s", record.getHeader().author.c_str(), record.getHeader().name.c_str(), record.getHeader().description.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// Try to run the game
|
// Try to run the game
|
||||||
Common::Error result = runGame(plugin, system, specialDebug);
|
Common::Error result = runGame(plugin, system, specialDebug);
|
||||||
|
|
||||||
|
@ -479,6 +499,11 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
||||||
#ifdef FORCE_RTL
|
#ifdef FORCE_RTL
|
||||||
g_system->getEventManager()->resetQuit();
|
g_system->getEventManager()->resetQuit();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_EVENTRECORDER
|
||||||
|
if (g_eventRec.checkForContinueGame()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Discard any command line options. It's unlikely that the user
|
// Discard any command line options. It's unlikely that the user
|
||||||
// wanted to apply them to *all* games ever launched.
|
// wanted to apply them to *all* games ever launched.
|
||||||
|
@ -502,7 +527,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
||||||
GUI::GuiManager::destroy();
|
GUI::GuiManager::destroy();
|
||||||
Common::ConfigManager::destroy();
|
Common::ConfigManager::destroy();
|
||||||
Common::DebugManager::destroy();
|
Common::DebugManager::destroy();
|
||||||
Common::EventRecorder::destroy();
|
GUI::EventRecorder::destroy();
|
||||||
Common::SearchManager::destroy();
|
Common::SearchManager::destroy();
|
||||||
#ifdef USE_TRANSLATION
|
#ifdef USE_TRANSLATION
|
||||||
Common::TranslationManager::destroy();
|
Common::TranslationManager::destroy();
|
||||||
|
|
|
@ -124,7 +124,7 @@ public:
|
||||||
LINK_PLUGIN(MT32)
|
LINK_PLUGIN(MT32)
|
||||||
#endif
|
#endif
|
||||||
LINK_PLUGIN(ADLIB)
|
LINK_PLUGIN(ADLIB)
|
||||||
//ResidualVM: disabled belows
|
//ResidualVM: disabled belows:
|
||||||
// LINK_PLUGIN(PCSPK)
|
// LINK_PLUGIN(PCSPK)
|
||||||
// LINK_PLUGIN(PCJR)
|
// LINK_PLUGIN(PCJR)
|
||||||
// LINK_PLUGIN(CMS)
|
// LINK_PLUGIN(CMS)
|
||||||
|
@ -133,8 +133,8 @@ public:
|
||||||
#endif
|
#endif
|
||||||
#ifndef DISABLE_SID
|
#ifndef DISABLE_SID
|
||||||
// LINK_PLUGIN(C64)
|
// LINK_PLUGIN(C64)
|
||||||
// LINK_PLUGIN(AMIGA)
|
|
||||||
#endif
|
#endif
|
||||||
|
// LINK_PLUGIN(AMIGA)
|
||||||
// LINK_PLUGIN(APPLEIIGS)
|
// LINK_PLUGIN(APPLEIIGS)
|
||||||
// LINK_PLUGIN(TOWNS)
|
// LINK_PLUGIN(TOWNS)
|
||||||
// LINK_PLUGIN(PC98)
|
// LINK_PLUGIN(PC98)
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
#include "common/array.h"
|
#include "common/array.h"
|
||||||
#include "common/fs.h"
|
#include "common/fs.h"
|
||||||
#include "common/str.h"
|
#include "common/str.h"
|
||||||
//#include "backends/plugins/elf/version.h"
|
//#include "backends/plugins/elf/version.h" // ResidualVM specific
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
EventDispatcher::EventDispatcher() : _mapper(0) {
|
EventDispatcher::EventDispatcher() : _autoFreeMapper(false), _mapper(0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
EventDispatcher::~EventDispatcher() {
|
EventDispatcher::~EventDispatcher() {
|
||||||
|
@ -38,7 +38,9 @@ EventDispatcher::~EventDispatcher() {
|
||||||
delete i->observer;
|
delete i->observer;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete _mapper;
|
if (_autoFreeMapper) {
|
||||||
|
delete _mapper;
|
||||||
|
}
|
||||||
_mapper = 0;
|
_mapper = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,11 +70,15 @@ void EventDispatcher::dispatch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventDispatcher::registerMapper(EventMapper *mapper) {
|
void EventDispatcher::registerMapper(EventMapper *mapper, bool autoFree) {
|
||||||
delete _mapper;
|
if (_autoFreeMapper) {
|
||||||
|
delete _mapper;
|
||||||
|
}
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_autoFreeMapper = autoFree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EventDispatcher::registerSource(EventSource *source, bool autoFree) {
|
void EventDispatcher::registerSource(EventSource *source, bool autoFree) {
|
||||||
SourceEntry newEntry;
|
SourceEntry newEntry;
|
||||||
|
|
||||||
|
|
|
@ -1,428 +0,0 @@
|
||||||
/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/EventRecorder.h"
|
|
||||||
|
|
||||||
#include "common/bufferedstream.h"
|
|
||||||
#include "common/config-manager.h"
|
|
||||||
#include "common/random.h"
|
|
||||||
#include "common/savefile.h"
|
|
||||||
#include "common/textconsole.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
DECLARE_SINGLETON(EventRecorder);
|
|
||||||
|
|
||||||
#define RECORD_SIGNATURE 0x54455354
|
|
||||||
#define RECORD_VERSION 1
|
|
||||||
|
|
||||||
uint32 readTime(ReadStream *inFile) {
|
|
||||||
uint32 d = inFile->readByte();
|
|
||||||
if (d == 0xff) {
|
|
||||||
d = inFile->readUint32LE();
|
|
||||||
}
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeTime(WriteStream *outFile, uint32 d) {
|
|
||||||
//Simple RLE compression
|
|
||||||
if (d >= 0xff) {
|
|
||||||
outFile->writeByte(0xff);
|
|
||||||
outFile->writeUint32LE(d);
|
|
||||||
} else {
|
|
||||||
outFile->writeByte(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void readRecord(SeekableReadStream *inFile, uint32 &diff, Event &event, uint32 &millis) {
|
|
||||||
millis = readTime(inFile);
|
|
||||||
|
|
||||||
diff = inFile->readUint32LE();
|
|
||||||
|
|
||||||
event.type = (EventType)inFile->readUint32LE();
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case EVENT_KEYDOWN:
|
|
||||||
case EVENT_KEYUP:
|
|
||||||
event.kbd.keycode = (KeyCode)inFile->readSint32LE();
|
|
||||||
event.kbd.ascii = inFile->readUint16LE();
|
|
||||||
event.kbd.flags = inFile->readByte();
|
|
||||||
break;
|
|
||||||
case EVENT_MOUSEMOVE:
|
|
||||||
case EVENT_LBUTTONDOWN:
|
|
||||||
case EVENT_LBUTTONUP:
|
|
||||||
case EVENT_RBUTTONDOWN:
|
|
||||||
case EVENT_RBUTTONUP:
|
|
||||||
case EVENT_WHEELUP:
|
|
||||||
case EVENT_WHEELDOWN:
|
|
||||||
case EVENT_MBUTTONDOWN:
|
|
||||||
case EVENT_MBUTTONUP:
|
|
||||||
event.mouse.x = inFile->readSint16LE();
|
|
||||||
event.mouse.y = inFile->readSint16LE();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeRecord(WriteStream *outFile, uint32 diff, const Event &event, uint32 millis) {
|
|
||||||
writeTime(outFile, millis);
|
|
||||||
|
|
||||||
outFile->writeUint32LE(diff);
|
|
||||||
|
|
||||||
outFile->writeUint32LE((uint32)event.type);
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case EVENT_KEYDOWN:
|
|
||||||
case EVENT_KEYUP:
|
|
||||||
outFile->writeSint32LE(event.kbd.keycode);
|
|
||||||
outFile->writeUint16LE(event.kbd.ascii);
|
|
||||||
outFile->writeByte(event.kbd.flags);
|
|
||||||
break;
|
|
||||||
case EVENT_MOUSEMOVE:
|
|
||||||
case EVENT_LBUTTONDOWN:
|
|
||||||
case EVENT_LBUTTONUP:
|
|
||||||
case EVENT_RBUTTONDOWN:
|
|
||||||
case EVENT_RBUTTONUP:
|
|
||||||
case EVENT_WHEELUP:
|
|
||||||
case EVENT_WHEELDOWN:
|
|
||||||
case EVENT_MBUTTONDOWN:
|
|
||||||
case EVENT_MBUTTONUP:
|
|
||||||
outFile->writeSint16LE(event.mouse.x);
|
|
||||||
outFile->writeSint16LE(event.mouse.y);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EventRecorder::EventRecorder() {
|
|
||||||
_recordFile = NULL;
|
|
||||||
_recordTimeFile = NULL;
|
|
||||||
_playbackFile = NULL;
|
|
||||||
_playbackTimeFile = NULL;
|
|
||||||
_timeMutex = g_system->createMutex();
|
|
||||||
_recorderMutex = g_system->createMutex();
|
|
||||||
|
|
||||||
_eventCount = 0;
|
|
||||||
_lastEventCount = 0;
|
|
||||||
_lastMillis = 0;
|
|
||||||
_lastEventMillis = 0;
|
|
||||||
|
|
||||||
_recordMode = kPassthrough;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventRecorder::~EventRecorder() {
|
|
||||||
deinit();
|
|
||||||
|
|
||||||
g_system->deleteMutex(_timeMutex);
|
|
||||||
g_system->deleteMutex(_recorderMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventRecorder::init() {
|
|
||||||
String recordModeString = ConfMan.get("record_mode");
|
|
||||||
if (recordModeString.compareToIgnoreCase("record") == 0) {
|
|
||||||
_recordMode = kRecorderRecord;
|
|
||||||
|
|
||||||
debug(3, "EventRecorder: record");
|
|
||||||
} else {
|
|
||||||
if (recordModeString.compareToIgnoreCase("playback") == 0) {
|
|
||||||
_recordMode = kRecorderPlayback;
|
|
||||||
debug(3, "EventRecorder: playback");
|
|
||||||
} else {
|
|
||||||
_recordMode = kPassthrough;
|
|
||||||
debug(3, "EventRecorder: passthrough");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_recordFileName = ConfMan.get("record_file_name");
|
|
||||||
if (_recordFileName.empty()) {
|
|
||||||
_recordFileName = "record.bin";
|
|
||||||
}
|
|
||||||
_recordTempFileName = ConfMan.get("record_temp_file_name");
|
|
||||||
if (_recordTempFileName.empty()) {
|
|
||||||
_recordTempFileName = "record.tmp";
|
|
||||||
}
|
|
||||||
_recordTimeFileName = ConfMan.get("record_time_file_name");
|
|
||||||
if (_recordTimeFileName.empty()) {
|
|
||||||
_recordTimeFileName = "record.time";
|
|
||||||
}
|
|
||||||
|
|
||||||
// recorder stuff
|
|
||||||
if (_recordMode == kRecorderRecord) {
|
|
||||||
_recordCount = 0;
|
|
||||||
_recordTimeCount = 0;
|
|
||||||
_recordFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTempFileName), 128 * 1024);
|
|
||||||
_recordTimeFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTimeFileName), 128 * 1024);
|
|
||||||
_recordSubtitles = ConfMan.getBool("subtitles");
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32 sign;
|
|
||||||
uint32 randomSourceCount;
|
|
||||||
if (_recordMode == kRecorderPlayback) {
|
|
||||||
_playbackCount = 0;
|
|
||||||
_playbackTimeCount = 0;
|
|
||||||
_playbackFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordFileName), 128 * 1024, DisposeAfterUse::YES);
|
|
||||||
_playbackTimeFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordTimeFileName), 128 * 1024, DisposeAfterUse::YES);
|
|
||||||
|
|
||||||
if (!_playbackFile) {
|
|
||||||
warning("Cannot open playback file %s. Playback was switched off", _recordFileName.c_str());
|
|
||||||
_recordMode = kPassthrough;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_playbackTimeFile) {
|
|
||||||
warning("Cannot open playback time file %s. Playback was switched off", _recordTimeFileName.c_str());
|
|
||||||
_recordMode = kPassthrough;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_recordMode == kRecorderPlayback) {
|
|
||||||
sign = _playbackFile->readUint32LE();
|
|
||||||
if (sign != RECORD_SIGNATURE) {
|
|
||||||
error("Unknown record file signature");
|
|
||||||
}
|
|
||||||
|
|
||||||
_playbackFile->readUint32LE(); // version
|
|
||||||
|
|
||||||
// conf vars
|
|
||||||
ConfMan.setBool("subtitles", _playbackFile->readByte() != 0);
|
|
||||||
|
|
||||||
_recordCount = _playbackFile->readUint32LE();
|
|
||||||
_recordTimeCount = _playbackFile->readUint32LE();
|
|
||||||
|
|
||||||
randomSourceCount = _playbackFile->readUint32LE();
|
|
||||||
for (uint i = 0; i < randomSourceCount; ++i) {
|
|
||||||
RandomSourceRecord rec;
|
|
||||||
rec.name = "";
|
|
||||||
uint32 sLen = _playbackFile->readUint32LE();
|
|
||||||
for (uint j = 0; j < sLen; ++j) {
|
|
||||||
char c = _playbackFile->readSByte();
|
|
||||||
rec.name += c;
|
|
||||||
}
|
|
||||||
rec.seed = _playbackFile->readUint32LE();
|
|
||||||
_randomSourceRecords.push_back(rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasPlaybackEvent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_system->getEventManager()->getEventDispatcher()->registerSource(this, false);
|
|
||||||
g_system->getEventManager()->getEventDispatcher()->registerObserver(this, EventManager::kEventRecorderPriority, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventRecorder::deinit() {
|
|
||||||
debug(3, "EventRecorder: deinit");
|
|
||||||
|
|
||||||
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
|
|
||||||
g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
|
|
||||||
|
|
||||||
g_system->lockMutex(_timeMutex);
|
|
||||||
g_system->lockMutex(_recorderMutex);
|
|
||||||
_recordMode = kPassthrough;
|
|
||||||
g_system->unlockMutex(_timeMutex);
|
|
||||||
g_system->unlockMutex(_recorderMutex);
|
|
||||||
|
|
||||||
delete _playbackFile;
|
|
||||||
delete _playbackTimeFile;
|
|
||||||
|
|
||||||
if (_recordFile != NULL) {
|
|
||||||
_recordFile->finalize();
|
|
||||||
delete _recordFile;
|
|
||||||
_recordTimeFile->finalize();
|
|
||||||
delete _recordTimeFile;
|
|
||||||
|
|
||||||
_playbackFile = g_system->getSavefileManager()->openForLoading(_recordTempFileName);
|
|
||||||
|
|
||||||
assert(_playbackFile);
|
|
||||||
|
|
||||||
_recordFile = g_system->getSavefileManager()->openForSaving(_recordFileName);
|
|
||||||
_recordFile->writeUint32LE(RECORD_SIGNATURE);
|
|
||||||
_recordFile->writeUint32LE(RECORD_VERSION);
|
|
||||||
|
|
||||||
// conf vars
|
|
||||||
_recordFile->writeByte(_recordSubtitles ? 1 : 0);
|
|
||||||
|
|
||||||
_recordFile->writeUint32LE(_recordCount);
|
|
||||||
_recordFile->writeUint32LE(_recordTimeCount);
|
|
||||||
|
|
||||||
_recordFile->writeUint32LE(_randomSourceRecords.size());
|
|
||||||
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
|
|
||||||
_recordFile->writeUint32LE(_randomSourceRecords[i].name.size());
|
|
||||||
_recordFile->writeString(_randomSourceRecords[i].name);
|
|
||||||
_recordFile->writeUint32LE(_randomSourceRecords[i].seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint i = 0; i < _recordCount; ++i) {
|
|
||||||
uint32 tempDiff;
|
|
||||||
Event tempEvent;
|
|
||||||
uint32 millis;
|
|
||||||
readRecord(_playbackFile, tempDiff, tempEvent, millis);
|
|
||||||
writeRecord(_recordFile, tempDiff, tempEvent, millis);
|
|
||||||
}
|
|
||||||
|
|
||||||
_recordFile->finalize();
|
|
||||||
delete _recordFile;
|
|
||||||
delete _playbackFile;
|
|
||||||
|
|
||||||
//TODO: remove recordTempFileName'ed file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventRecorder::registerRandomSource(RandomSource &rnd, const String &name) {
|
|
||||||
if (_recordMode == kRecorderRecord) {
|
|
||||||
RandomSourceRecord rec;
|
|
||||||
rec.name = name;
|
|
||||||
rec.seed = rnd.getSeed();
|
|
||||||
_randomSourceRecords.push_back(rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_recordMode == kRecorderPlayback) {
|
|
||||||
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
|
|
||||||
if (_randomSourceRecords[i].name == name) {
|
|
||||||
rnd.setSeed(_randomSourceRecords[i].seed);
|
|
||||||
_randomSourceRecords.remove_at(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventRecorder::processMillis(uint32 &millis) {
|
|
||||||
uint32 d;
|
|
||||||
if (_recordMode == kPassthrough) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_system->lockMutex(_timeMutex);
|
|
||||||
if (_recordMode == kRecorderRecord) {
|
|
||||||
d = millis - _lastMillis;
|
|
||||||
writeTime(_recordTimeFile, d);
|
|
||||||
|
|
||||||
_recordTimeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_recordMode == kRecorderPlayback) {
|
|
||||||
if (_recordTimeCount > _playbackTimeCount) {
|
|
||||||
d = readTime(_playbackTimeFile);
|
|
||||||
|
|
||||||
while ((_lastMillis + d > millis) && (_lastMillis + d - millis > 50)) {
|
|
||||||
_recordMode = kPassthrough;
|
|
||||||
g_system->delayMillis(50);
|
|
||||||
millis = g_system->getMillis();
|
|
||||||
_recordMode = kRecorderPlayback;
|
|
||||||
}
|
|
||||||
|
|
||||||
millis = _lastMillis + d;
|
|
||||||
_playbackTimeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastMillis = millis;
|
|
||||||
g_system->unlockMutex(_timeMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventRecorder::processDelayMillis(uint &msecs) {
|
|
||||||
if (_recordMode == kRecorderPlayback) {
|
|
||||||
_recordMode = kPassthrough;
|
|
||||||
|
|
||||||
uint32 millis = g_system->getMillis();
|
|
||||||
|
|
||||||
_recordMode = kRecorderPlayback;
|
|
||||||
|
|
||||||
if (_lastMillis > millis) {
|
|
||||||
// Skip delay if we're getting late
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventRecorder::notifyEvent(const Event &ev) {
|
|
||||||
if (_recordMode != kRecorderRecord)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
StackLock lock(_recorderMutex);
|
|
||||||
++_eventCount;
|
|
||||||
|
|
||||||
writeRecord(_recordFile, _eventCount - _lastEventCount, ev, _lastMillis - _lastEventMillis);
|
|
||||||
|
|
||||||
_recordCount++;
|
|
||||||
_lastEventCount = _eventCount;
|
|
||||||
_lastEventMillis = _lastMillis;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventRecorder::notifyPoll() {
|
|
||||||
if (_recordMode != kRecorderRecord)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
++_eventCount;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventRecorder::pollEvent(Event &ev) {
|
|
||||||
uint32 millis;
|
|
||||||
|
|
||||||
if (_recordMode != kRecorderPlayback)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
StackLock lock(_recorderMutex);
|
|
||||||
++_eventCount;
|
|
||||||
|
|
||||||
if (!_hasPlaybackEvent) {
|
|
||||||
if (_recordCount > _playbackCount) {
|
|
||||||
readRecord(_playbackFile, const_cast<uint32&>(_playbackDiff), _playbackEvent, millis);
|
|
||||||
_playbackCount++;
|
|
||||||
_hasPlaybackEvent = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_hasPlaybackEvent) {
|
|
||||||
if (_playbackDiff <= (_eventCount - _lastEventCount)) {
|
|
||||||
switch (_playbackEvent.type) {
|
|
||||||
case EVENT_MOUSEMOVE:
|
|
||||||
case EVENT_LBUTTONDOWN:
|
|
||||||
case EVENT_LBUTTONUP:
|
|
||||||
case EVENT_RBUTTONDOWN:
|
|
||||||
case EVENT_RBUTTONUP:
|
|
||||||
case EVENT_WHEELUP:
|
|
||||||
case EVENT_WHEELDOWN:
|
|
||||||
g_system->warpMouse(_playbackEvent.mouse.x, _playbackEvent.mouse.y);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ev = _playbackEvent;
|
|
||||||
_hasPlaybackEvent = false;
|
|
||||||
_lastEventCount = _eventCount;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
|
@ -1,110 +0,0 @@
|
||||||
/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COMMON_EVENTRECORDER_H
|
|
||||||
#define COMMON_EVENTRECORDER_H
|
|
||||||
|
|
||||||
#include "common/scummsys.h"
|
|
||||||
#include "common/events.h"
|
|
||||||
#include "common/singleton.h"
|
|
||||||
#include "common/mutex.h"
|
|
||||||
#include "common/array.h"
|
|
||||||
|
|
||||||
#define g_eventRec (Common::EventRecorder::instance())
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
class RandomSource;
|
|
||||||
class SeekableReadStream;
|
|
||||||
class WriteStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Our generic event recorder.
|
|
||||||
*
|
|
||||||
* TODO: Add more documentation.
|
|
||||||
*/
|
|
||||||
class EventRecorder : private EventSource, private EventObserver, public Singleton<EventRecorder> {
|
|
||||||
friend class Singleton<SingletonBaseType>;
|
|
||||||
EventRecorder();
|
|
||||||
~EventRecorder();
|
|
||||||
public:
|
|
||||||
void init();
|
|
||||||
void deinit();
|
|
||||||
|
|
||||||
/** Register random source so it can be serialized in game test purposes */
|
|
||||||
void registerRandomSource(RandomSource &rnd, const String &name);
|
|
||||||
|
|
||||||
/** TODO: Add documentation, this is only used by the backend */
|
|
||||||
void processMillis(uint32 &millis);
|
|
||||||
|
|
||||||
/** TODO: Add documentation, this is only used by the backend */
|
|
||||||
bool processDelayMillis(uint &msecs);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool notifyEvent(const Event &ev);
|
|
||||||
bool notifyPoll();
|
|
||||||
bool pollEvent(Event &ev);
|
|
||||||
bool allowMapping() const { return false; }
|
|
||||||
|
|
||||||
class RandomSourceRecord {
|
|
||||||
public:
|
|
||||||
String name;
|
|
||||||
uint32 seed;
|
|
||||||
};
|
|
||||||
Array<RandomSourceRecord> _randomSourceRecords;
|
|
||||||
|
|
||||||
bool _recordSubtitles;
|
|
||||||
volatile uint32 _recordCount;
|
|
||||||
volatile uint32 _lastRecordEvent;
|
|
||||||
volatile uint32 _recordTimeCount;
|
|
||||||
volatile uint32 _lastEventMillis;
|
|
||||||
WriteStream *_recordFile;
|
|
||||||
WriteStream *_recordTimeFile;
|
|
||||||
MutexRef _timeMutex;
|
|
||||||
MutexRef _recorderMutex;
|
|
||||||
volatile uint32 _lastMillis;
|
|
||||||
|
|
||||||
volatile uint32 _playbackCount;
|
|
||||||
volatile uint32 _playbackDiff;
|
|
||||||
volatile bool _hasPlaybackEvent;
|
|
||||||
volatile uint32 _playbackTimeCount;
|
|
||||||
Event _playbackEvent;
|
|
||||||
SeekableReadStream *_playbackFile;
|
|
||||||
SeekableReadStream *_playbackTimeFile;
|
|
||||||
|
|
||||||
volatile uint32 _eventCount;
|
|
||||||
volatile uint32 _lastEventCount;
|
|
||||||
|
|
||||||
enum RecordMode {
|
|
||||||
kPassthrough = 0,
|
|
||||||
kRecorderRecord = 1,
|
|
||||||
kRecorderPlayback = 2
|
|
||||||
};
|
|
||||||
volatile RecordMode _recordMode;
|
|
||||||
String _recordFileName;
|
|
||||||
String _recordTempFileName;
|
|
||||||
String _recordTimeFileName;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // End of namespace Common
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -118,7 +118,7 @@ void SearchSet::addDirectory(const String &name, const FSNode &dir, int priority
|
||||||
add(name, new FSDirectory(dir, depth, flat), priority);
|
add(name, new FSDirectory(dir, depth, flat), priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority) {
|
void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority, int depth, bool flat) {
|
||||||
FSList subDirs;
|
FSList subDirs;
|
||||||
if (!directory.getChildren(subDirs))
|
if (!directory.getChildren(subDirs))
|
||||||
return;
|
return;
|
||||||
|
@ -161,9 +161,9 @@ void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPa
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextPattern.empty())
|
if (nextPattern.empty())
|
||||||
addDirectory(name, *i, priority);
|
addDirectory(name, *i, priority, depth, flat);
|
||||||
else
|
else
|
||||||
addSubDirectoriesMatching(*i, nextPattern, ignoreCase, priority);
|
addSubDirectoriesMatching(*i, nextPattern, ignoreCase, priority, depth, flat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,8 +184,8 @@ public:
|
||||||
* to assume that this method is using anything other than a simple case insensitive compare.
|
* to assume that this method is using anything other than a simple case insensitive compare.
|
||||||
* Thus do not use any tokens like '*' or '?' in the "caselessName" parameter of this function!
|
* Thus do not use any tokens like '*' or '?' in the "caselessName" parameter of this function!
|
||||||
*/
|
*/
|
||||||
void addSubDirectoryMatching(const FSNode &directory, const String &caselessName, int priority = 0) {
|
void addSubDirectoryMatching(const FSNode &directory, const String &caselessName, int priority = 0, int depth = 1, bool flat = false) {
|
||||||
addSubDirectoriesMatching(directory, caselessName, true, priority);
|
addSubDirectoriesMatching(directory, caselessName, true, priority, depth, flat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +208,7 @@ public:
|
||||||
*
|
*
|
||||||
* @see Common::matchString
|
* @see Common::matchString
|
||||||
*/
|
*/
|
||||||
void addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority = 0);
|
void addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority = 0, int depth = 1, bool flat = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an archive from the searchable set.
|
* Remove an archive from the searchable set.
|
||||||
|
|
|
@ -61,6 +61,6 @@ SeekableReadStream *wrapBufferedSeekableReadStream(SeekableReadStream *parentStr
|
||||||
*/
|
*/
|
||||||
WriteStream *wrapBufferedWriteStream(WriteStream *parentStream, uint32 bufSize);
|
WriteStream *wrapBufferedWriteStream(WriteStream *parentStream, uint32 bufSize);
|
||||||
|
|
||||||
} // End of namespace Common
|
} // End of namespace Common
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
42
common/c++11-compat.h
Normal file
42
common/c++11-compat.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COMMON_CPP11_COMPAT_H
|
||||||
|
#define COMMON_CPP11_COMPAT_H
|
||||||
|
|
||||||
|
#if __cplusplus >= 201103L
|
||||||
|
#error "c++11-compat.h included when C++11 is available"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//
|
||||||
|
// Custom nullptr replacement. This is not type safe as the real C++11 nullptr
|
||||||
|
// though.
|
||||||
|
//
|
||||||
|
#define nullptr 0
|
||||||
|
|
||||||
|
//
|
||||||
|
// Replacement for the override keyword. This allows compilation of code
|
||||||
|
// which uses it, but does not feature any semantic.
|
||||||
|
//
|
||||||
|
#define override
|
||||||
|
|
||||||
|
#endif
|
|
@ -226,6 +226,15 @@ bool ConfigFile::saveToStream(WriteStream &stream) {
|
||||||
return !stream.err();
|
return !stream.err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigFile::addSection(const String §ion) {
|
||||||
|
Section *s = getSection(section);
|
||||||
|
if (s)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Section newSection;
|
||||||
|
newSection.name = section;
|
||||||
|
_sections.push_back(newSection);
|
||||||
|
}
|
||||||
|
|
||||||
void ConfigFile::removeSection(const String §ion) {
|
void ConfigFile::removeSection(const String §ion) {
|
||||||
assert(isValidName(section));
|
assert(isValidName(section));
|
||||||
|
@ -380,4 +389,4 @@ void ConfigFile::Section::removeKey(const String &key) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // End of namespace Common
|
} // End of namespace Common
|
||||||
|
|
|
@ -106,6 +106,7 @@ public:
|
||||||
bool saveToStream(WriteStream &stream);
|
bool saveToStream(WriteStream &stream);
|
||||||
|
|
||||||
bool hasSection(const String §ion) const;
|
bool hasSection(const String §ion) const;
|
||||||
|
void addSection(const String §ion);
|
||||||
void removeSection(const String §ion);
|
void removeSection(const String §ion);
|
||||||
void renameSection(const String &oldName, const String &newName);
|
void renameSection(const String &oldName, const String &newName);
|
||||||
|
|
||||||
|
@ -134,6 +135,6 @@ private:
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
} // End of namespace Common
|
} // End of namespace Common
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -175,7 +175,7 @@ private:
|
||||||
String _filename;
|
String _filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // End of namespace Common
|
} // End of namespace Common
|
||||||
|
|
||||||
/** Shortcut for accessing the configuration manager. */
|
/** Shortcut for accessing the configuration manager. */
|
||||||
#define ConfMan Common::ConfigManager::instance()
|
#define ConfMan Common::ConfigManager::instance()
|
||||||
|
|
|
@ -34,10 +34,10 @@ CosineTable::CosineTable(int bitPrecision) {
|
||||||
|
|
||||||
int m = 1 << _bitPrecision;
|
int m = 1 << _bitPrecision;
|
||||||
double freq = 2 * M_PI / m;
|
double freq = 2 * M_PI / m;
|
||||||
_table = new float[m];
|
_table = new float[m / 2];
|
||||||
|
|
||||||
// Table contains cos(2*pi*x/n) for 0<=x<=n/4,
|
// Table contains cos(2*pi*i/m) for 0<=i<m/4,
|
||||||
// followed by its reverse
|
// followed by 3m/4<=i<m
|
||||||
for (int i = 0; i <= m / 4; i++)
|
for (int i = 0; i <= m / 4; i++)
|
||||||
_table[i] = cos(i * freq);
|
_table[i] = cos(i * freq);
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,14 @@ public:
|
||||||
~CosineTable();
|
~CosineTable();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get pointer to table
|
* Get pointer to table.
|
||||||
|
*
|
||||||
|
* This table contains 2^bitPrecision/2 entries.
|
||||||
|
* The layout of this table is as follows:
|
||||||
|
* - Entries 0 up to (excluding) 2^bitPrecision/4:
|
||||||
|
* cos(0) till (excluding) cos(1/2*pi)
|
||||||
|
* - Entries 2^bitPrecision/4 up to (excluding) 2^bitPrecision/2:
|
||||||
|
* cos(3/2*pi) till (excluding) cos(2*pi)
|
||||||
*/
|
*/
|
||||||
const float *getTable() { return _table; }
|
const float *getTable() { return _table; }
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,6 @@ private:
|
||||||
/** Shortcut for accessing the debug manager. */
|
/** Shortcut for accessing the debug manager. */
|
||||||
#define DebugMan Common::DebugManager::instance()
|
#define DebugMan Common::DebugManager::instance()
|
||||||
|
|
||||||
} // End of namespace Common
|
} // End of namespace Common
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -102,7 +102,7 @@ bool DebugManager::isDebugChannelEnabled(uint32 channel) {
|
||||||
return (gDebugChannelsEnabled & channel) != 0;
|
return (gDebugChannelsEnabled & channel) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // End of namespace Common
|
} // End of namespace Common
|
||||||
|
|
||||||
|
|
||||||
#ifndef DISABLE_TEXT_CONSOLE
|
#ifndef DISABLE_TEXT_CONSOLE
|
||||||
|
|
|
@ -117,5 +117,9 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3);
|
||||||
*/
|
*/
|
||||||
extern int gDebugLevel;
|
extern int gDebugLevel;
|
||||||
|
|
||||||
|
//Global constant for EventRecorder debug channel
|
||||||
|
enum GlobalDebugLevels {
|
||||||
|
kDebugLevelEventRec = 1 << 30
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -295,11 +295,14 @@ public:
|
||||||
* to the EventDispatcher, thus it will be deleted
|
* to the EventDispatcher, thus it will be deleted
|
||||||
* with "delete", when EventDispatcher is destroyed.
|
* with "delete", when EventDispatcher is destroyed.
|
||||||
*
|
*
|
||||||
* Note there is only one mapper per EventDispatcher
|
* @param autoFree Destroy previous mapper [default]
|
||||||
* possible, thus when this method is called twice,
|
* Normally we allow only one event mapper to exists,
|
||||||
* the former mapper will be destroied.
|
* However Event Recorder must intervent into normal
|
||||||
|
* event flow without altering its semantics. Thus during
|
||||||
|
* Event Recorder playback and recording we allow
|
||||||
|
* two mappers.
|
||||||
*/
|
*/
|
||||||
void registerMapper(EventMapper *mapper);
|
void registerMapper(EventMapper *mapper, bool autoFree = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries the setup event mapper.
|
* Queries the setup event mapper.
|
||||||
|
@ -333,6 +336,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void unregisterObserver(EventObserver *obs);
|
void unregisterObserver(EventObserver *obs);
|
||||||
private:
|
private:
|
||||||
|
bool _autoFreeMapper;
|
||||||
EventMapper *_mapper;
|
EventMapper *_mapper;
|
||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue