ALL: synced with ScummVM
This commit is contained in:
parent
1f3ad19a38
commit
068ff94d20
228 changed files with 11689 additions and 4232 deletions
9
Makefile
9
Makefile
|
@ -32,8 +32,10 @@ ifeq "$(HAVE_GCC)" "1"
|
|||
# being helpful.
|
||||
#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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(); }
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ¤tSettings;
|
||||
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
|
||||
|
|
393
audio/softsynth/mt32/BReverbModel.cpp
Normal file
393
audio/softsynth/mt32/BReverbModel.cpp
Normal file
|
@ -0,0 +1,393 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mt32emu.h"
|
||||
|
||||
#if MT32EMU_USE_REVERBMODEL == 2
|
||||
|
||||
#include "BReverbModel.h"
|
||||
|
||||
// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
|
||||
// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
|
||||
// and followed by three parallel comb filters
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
|
||||
// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
|
||||
// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
|
||||
// so we can simply increase the input buffer size.
|
||||
static const Bit32u PROCESS_DELAY = 1;
|
||||
|
||||
static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
|
||||
static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
|
||||
|
||||
// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
|
||||
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
|
||||
|
||||
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
|
||||
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb.
|
||||
static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
|
||||
static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
|
||||
static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
|
||||
static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
|
||||
static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
|
||||
static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit32u MODE_0_LPF_AMP = 0x60;
|
||||
|
||||
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
|
||||
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
|
||||
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
|
||||
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
|
||||
static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
|
||||
static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
|
||||
static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit32u MODE_1_LPF_AMP = 0x60;
|
||||
|
||||
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
|
||||
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
|
||||
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
|
||||
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
|
||||
static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
|
||||
static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
|
||||
static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
|
||||
static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit32u MODE_2_LPF_AMP = 0x80;
|
||||
|
||||
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
|
||||
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
|
||||
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
|
||||
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
|
||||
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
|
||||
static const Bit32u MODE_3_COMB_FACTOR[] = {0x68};
|
||||
static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
|
||||
static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
|
||||
static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
|
||||
|
||||
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
|
||||
|
||||
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
|
||||
|
||||
// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
|
||||
// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation.
|
||||
static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) {
|
||||
Bit8u mask = 0x80;
|
||||
Bit32s res = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0;
|
||||
a >>= 1;
|
||||
res += (mask & addMask) > 0 ? a + carry : 0;
|
||||
mask >>= 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) {
|
||||
buffer = new Bit16s[size];
|
||||
}
|
||||
|
||||
RingBuffer::~RingBuffer() {
|
||||
delete[] buffer;
|
||||
buffer = NULL;
|
||||
}
|
||||
|
||||
Bit32s RingBuffer::next() {
|
||||
if (++index >= size) {
|
||||
index = 0;
|
||||
}
|
||||
return buffer[index];
|
||||
}
|
||||
|
||||
bool RingBuffer::isEmpty() const {
|
||||
if (buffer == NULL) return true;
|
||||
|
||||
Bit16s *buf = buffer;
|
||||
for (Bit32u i = 0; i < size; i++) {
|
||||
if (*buf < -8 || *buf > 8) return false;
|
||||
buf++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RingBuffer::mute() {
|
||||
Bit16s *buf = buffer;
|
||||
for (Bit32u i = 0; i < size; i++) {
|
||||
*buf++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
|
||||
|
||||
Bit32s AllpassFilter::process(const Bit32s in) {
|
||||
// This model corresponds to the allpass filter implementation of the real CM-32L device
|
||||
// found from sample analysis
|
||||
|
||||
Bit16s bufferOut = next();
|
||||
|
||||
// store input - feedback / 2
|
||||
buffer[index] = in - (bufferOut >> 1);
|
||||
|
||||
// return buffer output + feedforward / 2
|
||||
return bufferOut + (buffer[index] >> 1);
|
||||
}
|
||||
|
||||
CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {}
|
||||
|
||||
void CombFilter::process(const Bit32s in) {
|
||||
// This model corresponds to the comb filter implementation of the real CM-32L device
|
||||
|
||||
// the previously stored value
|
||||
Bit32s last = buffer[index];
|
||||
|
||||
// prepare input + feedback
|
||||
Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */);
|
||||
|
||||
// store input + feedback processed by a low-pass filter
|
||||
buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn;
|
||||
}
|
||||
|
||||
Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const {
|
||||
return buffer[(size + index - outIndex) % size];
|
||||
}
|
||||
|
||||
void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) {
|
||||
feedbackFactor = useFeedbackFactor;
|
||||
}
|
||||
|
||||
DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp)
|
||||
: CombFilter(useSize, useFilterFactor), amp(useAmp) {}
|
||||
|
||||
void DelayWithLowPassFilter::process(const Bit32s in) {
|
||||
// the previously stored value
|
||||
Bit32s last = buffer[index];
|
||||
|
||||
// move to the next index
|
||||
next();
|
||||
|
||||
// low-pass filter process
|
||||
Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in;
|
||||
|
||||
// store lpfOut multiplied by LPF amp factor
|
||||
buffer[index] = weirdMul(lpfOut, amp, 0xFF);
|
||||
}
|
||||
|
||||
TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {}
|
||||
|
||||
void TapDelayCombFilter::process(const Bit32s in) {
|
||||
// the previously stored value
|
||||
Bit32s last = buffer[index];
|
||||
|
||||
// move to the next index
|
||||
next();
|
||||
|
||||
// prepare input + feedback
|
||||
// Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
|
||||
Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0);
|
||||
|
||||
// store input + feedback processed by a low-pass filter
|
||||
buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn;
|
||||
}
|
||||
|
||||
Bit32s TapDelayCombFilter::getLeftOutput() const {
|
||||
return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||
}
|
||||
|
||||
Bit32s TapDelayCombFilter::getRightOutput() const {
|
||||
return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||
}
|
||||
|
||||
void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
|
||||
outL = useOutL;
|
||||
outR = useOutR;
|
||||
}
|
||||
|
||||
BReverbModel::BReverbModel(const ReverbMode mode)
|
||||
: allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {}
|
||||
|
||||
BReverbModel::~BReverbModel() {
|
||||
close();
|
||||
}
|
||||
|
||||
void BReverbModel::open() {
|
||||
if (currentSettings.numberOfAllpasses > 0) {
|
||||
allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses];
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
|
||||
}
|
||||
}
|
||||
combs = new CombFilter*[currentSettings.numberOfCombs];
|
||||
if (tapDelayMode) {
|
||||
*combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors);
|
||||
} else {
|
||||
combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
|
||||
for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
|
||||
}
|
||||
}
|
||||
mute();
|
||||
}
|
||||
|
||||
void BReverbModel::close() {
|
||||
if (allpasses != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
if (allpasses[i] != NULL) {
|
||||
delete allpasses[i];
|
||||
allpasses[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] allpasses;
|
||||
allpasses = NULL;
|
||||
}
|
||||
if (combs != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
if (combs[i] != NULL) {
|
||||
delete combs[i];
|
||||
combs[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] combs;
|
||||
combs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void BReverbModel::mute() {
|
||||
if (allpasses != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
allpasses[i]->mute();
|
||||
}
|
||||
}
|
||||
if (combs != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i]->mute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BReverbModel::setParameters(Bit8u time, Bit8u level) {
|
||||
if (combs == NULL) return;
|
||||
level &= 7;
|
||||
time &= 7;
|
||||
if (tapDelayMode) {
|
||||
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||
comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
|
||||
comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
|
||||
} else {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
|
||||
}
|
||||
}
|
||||
if (time == 0 && level == 0) {
|
||||
dryAmp = wetLevel = 0;
|
||||
} else {
|
||||
dryAmp = currentSettings.dryAmps[level];
|
||||
wetLevel = currentSettings.wetLevels[level];
|
||||
}
|
||||
}
|
||||
|
||||
bool BReverbModel::isActive() const {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
if (!allpasses[i]->isEmpty()) return true;
|
||||
}
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
if (!combs[i]->isEmpty()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||
Bit32s dry, link, outL1, outR1;
|
||||
|
||||
for (unsigned long i = 0; i < numSamples; i++) {
|
||||
if (tapDelayMode) {
|
||||
dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f);
|
||||
} else {
|
||||
dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2;
|
||||
}
|
||||
|
||||
// Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
|
||||
dry = weirdMul(dry, dryAmp, 0xFF);
|
||||
|
||||
if (tapDelayMode) {
|
||||
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||
comb->process(dry);
|
||||
*outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f;
|
||||
*outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f;
|
||||
} else {
|
||||
// Get the last stored sample before processing in order not to loose it
|
||||
link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
|
||||
|
||||
// Entrance LPF. Note, comb.process() differs a bit here.
|
||||
combs[0]->process(dry);
|
||||
|
||||
// This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
|
||||
link = link - 1;
|
||||
link = allpasses[0]->process(link);
|
||||
link = allpasses[1]->process(link);
|
||||
link = allpasses[2]->process(link);
|
||||
|
||||
// If the output position is equal to the comb size, get it now in order not to loose it
|
||||
outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
|
||||
outL1 += outL1 >> 1;
|
||||
|
||||
combs[1]->process(link);
|
||||
combs[2]->process(link);
|
||||
combs[3]->process(link);
|
||||
|
||||
link = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
|
||||
link += link >> 1;
|
||||
link += outL1;
|
||||
link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
|
||||
*outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
|
||||
|
||||
outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
|
||||
outR1 += outR1 >> 1;
|
||||
link = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
|
||||
link += link >> 1;
|
||||
link += outR1;
|
||||
link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
|
||||
*outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
|
||||
}
|
||||
|
||||
inLeft++;
|
||||
inRight++;
|
||||
outLeft++;
|
||||
outRight++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
112
audio/softsynth/mt32/BReverbModel.h
Normal file
112
audio/softsynth/mt32/BReverbModel.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_B_REVERB_MODEL_H
|
||||
#define MT32EMU_B_REVERB_MODEL_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
struct BReverbSettings {
|
||||
const Bit32u numberOfAllpasses;
|
||||
const Bit32u * const allpassSizes;
|
||||
const Bit32u numberOfCombs;
|
||||
const Bit32u * const combSizes;
|
||||
const Bit32u * const outLPositions;
|
||||
const Bit32u * const outRPositions;
|
||||
const Bit32u * const filterFactors;
|
||||
const Bit32u * const feedbackFactors;
|
||||
const Bit32u * const dryAmps;
|
||||
const Bit32u * const wetLevels;
|
||||
const Bit32u lpfAmp;
|
||||
};
|
||||
|
||||
class RingBuffer {
|
||||
protected:
|
||||
Bit16s *buffer;
|
||||
const Bit32u size;
|
||||
Bit32u index;
|
||||
|
||||
public:
|
||||
RingBuffer(const Bit32u size);
|
||||
virtual ~RingBuffer();
|
||||
Bit32s next();
|
||||
bool isEmpty() const;
|
||||
void mute();
|
||||
};
|
||||
|
||||
class AllpassFilter : public RingBuffer {
|
||||
public:
|
||||
AllpassFilter(const Bit32u size);
|
||||
Bit32s process(const Bit32s in);
|
||||
};
|
||||
|
||||
class CombFilter : public RingBuffer {
|
||||
protected:
|
||||
const Bit32u filterFactor;
|
||||
Bit32u feedbackFactor;
|
||||
|
||||
public:
|
||||
CombFilter(const Bit32u size, const Bit32u useFilterFactor);
|
||||
virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure
|
||||
Bit32s getOutputAt(const Bit32u outIndex) const;
|
||||
void setFeedbackFactor(const Bit32u useFeedbackFactor);
|
||||
};
|
||||
|
||||
class DelayWithLowPassFilter : public CombFilter {
|
||||
Bit32u amp;
|
||||
|
||||
public:
|
||||
DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp);
|
||||
void process(const Bit32s in);
|
||||
void setFeedbackFactor(const Bit32u) {}
|
||||
};
|
||||
|
||||
class TapDelayCombFilter : public CombFilter {
|
||||
Bit32u outL;
|
||||
Bit32u outR;
|
||||
|
||||
public:
|
||||
TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor);
|
||||
void process(const Bit32s in);
|
||||
Bit32s getLeftOutput() const;
|
||||
Bit32s getRightOutput() const;
|
||||
void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR);
|
||||
};
|
||||
|
||||
class BReverbModel : public ReverbModel {
|
||||
AllpassFilter **allpasses;
|
||||
CombFilter **combs;
|
||||
|
||||
const BReverbSettings ¤tSettings;
|
||||
const bool tapDelayMode;
|
||||
Bit32u dryAmp;
|
||||
Bit32u wetLevel;
|
||||
void mute();
|
||||
|
||||
public:
|
||||
BReverbModel(const ReverbMode mode);
|
||||
~BReverbModel();
|
||||
void open();
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
bool isActive() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,5 +1,5 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
418
audio/softsynth/mt32/LA32WaveGenerator.cpp
Normal file
418
audio/softsynth/mt32/LA32WaveGenerator.cpp
Normal file
|
@ -0,0 +1,418 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cmath>
|
||||
#include "mt32emu.h"
|
||||
#include "mmath.h"
|
||||
#include "LA32WaveGenerator.h"
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 0
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;
|
||||
static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18;
|
||||
static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18;
|
||||
static const Bit32u MAX_CUTOFF_VALUE = 240 << 18;
|
||||
static const LogSample SILENCE = {65535, LogSample::POSITIVE};
|
||||
|
||||
Bit16u LA32Utilites::interpolateExp(const Bit16u fract) {
|
||||
Bit16u expTabIndex = fract >> 3;
|
||||
Bit16u extraBits = ~fract & 7;
|
||||
Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex];
|
||||
Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]);
|
||||
return expTabEntry2 + (((expTabEntry1 - expTabEntry2) * extraBits) >> 3);
|
||||
}
|
||||
|
||||
Bit16s LA32Utilites::unlog(const LogSample &logSample) {
|
||||
//Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f);
|
||||
Bit32u intLogValue = logSample.logValue >> 12;
|
||||
Bit16u fracLogValue = logSample.logValue & 4095;
|
||||
Bit16s sample = interpolateExp(fracLogValue) >> intLogValue;
|
||||
return logSample.sign == LogSample::POSITIVE ? sample : -sample;
|
||||
}
|
||||
|
||||
void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) {
|
||||
Bit32u logSampleValue = logSample1.logValue + logSample2.logValue;
|
||||
logSample1.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getSampleStep() {
|
||||
// sampleStep = EXP2F(pitch / 4096.0f + 4.0f)
|
||||
Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||
sampleStep <<= pitch >> 12;
|
||||
sampleStep >>= 8;
|
||||
sampleStep &= ~1;
|
||||
return sampleStep;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) {
|
||||
// resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f);
|
||||
Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095);
|
||||
resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12;
|
||||
return resonanceWaveLengthFactor;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) {
|
||||
// Ratio of positive segment to wave length
|
||||
Bit32u effectivePulseWidthValue = 0;
|
||||
if (pulseWidth > 128) {
|
||||
effectivePulseWidthValue = (pulseWidth - 128) << 6;
|
||||
}
|
||||
|
||||
Bit32u highLinearLength = 0;
|
||||
// highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (effectivePulseWidthValue < effectiveCutoffValue) {
|
||||
Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue;
|
||||
highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095);
|
||||
highLinearLength <<= 7 + (expArg >> 12);
|
||||
highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
}
|
||||
return highLinearLength;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) {
|
||||
// Assuming 12-bit multiplication used here
|
||||
squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4);
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (squareWavePosition < highLinearLength) {
|
||||
phase = POSITIVE_LINEAR_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= highLinearLength;
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = POSITIVE_FALLING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
resonanceSinePosition = squareWavePosition;
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = NEGATIVE_FALLING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (squareWavePosition < lowLinearLength) {
|
||||
phase = NEGATIVE_LINEAR_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= lowLinearLength;
|
||||
phase = NEGATIVE_RISING_SINE_SEGMENT;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::advancePosition() {
|
||||
wavePosition += getSampleStep();
|
||||
wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
|
||||
Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0;
|
||||
Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue);
|
||||
Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue);
|
||||
Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength;
|
||||
computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor);
|
||||
|
||||
// resonancePhase computation hack
|
||||
*(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSquareWaveLogSample() {
|
||||
Bit32u logSampleValue;
|
||||
switch (phase) {
|
||||
case POSITIVE_RISING_SINE_SEGMENT:
|
||||
case NEGATIVE_FALLING_SINE_SEGMENT:
|
||||
logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511];
|
||||
break;
|
||||
case POSITIVE_FALLING_SINE_SEGMENT:
|
||||
case NEGATIVE_RISING_SINE_SEGMENT:
|
||||
logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511];
|
||||
break;
|
||||
case POSITIVE_LINEAR_SEGMENT:
|
||||
case NEGATIVE_LINEAR_SEGMENT:
|
||||
default:
|
||||
logSampleValue = 0;
|
||||
break;
|
||||
}
|
||||
logSampleValue <<= 2;
|
||||
logSampleValue += amp >> 10;
|
||||
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||
logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9;
|
||||
}
|
||||
|
||||
squareLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
|
||||
Bit32u logSampleValue;
|
||||
if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) {
|
||||
logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511];
|
||||
} else {
|
||||
logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511];
|
||||
}
|
||||
logSampleValue <<= 2;
|
||||
logSampleValue += amp >> 10;
|
||||
|
||||
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||
Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1;
|
||||
// Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
|
||||
logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
|
||||
|
||||
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||
if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
|
||||
// The window is synchronous sine here
|
||||
logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
|
||||
} else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) {
|
||||
// The window is synchronous square sine here
|
||||
logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3;
|
||||
}
|
||||
|
||||
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||
// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
|
||||
logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
|
||||
} else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
|
||||
// For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
|
||||
Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13;
|
||||
logSampleValue += Tables::getInstance().logsin9[sineIx] << 2;
|
||||
}
|
||||
|
||||
// After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures
|
||||
logSampleValue -= 1 << 12;
|
||||
|
||||
resonanceLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const {
|
||||
Bit32u sawtoothCosinePosition = wavePosition + (1 << 18);
|
||||
if ((sawtoothCosinePosition & (1 << 18)) > 0) {
|
||||
logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511];
|
||||
} else {
|
||||
logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511];
|
||||
}
|
||||
logSample.logValue <<= 2;
|
||||
logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const {
|
||||
Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1;
|
||||
logSampleValue += amp >> 10;
|
||||
logSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextPCMWaveLogSamples() {
|
||||
// This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc.
|
||||
// The most probable cause is the factor in the interpolation formula is one bit less
|
||||
// accurate than the sample position counter
|
||||
pcmInterpolationFactor = (wavePosition & 255) >> 1;
|
||||
Bit32u pcmWaveTableIx = wavePosition >> 8;
|
||||
pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
if (pcmWaveInterpolated) {
|
||||
pcmWaveTableIx++;
|
||||
if (pcmWaveTableIx < pcmWaveLength) {
|
||||
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
} else {
|
||||
if (pcmWaveLooped) {
|
||||
pcmWaveTableIx -= pcmWaveLength;
|
||||
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
} else {
|
||||
secondPCMLogSample = SILENCE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
secondPCMLogSample = SILENCE;
|
||||
}
|
||||
// pcmSampleStep = (Bit32u)EXP2F(pitch / 4096.0f + 3.0f);
|
||||
Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||
pcmSampleStep <<= pitch >> 12;
|
||||
// Seeing the actual lengths of the PCM wave for pitches 00..12,
|
||||
// the pcmPosition counter can be assumed to have 8-bit fractions
|
||||
pcmSampleStep >>= 9;
|
||||
wavePosition += pcmSampleStep;
|
||||
if (wavePosition >= (pcmWaveLength << 8)) {
|
||||
if (pcmWaveLooped) {
|
||||
wavePosition -= pcmWaveLength << 8;
|
||||
} else {
|
||||
deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
|
||||
sawtoothWaveform = useSawtoothWaveform;
|
||||
pulseWidth = usePulseWidth;
|
||||
resonance = useResonance;
|
||||
|
||||
wavePosition = 0;
|
||||
|
||||
squareWavePosition = 0;
|
||||
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||
|
||||
resonanceSinePosition = 0;
|
||||
resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT;
|
||||
resonanceAmpSubtraction = (32 - resonance) << 10;
|
||||
resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
|
||||
|
||||
pcmWaveAddress = NULL;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
|
||||
pcmWaveAddress = usePCMWaveAddress;
|
||||
pcmWaveLength = usePCMWaveLength;
|
||||
pcmWaveLooped = usePCMWaveLooped;
|
||||
pcmWaveInterpolated = usePCMWaveInterpolated;
|
||||
|
||||
wavePosition = 0;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
amp = useAmp;
|
||||
pitch = usePitch;
|
||||
|
||||
if (isPCMWave()) {
|
||||
generateNextPCMWaveLogSamples();
|
||||
return;
|
||||
}
|
||||
|
||||
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||
// More research is needed to be sure that this is correct, however.
|
||||
cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal;
|
||||
|
||||
generateNextSquareWaveLogSample();
|
||||
generateNextResonanceWaveLogSample();
|
||||
if (sawtoothWaveform) {
|
||||
LogSample cosineLogSample;
|
||||
generateNextSawtoothCosineLogSample(cosineLogSample);
|
||||
LA32Utilites::addLogSamples(squareLogSample, cosineLogSample);
|
||||
LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample);
|
||||
}
|
||||
advancePosition();
|
||||
}
|
||||
|
||||
LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const {
|
||||
if (!isActive()) {
|
||||
return SILENCE;
|
||||
}
|
||||
if (isPCMWave()) {
|
||||
return first ? firstPCMLogSample : secondPCMLogSample;
|
||||
}
|
||||
return first ? squareLogSample : resonanceLogSample;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::deactivate() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isActive() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isPCMWave() const {
|
||||
return pcmWaveAddress != NULL;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
|
||||
return pcmInterpolationFactor;
|
||||
}
|
||||
|
||||
void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) {
|
||||
ringModulated = useRingModulated;
|
||||
mixed = useMixed;
|
||||
}
|
||||
|
||||
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
} else {
|
||||
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||
} else {
|
||||
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||
if (useMaster == MASTER) {
|
||||
master.generateNextSample(amp, pitch, cutoff);
|
||||
} else {
|
||||
slave.generateNextSample(amp, pitch, cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) {
|
||||
if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) {
|
||||
return 0;
|
||||
}
|
||||
LogSample firstLogSample = wg.getOutputLogSample(true);
|
||||
LogSample secondLogSample = wg.getOutputLogSample(false);
|
||||
if (ringModulatingLogSample != NULL) {
|
||||
LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample);
|
||||
LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample);
|
||||
}
|
||||
Bit16s firstSample = LA32Utilites::unlog(firstLogSample);
|
||||
Bit16s secondSample = LA32Utilites::unlog(secondLogSample);
|
||||
if (wg.isPCMWave()) {
|
||||
return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7));
|
||||
}
|
||||
return firstSample + secondSample;
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::nextOutSample() {
|
||||
if (ringModulated) {
|
||||
LogSample slaveFirstLogSample = slave.getOutputLogSample(true);
|
||||
LogSample slaveSecondLogSample = slave.getOutputLogSample(false);
|
||||
Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample);
|
||||
if (!slave.isPCMWave()) {
|
||||
sample += unlogAndMixWGOutput(master, &slaveSecondLogSample);
|
||||
}
|
||||
if (mixed) {
|
||||
sample += unlogAndMixWGOutput(master, NULL);
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL);
|
||||
}
|
||||
|
||||
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||
if (useMaster == MASTER) {
|
||||
master.deactivate();
|
||||
} else {
|
||||
slave.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 0
|
246
audio/softsynth/mt32/LA32WaveGenerator.h
Normal file
246
audio/softsynth/mt32/LA32WaveGenerator.h
Normal file
|
@ -0,0 +1,246 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 0
|
||||
|
||||
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
#define MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
/**
|
||||
* LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions
|
||||
* It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these:
|
||||
* - interpolation of exponent table (obvious, a delta value has 4 bits)
|
||||
* - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion)
|
||||
* - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip)
|
||||
* and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss
|
||||
* Subtraction is supposed to be replaced by simple inversion
|
||||
* As the logarithmic sine is always negative, all the logarithmic values are treated as decrements
|
||||
*/
|
||||
struct LogSample {
|
||||
// 16-bit fixed point value, includes 12-bit fractional part
|
||||
// 4-bit integer part allows to present any 16-bit sample in the log-space
|
||||
// Obviously, the log value doesn't contain the sign of the resulting sample
|
||||
Bit16u logValue;
|
||||
enum {
|
||||
POSITIVE,
|
||||
NEGATIVE
|
||||
} sign;
|
||||
};
|
||||
|
||||
class LA32Utilites {
|
||||
public:
|
||||
static Bit16u interpolateExp(const Bit16u fract);
|
||||
static Bit16s unlog(const LogSample &logSample);
|
||||
static void addLogSamples(LogSample &logSample1, const LogSample &logSample2);
|
||||
};
|
||||
|
||||
/**
|
||||
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||
* The output square wave is created by adding high / low linear segments in-between
|
||||
* the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
|
||||
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||
*/
|
||||
class LA32WaveGenerator {
|
||||
//***************************************************************************
|
||||
// The local copy of partial parameters below
|
||||
//***************************************************************************
|
||||
|
||||
bool active;
|
||||
|
||||
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||
bool sawtoothWaveform;
|
||||
|
||||
// Logarithmic amp of the wave generator
|
||||
Bit32u amp;
|
||||
|
||||
// Logarithmic frequency of the resulting wave
|
||||
Bit16u pitch;
|
||||
|
||||
// Values in range [1..31]
|
||||
// Value 1 correspong to the minimum resonance
|
||||
Bit8u resonance;
|
||||
|
||||
// Processed value in range [0..255]
|
||||
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||
Bit8u pulseWidth;
|
||||
|
||||
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||
Bit32u cutoffVal;
|
||||
|
||||
// Logarithmic PCM sample start address
|
||||
const Bit16s *pcmWaveAddress;
|
||||
|
||||
// Logarithmic PCM sample length
|
||||
Bit32u pcmWaveLength;
|
||||
|
||||
// true for looped logarithmic PCM samples
|
||||
bool pcmWaveLooped;
|
||||
|
||||
// false for slave PCM partials in the structures with the ring modulation
|
||||
bool pcmWaveInterpolated;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal variables below
|
||||
//***************************************************************************
|
||||
|
||||
// Relative position within either the synth wave or the PCM sampled wave
|
||||
// 0 - start of the positive rising sine segment of the square wave or start of the PCM sample
|
||||
// 1048576 (2^20) - end of the negative rising sine segment of the square wave
|
||||
// For PCM waves, the address of the currently playing sample equals (wavePosition / 256)
|
||||
Bit32u wavePosition;
|
||||
|
||||
// Relative position within a square wave phase:
|
||||
// 0 - start of the phase
|
||||
// 262144 (2^18) - end of a sine phase in the square wave
|
||||
Bit32u squareWavePosition;
|
||||
|
||||
// Relative position within the positive or negative wave segment:
|
||||
// 0 - start of the corresponding positive or negative segment of the square wave
|
||||
// 262144 (2^18) - corresponds to end of the first sine phase in the square wave
|
||||
// The same increment sampleStep is used to indicate the current position
|
||||
// since the length of the resonance wave is always equal to four square wave sine segments.
|
||||
Bit32u resonanceSinePosition;
|
||||
|
||||
// The amp of the resonance sine wave grows with the resonance value
|
||||
// As the resonance value cannot change while the partial is active, it is initialised once
|
||||
Bit32u resonanceAmpSubtraction;
|
||||
|
||||
// The decay speed of resonance sine wave, depends on the resonance value
|
||||
Bit32u resAmpDecayFactor;
|
||||
|
||||
// Fractional part of the pcmPosition
|
||||
Bit32u pcmInterpolationFactor;
|
||||
|
||||
// Current phase of the square wave
|
||||
enum {
|
||||
POSITIVE_RISING_SINE_SEGMENT,
|
||||
POSITIVE_LINEAR_SEGMENT,
|
||||
POSITIVE_FALLING_SINE_SEGMENT,
|
||||
NEGATIVE_FALLING_SINE_SEGMENT,
|
||||
NEGATIVE_LINEAR_SEGMENT,
|
||||
NEGATIVE_RISING_SINE_SEGMENT
|
||||
} phase;
|
||||
|
||||
// Current phase of the resonance wave
|
||||
enum {
|
||||
POSITIVE_RISING_RESONANCE_SINE_SEGMENT,
|
||||
POSITIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||
NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||
NEGATIVE_RISING_RESONANCE_SINE_SEGMENT
|
||||
} resonancePhase;
|
||||
|
||||
// Resulting log-space samples of the square and resonance waves
|
||||
LogSample squareLogSample;
|
||||
LogSample resonanceLogSample;
|
||||
|
||||
// Processed neighbour log-space samples of the PCM wave
|
||||
LogSample firstPCMLogSample;
|
||||
LogSample secondPCMLogSample;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal methods below
|
||||
//***************************************************************************
|
||||
|
||||
Bit32u getSampleStep();
|
||||
Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue);
|
||||
Bit32u getHighLinearLength(Bit32u effectiveCutoffValue);
|
||||
|
||||
void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor);
|
||||
void advancePosition();
|
||||
|
||||
void generateNextSquareWaveLogSample();
|
||||
void generateNextResonanceWaveLogSample();
|
||||
void generateNextSawtoothCosineLogSample(LogSample &logSample) const;
|
||||
|
||||
void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const;
|
||||
void generateNextPCMWaveLogSamples();
|
||||
|
||||
public:
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards
|
||||
LogSample getOutputLogSample(const bool first) const;
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate();
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive() const;
|
||||
|
||||
// Return true if the WG engine generates PCM wave samples
|
||||
bool isPCMWave() const;
|
||||
|
||||
// Return current PCM interpolation factor
|
||||
Bit32u getPCMInterpolationFactor() const;
|
||||
};
|
||||
|
||||
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||
class LA32PartialPair {
|
||||
LA32WaveGenerator master;
|
||||
LA32WaveGenerator slave;
|
||||
bool ringModulated;
|
||||
bool mixed;
|
||||
|
||||
static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample);
|
||||
|
||||
public:
|
||||
enum PairType {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||
// ringModulated should be set to true for the structures with ring modulation
|
||||
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||
void init(const bool ringModulated, const bool mixed);
|
||||
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Perform mixing / ring modulation and return the result
|
||||
Bit16s nextOutSample();
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate(const PairType master);
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive(const PairType master) const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 0
|
347
audio/softsynth/mt32/LegacyWaveGenerator.cpp
Normal file
347
audio/softsynth/mt32/LegacyWaveGenerator.cpp
Normal file
|
@ -0,0 +1,347 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cmath>
|
||||
#include "mt32emu.h"
|
||||
#include "mmath.h"
|
||||
#include "LegacyWaveGenerator.h"
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 1
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const float MIDDLE_CUTOFF_VALUE = 128.0f;
|
||||
static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f;
|
||||
static const float MAX_CUTOFF_VALUE = 240.0f;
|
||||
|
||||
float LA32WaveGenerator::getPCMSample(unsigned int position) {
|
||||
if (position >= pcmWaveLength) {
|
||||
if (!pcmWaveLooped) {
|
||||
return 0;
|
||||
}
|
||||
position = position % pcmWaveLength;
|
||||
}
|
||||
Bit16s pcmSample = pcmWaveAddress[position];
|
||||
float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f);
|
||||
return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
this->sawtoothWaveform = sawtoothWaveform;
|
||||
this->pulseWidth = pulseWidth;
|
||||
this->resonance = resonance;
|
||||
|
||||
wavePos = 0.0f;
|
||||
lastFreq = 0.0f;
|
||||
|
||||
pcmWaveAddress = NULL;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) {
|
||||
this->pcmWaveAddress = pcmWaveAddress;
|
||||
this->pcmWaveLength = pcmWaveLength;
|
||||
this->pcmWaveLooped = pcmWaveLooped;
|
||||
this->pcmWaveInterpolated = pcmWaveInterpolated;
|
||||
|
||||
pcmPosition = 0.0f;
|
||||
active = true;
|
||||
}
|
||||
|
||||
float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) {
|
||||
if (!active) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
this->amp = amp;
|
||||
this->pitch = pitch;
|
||||
|
||||
float sample = 0.0f;
|
||||
|
||||
// SEMI-CONFIRMED: From sample analysis:
|
||||
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
|
||||
// This gives results within +/- 2 at the output (before any DAC bitshifting)
|
||||
// when sustaining at levels 156 - 255 with no modifiers.
|
||||
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
|
||||
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
|
||||
// positive amps, so negative still needs to be explored, as well as lower levels.
|
||||
//
|
||||
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
|
||||
|
||||
float amp = EXP2F(ampVal / -1024.0f / 4096.0f);
|
||||
float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
|
||||
|
||||
if (isPCMWave()) {
|
||||
// Render PCM waveform
|
||||
int len = pcmWaveLength;
|
||||
int intPCMPosition = (int)pcmPosition;
|
||||
if (intPCMPosition >= len && !pcmWaveLooped) {
|
||||
// We're now past the end of a non-looping PCM waveform so it's time to die.
|
||||
deactivate();
|
||||
return 0.0f;
|
||||
}
|
||||
float positionDelta = freq * 2048.0f / SAMPLE_RATE;
|
||||
|
||||
// Linear interpolation
|
||||
float firstSample = getPCMSample(intPCMPosition);
|
||||
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
|
||||
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
|
||||
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
|
||||
if (pcmWaveInterpolated) {
|
||||
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
|
||||
} else {
|
||||
sample = firstSample;
|
||||
}
|
||||
|
||||
float newPCMPosition = pcmPosition + positionDelta;
|
||||
if (pcmWaveLooped) {
|
||||
newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength);
|
||||
}
|
||||
pcmPosition = newPCMPosition;
|
||||
} else {
|
||||
// Render synthesised waveform
|
||||
wavePos *= lastFreq / freq;
|
||||
lastFreq = freq;
|
||||
|
||||
float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f);
|
||||
{
|
||||
//static const float resAmpFactor = EXP2F(-7);
|
||||
//resAmp = EXP2I(resonance << 10) * resAmpFactor;
|
||||
}
|
||||
|
||||
// The cutoffModifier may not be supposed to be directly added to the cutoff -
|
||||
// it may for example need to be multiplied in some way.
|
||||
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||
// More research is needed to be sure that this is correct, however.
|
||||
float cutoffVal = cutoffRampVal / 262144.0f;
|
||||
if (cutoffVal > MAX_CUTOFF_VALUE) {
|
||||
cutoffVal = MAX_CUTOFF_VALUE;
|
||||
}
|
||||
|
||||
// Wave length in samples
|
||||
float waveLen = SAMPLE_RATE / freq;
|
||||
|
||||
// Init cosineLen
|
||||
float cosineLen = 0.5f * waveLen;
|
||||
if (cutoffVal > MIDDLE_CUTOFF_VALUE) {
|
||||
cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis
|
||||
}
|
||||
|
||||
// Start playing in center of first cosine segment
|
||||
// relWavePos is shifted by a half of cosineLen
|
||||
float relWavePos = wavePos + 0.5f * cosineLen;
|
||||
if (relWavePos > waveLen) {
|
||||
relWavePos -= waveLen;
|
||||
}
|
||||
|
||||
// Ratio of positive segment to wave length
|
||||
float pulseLen = 0.5f;
|
||||
if (pulseWidth > 128) {
|
||||
pulseLen = EXP2F((64 - pulseWidth) / 64.0f);
|
||||
//static const float pulseLenFactor = EXP2F(-192 / 64);
|
||||
//pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
|
||||
}
|
||||
pulseLen *= waveLen;
|
||||
|
||||
float hLen = pulseLen - cosineLen;
|
||||
|
||||
// Ignore pulsewidths too high for given freq
|
||||
if (hLen < 0.0f) {
|
||||
hLen = 0.0f;
|
||||
}
|
||||
|
||||
// Ignore pulsewidths too high for given freq and cutoff
|
||||
float lLen = waveLen - hLen - 2 * cosineLen;
|
||||
if (lLen < 0.0f) {
|
||||
lLen = 0.0f;
|
||||
}
|
||||
|
||||
// Correct resAmp for cutoff in range 50..66
|
||||
if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
|
||||
resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
|
||||
}
|
||||
|
||||
// Produce filtered square wave with 2 cosine waves on slopes
|
||||
|
||||
// 1st cosine segment
|
||||
if (relWavePos < cosineLen) {
|
||||
sample = -cos(FLOAT_PI * relWavePos / cosineLen);
|
||||
} else
|
||||
|
||||
// high linear segment
|
||||
if (relWavePos < (cosineLen + hLen)) {
|
||||
sample = 1.f;
|
||||
} else
|
||||
|
||||
// 2nd cosine segment
|
||||
if (relWavePos < (2 * cosineLen + hLen)) {
|
||||
sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
|
||||
} else {
|
||||
|
||||
// low linear segment
|
||||
sample = -1.f;
|
||||
}
|
||||
|
||||
if (cutoffVal < 128.0f) {
|
||||
|
||||
// Attenuate samples below cutoff 50
|
||||
// Found by sample analysis
|
||||
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
|
||||
} else {
|
||||
|
||||
// Add resonance sine. Effective for cutoff > 50 only
|
||||
float resSample = 1.0f;
|
||||
|
||||
// Resonance decay speed factor
|
||||
float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2];
|
||||
|
||||
// Now relWavePos counts from the middle of first cosine
|
||||
relWavePos = wavePos;
|
||||
|
||||
// negative segments
|
||||
if (!(relWavePos < (cosineLen + hLen))) {
|
||||
resSample = -resSample;
|
||||
relWavePos -= cosineLen + hLen;
|
||||
|
||||
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||
resAmpDecayFactor += 0.25f;
|
||||
}
|
||||
|
||||
// Resonance sine WG
|
||||
resSample *= sin(FLOAT_PI * relWavePos / cosineLen);
|
||||
|
||||
// Resonance sine amp
|
||||
float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact
|
||||
float resAmpFade = EXP2F(resAmpFadeLog2);
|
||||
|
||||
// Now relWavePos set negative to the left from center of any cosine
|
||||
relWavePos = wavePos;
|
||||
|
||||
// negative segment
|
||||
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
|
||||
relWavePos -= waveLen;
|
||||
} else
|
||||
|
||||
// positive segment
|
||||
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
|
||||
relWavePos -= cosineLen + hLen;
|
||||
}
|
||||
|
||||
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||
if (relWavePos < 0.5f * cosineLen) {
|
||||
float syncSine = sin(FLOAT_PI * relWavePos / cosineLen);
|
||||
if (relWavePos < 0.0f) {
|
||||
// The window is synchronous square sine here
|
||||
resAmpFade *= syncSine * syncSine;
|
||||
} else {
|
||||
// The window is synchronous sine here
|
||||
resAmpFade *= syncSine;
|
||||
}
|
||||
}
|
||||
|
||||
sample += resSample * resAmp * resAmpFade;
|
||||
}
|
||||
|
||||
// sawtooth waves
|
||||
if (sawtoothWaveform) {
|
||||
sample *= cos(FLOAT_2PI * wavePos / waveLen);
|
||||
}
|
||||
|
||||
wavePos++;
|
||||
|
||||
// wavePos isn't supposed to be > waveLen
|
||||
if (wavePos > waveLen) {
|
||||
wavePos -= waveLen;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply sample with current TVA value
|
||||
sample *= amp;
|
||||
return sample;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::deactivate() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isActive() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isPCMWave() const {
|
||||
return pcmWaveAddress != NULL;
|
||||
}
|
||||
|
||||
void LA32PartialPair::init(const bool ringModulated, const bool mixed) {
|
||||
this->ringModulated = ringModulated;
|
||||
this->mixed = mixed;
|
||||
masterOutputSample = 0.0f;
|
||||
slaveOutputSample = 0.0f;
|
||||
}
|
||||
|
||||
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
} else {
|
||||
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||
} else {
|
||||
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||
if (useMaster == MASTER) {
|
||||
masterOutputSample = master.generateNextSample(amp, pitch, cutoff);
|
||||
} else {
|
||||
slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::nextOutSample() {
|
||||
float outputSample;
|
||||
if (ringModulated) {
|
||||
float ringModulatedSample = masterOutputSample * slaveOutputSample;
|
||||
outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
|
||||
} else {
|
||||
outputSample = masterOutputSample + slaveOutputSample;
|
||||
}
|
||||
return Bit16s(outputSample * 8192.0f);
|
||||
}
|
||||
|
||||
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||
if (useMaster == MASTER) {
|
||||
master.deactivate();
|
||||
masterOutputSample = 0.0f;
|
||||
} else {
|
||||
slave.deactivate();
|
||||
slaveOutputSample = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 1
|
146
audio/softsynth/mt32/LegacyWaveGenerator.h
Normal file
146
audio/softsynth/mt32/LegacyWaveGenerator.h
Normal file
|
@ -0,0 +1,146 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 1
|
||||
|
||||
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
#define MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
/**
|
||||
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||
* The output square wave is created by adding high / low linear segments in-between
|
||||
* the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
|
||||
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||
*/
|
||||
class LA32WaveGenerator {
|
||||
//***************************************************************************
|
||||
// The local copy of partial parameters below
|
||||
//***************************************************************************
|
||||
|
||||
bool active;
|
||||
|
||||
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||
bool sawtoothWaveform;
|
||||
|
||||
// Logarithmic amp of the wave generator
|
||||
Bit32u amp;
|
||||
|
||||
// Logarithmic frequency of the resulting wave
|
||||
Bit16u pitch;
|
||||
|
||||
// Values in range [1..31]
|
||||
// Value 1 correspong to the minimum resonance
|
||||
Bit8u resonance;
|
||||
|
||||
// Processed value in range [0..255]
|
||||
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||
Bit8u pulseWidth;
|
||||
|
||||
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||
Bit32u cutoffVal;
|
||||
|
||||
// Logarithmic PCM sample start address
|
||||
const Bit16s *pcmWaveAddress;
|
||||
|
||||
// Logarithmic PCM sample length
|
||||
Bit32u pcmWaveLength;
|
||||
|
||||
// true for looped logarithmic PCM samples
|
||||
bool pcmWaveLooped;
|
||||
|
||||
// false for slave PCM partials in the structures with the ring modulation
|
||||
bool pcmWaveInterpolated;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal variables below
|
||||
//***************************************************************************
|
||||
|
||||
float wavePos;
|
||||
float lastFreq;
|
||||
float pcmPosition;
|
||||
|
||||
float getPCMSample(unsigned int position);
|
||||
|
||||
public:
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate();
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive() const;
|
||||
|
||||
// Return true if the WG engine generates PCM wave samples
|
||||
bool isPCMWave() const;
|
||||
};
|
||||
|
||||
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||
class LA32PartialPair {
|
||||
LA32WaveGenerator master;
|
||||
LA32WaveGenerator slave;
|
||||
bool ringModulated;
|
||||
bool mixed;
|
||||
float masterOutputSample;
|
||||
float slaveOutputSample;
|
||||
|
||||
public:
|
||||
enum PairType {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||
// ringModulated should be set to true for the structures with ring modulation
|
||||
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||
void init(const bool ringModulated, const bool mixed);
|
||||
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Perform mixing / ring modulation and return the result
|
||||
Bit16s nextOutSample();
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate(const PairType master);
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive(const PairType master) const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 1
|
|
@ -1,5 +1,5 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, &Ramp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) {
|
||||
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) {
|
||||
// Initialisation of tva, tvp and tvf uses 'this' pointer
|
||||
// and thus should not be in the initializer list to avoid a compiler warning
|
||||
tva = new TVA(this, &Ramp);
|
||||
tvp = new TVP(this);
|
||||
tvf = new TVF(this, &cutoffModifierRamp);
|
||||
ownerPart = -1;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
111
audio/softsynth/mt32/ROMInfo.cpp
Normal file
111
audio/softsynth/mt32/ROMInfo.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cstring>
|
||||
#include "ROMInfo.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Known ROMs
|
||||
static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL, NULL};
|
||||
|
||||
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL, NULL};
|
||||
|
||||
static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL, NULL};
|
||||
|
||||
static const ROMInfo * const ROM_INFOS[] = {
|
||||
&CTRL_MT32_V1_04,
|
||||
&CTRL_MT32_V1_05,
|
||||
&CTRL_MT32_V1_06,
|
||||
&CTRL_MT32_V1_07,
|
||||
&CTRL_MT32_BLUER,
|
||||
&CTRL_CM32L_V1_00,
|
||||
&CTRL_CM32L_V1_02,
|
||||
&PCM_MT32,
|
||||
&PCM_CM32L,
|
||||
NULL};
|
||||
|
||||
const ROMInfo* ROMInfo::getROMInfo(Common::File *file) {
|
||||
size_t fileSize = file->size();
|
||||
// We haven't added the SHA1 checksum code in ScummVM, as the file size
|
||||
// suffices for our needs for now.
|
||||
//const char *fileDigest = file->getSHA1();
|
||||
for (int i = 0; ROM_INFOS[i] != NULL; i++) {
|
||||
const ROMInfo *romInfo = ROM_INFOS[i];
|
||||
if (fileSize == romInfo->fileSize /*&& !strcmp(fileDigest, romInfo->sha1Digest)*/) {
|
||||
return romInfo;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
|
||||
(void) romInfo;
|
||||
}
|
||||
|
||||
static int getROMCount() {
|
||||
int count;
|
||||
for(count = 0; ROM_INFOS[count] != NULL; count++) {
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
const ROMInfo** ROMInfo::getROMInfoList(unsigned int types, unsigned int pairTypes) {
|
||||
const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1];
|
||||
const ROMInfo **currentROMInList = romInfoList;
|
||||
for(int i = 0; ROM_INFOS[i] != NULL; i++) {
|
||||
const ROMInfo *romInfo = ROM_INFOS[i];
|
||||
if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) {
|
||||
*currentROMInList++ = romInfo;
|
||||
}
|
||||
}
|
||||
*currentROMInList = NULL;
|
||||
return romInfoList;
|
||||
}
|
||||
|
||||
void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
|
||||
delete[] romInfoList;
|
||||
}
|
||||
|
||||
const ROMImage* ROMImage::makeROMImage(Common::File *file) {
|
||||
ROMImage *romImage = new ROMImage;
|
||||
romImage->file = file;
|
||||
romImage->romInfo = ROMInfo::getROMInfo(romImage->file);
|
||||
return romImage;
|
||||
}
|
||||
|
||||
void ROMImage::freeROMImage(const ROMImage *romImage) {
|
||||
ROMInfo::freeROMInfo(romImage->romInfo);
|
||||
delete romImage;
|
||||
}
|
||||
|
||||
|
||||
Common::File* ROMImage::getFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
const ROMInfo* ROMImage::getROMInfo() const {
|
||||
return romInfo;
|
||||
}
|
||||
|
||||
}
|
77
audio/softsynth/mt32/ROMInfo.h
Normal file
77
audio/softsynth/mt32/ROMInfo.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_ROMINFO_H
|
||||
#define MT32EMU_ROMINFO_H
|
||||
|
||||
//#include <cstddef>
|
||||
#include "common/file.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Defines vital info about ROM file to be used by synth and applications
|
||||
|
||||
struct ROMInfo {
|
||||
public:
|
||||
size_t fileSize;
|
||||
const char *sha1Digest;
|
||||
enum Type {PCM, Control, Reverb} type;
|
||||
const char *shortName;
|
||||
const char *description;
|
||||
enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType;
|
||||
ROMInfo *pairROMInfo;
|
||||
void *controlROMInfo;
|
||||
|
||||
// Returns a ROMInfo struct by inspecting the size and the SHA1 hash
|
||||
static const ROMInfo* getROMInfo(Common::File *file);
|
||||
|
||||
// Currently no-op
|
||||
static void freeROMInfo(const ROMInfo *romInfo);
|
||||
|
||||
// Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes
|
||||
// (specified by bitmasks)
|
||||
// Useful for GUI/console app to output information on what ROMs it supports
|
||||
static const ROMInfo** getROMInfoList(unsigned int types, unsigned int pairTypes);
|
||||
|
||||
// Frees the list of ROMInfos given
|
||||
static void freeROMInfoList(const ROMInfo **romInfos);
|
||||
};
|
||||
|
||||
// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work
|
||||
|
||||
class ROMImage {
|
||||
private:
|
||||
Common::File *file;
|
||||
const ROMInfo *romInfo;
|
||||
|
||||
public:
|
||||
|
||||
// Creates a ROMImage object given a ROMInfo and a File. Keeps a reference
|
||||
// to the File and ROMInfo given, which must be freed separately by the user
|
||||
// after the ROMImage is freed
|
||||
static const ROMImage* makeROMImage(Common::File *file);
|
||||
|
||||
// Must only be done after all Synths using the ROMImage are deleted
|
||||
static void freeROMImage(const ROMImage *romImage);
|
||||
|
||||
Common::File *getFile() const;
|
||||
const ROMInfo *getROMInfo() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,5 +1,5 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 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
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
//@}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; \
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
35
backends/saves/recorder/recorder-saves.cpp
Normal file
35
backends/saves/recorder/recorder-saves.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/saves/recorder/recorder-saves.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
#include "common/savefile.h"
|
||||
|
||||
Common::InSaveFile *RecorderSaveFileManager::openForLoading(const Common::String &filename) {
|
||||
Common::InSaveFile *result = g_eventRec.processSaveStream(filename);
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::StringArray RecorderSaveFileManager::listSaveFiles(const Common::String &pattern) {
|
||||
return g_eventRec.listSaveFiles(pattern);
|
||||
}
|
||||
|
36
backends/saves/recorder/recorder-saves.h
Normal file
36
backends/saves/recorder/recorder-saves.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKEND_SAVES_RECORDER_H
|
||||
#define BACKEND_SAVES_RECORDER_H
|
||||
|
||||
#include "backends/saves/default/default-saves.h"
|
||||
|
||||
/**
|
||||
* Provides a savefile manager implementation for event recorder.
|
||||
*/
|
||||
class RecorderSaveFileManager : public DefaultSaveFileManager {
|
||||
virtual Common::StringArray listSaveFiles(const Common::String &pattern);
|
||||
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -80,7 +80,7 @@ DefaultTimerManager::~DefaultTimerManager() {
|
|||
void DefaultTimerManager::handler() {
|
||||
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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,428 +0,0 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/EventRecorder.h"
|
||||
|
||||
#include "common/bufferedstream.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/random.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
DECLARE_SINGLETON(EventRecorder);
|
||||
|
||||
#define RECORD_SIGNATURE 0x54455354
|
||||
#define RECORD_VERSION 1
|
||||
|
||||
uint32 readTime(ReadStream *inFile) {
|
||||
uint32 d = inFile->readByte();
|
||||
if (d == 0xff) {
|
||||
d = inFile->readUint32LE();
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void writeTime(WriteStream *outFile, uint32 d) {
|
||||
//Simple RLE compression
|
||||
if (d >= 0xff) {
|
||||
outFile->writeByte(0xff);
|
||||
outFile->writeUint32LE(d);
|
||||
} else {
|
||||
outFile->writeByte(d);
|
||||
}
|
||||
}
|
||||
|
||||
void readRecord(SeekableReadStream *inFile, uint32 &diff, Event &event, uint32 &millis) {
|
||||
millis = readTime(inFile);
|
||||
|
||||
diff = inFile->readUint32LE();
|
||||
|
||||
event.type = (EventType)inFile->readUint32LE();
|
||||
|
||||
switch (event.type) {
|
||||
case EVENT_KEYDOWN:
|
||||
case EVENT_KEYUP:
|
||||
event.kbd.keycode = (KeyCode)inFile->readSint32LE();
|
||||
event.kbd.ascii = inFile->readUint16LE();
|
||||
event.kbd.flags = inFile->readByte();
|
||||
break;
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
case EVENT_MBUTTONDOWN:
|
||||
case EVENT_MBUTTONUP:
|
||||
event.mouse.x = inFile->readSint16LE();
|
||||
event.mouse.y = inFile->readSint16LE();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void writeRecord(WriteStream *outFile, uint32 diff, const Event &event, uint32 millis) {
|
||||
writeTime(outFile, millis);
|
||||
|
||||
outFile->writeUint32LE(diff);
|
||||
|
||||
outFile->writeUint32LE((uint32)event.type);
|
||||
|
||||
switch (event.type) {
|
||||
case EVENT_KEYDOWN:
|
||||
case EVENT_KEYUP:
|
||||
outFile->writeSint32LE(event.kbd.keycode);
|
||||
outFile->writeUint16LE(event.kbd.ascii);
|
||||
outFile->writeByte(event.kbd.flags);
|
||||
break;
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
case EVENT_MBUTTONDOWN:
|
||||
case EVENT_MBUTTONUP:
|
||||
outFile->writeSint16LE(event.mouse.x);
|
||||
outFile->writeSint16LE(event.mouse.y);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EventRecorder::EventRecorder() {
|
||||
_recordFile = NULL;
|
||||
_recordTimeFile = NULL;
|
||||
_playbackFile = NULL;
|
||||
_playbackTimeFile = NULL;
|
||||
_timeMutex = g_system->createMutex();
|
||||
_recorderMutex = g_system->createMutex();
|
||||
|
||||
_eventCount = 0;
|
||||
_lastEventCount = 0;
|
||||
_lastMillis = 0;
|
||||
_lastEventMillis = 0;
|
||||
|
||||
_recordMode = kPassthrough;
|
||||
}
|
||||
|
||||
EventRecorder::~EventRecorder() {
|
||||
deinit();
|
||||
|
||||
g_system->deleteMutex(_timeMutex);
|
||||
g_system->deleteMutex(_recorderMutex);
|
||||
}
|
||||
|
||||
void EventRecorder::init() {
|
||||
String recordModeString = ConfMan.get("record_mode");
|
||||
if (recordModeString.compareToIgnoreCase("record") == 0) {
|
||||
_recordMode = kRecorderRecord;
|
||||
|
||||
debug(3, "EventRecorder: record");
|
||||
} else {
|
||||
if (recordModeString.compareToIgnoreCase("playback") == 0) {
|
||||
_recordMode = kRecorderPlayback;
|
||||
debug(3, "EventRecorder: playback");
|
||||
} else {
|
||||
_recordMode = kPassthrough;
|
||||
debug(3, "EventRecorder: passthrough");
|
||||
}
|
||||
}
|
||||
|
||||
_recordFileName = ConfMan.get("record_file_name");
|
||||
if (_recordFileName.empty()) {
|
||||
_recordFileName = "record.bin";
|
||||
}
|
||||
_recordTempFileName = ConfMan.get("record_temp_file_name");
|
||||
if (_recordTempFileName.empty()) {
|
||||
_recordTempFileName = "record.tmp";
|
||||
}
|
||||
_recordTimeFileName = ConfMan.get("record_time_file_name");
|
||||
if (_recordTimeFileName.empty()) {
|
||||
_recordTimeFileName = "record.time";
|
||||
}
|
||||
|
||||
// recorder stuff
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
_recordCount = 0;
|
||||
_recordTimeCount = 0;
|
||||
_recordFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTempFileName), 128 * 1024);
|
||||
_recordTimeFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTimeFileName), 128 * 1024);
|
||||
_recordSubtitles = ConfMan.getBool("subtitles");
|
||||
}
|
||||
|
||||
uint32 sign;
|
||||
uint32 randomSourceCount;
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
_playbackCount = 0;
|
||||
_playbackTimeCount = 0;
|
||||
_playbackFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordFileName), 128 * 1024, DisposeAfterUse::YES);
|
||||
_playbackTimeFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordTimeFileName), 128 * 1024, DisposeAfterUse::YES);
|
||||
|
||||
if (!_playbackFile) {
|
||||
warning("Cannot open playback file %s. Playback was switched off", _recordFileName.c_str());
|
||||
_recordMode = kPassthrough;
|
||||
}
|
||||
|
||||
if (!_playbackTimeFile) {
|
||||
warning("Cannot open playback time file %s. Playback was switched off", _recordTimeFileName.c_str());
|
||||
_recordMode = kPassthrough;
|
||||
}
|
||||
}
|
||||
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
sign = _playbackFile->readUint32LE();
|
||||
if (sign != RECORD_SIGNATURE) {
|
||||
error("Unknown record file signature");
|
||||
}
|
||||
|
||||
_playbackFile->readUint32LE(); // version
|
||||
|
||||
// conf vars
|
||||
ConfMan.setBool("subtitles", _playbackFile->readByte() != 0);
|
||||
|
||||
_recordCount = _playbackFile->readUint32LE();
|
||||
_recordTimeCount = _playbackFile->readUint32LE();
|
||||
|
||||
randomSourceCount = _playbackFile->readUint32LE();
|
||||
for (uint i = 0; i < randomSourceCount; ++i) {
|
||||
RandomSourceRecord rec;
|
||||
rec.name = "";
|
||||
uint32 sLen = _playbackFile->readUint32LE();
|
||||
for (uint j = 0; j < sLen; ++j) {
|
||||
char c = _playbackFile->readSByte();
|
||||
rec.name += c;
|
||||
}
|
||||
rec.seed = _playbackFile->readUint32LE();
|
||||
_randomSourceRecords.push_back(rec);
|
||||
}
|
||||
|
||||
_hasPlaybackEvent = false;
|
||||
}
|
||||
|
||||
g_system->getEventManager()->getEventDispatcher()->registerSource(this, false);
|
||||
g_system->getEventManager()->getEventDispatcher()->registerObserver(this, EventManager::kEventRecorderPriority, false, true);
|
||||
}
|
||||
|
||||
void EventRecorder::deinit() {
|
||||
debug(3, "EventRecorder: deinit");
|
||||
|
||||
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
|
||||
g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
|
||||
|
||||
g_system->lockMutex(_timeMutex);
|
||||
g_system->lockMutex(_recorderMutex);
|
||||
_recordMode = kPassthrough;
|
||||
g_system->unlockMutex(_timeMutex);
|
||||
g_system->unlockMutex(_recorderMutex);
|
||||
|
||||
delete _playbackFile;
|
||||
delete _playbackTimeFile;
|
||||
|
||||
if (_recordFile != NULL) {
|
||||
_recordFile->finalize();
|
||||
delete _recordFile;
|
||||
_recordTimeFile->finalize();
|
||||
delete _recordTimeFile;
|
||||
|
||||
_playbackFile = g_system->getSavefileManager()->openForLoading(_recordTempFileName);
|
||||
|
||||
assert(_playbackFile);
|
||||
|
||||
_recordFile = g_system->getSavefileManager()->openForSaving(_recordFileName);
|
||||
_recordFile->writeUint32LE(RECORD_SIGNATURE);
|
||||
_recordFile->writeUint32LE(RECORD_VERSION);
|
||||
|
||||
// conf vars
|
||||
_recordFile->writeByte(_recordSubtitles ? 1 : 0);
|
||||
|
||||
_recordFile->writeUint32LE(_recordCount);
|
||||
_recordFile->writeUint32LE(_recordTimeCount);
|
||||
|
||||
_recordFile->writeUint32LE(_randomSourceRecords.size());
|
||||
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
|
||||
_recordFile->writeUint32LE(_randomSourceRecords[i].name.size());
|
||||
_recordFile->writeString(_randomSourceRecords[i].name);
|
||||
_recordFile->writeUint32LE(_randomSourceRecords[i].seed);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _recordCount; ++i) {
|
||||
uint32 tempDiff;
|
||||
Event tempEvent;
|
||||
uint32 millis;
|
||||
readRecord(_playbackFile, tempDiff, tempEvent, millis);
|
||||
writeRecord(_recordFile, tempDiff, tempEvent, millis);
|
||||
}
|
||||
|
||||
_recordFile->finalize();
|
||||
delete _recordFile;
|
||||
delete _playbackFile;
|
||||
|
||||
//TODO: remove recordTempFileName'ed file
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::registerRandomSource(RandomSource &rnd, const String &name) {
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
RandomSourceRecord rec;
|
||||
rec.name = name;
|
||||
rec.seed = rnd.getSeed();
|
||||
_randomSourceRecords.push_back(rec);
|
||||
}
|
||||
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
|
||||
if (_randomSourceRecords[i].name == name) {
|
||||
rnd.setSeed(_randomSourceRecords[i].seed);
|
||||
_randomSourceRecords.remove_at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::processMillis(uint32 &millis) {
|
||||
uint32 d;
|
||||
if (_recordMode == kPassthrough) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_system->lockMutex(_timeMutex);
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
d = millis - _lastMillis;
|
||||
writeTime(_recordTimeFile, d);
|
||||
|
||||
_recordTimeCount++;
|
||||
}
|
||||
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
if (_recordTimeCount > _playbackTimeCount) {
|
||||
d = readTime(_playbackTimeFile);
|
||||
|
||||
while ((_lastMillis + d > millis) && (_lastMillis + d - millis > 50)) {
|
||||
_recordMode = kPassthrough;
|
||||
g_system->delayMillis(50);
|
||||
millis = g_system->getMillis();
|
||||
_recordMode = kRecorderPlayback;
|
||||
}
|
||||
|
||||
millis = _lastMillis + d;
|
||||
_playbackTimeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_lastMillis = millis;
|
||||
g_system->unlockMutex(_timeMutex);
|
||||
}
|
||||
|
||||
bool EventRecorder::processDelayMillis(uint &msecs) {
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
_recordMode = kPassthrough;
|
||||
|
||||
uint32 millis = g_system->getMillis();
|
||||
|
||||
_recordMode = kRecorderPlayback;
|
||||
|
||||
if (_lastMillis > millis) {
|
||||
// Skip delay if we're getting late
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventRecorder::notifyEvent(const Event &ev) {
|
||||
if (_recordMode != kRecorderRecord)
|
||||
return false;
|
||||
|
||||
StackLock lock(_recorderMutex);
|
||||
++_eventCount;
|
||||
|
||||
writeRecord(_recordFile, _eventCount - _lastEventCount, ev, _lastMillis - _lastEventMillis);
|
||||
|
||||
_recordCount++;
|
||||
_lastEventCount = _eventCount;
|
||||
_lastEventMillis = _lastMillis;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventRecorder::notifyPoll() {
|
||||
if (_recordMode != kRecorderRecord)
|
||||
return false;
|
||||
|
||||
++_eventCount;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventRecorder::pollEvent(Event &ev) {
|
||||
uint32 millis;
|
||||
|
||||
if (_recordMode != kRecorderPlayback)
|
||||
return false;
|
||||
|
||||
StackLock lock(_recorderMutex);
|
||||
++_eventCount;
|
||||
|
||||
if (!_hasPlaybackEvent) {
|
||||
if (_recordCount > _playbackCount) {
|
||||
readRecord(_playbackFile, const_cast<uint32&>(_playbackDiff), _playbackEvent, millis);
|
||||
_playbackCount++;
|
||||
_hasPlaybackEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_hasPlaybackEvent) {
|
||||
if (_playbackDiff <= (_eventCount - _lastEventCount)) {
|
||||
switch (_playbackEvent.type) {
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
g_system->warpMouse(_playbackEvent.mouse.x, _playbackEvent.mouse.y);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ev = _playbackEvent;
|
||||
_hasPlaybackEvent = false;
|
||||
_lastEventCount = _eventCount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // End of namespace Common
|
|
@ -1,110 +0,0 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COMMON_EVENTRECORDER_H
|
||||
#define COMMON_EVENTRECORDER_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/events.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/array.h"
|
||||
|
||||
#define g_eventRec (Common::EventRecorder::instance())
|
||||
|
||||
namespace Common {
|
||||
|
||||
class RandomSource;
|
||||
class SeekableReadStream;
|
||||
class WriteStream;
|
||||
|
||||
/**
|
||||
* Our generic event recorder.
|
||||
*
|
||||
* TODO: Add more documentation.
|
||||
*/
|
||||
class EventRecorder : private EventSource, private EventObserver, public Singleton<EventRecorder> {
|
||||
friend class Singleton<SingletonBaseType>;
|
||||
EventRecorder();
|
||||
~EventRecorder();
|
||||
public:
|
||||
void init();
|
||||
void deinit();
|
||||
|
||||
/** Register random source so it can be serialized in game test purposes */
|
||||
void registerRandomSource(RandomSource &rnd, const String &name);
|
||||
|
||||
/** TODO: Add documentation, this is only used by the backend */
|
||||
void processMillis(uint32 &millis);
|
||||
|
||||
/** TODO: Add documentation, this is only used by the backend */
|
||||
bool processDelayMillis(uint &msecs);
|
||||
|
||||
private:
|
||||
bool notifyEvent(const Event &ev);
|
||||
bool notifyPoll();
|
||||
bool pollEvent(Event &ev);
|
||||
bool allowMapping() const { return false; }
|
||||
|
||||
class RandomSourceRecord {
|
||||
public:
|
||||
String name;
|
||||
uint32 seed;
|
||||
};
|
||||
Array<RandomSourceRecord> _randomSourceRecords;
|
||||
|
||||
bool _recordSubtitles;
|
||||
volatile uint32 _recordCount;
|
||||
volatile uint32 _lastRecordEvent;
|
||||
volatile uint32 _recordTimeCount;
|
||||
volatile uint32 _lastEventMillis;
|
||||
WriteStream *_recordFile;
|
||||
WriteStream *_recordTimeFile;
|
||||
MutexRef _timeMutex;
|
||||
MutexRef _recorderMutex;
|
||||
volatile uint32 _lastMillis;
|
||||
|
||||
volatile uint32 _playbackCount;
|
||||
volatile uint32 _playbackDiff;
|
||||
volatile bool _hasPlaybackEvent;
|
||||
volatile uint32 _playbackTimeCount;
|
||||
Event _playbackEvent;
|
||||
SeekableReadStream *_playbackFile;
|
||||
SeekableReadStream *_playbackTimeFile;
|
||||
|
||||
volatile uint32 _eventCount;
|
||||
volatile uint32 _lastEventCount;
|
||||
|
||||
enum RecordMode {
|
||||
kPassthrough = 0,
|
||||
kRecorderRecord = 1,
|
||||
kRecorderPlayback = 2
|
||||
};
|
||||
volatile RecordMode _recordMode;
|
||||
String _recordFileName;
|
||||
String _recordTempFileName;
|
||||
String _recordTimeFileName;
|
||||
};
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
|
@ -118,7 +118,7 @@ void SearchSet::addDirectory(const String &name, const FSNode &dir, int priority
|
|||
add(name, new FSDirectory(dir, depth, flat), priority);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
42
common/c++11-compat.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COMMON_CPP11_COMPAT_H
|
||||
#define COMMON_CPP11_COMPAT_H
|
||||
|
||||
#if __cplusplus >= 201103L
|
||||
#error "c++11-compat.h included when C++11 is available"
|
||||
#endif
|
||||
|
||||
//
|
||||
// Custom nullptr replacement. This is not type safe as the real C++11 nullptr
|
||||
// though.
|
||||
//
|
||||
#define nullptr 0
|
||||
|
||||
//
|
||||
// Replacement for the override keyword. This allows compilation of code
|
||||
// which uses it, but does not feature any semantic.
|
||||
//
|
||||
#define override
|
||||
|
||||
#endif
|
|
@ -226,6 +226,15 @@ bool ConfigFile::saveToStream(WriteStream &stream) {
|
|||
return !stream.err();
|
||||
}
|
||||
|
||||
void ConfigFile::addSection(const String §ion) {
|
||||
Section *s = getSection(section);
|
||||
if (s)
|
||||
return;
|
||||
|
||||
Section newSection;
|
||||
newSection.name = section;
|
||||
_sections.push_back(newSection);
|
||||
}
|
||||
|
||||
void ConfigFile::removeSection(const String §ion) {
|
||||
assert(isValidName(section));
|
||||
|
@ -380,4 +389,4 @@ void ConfigFile::Section::removeKey(const String &key) {
|
|||
}
|
||||
}
|
||||
|
||||
} // End of namespace Common
|
||||
} // End of namespace Common
|
||||
|
|
|
@ -106,6 +106,7 @@ public:
|
|||
bool saveToStream(WriteStream &stream);
|
||||
|
||||
bool hasSection(const String §ion) const;
|
||||
void addSection(const String §ion);
|
||||
void removeSection(const String §ion);
|
||||
void renameSection(const String &oldName, const String &newName);
|
||||
|
||||
|
@ -134,6 +135,6 @@ private:
|
|||
|
||||
*/
|
||||
|
||||
} // End of namespace Common
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue