ALL: synced with ScummVM

This commit is contained in:
Pawel Kolodziejski 2013-07-07 15:29:33 +02:00
parent 1f3ad19a38
commit 068ff94d20
228 changed files with 11689 additions and 4232 deletions

View file

@ -32,8 +32,10 @@ ifeq "$(HAVE_GCC)" "1"
# being helpful.
#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
endif
ifneq "$(HAVE_CLANG)" "1"
# enable checking of pointers returned by "new", but only when we do not
@ -44,6 +46,11 @@ endif
ifeq "$(HAVE_CLANG)" "1"
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
ifeq "$(HAVE_ICC)" "1"

View file

@ -80,7 +80,7 @@ endif
$(EXECUTABLE): $(OBJS)
$(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
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")
# Get the working copy base revision
#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
else
GITROOT := git://github.com/residualvm/residualvm.git

View file

@ -30,7 +30,7 @@
#include "audio/audiostream.h"
#include "audio/decoders/flac.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/vorbis.h"
@ -99,6 +99,10 @@ LoopingAudioStream::LoopingAudioStream(RewindableAudioStream *stream, uint loops
// TODO: Properly indicate error
_loops = _completeIterations = 1;
}
if (stream->endOfData()) {
// Apparently this is an empty stream
_loops = _completeIterations = 1;
}
}
int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
@ -119,6 +123,10 @@ int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
_loops = _completeIterations = 1;
return samplesRead;
}
if (_parent->endOfData()) {
// Apparently this is an empty stream
_loops = _completeIterations = 1;
}
return samplesRead + readBuffer(buffer + samplesRead, remainingSamples);
}

View file

@ -268,7 +268,6 @@ static const int MSADPCMAdaptationTable[] = {
768, 614, 512, 409, 307, 230, 230, 230
};
int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
int32 predictor;
@ -290,40 +289,42 @@ int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
int samples;
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) {
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];
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++)
_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) {
data = _stream->readByte();
_blockPos[0]++;
buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
}
// (1 - (count - 1)) ensures that _decodedSamples acts as a FIFO of depth 2
buffer[samples] = _decodedSamples[1 - (_decodedSampleCount - 1)];
_decodedSampleCount--;
}
return samples;
@ -432,7 +433,7 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
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)
size = stream->size();

View file

@ -51,7 +51,7 @@ class RewindableAudioStream;
// 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
// string intact, it's easy to discern which encoding is used
enum typesADPCM {
enum ADPCMType {
kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
kADPCMMSIma, // Microsoft IMA ADPCM
kADPCMMS, // Microsoft ADPCM
@ -76,9 +76,9 @@ enum typesADPCM {
RewindableAudioStream *makeADPCMStream(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse,
uint32 size, typesADPCM type,
int rate = 22050,
int channels = 2,
uint32 size, ADPCMType type,
int rate,
int channels,
uint32 blockAlign = 0);
} // End of namespace Audio

View file

@ -206,12 +206,19 @@ public:
if (blockAlign == 0)
error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
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);
protected:
int16 decodeMS(ADPCMChannelStatus *c, byte);
private:
uint8 _decodedSampleCount;
int16 _decodedSamples[4];
};
// Duck DK3 IMA ADPCM Decoder

View file

@ -48,7 +48,7 @@ class SeekableAudioStream;
* 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
* 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);

View file

@ -137,7 +137,7 @@ OPL *Config::create(DriverId driver, OplType type) {
return new MAME::OPL();
else
warning("MAME OPL emulator only supports OPL2 emulation");
return 0;
return 0;
#ifndef DISABLE_DOSBOX_OPL
case kDOSBox:

View file

@ -129,7 +129,9 @@ public:
/**
* 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 v value, which will be written

View file

@ -194,7 +194,9 @@ public:
enum {
// PROP_TIMEDIV = 1,
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
};
/**

View file

@ -46,6 +46,7 @@ _numTracks(0),
_activeTrack(255),
_abortParse(0) {
memset(_activeNotes, 0, sizeof(_activeNotes));
memset(_tracks, 0, sizeof(_tracks));
_nextEvent.start = NULL;
_nextEvent.delta = 0;
_nextEvent.event = 0;

View file

@ -394,6 +394,7 @@ public:
static MidiParser *createParser_SMF();
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
static MidiParser *createParser_QT();
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
};

View file

@ -20,6 +20,8 @@
*
*/
#include "gui/EventRecorder.h"
#include "common/util.h"
#include "common/system.h"
#include "common/textconsole.h"
@ -171,9 +173,9 @@ private:
#pragma mark --- Mixer ---
#pragma mark -
// TODO: parameter "system" is unused
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);
@ -427,6 +429,7 @@ void MixerImpl::pauseHandle(SoundHandle handle, bool paused) {
bool MixerImpl::isSoundIDActive(int id) {
Common::StackLock lock(_mutex);
g_eventRec.updateSubsystems();
for (int i = 0; i != NUM_CHANNELS; i++)
if (_channels[i] && _channels[i]->getId() == id)
return true;
@ -443,6 +446,7 @@ int MixerImpl::getSoundID(SoundHandle handle) {
bool MixerImpl::isSoundHandleActive(SoundHandle handle) {
Common::StackLock lock(_mutex);
g_eventRec.updateSubsystems();
const int index = handle._val % NUM_CHANNELS;
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)
: _type(type), _mixer(mixer), _id(id), _permanent(permanent), _volume(Mixer::kMaxChannelVolume),
_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) {
assert(mixer);
assert(stream);
@ -556,12 +560,12 @@ void Channel::pause(bool paused) {
_pauseLevel++;
if (_pauseLevel == 1)
_pauseStartTime = g_system->getMillis();
_pauseStartTime = g_system->getMillis(true);
} else if (_pauseLevel > 0) {
_pauseLevel--;
if (!_pauseLevel) {
_pauseTime = (g_system->getMillis() - _pauseStartTime);
_pauseTime = (g_system->getMillis(true) - _pauseStartTime);
_pauseStartTime = 0;
}
}
@ -579,7 +583,7 @@ Timestamp Channel::getElapsedTime() {
if (isPaused())
delta = _pauseStartTime - _mixerTimeStamp;
else
delta = g_system->getMillis() - _mixerTimeStamp - _pauseTime;
delta = g_system->getMillis(true) - _mixerTimeStamp - _pauseTime;
// Convert the number of samples into a time duration.
@ -599,13 +603,12 @@ int Channel::mix(int16 *data, uint len) {
assert(_stream);
int res = 0;
if (_stream->endOfData()) {
// TODO: call drain method
} else {
assert(_converter);
_samplesConsumed = _samplesDecoded;
_mixerTimeStamp = g_system->getMillis();
_mixerTimeStamp = g_system->getMillis(true);
_pauseTime = 0;
res = _converter->flow(*_stream, data, len, _volL, _volR);
_samplesDecoded += res;

View file

@ -51,10 +51,9 @@ namespace Audio {
class MixerImpl : public Mixer {
private:
enum {
NUM_CHANNELS = 32
NUM_CHANNELS = 32 // ResidualVM specific
};
OSystem *_syst;
Common::Mutex _mutex;
const uint _sampleRate;

File diff suppressed because it is too large Load diff

View file

@ -127,12 +127,54 @@ int MidiDriver_FluidSynth::open() {
_synth = new_fluid_synth(_settings);
// In theory, this ought to reduce CPU load... but it doesn't make any
// noticeable difference for me, so disable it for now.
if (ConfMan.getBool("fluidsynth_chorus_activate")) {
fluid_synth_set_chorus_on(_synth, 1);
// fluid_synth_set_interp_method(_synth, -1, FLUID_INTERP_LINEAR);
// fluid_synth_set_reverb_on(_synth, 0);
// fluid_synth_set_chorus_on(_synth, 0);
int chorusNr = ConfMan.getInt("fluidsynth_chorus_nr");
double chorusLevel = (double)ConfMan.getInt("fluidsynth_chorus_level") / 100.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();

View file

@ -25,6 +25,7 @@
#ifdef USE_MT32EMU
#include "audio/softsynth/mt32/mt32emu.h"
#include "audio/softsynth/mt32/ROMInfo.h"
#include "audio/softsynth/emumidi.h"
#include "audio/musicplugin.h"
@ -47,6 +48,51 @@
#include "graphics/palette.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 {
void effectLevel(byte value) { }
void chorusLevel(byte value) { }
@ -57,6 +103,10 @@ private:
MidiChannel_MT32 _midiChannels[16];
uint16 _channelMask;
MT32Emu::Synth *_synth;
MT32Emu::ReportHandlerScummVM *_reportHandler;
const MT32Emu::ROMImage *_controlROM, *_pcmROM;
Common::File *_controlFile, *_pcmFile;
void deleteMuntStructures();
int _outputRate;
@ -84,149 +134,6 @@ public:
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
@ -239,43 +146,51 @@ MidiDriver_MT32::MidiDriver_MT32(Audio::Mixer *mixer) : MidiDriver_Emulated(mixe
for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) {
_midiChannels[i].init(this, i);
}
_reportHandler = 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
// at rates other than 32KHz, thus we produce data at 32KHz and
// rely on Mixer to convert.
_outputRate = 32000; //_mixer->getOutputRate();
_initializing = false;
// Initialized in open()
_controlROM = NULL;
_pcmROM = NULL;
_controlFile = NULL;
_pcmFile = NULL;
}
MidiDriver_MT32::~MidiDriver_MT32() {
deleteMuntStructures();
}
void MidiDriver_MT32::deleteMuntStructures() {
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() {
MT32Emu::SynthProperties prop;
if (_isOpen)
return MERR_ALREADY_OPEN;
MidiDriver_Emulated::open();
memset(&prop, 0, sizeof(prop));
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();
_reportHandler = new MT32Emu::ReportHandlerScummVM();
_synth = new MT32Emu::Synth(_reportHandler);
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
@ -290,8 +205,16 @@ int MidiDriver_MT32::open() {
}
_initializing = true;
drawMessage(-1, _s("Initializing MT-32 Emulator"));
if (!_synth->open(prop))
debug(4, _s("Initializing MT-32 Emulator"));
_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;
double gain = (double)ConfMan.getInt("midi_gain") / 100.0;
@ -352,8 +275,7 @@ void MidiDriver_MT32::close() {
_mixer->stopHandle(_mixerSoundHandle);
_synth->close();
delete _synth;
_synth = NULL;
deleteMuntStructures();
}
void MidiDriver_MT32::generateSamples(int16 *data, int len) {

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -16,64 +16,97 @@
*/
#include "mt32emu.h"
#if MT32EMU_USE_REVERBMODEL == 1
#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 {
// 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;
static const unsigned int NUM_DELAYS = 5;
// 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_ALLPASSES[] = {729, 78, 394, 994, 1250, 1889};
static const Bit32u MODE_0_DELAYS[] = {846, 4, 1819, 778, 346};
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 NUM_ALLPASSES = 3;
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 Bit32u MODE_1_ALLPASSES[] = {176, 809, 1324, 1258};
static const Bit32u MODE_1_DELAYS[] = {2262, 124, 974, 2516, 356};
static const float MODE_1_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.95f};
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_ALLPASSES[] = {994, 729, 78};
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[] = {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_2_DELAYS[] = {846, 4, 1819, 778, 346};
static const float MODE_2_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
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_ALLPASSES[] = {1324, 809, 176};
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[] = {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};
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};
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_ALLPASSES[] = {969, 644, 157};
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_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) {
index = 0;
size = 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};
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};
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];
}
RingBuffer::~RingBuffer() {
delete[] buffer;
buffer = NULL;
size = 0;
}
float RingBuffer::next() {
index++;
if (index >= size) {
if (++index >= size) {
index = 0;
}
return buffer[index];
}
bool RingBuffer::isEmpty() {
bool RingBuffer::isEmpty() const {
if (buffer == NULL) return true;
float *buf = buffer;
float total = 0;
float max = 0.001f;
for (Bit32u i = 0; i < size; i++) {
total += (*buf < 0 ? -*buf : *buf);
if ((*buf < -max) || (*buf > max)) return false;
buf++;
}
return ((total / size) < .0002 ? true : false);
return true;
}
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(float in) {
// This model corresponds to the allpass filter implementation in the real CM-32L device
float AllpassFilter::process(const float in) {
// This model corresponds to the allpass filter implementation of the real CM-32L device
// found from sample analysis
float out;
out = next();
const float bufferOut = next();
// store input - feedback / 2
buffer[index] = in - 0.5f * out;
buffer[index] = in - 0.5f * bufferOut;
// return buffer output + feedforward / 2
return out + 0.5f * buffer[index];
return bufferOut + 0.5f * buffer[index];
}
float Delay::process(float in) {
// Implements a very simple delay
CombFilter::CombFilter(const Bit32u useSize) : RingBuffer(useSize) {}
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
buffer[index] = in;
// prepare input + feedback
float filterIn = in + next() * feedbackFactor;
// return buffer output
return out;
// store input + feedback processed by a low-pass filter
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() {
close();
}
void AReverbModel::open(unsigned int /*sampleRate*/) {
// FIXME: filter sizes must be multiplied by sample rate to 32000Hz ratio
// IIR filter values depend on sample rate as well
void AReverbModel::open() {
allpasses = new AllpassFilter*[NUM_ALLPASSES];
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];
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
delays[i] = new Delay(currentSettings->delaySizes[i]);
combs = new CombFilter*[NUM_COMBS];
for (Bit32u i = 0; i < NUM_COMBS; i++) {
combs[i] = new CombFilter(currentSettings.combSizes[i]);
combs[i]->setFilterFactor(currentSettings.filterFactor[i] / 256.0f);
}
lpfAmp = currentSettings.lpfAmp / 16.0f;
mute();
}
@ -150,84 +190,80 @@ void AReverbModel::close() {
delete[] allpasses;
allpasses = NULL;
}
if (delays != NULL) {
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
if (delays[i] != NULL) {
delete delays[i];
delays[i] = NULL;
if (combs != NULL) {
for (Bit32u i = 0; i < NUM_COMBS; i++) {
if (combs[i] != NULL) {
delete combs[i];
combs[i] = NULL;
}
}
delete[] delays;
delays = NULL;
delete[] combs;
combs = NULL;
}
}
void AReverbModel::mute() {
if (allpasses == NULL || combs == NULL) return;
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
allpasses[i]->mute();
}
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
delays[i]->mute();
for (Bit32u i = 0; i < NUM_COMBS; i++) {
combs[i]->mute();
}
filterhist1 = 0;
filterhist2 = 0;
combhist = 0;
}
void AReverbModel::setParameters(Bit8u time, Bit8u level) {
// FIXME: wetLevel definitely needs ramping when changed
// Although, most games don't set reverb level during MIDI playback
decayTime = currentSettings->decayTimes[time];
wetLevel = currentSettings->wetLevels[level];
if (combs == NULL) return;
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 bActive = false;
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++) {
bActive |= !delays[i]->isEmpty();
for (Bit32u i = 0; i < NUM_COMBS; i++) {
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) {
// Three series allpass filters followed by a delay, fourth allpass filter and another delay
float dry, link, outL1, outL2, outR1, outR2;
float dry, link, outL1;
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
// found at the entrance of reverb processing on real devices
filterhist1 += (dry - filterhist1) * currentSettings->filtVal;
filterhist2 += (filterhist1 - filterhist2) * currentSettings->filtVal;
// Get the last stored sample before processing in order not to loose it
link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
link = allpasses[0]->process(-filterhist2);
combs[0]->process(-dry);
link = allpasses[0]->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 = 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
combhist = combhist * currentSettings->damp1 + link * currentSettings->damp2;
// If the output position is equal to the comb size, get it now in order not to loose it
outL1 = 1.5f * combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
*outLeft = (outL1 + outL2) * wetLevel;
*outRight = (outR1 + outR2) * wetLevel;
combs[1]->process(link);
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++;
inRight++;
@ -237,3 +273,5 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out
}
}
#endif

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -21,66 +21,67 @@
namespace MT32Emu {
struct AReverbSettings {
const Bit32u *allpassSizes;
const Bit32u *delaySizes;
const float *decayTimes;
const float *wetLevels;
float filtVal;
float damp1;
float damp2;
const Bit32u * const allpassSizes;
const Bit32u * const combSizes;
const Bit32u * const outLPositions;
const Bit32u * const outRPositions;
const Bit32u * const filterFactor;
const Bit32u * const decayTimes;
const Bit32u * const wetLevels;
const Bit32u lpfAmp;
};
class RingBuffer {
protected:
float *buffer;
Bit32u size;
const Bit32u size;
Bit32u index;
public:
RingBuffer(Bit32u size);
RingBuffer(const Bit32u size);
virtual ~RingBuffer();
float next();
bool isEmpty();
bool isEmpty() const;
void mute();
};
class AllpassFilter : public RingBuffer {
public:
AllpassFilter(Bit32u size);
float process(float in);
AllpassFilter(const Bit32u size);
float process(const float in);
};
class Delay : public RingBuffer {
class CombFilter : public RingBuffer {
float feedbackFactor;
float filterFactor;
public:
Delay(Bit32u size);
float process(float in);
CombFilter(const Bit32u size);
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 {
AllpassFilter **allpasses;
Delay **delays;
CombFilter **combs;
const AReverbSettings *currentSettings;
float decayTime;
const AReverbSettings &currentSettings;
float lpfAmp;
float wetLevel;
float filterhist1, filterhist2;
float combhist;
void mute();
public:
AReverbModel(const AReverbSettings *newSettings);
AReverbModel(const ReverbMode mode);
~AReverbModel();
void open(unsigned int sampleRate);
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;
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

View 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

View 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 &currentSettings;
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

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -22,7 +22,7 @@
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:
// rightDelay = (leftDelay - 2) * 2 + 2
// echoDelay = rightDelay - 1
@ -39,14 +39,16 @@ static const Bit32u REVERB_TIMINGS[8][3]= {
{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};
const float REVERB_FEEDBACK67 = -0.629960524947437f; // = -EXP2F(-2 / 3)
const float REVERB_FEEDBACK = -0.682034520443118f; // = -EXP2F(-53 / 96)
const float LPF_VALUE = 0.594603558f; // = EXP2F(-0.75f)
// Reverb amp is found as dryAmp * wetAmp
static const Bit32u REVERB_AMP[8] = {0x20*0x18, 0x50*0x18, 0x50*0x28, 0x50*0x40, 0x50*0x60, 0x50*0x80, 0x50*0xA8, 0x50*0xF8};
static const Bit32u REVERB_FEEDBACK67 = 0x60;
static const Bit32u REVERB_FEEDBACK = 0x68;
static const float LPF_VALUE = 0x68 / 256.0f;
static const Bit32u BUFFER_SIZE = 16384;
DelayReverb::DelayReverb() {
buf = NULL;
sampleRate = 0;
setParameters(0, 0);
}
@ -54,27 +56,22 @@ DelayReverb::~DelayReverb() {
delete[] buf;
}
void DelayReverb::open(unsigned int newSampleRate) {
if (newSampleRate != sampleRate || buf == NULL) {
sampleRate = newSampleRate;
void DelayReverb::open() {
if (buf == NULL) {
delete[] buf;
// If we ever need a speedup, set bufSize to EXP2F(ceil(log2(bufSize))) and use & instead of % to find buf indexes
bufSize = 16384 * sampleRate / 32000;
buf = new float[bufSize];
buf = new float[BUFFER_SIZE];
recalcParameters();
// mute buffer
bufIx = 0;
if (buf != NULL) {
for (unsigned int i = 0; i < bufSize; i++) {
for (unsigned int i = 0; i < BUFFER_SIZE; i++) {
buf[i] = 0.0f;
}
}
}
// FIXME: IIR filter value depends on sample rate as well
}
void DelayReverb::close() {
@ -91,59 +88,53 @@ void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) {
void DelayReverb::recalcParameters() {
// 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
delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000;
delayRight = REVERB_TIMINGS[time][1];
// 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) {
feedback = REVERB_FEEDBACK;
if (level < 3 || time < 6) {
feedback = REVERB_FEEDBACK / 256.0f;
} else {
feedback = REVERB_FEEDBACK67;
feedback = REVERB_FEEDBACK67 / 256.0f;
}
// Fading speed, i.e. amplitude ratio of neighbor responses
fade = REVERB_FADE[level];
// Overall output amp
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) {
if (buf == NULL) {
return;
}
if (buf == NULL) return;
for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) {
// The ring buffer write index moves backwards; reads are all done with positive offsets.
Bit32u bufIxPrev = (bufIx + 1) % bufSize;
Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize;
Bit32u bufIxRight = (bufIx + delayRight) % bufSize;
Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize;
Bit32u bufIxPrev = (bufIx + 1) % BUFFER_SIZE;
Bit32u bufIxLeft = (bufIx + delayLeft) % BUFFER_SIZE;
Bit32u bufIxRight = (bufIx + delayRight) % BUFFER_SIZE;
Bit32u bufIxFeedback = (bufIx + delayFeedback) % BUFFER_SIZE;
// 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
buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE;
buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn;
outLeft[sampleIx] = buf[bufIxLeft];
outRight[sampleIx] = buf[bufIxRight];
bufIx = (bufSize + bufIx - 1) % bufSize;
bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE;
}
}
bool DelayReverb::isActive() const {
// Quick hack: Return true iff all samples in the left buffer are the same and
// all samples in the right buffers are the same (within the sample output threshold).
if (buf == NULL) {
return false;
}
float last = buf[0] * 8192.0f;
for (unsigned int i = 1; i < bufSize; i++) {
float s = (buf[i] * 8192.0f);
if (fabs(s - last) > 1.0f) {
return true;
}
if (buf == NULL) return false;
float *b = buf;
float max = 0.001f;
for (Bit32u i = 0; i < BUFFER_SIZE; i++) {
if ((*b < -max) || (*b > max)) return true;
b++;
}
return false;
}

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -25,17 +25,14 @@ private:
Bit8u time;
Bit8u level;
unsigned int sampleRate;
Bit32u bufSize;
Bit32u bufIx;
float *buf;
Bit32u delayLeft;
Bit32u delayRight;
Bit32u delayFeedback;
float fade;
float amp;
float feedback;
void recalcParameters();
@ -43,7 +40,7 @@ private:
public:
DelayReverb();
~DelayReverb();
void open(unsigned int sampleRate);
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);

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -35,9 +35,7 @@ FreeverbModel::~FreeverbModel() {
delete freeverb;
}
void FreeverbModel::open(unsigned int /*sampleRate*/) {
// FIXME: scaleTuning must be multiplied by sample rate to 32000Hz ratio
// IIR filter values depend on sample rate as well
void FreeverbModel::open() {
if (freeverb == NULL) {
freeverb = new revmodel(scaleTuning);
}

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -32,7 +32,7 @@ class FreeverbModel : public ReverbModel {
public:
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
~FreeverbModel();
void open(unsigned int sampleRate);
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);

View file

@ -1,5 +1,5 @@
/* 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
* 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) {
largeIncrement = 0;
} 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)
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;
if (descending) {

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View 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

View 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, its 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

View 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

View 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, its 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

View file

@ -1,5 +1,5 @@
/* 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
* 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;
memset(patchCache, 0, sizeof(patchCache));
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
freePolys.push_front(new Poly(this));
freePolys.prepend(new Poly(this));
}
}
Part::~Part() {
while (!activePolys.empty()) {
delete activePolys.front();
activePolys.pop_front();
while (!activePolys.isEmpty()) {
delete activePolys.takeFirst();
}
while (!freePolys.empty()) {
delete freePolys.front();
freePolys.pop_front();
while (!freePolys.isEmpty()) {
delete freePolys.takeFirst();
}
}
@ -177,6 +175,7 @@ void Part::refresh() {
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
}
memcpy(currentInstr, timbreTemp->common.name, 10);
synth->newTimbreSet(partNum, patchTemp->patch.timbreGroup, currentInstr);
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
// 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
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
(*polyIt)->backupCacheToPartials(cache);
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->backupCacheToPartials(cache);
}
}
@ -445,8 +444,7 @@ void Part::abortPoly(Poly *poly) {
}
bool Part::abortFirstPoly(unsigned int key) {
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getKey() == key) {
abortPoly(poly);
return true;
@ -456,8 +454,7 @@ bool Part::abortFirstPoly(unsigned int key) {
}
bool Part::abortFirstPoly(PolyState polyState) {
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getState() == polyState) {
abortPoly(poly);
return true;
@ -474,10 +471,10 @@ bool Part::abortFirstPolyPreferHeld() {
}
bool Part::abortFirstPoly() {
if (activePolys.empty()) {
if (activePolys.isEmpty()) {
return false;
}
abortPoly(activePolys.front());
abortPoly(activePolys.getFirst());
return true;
}
@ -502,17 +499,16 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
return;
}
if (freePolys.empty()) {
if (freePolys.isEmpty()) {
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
return;
}
Poly *poly = freePolys.front();
freePolys.pop_front();
Poly *poly = freePolys.takeFirst();
if (patchTemp->patch.assignMode & 1) {
// Priority to data first received
activePolys.push_front(poly);
activePolys.prepend(poly);
} else {
activePolys.push_back(poly);
activePolys.append(poly);
}
Partial *partials[4];
@ -537,13 +533,13 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
#if MT32EMU_MONITOR_PARTIALS > 1
synth->printPartialUsage();
#endif
synth->polyStateChanged(partNum);
}
void Part::allNotesOff() {
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
// should treat the hold pedal as usual.
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
// 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) {
// 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.
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
// we're only using this method internally.
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->startDecay();
}
}
void Part::stopPedalHold() {
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->stopPedalHold();
}
}
@ -583,8 +577,7 @@ void Part::stopNote(unsigned int key) {
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
#endif
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
// 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.
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
@ -605,8 +598,7 @@ unsigned int Part::getActivePartialCount() const {
unsigned int Part::getActiveNonReleasingPartialCount() const {
unsigned int activeNonReleasingPartialCount = 0;
for (Common::List<Poly *>::const_iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getState() != POLY_Releasing) {
activeNonReleasingPartialCount += poly->getActivePartialCount();
}
@ -618,7 +610,100 @@ void Part::partialDeactivated(Poly *poly) {
activePartialCount--;
if (!poly->isActive()) {
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;
}
}
}

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -18,13 +18,27 @@
#ifndef MT32EMU_PART_H
#define MT32EMU_PART_H
#include <common/list.h>
namespace MT32Emu {
class PartialManager;
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 {
private:
// 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;
PatchCache patchCache[4];
Common::List<Poly *> freePolys;
Common::List<Poly *> activePolys;
PolyList freePolys;
PolyList activePolys;
void setPatch(const PatchParam *patch);
unsigned int midiKeyToKey(unsigned int midiKey);

View file

@ -1,5 +1,5 @@
/* 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
* 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};
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0), tva(new TVA(this, &ampRamp)), 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, &ampRamp);
tvp = new TVP(this);
tvf = new TVF(this, &cutoffModifierRamp);
ownerPart = -1;
poly = NULL;
pair = NULL;
@ -81,26 +86,22 @@ void Partial::deactivate() {
ownerPart = -1;
if (poly != NULL) {
poly->partialDeactivated(this);
if (pair != NULL) {
pair->pair = NULL;
}
}
#if MT32EMU_MONITOR_PARTIALS > 2
synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum);
synth->printPartialUsage(sampleNum);
#endif
}
// 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;
if (isRingModulatingSlave()) {
pair->la32Pair.deactivate(LA32PartialPair::SLAVE);
} 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];
} else {
pcmWave = NULL;
wavePos = 0.0f;
lastFreq = 0.0;
}
// 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;
}
pcmPosition = 0.0f;
pair = pairPartial;
alreadyOutputed = false;
tva->reset(part, patchCache->partialParam, rhythmTemp);
tvp->reset(part, patchCache->partialParam);
tvf->reset(patchCache->partialParam, tvp->getBasePitch());
}
float Partial::getPCMSample(unsigned int position) {
if (position >= pcmWave->len) {
if (!pcmWave->loop) {
return 0;
}
position = position % pcmWave->len;
LA32PartialPair::PairType pairType;
LA32PartialPair *useLA32Pair;
if (isRingModulatingSlave()) {
pairType = LA32PartialPair::SLAVE;
useLA32Pair = &pair->la32Pair;
} 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) {
const Tables &tables = Tables::getInstance();
Bit32u Partial::getAmpValue() {
// 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) {
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);
return 0;
}
alreadyOutputed = true;
// Generate samples
for (sampleNum = 0; sampleNum < length; sampleNum++) {
float sample = 0;
Bit32u ampRampVal = ampRamp.nextValue();
if (ampRamp.checkInterrupt()) {
tva->handleInterrupt();
}
if (!tva->isPlaying()) {
if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) {
deactivate();
break;
}
Bit16u pitch = tvp->nextPitch();
// 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.
#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;
la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue());
if (hasRingModulatingSlave()) {
la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue());
if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) {
pair->deactivate();
if (mixType == 2) {
deactivate();
break;
}
// 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;
}
}
// Multiply sample with current TVA value
sample *= amp;
*partialBuf++ = sample;
*partialBuf++ = la32Pair.nextOutSample();
}
unsigned long renderedSamples = sampleNum;
sampleNum = 0;
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 {
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);
return false;
}
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();
}
}
}
unsigned long numGenerated = generateSamples(myBuffer, length);
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++) {
*rightBuf++ = partialBuf[i] * stereoVolume.rightVol;
}
while (numGenerated < length) {
for (; numGenerated < length; numGenerated++) {
*leftBuf++ = 0.0f;
*rightBuf++ = 0.0f;
numGenerated++;
}
return true;
}

View file

@ -1,5 +1,5 @@
/* 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
* 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
StereoVolume stereoVolume;
// Distance in (possibly fractional) samples from the start of the current pulse
float wavePos;
float lastFreq;
float myBuffer[MAX_SAMPLES_PER_RUN];
Bit16s myBuffer[MAX_SAMPLES_PER_RUN];
// Only used for PCM partials
int pcmNum;
@ -60,17 +55,16 @@ private:
// Range: 0-255
int pulseWidthVal;
float pcmPosition;
Poly *poly;
LA32Ramp ampRamp;
LA32Ramp cutoffModifierRamp;
float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len);
float *mixBuffersRing(float *buf1, float *buf2, unsigned long len);
// TODO: This should be owned by PartialPair
LA32PartialPair la32Pair;
float getPCMSample(unsigned int position);
Bit32u getAmpValue();
Bit32u getCutoffValue();
public:
const PatchCache *patchCache;
@ -90,7 +84,6 @@ public:
unsigned long debugGetSampleNum() const;
int getOwnerPart() const;
int getKey() const;
const Poly *getPoly() const;
bool isActive() const;
void activate(int part);
@ -111,7 +104,7 @@ public:
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
unsigned long generateSamples(float *partialBuf, unsigned long length);
unsigned long generateSamples(Bit16s *partialBuf, unsigned long length);
};
}

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View file

@ -1,5 +1,5 @@
/* 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
* 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;
}
state = POLY_Inactive;
next = NULL;
}
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);
}
Poly *Poly::getNext() {
return next;
}
void Poly::setNext(Poly *poly) {
next = poly;
}
}

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -41,6 +41,8 @@ private:
Partial *partials[4];
Poly *next;
public:
Poly(Part *part);
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
@ -60,6 +62,9 @@ public:
bool isActive() const;
void partialDeactivated(Partial *partial);
Poly *getNext();
void setNext(Poly *poly);
};
}

View 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;
}
}

View 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

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -27,8 +27,10 @@
#include "mmath.h"
#include "PartialManager.h"
#if MT32EMU_USE_AREVERBMODEL == 1
#if MT32EMU_USE_REVERBMODEL == 1
#include "AReverbModel.h"
#elif MT32EMU_USE_REVERBMODEL == 2
#include "BReverbModel.h"
#else
#include "FreeverbModel.h"
#endif
@ -140,22 +142,36 @@ Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) {
return checksum;
}
Synth::Synth() {
Synth::Synth(ReportHandler *useReportHandler) {
isOpen = false;
reverbEnabled = true;
reverbOverridden = false;
#if MT32EMU_USE_AREVERBMODEL == 1
reverbModels[0] = new AReverbModel(&AReverbModel::REVERB_MODE_0_SETTINGS);
reverbModels[1] = new AReverbModel(&AReverbModel::REVERB_MODE_1_SETTINGS);
reverbModels[2] = new AReverbModel(&AReverbModel::REVERB_MODE_2_SETTINGS);
if (useReportHandler == NULL) {
reportHandler = new ReportHandler;
isDefaultReportHandler = true;
} 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
reverbModels[0] = 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[2] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f);
reverbModels[REVERB_MODE_ROOM] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f);
reverbModels[REVERB_MODE_HALL] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f);
reverbModels[REVERB_MODE_PLATE] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f);
reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb();
#endif
reverbModels[3] = new DelayReverb();
reverbModel = NULL;
setDACInputMode(DACInputMode_NICE);
setOutputGain(1.0f);
@ -170,31 +186,36 @@ Synth::~Synth() {
for (int i = 0; i < 4; i++) {
delete reverbModels[i];
}
}
int Synth::report(ReportType type, const void *data) {
if (myProp.report != NULL) {
return myProp.report(myProp.userData, type, data);
if (isDefaultReportHandler) {
delete reportHandler;
}
return 0;
}
unsigned int Synth::getSampleRate() const {
return myProp.sampleRate;
void ReportHandler::showLCDMessage(const char *data) {
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, ...) {
va_list ap;
va_start(ap, fmt);
if (myProp.printDebug != NULL) {
myProp.printDebug(myProp.userData, fmt, ap);
} else {
#if MT32EMU_DEBUG_SAMPLESTAMPS > 0
printf("[%u] ", renderedSampleCount);
reportHandler->printDebug("[%u] ", renderedSampleCount);
#endif
vprintf(fmt, ap);
printf("\n");
}
reportHandler->printDebug(fmt, ap);
va_end(ap);
}
@ -244,80 +265,60 @@ void Synth::setReverbOutputGain(float newReverbOutputGain) {
reverbOutputGain = newReverbOutputGain;
}
Common::File *Synth::openFile(const char *filename) {
if (myProp.openFile != NULL) {
return myProp.openFile(myProp.userData, filename);
}
char pathBuf[2048];
if (myProp.baseDir != NULL) {
strcpy(&pathBuf[0], myProp.baseDir);
strcat(&pathBuf[0], filename);
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);
bool Synth::loadControlROM(const ROMImage &controlROMImage) {
if (&controlROMImage == NULL) return false;
Common::File *file = controlROMImage.getFile();
const ROMInfo *controlROMInfo = controlROMImage.getROMInfo();
if ((controlROMInfo == NULL)
|| (controlROMInfo->type != ROMInfo::Control)
|| (controlROMInfo->pairType != ROMInfo::Full)) {
return false;
}
#if MT32EMU_MONITOR_INIT
printDebug("Found Control ROM: %s, %s", controlROMInfo->shortName, controlROMInfo->description);
#endif
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
controlROMMap = NULL;
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) {
controlROMMap = &ControlROMMaps[i];
return LoadResult_OK;
return true;
}
}
printDebug("%s does not match a known control ROM type", filename);
return LoadResult_Invalid;
#if MT32EMU_MONITOR_INIT
printDebug("Control ROM failed to load");
#endif
return false;
}
LoadResult Synth::loadPCMROM(const char *filename) {
Common::File *file = openFile(filename); // ROM File
if (file == NULL) {
return LoadResult_NotFound;
bool Synth::loadPCMROM(const ROMImage &pcmROMImage) {
if (&pcmROMImage == NULL) return false;
Common::File *file = pcmROMImage.getFile();
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();
if (fileSize < (size_t)(2 * pcmROMSize)) {
printDebug("PCM ROM file is too short (expected %d, got %d)", 2 * pcmROMSize, fileSize);
closeFile(file);
return LoadResult_Invalid;
if (fileSize != (2 * pcmROMSize)) {
#if MT32EMU_MONITOR_INIT
printDebug("PCM ROM file has wrong size (expected %d, got %d)", 2 * pcmROMSize, fileSize);
#endif
return false;
}
if (file->err()) {
closeFile(file);
return LoadResult_Unreadable;
}
LoadResult rc = LoadResult_OK;
for (int i = 0; i < pcmROMSize; i++) {
Bit8u s = file->readByte();
Bit8u c = file->readByte();
byte *buffer = new byte[file->size()];
file->read(buffer, file->size());
const byte *fileData = buffer;
for (size_t i = 0; i < pcmROMSize; i++) {
Bit8u s = *(fileData++);
Bit8u c = *(fileData++);
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));
}
bool negative = log < 0;
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;
pcmROMData[i] = log;
}
closeFile(file);
return rc;
delete[] buffer;
return true;
}
bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) {
ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress];
for (int i = 0; i < count; i++) {
int rAddr = tps[i].pos * 0x800;
int rLenExp = (tps[i].len & 0x70) >> 4;
int rLen = 0x800 << rLenExp;
size_t rAddr = tps[i].pos * 0x800;
size_t rLenExp = (tps[i].len & 0x70) >> 4;
size_t rLen = 0x800 << rLenExp;
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);
return false;
@ -414,12 +407,11 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi
return true;
}
bool Synth::open(SynthProperties &useProp) {
bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) {
if (isOpen) {
return false;
}
prerenderReadIx = prerenderWriteIx = 0;
myProp = useProp;
#if MT32EMU_MONITOR_INIT
printDebug("Initialising Constant Tables");
#endif
@ -428,11 +420,6 @@ bool Synth::open(SynthProperties &useProp) {
reverbModels[i]->open(useProp.sampleRate);
}
#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
memset(&mt32ram, '?', sizeof(mt32ram));
@ -440,12 +427,10 @@ bool Synth::open(SynthProperties &useProp) {
#if MT32EMU_MONITOR_INIT
printDebug("Loading Control ROM");
#endif
if (loadControlROM("CM32L_CONTROL.ROM") != LoadResult_OK) {
if (loadControlROM("MT32_CONTROL.ROM") != LoadResult_OK) {
printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM");
//report(ReportType_errorControlROM, &errno);
return false;
}
if (!loadControlROM(controlROMImage)) {
printDebug("Init Error - Missing or invalid Control ROM image");
reportHandler->onErrorControlROM();
return false;
}
initMemoryRegions();
@ -454,17 +439,15 @@ bool Synth::open(SynthProperties &useProp) {
// 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
pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024;
pcmROMData = new float[pcmROMSize];
pcmROMData = new Bit16s[pcmROMSize];
#if MT32EMU_MONITOR_INIT
printDebug("Loading PCM ROM");
#endif
if (loadPCMROM("CM32L_PCM.ROM") != LoadResult_OK) {
if (loadPCMROM("MT32_PCM.ROM") != LoadResult_OK) {
printDebug("Init Error - Missing MT32_PCM.ROM");
//report(ReportType_errorPCMROM, &errno);
return false;
}
if (!loadPCMROM(pcmROMImage)) {
printDebug("Init Error - Missing PCM ROM image");
reportHandler->onErrorPCMROM();
return false;
}
#if MT32EMU_MONITOR_INIT
@ -593,9 +576,6 @@ void Synth::close() {
parts[i] = NULL;
}
delete[] myProp.baseDir;
myProp.baseDir = NULL;
delete[] pcmWaves;
delete[] pcmROMData;
@ -1182,7 +1162,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
case MR_System:
region->write(0, off, data, len);
report(ReportType_devReconfig, NULL);
reportHandler->onDeviceReconfig();
// 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
// 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
printDebug("WRITE-LCD: %s", buf);
#endif
report(ReportType_lcdMessage, buf);
reportHandler->showLCDMessage(buf);
break;
case MR_Reset:
reset();
@ -1248,9 +1228,9 @@ void Synth::refreshSystemReverbParameters() {
#endif
return;
}
report(ReportType_newReverbMode, &mt32ram.system.reverbMode);
report(ReportType_newReverbTime, &mt32ram.system.reverbTime);
report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel);
reportHandler->onNewReverbMode(mt32ram.system.reverbMode);
reportHandler->onNewReverbTime(mt32ram.system.reverbTime);
reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel);
ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode];
#if MT32EMU_REDUCE_REVERB_MEMORY
@ -1258,7 +1238,7 @@ void Synth::refreshSystemReverbParameters() {
if (reverbModel != NULL) {
reverbModel->close();
}
newReverbModel->open(myProp.sampleRate);
newReverbModel->open();
}
#endif
reverbModel = newReverbModel;
@ -1313,7 +1293,7 @@ void Synth::reset() {
#if MT32EMU_MONITOR_SYSEX > 0
printDebug("RESET");
#endif
report(ReportType_devReset, NULL);
reportHandler->onDeviceReset();
partialManager->deactivateAll();
mt32ram = mt32default;
for (int i = 0; i < 9; i++) {

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -26,6 +26,7 @@ class TableInitialiser;
class Partial;
class PartialManager;
class Part;
class ROMImage;
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
@ -57,71 +58,6 @@ enum DACInputMode {
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);
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
};
enum ReverbMode {
REVERB_MODE_ROOM,
REVERB_MODE_HALL,
REVERB_MODE_PLATE,
REVERB_MODE_TAP_DELAY
};
class MemoryRegion {
private:
Synth *synth;
@ -278,7 +221,7 @@ class ReverbModel {
public:
virtual ~ReverbModel() {}
// 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.
virtual void close() = 0;
virtual void setParameters(Bit8u time, Bit8u level) = 0;
@ -286,6 +229,32 @@ public:
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 {
friend class Part;
friend class RhythmPart;
@ -314,8 +283,8 @@ private:
const ControlROMMap *controlROMMap;
Bit8u controlROMData[CONTROL_ROM_SIZE];
float *pcmROMData;
int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
Bit16s *pcmROMData;
size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
Bit8s chantable[32];
@ -336,6 +305,9 @@ private:
bool isOpen;
bool isDefaultReportHandler;
ReportHandler *reportHandler;
PartialManager *partialManager;
Part *parts[9];
@ -368,8 +340,6 @@ private:
int prerenderReadIx;
int prerenderWriteIx;
SynthProperties myProp;
bool prerender();
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);
@ -383,8 +353,8 @@ private:
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
LoadResult loadControlROM(const char *filename);
LoadResult loadPCMROM(const char *filename);
bool loadControlROM(const ROMImage &controlROMImage);
bool loadPCMROM(const ROMImage &pcmROMImage);
bool initPCMList(Bit16u mapAddress, Bit16u count);
bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed);
@ -398,24 +368,23 @@ private:
void refreshSystem();
void reset();
unsigned int getSampleRate() const;
void printPartialUsage(unsigned long sampleOffset = 0);
protected:
int report(ReportType type, const void *reportData);
Common::File *openFile(const char *filename);
void closeFile(Common::File *file);
void polyStateChanged(int partNum);
void newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]);
void printDebug(const char *fmt, ...);
public:
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();
// Used to initialise the MT-32. Must be called before any other function.
// 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
void close(void);

View file

@ -1,5 +1,5 @@
/* 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
* 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};
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) {
@ -274,7 +274,7 @@ void TVA::nextPhase() {
}
int newTarget;
int newIncrement = 0;
int newIncrement = 0; // Initialised to please compilers
int envPointIndex = phase;
if (!allLevelsZeroFromNowOn) {

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View file

@ -1,5 +1,5 @@
/* 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
* 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) :
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.
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.
// 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) {

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by

View file

@ -1,5 +1,5 @@
/* 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
* 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]);
}
// Ratio of negative segment to wave length
for (int i = 0; i < 128; i++) {
// Formula determined from sample analysis.
float pt = 0.5f / 127.0f * i;
pulseLenFactor[i] = (1.241857812f - pt) * pt; // seems to be 2 ^ (5 / 16) = 1.241857812f
// The LA32 chip contains an exponent table inside. The table contains 12-bit integer values.
// The actual table size is 512 rows. The 9 higher bits of the fractional part of the argument are used as a lookup address.
// To improve the precision of computations, the lower bits are supposed to be used for interpolation as the LA32 chip also
// contains another 512-row table with inverted differences between the main table values.
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
for (int i = 0; i < 4096; i++) {
exp2[i] = EXP2F(i / 4096.0f);
// There is a logarithmic sine table inside the LA32 chip. The table contains 13-bit integer values.
for (int i = 1; i < 512; i++) {
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
for (int i = 0; i < 32; i++) {
resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.0f);
}
// 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);
}
static const Bit8u resAmpDecayFactorTable[] = {31, 16, 12, 8, 5, 3, 2, 1};
resAmpDecayFactor = resAmpDecayFactorTable;
}
}

View file

@ -1,5 +1,5 @@
/* 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
* it under the terms of the GNU Lesser General Public License as published by
@ -20,7 +20,9 @@
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 int MIDDLEC = 60;
@ -54,11 +56,10 @@ public:
// CONFIRMED:
Bit8u pulseWidth100To255[101];
float exp2[4096];
float pulseLenFactor[128];
float resAmpMax[32];
float resAmpFadeFactor[8];
float sinf10[5120];
Bit16u exp9[512];
Bit16u logsin9[512];
const Bit8u *resAmpDecayFactor;
};
}

View file

@ -1,5 +1,5 @@
/* 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
* 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
}
static inline float EXP2I(unsigned int i) {
return float(1 << (i >> 12)) * Tables::getInstance().exp2[i & 0x0FFF];
}
static inline float EXP10F(float x) {
return exp(FLOAT_LN_10 * x);
}

View file

@ -2,13 +2,17 @@ MODULE := audio/softsynth/mt32
MODULE_OBJS := \
AReverbModel.o \
BReverbModel.o \
DelayReverb.o \
FreeverbModel.o \
LA32Ramp.o \
LA32WaveGenerator.o \
LegacyWaveGenerator.o \
Part.o \
Partial.o \
PartialManager.o \
Poly.o \
ROMInfo.o \
Synth.o \
TVA.o \
TVF.o \

View file

@ -1,5 +1,5 @@
/* 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
* 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_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
// The maximum number of partials playing simultaneously
#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.
#define MT32EMU_REDUCE_REVERB_MEMORY 1
// 0: Use standard Freeverb
// 1: Use AReverb (currently not properly tuned)
#define MT32EMU_USE_AREVERBMODEL 0
// 0: Use legacy Freeverb
// 1: Use Accurate Reverb model aka AReverb
// 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
{
@ -111,11 +102,14 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024;
#include "Tables.h"
#include "Poly.h"
#include "LA32Ramp.h"
#include "LA32WaveGenerator.h"
#include "LegacyWaveGenerator.h"
#include "TVA.h"
#include "TVP.h"
#include "TVF.h"
#include "Partial.h"
#include "Part.h"
#include "ROMInfo.h"
#include "Synth.h"
#endif

View file

@ -247,7 +247,7 @@ byte OPL::read(int port) {
}
void OPL::writeReg(int r, int v) {
byte tempReg = 0;
int tempReg = 0;
switch (_type) {
case Config::kOpl2:
case Config::kDualOpl2:
@ -257,12 +257,27 @@ void OPL::writeReg(int r, int v) {
// Backup old setup register
tempReg = _reg.normal;
// 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);
// We directly allow writing to secondary OPL3 registers by using
// register values >= 0x100.
if (_type == Config::kOpl3 && r >= 0x100) {
// 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
write(0x388, tempReg);
if (_type == Config::kOpl3 && tempReg >= 0x100) {
write(0x222, tempReg & ~0x100);
} else {
write(0x388, tempReg);
}
break;
};
}

View file

@ -223,7 +223,7 @@ static int *ENV_CURVE;
/* multiple table */
#define ML(a) (int)(a * 2)
#define ML(a) (uint)(a * 2)
static const uint MUL_TABLE[16]= {
/* 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),

View file

@ -51,6 +51,8 @@ DefaultEventManager::DefaultEventManager(Common::EventSource *boss) :
// Reset key repeat
_currentKeyDown.keycode = 0;
_currentKeyDown.ascii = 0;
_currentKeyDown.flags = 0;
#ifdef ENABLE_VKEYBD
_vk = new Common::VirtualKeyboard();
@ -82,7 +84,8 @@ void DefaultEventManager::init() {
}
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;
_dispatcher.dispatch();

View file

@ -101,6 +101,7 @@ int SdlEventSource::mapKey(SDLKey key, SDLMod mod, Uint16 unicode) {
return key;
}
// ResidualVM specific relMouse x,y
void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y, int relx, int rely) {
event.mouse.x = x;
event.mouse.y = y;
@ -118,7 +119,9 @@ void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y, int r
}
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) {
_km.last_time = curTime;
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) {
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;
}

View file

@ -100,7 +100,7 @@ public:
* @param mode Mode to use while listing the directory.
* @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;

View file

@ -40,6 +40,8 @@
#include "graphics/scaler.h"
#include "graphics/surface.h"
#include "graphics/pixelbuffer.h"
#include "gui/EventRecorder.h"
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{0, 0, 0}
};

View file

@ -69,8 +69,8 @@ public:
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
#endif
virtual void initSize(uint w, uint h, const Graphics::PixelFormat *format = NULL);
virtual void launcherInitSize(uint w, uint h);
Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d);
virtual void launcherInitSize(uint w, uint h); // ResidualVM specific method
Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d); // ResidualVM specific method
virtual int getScreenChangeID() const { return _screenChangeCount; }
virtual void beginGFXTransaction();
@ -100,17 +100,17 @@ public:
virtual void clearOverlay();
virtual void grabOverlay(void *buf, int pitch);
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 getOverlayWidth() { return _overlayWidth; }
void closeOverlay();
void closeOverlay(); // ResidualVM specific method
virtual bool showMouse(bool visible);
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 setCursorPalette(const byte *colors, uint start, uint num);
// ResidualVM specific method
virtual bool lockMouse(bool lock);
virtual bool lockMouse(bool lock); // ResidualVM specific method
#ifdef USE_OSD
virtual void displayMessageOnOSD(const char *msg);

View file

@ -128,7 +128,7 @@ public:
* @param name name of the keymap to push
* @param transparent if true keymapper will iterate down the
* 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);

View file

@ -102,6 +102,7 @@ public:
void sysEx(const byte *msg, uint16 length);
private:
void loadSoundFont(const char *soundfont);
AUGraph _auGraph;
AudioUnit _synth;
};
@ -171,52 +172,8 @@ int MidiDriver_CORE::open() {
#endif
// Load custom soundfont, if specified
if (ConfMan.hasKey("soundfont")) {
const char *soundfont = 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);
}
if (ConfMan.hasKey("soundfont"))
loadSoundFont(ConfMan.get("soundfont").c_str());
#ifdef COREAUDIO_DISABLE_REVERB
// Disable reverb mode, as that sucks up a lot of CPU power, which can
@ -242,6 +199,74 @@ bail:
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() {
MidiDriver_MPU401::close();
if (_auGraph) {

View file

@ -88,12 +88,9 @@ int MidiDriver_SEQ::open() {
device = ::open((device_name), O_RDWR, 0);
if ((device_name == NULL) || (device < 0)) {
if (device_name == NULL)
warning("Opening /dev/null (no music will be heard) ");
else
warning("Cannot open rawmidi device %s - using /dev/null (no music will be heard) ",
device_name);
if (device < 0) {
warning("Cannot open rawmidi device %s - using /dev/null (no music will be heard)",
device_name);
device = (::open(("/dev/null"), O_RDWR, 0));
if (device < 0)
error("Cannot open /dev/null to dump midi output");
@ -145,7 +142,7 @@ void MidiDriver_SEQ::send(uint32 b) {
buf[position++] = 0;
break;
default:
warning("MidiDriver_SEQ::send: unknown : %08x", (int)b);
warning("MidiDriver_SEQ::send: unknown: %08x", (int)b);
break;
}
if (write(device, buf, position) == -1)

View file

@ -153,7 +153,7 @@ void SdlMixerManager::suspendAudio() {
int SdlMixerManager::resumeAudio() {
if (!_audioSuspended)
return -2;
if (SDL_OpenAudio(&_obtained, NULL) < 0){
if (SDL_OpenAudio(&_obtained, NULL) < 0) {
return -1;
}
SDL_PauseAudio(0);

View file

@ -26,11 +26,11 @@
#include "backends/graphics/graphics.h"
#include "backends/mutex/mutex.h"
#include "gui/EventRecorder.h"
#include "audio/mixer.h"
#include "graphics/pixelformat.h"
// ResidualVM specific:
#include "graphics/pixelbuffer.h"
#include "graphics/pixelbuffer.h" // ResidualVM specific:
ModularBackend::ModularBackend()
:
@ -54,7 +54,7 @@ bool ModularBackend::hasFeature(Feature f) {
}
void ModularBackend::setFeatureState(Feature f, bool enable) {
return _graphicsManager->setFeatureState(f, enable);
_graphicsManager->setFeatureState(f, enable);
}
bool ModularBackend::getFeatureState(Feature f) {
@ -153,7 +153,9 @@ void ModularBackend::fillScreen(uint32 col) {
}
void ModularBackend::updateScreen() {
g_eventRec.preDrawOverlayGui();
_graphicsManager->updateScreen();
g_eventRec.postDrawOverlayGui();
}
void ModularBackend::setShakePos(int shakeOffset) {

View file

@ -72,10 +72,8 @@ public:
virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const;
#endif
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 Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d);
virtual void launcherInitSize(uint w, uint h); // ResidualVM specific method
virtual Graphics::PixelBuffer setupScreen(int screenW, int screenH, bool fullscreen, bool accel3d); // ResidualVM specific method
virtual int getScreenChangeID() const;
virtual void beginGFXTransaction();
@ -106,8 +104,7 @@ public:
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 setCursorPalette(const byte *colors, uint start, uint num);
// ResidualVM specific method
virtual bool lockMouse(bool lock);
virtual bool lockMouse(bool lock); // ResidualVM specific method
//@}

View file

@ -112,9 +112,9 @@ MODULE_OBJS += \
mixer/sdl13/sdl13-mixer.o
endif
ifeq ($(BACKEND),bada)
ifeq ($(BACKEND),tizen)
MODULE_OBJS += \
timer/bada/timer.o
timer/tizen/timer.o
endif
ifeq ($(BACKEND),ds)
@ -206,5 +206,11 @@ MODULE_OBJS += \
plugins/wii/wii-provider.o
endif
ifdef ENABLE_EVENTRECORDER
MODULE_OBJS += \
mixer/nullmixer/nullsdl-mixer.o \
saves/recorder/recorder-saves.o
endif
# Include common rules
include $(srcdir)/rules.mk

View file

@ -33,15 +33,15 @@ OSystem::MutexRef SdlMutexManager::createMutex() {
}
void SdlMutexManager::lockMutex(OSystem::MutexRef mutex) {
SDL_mutexP((SDL_mutex *) mutex);
SDL_mutexP((SDL_mutex *)mutex);
}
void SdlMutexManager::unlockMutex(OSystem::MutexRef mutex) {
SDL_mutexV((SDL_mutex *) mutex);
SDL_mutexV((SDL_mutex *)mutex);
}
void SdlMutexManager::deleteMutex(OSystem::MutexRef mutex) {
SDL_DestroyMutex((SDL_mutex *) mutex);
SDL_DestroyMutex((SDL_mutex *)mutex);
}
#endif

View file

@ -116,7 +116,7 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
_screen_changeid(0),
_egl_surface_width(0),
_egl_surface_height(0),
_htc_fail(false),
_htc_fail(true),
_force_redraw(false),
_game_texture(0),
_game_pbuf(),
@ -149,8 +149,7 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
_touchpad_scale(66),
_dpad_scale(4),
_fingersDown(0),
_trackball_scale(2)
{
_trackball_scale(2) {
_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());
mf.toLowercase();
_htc_fail = mf.contains("htc");
/*_htc_fail = mf.contains("htc");
if (_htc_fail)
LOGI("Enabling HTC workaround");
LOGI("Enabling HTC workaround");*/
}
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;
gettimeofday(&curTime, 0);

View file

@ -288,7 +288,7 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num);
virtual bool pollEvent(Common::Event &event);
virtual uint32 getMillis();
virtual uint32 getMillis(bool skipRecord = false);
virtual void delayMillis(uint msecs);
virtual MutexRef createMutex(void);

View file

@ -50,7 +50,7 @@ JAVACFLAGS = -source 1.5 -target 1.5
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_CLASSES_MAIN_TOP = $(PATH_BUILD)/classes.main
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`; \
for i in $(PATH_BUILD_ASSETS)/*.zip; do \
echo "recompress $$i"; \
cd $$work_dir; \
cd "$$work_dir"; \
$(RM) -rf $(PATH_BUILD_ASSETS)/tmp; \
$(MKDIR) $(PATH_BUILD_ASSETS)/tmp; \
unzip -q $$i -d $(PATH_BUILD_ASSETS)/tmp; \

View file

@ -62,8 +62,7 @@ void OSystem_PS3::initBackend() {
ConfMan.set("joystick_num", 0);
ConfMan.set("vkeybdpath", PREFIX "/data");
ConfMan.registerDefault("fullscreen", true);
//ResidualVM: not used
//ConfMan.registerDefault("aspect_ratio", true);
//ConfMan.registerDefault("aspect_ratio", true); //ResidualVM: not used
// Create the savefile manager
if (_savefileManager == 0)

View file

@ -35,8 +35,11 @@
// it with an alternate slightly less unfriendly override.
#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_FILE)
#undef FILE
// Solaris has typedef __FILE FILE in several places already
#if !defined(__sun)
typedef struct { int FAKE; } FAKE_FILE;
#define FILE FAKE_FILE
#endif // (__sun)
#endif
#if !defined(FORBIDDEN_SYMBOL_ALLOW_ALL) && !defined(FORBIDDEN_SYMBOL_EXCEPTION_strcasecmp)

View file

@ -30,7 +30,7 @@
#include "backends/platform/sdl/sdl.h"
#include "common/config-manager.h"
#include "common/EventRecorder.h"
#include "gui/EventRecorder.h"
#include "common/taskbar.h"
#include "common/textconsole.h"
@ -87,7 +87,9 @@ OSystem_SDL::~OSystem_SDL() {
_audiocdManager = 0;
delete _mixerManager;
_mixerManager = 0;
delete _timerManager;
delete g_eventRec.getTimerManager();
_timerManager = 0;
delete _mutexManager;
_mutexManager = 0;
@ -117,9 +119,6 @@ void OSystem_SDL::init() {
if (_mutexManager == 0)
_mutexManager = new SdlMutexManager();
if (_timerManager == 0)
_timerManager = new SdlTimerManager();
#if defined(USE_TASKBAR)
if (_taskbarManager == 0)
_taskbarManager = new Common::TaskbarManager();
@ -149,10 +148,12 @@ void OSystem_SDL::initBackend() {
if (_mixerManager == 0) {
_mixerManager = new SdlMixerManager();
// Setup and start mixer
_mixerManager->init();
}
g_eventRec.registerMixerManager(_mixerManager);
g_eventRec.registerTimerManager(new SdlTimerManager());
if (_audiocdManager == 0) {
// Audio CD support was removed with SDL 1.3
@ -419,14 +420,15 @@ void OSystem_SDL::setupIcon() {
free(icon);
}
uint32 OSystem_SDL::getMillis() {
uint32 OSystem_SDL::getMillis(bool skipRecord) {
uint32 millis = SDL_GetTicks();
g_eventRec.processMillis(millis);
g_eventRec.processMillis(millis, skipRecord);
return millis;
}
void OSystem_SDL::delayMillis(uint msecs) {
if (!g_eventRec.processDelayMillis(msecs))
if (!g_eventRec.processDelayMillis())
SDL_Delay(msecs);
}
@ -444,11 +446,15 @@ void OSystem_SDL::getTimeAndDate(TimeDate &td) const {
Audio::Mixer *OSystem_SDL::getMixer() {
assert(_mixerManager);
return _mixerManager->getMixer();
return getMixerManager()->getMixer();
}
SdlMixerManager *OSystem_SDL::getMixerManager() {
assert(_mixerManager);
return _mixerManager;
return g_eventRec.getMixerManager();
}
Common::TimerManager *OSystem_SDL::getTimerManager() {
return g_eventRec.getTimerManager();
}

View file

@ -24,6 +24,7 @@
#define PLATFORM_SDL_H
#include "backends/platform/sdl/sdl-sys.h"
// ResidualVM specific:
#ifdef USE_OPENGL
#include <SDL_opengl.h>
#endif
@ -72,10 +73,11 @@ public:
virtual void setWindowCaption(const char *caption);
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 getTimeAndDate(TimeDate &td) const;
virtual Audio::Mixer *getMixer();
virtual Common::TimerManager *getTimerManager();
protected:
bool _inited;

View 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);
}

View 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

View file

@ -80,7 +80,7 @@ DefaultTimerManager::~DefaultTimerManager() {
void DefaultTimerManager::handler() {
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.
TimerSlot *slot = _head->next;

View file

@ -117,6 +117,13 @@ static const char HELP_STRING[] =
" --no-show-fps Set the turn off display FPS info\n"
" --soft-renderer Switch to 3D software 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"
#ifdef ENABLE_GRIM
" --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 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, ...) {
char buf[STRINGBUFLEN];
@ -178,7 +185,7 @@ void registerDefaults() {
// Game specific
ConfMan.registerDefault("path", "");
ConfMan.registerDefault("platform", Common::kPlatformPC);
ConfMan.registerDefault("platform", Common::kPlatformDOS);
ConfMan.registerDefault("language", "en");
ConfMan.registerDefault("subtitles", false);
ConfMan.registerDefault("boot_param", 0);
@ -197,11 +204,34 @@ void registerDefaults() {
ConfMan.registerDefault("confirm_exit", false);
ConfMan.registerDefault("disable_sdl_parachute", false);
ConfMan.registerDefault("disable_display", false);
ConfMan.registerDefault("record_mode", "none");
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; \
}
// End an option handler
#define END_COMMAND \
}
Common::String parseCommandLine(Common::StringMap &settings, int argc, const char * const *argv) {
const char *s, *s2;
if (!argv)
return Common::String();
// argv[0] contains the name of the executable.
if (argv && argv[0]) {
if (argv[0]) {
s = strrchr(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] == '-');
DO_COMMAND('h', "help")
END_OPTION
END_COMMAND
DO_COMMAND('v', "version")
END_OPTION
END_COMMAND
DO_COMMAND('t', "list-targets")
END_OPTION
END_COMMAND
DO_COMMAND('z', "list-games")
END_OPTION
END_COMMAND
#ifdef DETECTOR_TESTING_HACK
// HACK FIXME TODO: This command is intentionally *not* documented!
DO_LONG_COMMAND("test-detector")
END_OPTION
END_COMMAND
#endif
#ifdef UPGRADE_ALL_TARGETS_HACK
// HACK FIXME TODO: This command is intentionally *not* documented!
DO_LONG_COMMAND("upgrade-targets")
END_OPTION
END_COMMAND
#endif
DO_LONG_OPTION("list-saves")
@ -350,7 +387,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
END_OPTION
DO_LONG_COMMAND("list-audio-devices")
END_OPTION
END_COMMAND
DO_LONG_OPTION_INT("output-rate")
END_OPTION
@ -358,6 +395,17 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
DO_OPTION_BOOL('f', "fullscreen")
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")
END_OPTION
@ -480,7 +528,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
END_OPTION
DO_LONG_COMMAND("list-themes")
END_OPTION
END_COMMAND
DO_LONG_OPTION("target-md5")
END_OPTION
@ -494,18 +542,6 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
DO_LONG_OPTION("speech-mode")
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
// This is automatically set when launched from the Springboard.
DO_LONG_OPTION_OPT("launchedFromSB", 0)
@ -533,8 +569,7 @@ static void listGames() {
"-------------------- ------------------------------------------------------\n");
const EnginePlugin::List &plugins = EngineMan.getPlugins();
EnginePlugin::List::const_iterator iter = plugins.begin();
for (iter = plugins.begin(); iter != plugins.end(); ++iter) {
for (EnginePlugin::List::const_iterator iter = plugins.begin(); iter != plugins.end(); ++iter) {
GameList list = (**iter)->getSupportedGames();
for (GameList::iterator v = list.begin(); v != list.end(); ++v) {
printf("%-20s %s\n", v->gameid().c_str(), v->description().c_str());

View file

@ -42,8 +42,11 @@
#include "common/debug.h"
#include "common/debug-channels.h" /* for debug manager */
#include "common/events.h"
#include "common/EventRecorder.h"
#include "gui/EventRecorder.h"
#include "common/fs.h"
#ifdef ENABLE_EVENTRECORDER
#include "common/recorderfile.h"
#endif
#include "common/system.h"
#include "common/textconsole.h"
#include "common/tokenizer.h"
@ -251,7 +254,7 @@ static void setupGraphics(OSystem &system) {
system.setGraphicsMode(ConfMan.get("gfx_mode").c_str());
system.initSize(320, 200);
system.launcherInitSize(640, 400);//ResidualVM specific
system.launcherInitSize(640, 400); //ResidualVM specific
if (ConfMan.hasKey("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";
}
}
if (settings.contains("disable-display")) {
ConfMan.setInt("disable-display", 1, Common::ConfigManager::kTransientDomain);
}
setupGraphics(system);
// 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
// our event recorder, we might do this at another place. Or even change
// the whole API for that ;-).
g_eventRec.init();
g_eventRec.RegisterEventSource();
// Now as the event manager is created, setup the keymapper
setupKeymapper(system);
@ -449,6 +454,21 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
// to save memory
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
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
g_system->getEventManager()->resetQuit();
#endif
#ifdef ENABLE_EVENTRECORDER
if (g_eventRec.checkForContinueGame()) {
continue;
}
#endif
// Discard any command line options. It's unlikely that the user
// 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();
Common::ConfigManager::destroy();
Common::DebugManager::destroy();
Common::EventRecorder::destroy();
GUI::EventRecorder::destroy();
Common::SearchManager::destroy();
#ifdef USE_TRANSLATION
Common::TranslationManager::destroy();

View file

@ -124,7 +124,7 @@ public:
LINK_PLUGIN(MT32)
#endif
LINK_PLUGIN(ADLIB)
//ResidualVM: disabled belows
//ResidualVM: disabled belows:
// LINK_PLUGIN(PCSPK)
// LINK_PLUGIN(PCJR)
// LINK_PLUGIN(CMS)
@ -133,8 +133,8 @@ public:
#endif
#ifndef DISABLE_SID
// LINK_PLUGIN(C64)
// LINK_PLUGIN(AMIGA)
#endif
// LINK_PLUGIN(AMIGA)
// LINK_PLUGIN(APPLEIIGS)
// LINK_PLUGIN(TOWNS)
// LINK_PLUGIN(PC98)

View file

@ -26,7 +26,7 @@
#include "common/array.h"
#include "common/fs.h"
#include "common/str.h"
//#include "backends/plugins/elf/version.h"
//#include "backends/plugins/elf/version.h" // ResidualVM specific
/**

View file

@ -24,7 +24,7 @@
namespace Common {
EventDispatcher::EventDispatcher() : _mapper(0) {
EventDispatcher::EventDispatcher() : _autoFreeMapper(false), _mapper(0) {
}
EventDispatcher::~EventDispatcher() {
@ -38,7 +38,9 @@ EventDispatcher::~EventDispatcher() {
delete i->observer;
}
delete _mapper;
if (_autoFreeMapper) {
delete _mapper;
}
_mapper = 0;
}
@ -68,11 +70,15 @@ void EventDispatcher::dispatch() {
}
}
void EventDispatcher::registerMapper(EventMapper *mapper) {
delete _mapper;
void EventDispatcher::registerMapper(EventMapper *mapper, bool autoFree) {
if (_autoFreeMapper) {
delete _mapper;
}
_mapper = mapper;
_autoFreeMapper = autoFree;
}
void EventDispatcher::registerSource(EventSource *source, bool autoFree) {
SourceEntry newEntry;

View file

@ -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

View file

@ -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

View file

@ -118,7 +118,7 @@ void SearchSet::addDirectory(const String &name, const FSNode &dir, int 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;
if (!directory.getChildren(subDirs))
return;
@ -161,9 +161,9 @@ void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPa
}
if (nextPattern.empty())
addDirectory(name, *i, priority);
addDirectory(name, *i, priority, depth, flat);
else
addSubDirectoriesMatching(*i, nextPattern, ignoreCase, priority);
addSubDirectoriesMatching(*i, nextPattern, ignoreCase, priority, depth, flat);
}
}
}

View file

@ -184,8 +184,8 @@ public:
* 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!
*/
void addSubDirectoryMatching(const FSNode &directory, const String &caselessName, int priority = 0) {
addSubDirectoriesMatching(directory, caselessName, true, priority);
void addSubDirectoryMatching(const FSNode &directory, const String &caselessName, int priority = 0, int depth = 1, bool flat = false) {
addSubDirectoriesMatching(directory, caselessName, true, priority, depth, flat);
}
/**
@ -208,7 +208,7 @@ public:
*
* @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.

View file

@ -61,6 +61,6 @@ SeekableReadStream *wrapBufferedSeekableReadStream(SeekableReadStream *parentStr
*/
WriteStream *wrapBufferedWriteStream(WriteStream *parentStream, uint32 bufSize);
} // End of namespace Common
} // End of namespace Common
#endif

42
common/c++11-compat.h Normal file
View 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

View file

@ -226,6 +226,15 @@ bool ConfigFile::saveToStream(WriteStream &stream) {
return !stream.err();
}
void ConfigFile::addSection(const String &section) {
Section *s = getSection(section);
if (s)
return;
Section newSection;
newSection.name = section;
_sections.push_back(newSection);
}
void ConfigFile::removeSection(const String &section) {
assert(isValidName(section));
@ -380,4 +389,4 @@ void ConfigFile::Section::removeKey(const String &key) {
}
}
} // End of namespace Common
} // End of namespace Common

View file

@ -106,6 +106,7 @@ public:
bool saveToStream(WriteStream &stream);
bool hasSection(const String &section) const;
void addSection(const String &section);
void removeSection(const String &section);
void renameSection(const String &oldName, const String &newName);
@ -134,6 +135,6 @@ private:
*/
} // End of namespace Common
} // End of namespace Common
#endif

View file

@ -175,7 +175,7 @@ private:
String _filename;
};
} // End of namespace Common
} // End of namespace Common
/** Shortcut for accessing the configuration manager. */
#define ConfMan Common::ConfigManager::instance()

View file

@ -34,10 +34,10 @@ CosineTable::CosineTable(int bitPrecision) {
int m = 1 << _bitPrecision;
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,
// followed by its reverse
// Table contains cos(2*pi*i/m) for 0<=i<m/4,
// followed by 3m/4<=i<m
for (int i = 0; i <= m / 4; i++)
_table[i] = cos(i * freq);

View file

@ -36,7 +36,14 @@ public:
~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; }

View file

@ -124,6 +124,6 @@ private:
/** Shortcut for accessing the debug manager. */
#define DebugMan Common::DebugManager::instance()
} // End of namespace Common
} // End of namespace Common
#endif

View file

@ -102,7 +102,7 @@ bool DebugManager::isDebugChannelEnabled(uint32 channel) {
return (gDebugChannelsEnabled & channel) != 0;
}
} // End of namespace Common
} // End of namespace Common
#ifndef DISABLE_TEXT_CONSOLE

View file

@ -117,5 +117,9 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3);
*/
extern int gDebugLevel;
//Global constant for EventRecorder debug channel
enum GlobalDebugLevels {
kDebugLevelEventRec = 1 << 30
};
#endif

View file

@ -295,11 +295,14 @@ public:
* to the EventDispatcher, thus it will be deleted
* with "delete", when EventDispatcher is destroyed.
*
* Note there is only one mapper per EventDispatcher
* possible, thus when this method is called twice,
* the former mapper will be destroied.
* @param autoFree Destroy previous mapper [default]
* Normally we allow only one event mapper to exists,
* 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.
@ -333,6 +336,7 @@ public:
*/
void unregisterObserver(EventObserver *obs);
private:
bool _autoFreeMapper;
EventMapper *_mapper;
struct Entry {

Some files were not shown because too many files have changed in this diff Show more