Merge branch 'master' of https://github.com/scummvm/scummvm into mortevielle

Conflicts:
	engines/engines.mk
This commit is contained in:
Strangerke 2013-06-26 23:11:34 +02:00
commit 6e2d567bca
1433 changed files with 138197 additions and 29190 deletions

2
.gitignore vendored
View File

@ -116,8 +116,10 @@ project.xcworkspace
/devtools/create_kyradat/create_kyradat
/devtools/create_lure/create_lure
/devtools/create_mads/create_mads
/devtools/create_neverhood/create_neverhood
/devtools/create_project/create_project
/devtools/create_teenagent/create_teenagent
/devtools/create_tony/create_tony
/devtools/create_toon/create_toon
/devtools/create_translations/create_translations
/devtools/qtable/qtable

16
AUTHORS
View File

@ -106,6 +106,10 @@ ScummVM Team
Scott Thomas
Jordi Vilalta Prat
Hopkins:
Arnaud Boutonne
Paul Gilbert
Hugo:
Arnaud Boutonne
Oystein Eftevaag
@ -203,6 +207,10 @@ ScummVM Team
Filippos Karapetis
Joost Peters
Toltecs:
Benjamin Haisch
Filippos Karapetis
Tony:
Arnaud Boutonne
Paul Gilbert
@ -307,6 +315,8 @@ ScummVM Team
Benjamin Haisch - Heavily improved de-/encoder for DXA videos
Jochen Hoenicke - Speaker & PCjr sound support, AdLib work
(retired)
Daniel ter Laan - Restoring original Drascula tracks, and
writing convert_dxa.bat
Chris Page - Return to launcher, savestate improvements,
leak fixes, ... (GSoC 2008 task) (retired)
Robin Watts - ARM assembly routines for nice speedups on
@ -411,6 +421,9 @@ Other contributions
Basque:
Mikel Iturbe Urretxa
Belarusian:
Ivan Lukyanov
Catalan:
Jordi Vilalta Prat
@ -420,6 +433,9 @@ Other contributions
Danish:
Steffen Nyeland
Finnish:
Toni Saarela
French:
Thierry Crozat

View File

@ -1,5 +1,5 @@
ScummVM
Copyright (C) 2001-2012 by the following:
Copyright (C) 2001-2013 by the following:
If you have contributed to this project then you deserve to be on this
list. Contact us (see: AUTHORS) and we'll add you.

View File

@ -78,7 +78,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:
@ -171,7 +171,7 @@ ifeq ($(origin VER_REV), undefined)
# Are there uncommitted changes? (describe --dirty is only available since 1.6.6)
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
VER_REV := $(shell cd $(srcdir); git describe --match desc/\* | cut -d '-' -f 2-)$(VER_DIRTY)
VER_REV := $(shell cd $(srcdir); git describe --long --match desc/\* | cut -d '-' -f 2-)$(VER_DIRTY)
endif
else
GITROOT := git://github.com/scummvm/scummvm.git
@ -253,6 +253,9 @@ endif
ifdef ENABLE_LURE
DIST_FILES_ENGINEDATA+=lure.dat
endif
ifdef ENABLE_NEVERHOOD
DIST_FILES_ENGINEDATA+=neverhood.dat
endif
ifdef ENABLE_QUEEN
DIST_FILES_ENGINEDATA+=queen.tbl
endif
@ -262,9 +265,15 @@ endif
ifdef ENABLE_TEENAGENT
DIST_FILES_ENGINEDATA+=teenagent.dat
endif
ifdef ENABLE_TONY
DIST_FILES_ENGINEDATA+=tony.dat
endif
ifdef ENABLE_TOON
DIST_FILES_ENGINEDATA+=toon.dat
endif
ifdef ENABLE_WINTERMUTE
DIST_FILES_ENGINEDATA+=wintermute.zip
endif
DIST_FILES_ENGINEDATA:=$(addprefix $(srcdir)/dists/engine-data/,$(DIST_FILES_ENGINEDATA))
# pred.dic is currently only used for the AGI engine

67
NEWS
View File

@ -1,7 +1,19 @@
For a more comprehensive changelog of the latest experimental code, see:
https://github.com/scummvm/scummvm/commits/
1.6.0 (????-??-??)
1.7.0 (????-??-??)
1.6.0 (2013-05-31)
New Games:
- Added support for 3 Skulls of the Toltecs.
- Added support for Eye of the Beholder.
- Added support for Eye of the Beholder II: The Legend of Darkmoon.
- Added support for Hopkins FBI.
- Added support for Tony Tough and the Night of Roasted Moths.
- Added support for The Journeyman Project: Pegasus Prime.
- Added support for the Macintosh version of Discworld 1.
General:
- Added a new save/load chooser based on a grid of thumbnails. This is only
supported for resolutions bigger than 640x400. The old chooser is still
@ -9,16 +21,69 @@ For a more comprehensive changelog of the latest experimental code, see:
select the old one as default too.
- Rewrote VideoDecoder subsystem.
- Added Galician translation.
- Added Finnish translation.
- Added Belarusian translation.
- Using the mouse wheel on a slider widget now changes the value by the
smallest possible amount. This is more predictable than the old behaviour,
which was to change the value by "one pixel" which would sometimes not
change it at all.
- Updated MT-32 emulation code to latest munt project snapshot.
- Added FluidSynth settings dialog, mainly for reverb and chorus settings.
- Fixed crash on certain Smacker movies.
Cine:
- Improved audio support for Amiga and AtariST versions of Future Wars.
Now music fades out slowly instead of stopping immediately. Sound
effects are now properly panned, when requested by the game.
CGE:
- Soltys contains a puzzle requiring the ALT key to be pressed while clicking
on an object. This puzzle has been disabled on devices not using this key.
Drascula:
- Resolved multiple UI issues with the original save/load screen.
- Added advanced savegame functionality, including savegame timestamps and
thumbnails and the ability to load and delete savegames from the launcher.
It's now possible to use the ScummvM save/load dialogs.
- The F7 key (previously unmapped) now always shows the ScummVM load screen.
The F10 key displays either the original save/load screen, or the ScummVM
save screen, if the user has selected to use the ScummVM save/load
dialogs.
Dreamweb:
- Now that the game is freeware, there is a small extra help text showing
the available commands in the in-game terminals when the player uses the
'help' command. Previously, players needed to consult the manual for the
available commands. Since this reference to the manual is a form of copy
protection, this extra line can be toggled by the ScummVM copy protection
command line option.
Groovie:
- Simplified the movie speed options, and added a custom option for The 7th
Guest. Movie options are now "normal" and "fast", with the latter changing
the movie speed in T7G to match the faster movie speed of the iOS version.
The game entry might need to be readded in the launcher for the new setting
to appear.
SAGA:
- Added music support for the Macintosh version of I Have No Mouth and, I
Must Scream.
SCUMM:
- Implemented Monkey Island 2 Macintosh's audio driver. Now we properly
support its sample based audio output. The same output is also used for
the m68k Macintosh version of Indiana Jones and the Fate of Atlantis.
- Improved music support for the Macintosh version of Monkey Island 1. It
now uses the original instruments, rather than approximating them with
General MIDI instruments, and should sound a lot closer to the original.
- Added sound and music support for the Macintosh version of Loom.
- Handle double-clicking in the Macintosh version of Loom.
- Major bugfixes in INSANE (the Full Throttle bike fights).
TOUCHE:
- Added support for Enhanced Music by James Woodcock
(http://www.jameswoodcock.co.uk/category/scummvm-music-enhancement-project/).
1.5.0 (2012-07-27)
New Games:

52
README
View File

@ -264,10 +264,15 @@ Other Games:
Discworld 2: Missing Presumed ...!? [dw2]
Dragon History [draci]
Drascula: The Vampire Strikes Back [drascula]
Eye of the Beholder [eob]
Eye of the Beholder II: The Legend of
Darkmoon [eob2]
Flight of the Amazon Queen [queen]
Future Wars [fw]
Inherit the Earth: Quest for the Orb [ite]
Nippon Safes Inc. [nippon]
Lands of Lore: The Throne of Chaos [lol]
The Journeyman Project: Pegasus Prime [pegasus]
The Legend of Kyrandia [kyra1]
The Legend of Kyrandia: The Hand of Fate [kyra2]
The Legend of Kyrandia: Malcolm's Revenge [kyra3]
@ -384,21 +389,24 @@ entering any answer. Chances are that it will work.
ScummVM will skip copy protection in the following games:
* Maniac Mansion
* Zak McKracken and the Alien Mindbenders
* Loom (EGA)
* The Secret of Monkey Island (VGA)
* Monkey Island 2: LeChuck's Revenge
* Beneath a Steel Sky
-- bypassed with kind permission from Revolution Software.
* Dreamweb
-- a list of available commands in the in-game terminals is now shown
when the player uses the 'help' command
* Inherit the Earth: Quest for the Orb (Floppy version)
-- bypassed with kind permission from Wyrmkeep Entertainment,
since it was bypassed in all CD releases of the game.
* Loom (EGA DOS)
* Maniac Mansion
* Monkey Island 2: LeChuck's Revenge
* Simon the Sorcerer 1 (Floppy version)
* Simon the Sorcerer 2 (Floppy version)
-- bypassed with kind permission from Adventure Soft,
since it was bypassed in all CD releases of the game.
* The Secret of Monkey Island (VGA)
* Waxworks
* Zak McKracken and the Alien Mindbenders
3.2) Commodore64 games notes:
@ -2033,9 +2041,10 @@ The following keywords are recognized:
super2xsai, supereagle, advmame2x, advmame3x,
hq2x, hq3x, tv2x, dotmatrix)
confirm_exit bool Ask for confirmation by the user before quitting
(SDL backend only).
console bool Enable the console window (default: enabled) (Windows only).
confirm_exit bool Ask for confirmation by the user before
quitting (SDL backend only).
console bool Enable the console window (default: enabled)
(Windows only).
cdrom number Number of CD-ROM unit to use for audio. If
negative, don't even try to access the CD-ROM.
joystick_num number Number of joystick device to use for input
@ -2063,7 +2072,8 @@ The following keywords are recognized:
supported by some MIDI drivers.)
copy_protection bool Enable copy protection in certain games, in
those cases where ScummVM disables it by default.
those cases where ScummVM disables it by
default.
demo_mode bool Start demo in Maniac Mansion
alt_intro bool Use alternative intro for CD versions of
Beneath a Steel Sky and Flight of the Amazon
@ -2124,8 +2134,9 @@ Lands of Lore: The Throne of Chaos adds the following non-standard keywords:
Space Quest IV CD adds the following non-standard keyword:
silver_cursors bool If true, an alternate set of silver mouse cursors
is used instead of the original golden ones
silver_cursors bool If true, an alternate set of silver mouse
cursors is used instead of the original golden
ones
Simon the Sorcerer 1 and 2 add the following non-standard keywords:
@ -2134,19 +2145,32 @@ Simon the Sorcerer 1 and 2 add the following non-standard keywords:
The Legend of Kyrandia adds the following non-standard keyword:
walkspeed int The walk speed (0-4)
walkspeed number The walk speed (0-4)
The Legend of Kyrandia: The Hand of Fate adds the following non-standard
keyword:
walkspeed number The walk speed (3 or 5, resp. fast or
slow)
The Legend of Kyrandia: Malcolm's Revenge adds the following non-standard
keywords:
walkspeed number The walk speed (3 or 5, resp. fast or
slow)
studio_audience bool If true, applause and cheering sounds are heard
whenever Malcolm makes a joke
skip_support bool If true, the player can skip text and cutscenes
helium_mode bool If true, people sound like they've inhaled Helium
helium_mode bool If true, people sound like they've inhaled
Helium
The 7th Guest adds the following non-standard keyword:
t7g_speed string Video playback speed (normal, tweaked, im_an_ios)
fast_movie_speed bool If true, movies are played at an increased
speed, matching the speed of the iOS version.
Movies without sound are still played at their
normal speed, to avoid music synchronization
issues
8.2) Custom game options that can be toggled via the GUI

View File

@ -98,6 +98,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) {
@ -118,6 +122,10 @@ int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
_loops = _completeIterations = 1;
return samplesRead;
}
if (_parent->endOfData()) {
// Apparently this is an empty stream
_loops = _completeIterations = 1;
}
return samplesRead + readBuffer(buffer + samplesRead, remainingSamples);
}

View File

@ -268,7 +268,6 @@ static const int MSADPCMAdaptationTable[] = {
768, 614, 512, 409, 307, 230, 230, 230
};
int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
int32 predictor;
@ -290,40 +289,42 @@ int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
int samples;
byte data;
int i = 0;
int i;
samples = 0;
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
if (_decodedSampleCount == 0) {
if (_blockPos[0] == _blockAlign) {
// read block header
for (i = 0; i < _channels; i++) {
_status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
_status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
_status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
}
while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
if (_blockPos[0] == _blockAlign) {
// read block header
for (i = 0; i < _channels; i++) {
_status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
_status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
_status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
for (i = 0; i < _channels; i++)
_status.ch[i].delta = _stream->readSint16LE();
for (i = 0; i < _channels; i++)
_status.ch[i].sample1 = _stream->readSint16LE();
for (i = 0; i < _channels; i++)
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample2 = _stream->readSint16LE();
for (i = 0; i < _channels; i++)
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample1;
_blockPos[0] = _channels * 7;
} else {
data = _stream->readByte();
_blockPos[0]++;
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
}
for (i = 0; i < _channels; i++)
_status.ch[i].delta = _stream->readSint16LE();
for (i = 0; i < _channels; i++)
_status.ch[i].sample1 = _stream->readSint16LE();
for (i = 0; i < _channels; i++)
buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE();
for (i = 0; i < _channels; i++)
buffer[samples++] = _status.ch[i].sample1;
_blockPos[0] = _channels * 7;
}
for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
data = _stream->readByte();
_blockPos[0]++;
buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
}
// (1 - (count - 1)) ensures that _decodedSamples acts as a FIFO of depth 2
buffer[samples] = _decodedSamples[1 - (_decodedSampleCount - 1)];
_decodedSampleCount--;
}
return samples;
@ -432,7 +433,7 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
return samp;
}
RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) {
RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, ADPCMType type, int rate, int channels, uint32 blockAlign) {
// If size is 0, report the entire size of the stream
if (!size)
size = stream->size();

View File

@ -51,7 +51,7 @@ class RewindableAudioStream;
// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs
// Usually, if the audio stream we're trying to play has the FourCC header
// string intact, it's easy to discern which encoding is used
enum typesADPCM {
enum ADPCMType {
kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
kADPCMMSIma, // Microsoft IMA ADPCM
kADPCMMS, // Microsoft ADPCM
@ -76,9 +76,9 @@ enum typesADPCM {
RewindableAudioStream *makeADPCMStream(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse,
uint32 size, typesADPCM type,
int rate = 22050,
int channels = 2,
uint32 size, ADPCMType type,
int rate,
int channels,
uint32 blockAlign = 0);
} // End of namespace Audio

View File

@ -206,12 +206,19 @@ public:
if (blockAlign == 0)
error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
memset(&_status, 0, sizeof(_status));
_decodedSampleCount = 0;
}
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); }
virtual int readBuffer(int16 *buffer, const int numSamples);
protected:
int16 decodeMS(ADPCMChannelStatus *c, byte);
private:
uint8 _decodedSampleCount;
int16 _decodedSamples[4];
};
// Duck DK3 IMA ADPCM Decoder

View File

@ -48,7 +48,7 @@ class SeekableAudioStream;
* successful. In that case, the stream's seek position will be set to the
* start of the audio data, and size, rate and flags contain information
* necessary for playback. Currently this function only supports uncompressed
* raw PCM data as well as IMA ADPCM.
* raw PCM.
*/
extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);

View File

@ -872,7 +872,7 @@ void initVlcSparse(VLC *vlc, int nb_bits, int nb_codes,
codes, codes_wrap, codes_size,
symbols, symbols_wrap, symbols_size,
0, 0, 4 | 2) < 0) {
free(&vlc->table);
free(vlc->table);
return; // Error
}

View File

@ -134,7 +134,7 @@ void QuickTimeAudioDecoder::init() {
_audioTracks.push_back(new QuickTimeAudioTrack(this, _tracks[i]));
}
Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format) {
Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_AUDIO) {
debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format));

View File

@ -131,7 +131,7 @@ protected:
};
// Common::QuickTimeParser API
virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format);
virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize);
void init();

View File

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

View File

@ -129,7 +129,9 @@ public:
/**
* Function to directly write to a specific OPL register.
* This writes to *both* chips for a Dual OPL2.
* This writes to *both* chips for a Dual OPL2. We allow
* writing to secondary OPL registers by using register
* values >= 0x100.
*
* @param r hardware register number to write to
* @param v value, which will be written

View File

@ -194,7 +194,9 @@ public:
enum {
// PROP_TIMEDIV = 1,
PROP_OLD_ADLIB = 2,
PROP_CHANNEL_MASK = 3
PROP_CHANNEL_MASK = 3,
// HACK: Not so nice, but our SCUMM AdLib code is in audio/
PROP_SCUMM_OPL3 = 4
};
/**

View File

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

View File

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

496
audio/midiparser_qt.cpp Normal file
View File

@ -0,0 +1,496 @@
/* 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 "audio/midiparser_qt.h"
#include "common/debug.h"
#include "common/memstream.h"
bool MidiParser_QT::loadMusic(byte *data, uint32 size) {
if (size < 8)
return false;
Common::SeekableReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::NO);
// Attempt to detect what format we have
bool result;
if (READ_BE_UINT32(data + 4) == MKTAG('m', 'u', 's', 'i'))
result = loadFromTune(stream);
else
result = loadFromContainerStream(stream);
if (!result) {
delete stream;
return false;
}
return true;
}
void MidiParser_QT::unloadMusic() {
MidiParser::unloadMusic();
close();
// Unlike those lesser formats, we *do* hold track data
for (uint i = 0; i < _trackInfo.size(); i++)
free(_trackInfo[i].data);
_trackInfo.clear();
}
bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
unloadMusic();
// a tune starts off with a sample description
stream->readUint32BE(); // header size
if (stream->readUint32BE() != MKTAG('m', 'u', 's', 'i'))
return false;
stream->readUint32BE(); // reserved
stream->readUint16BE(); // reserved
stream->readUint16BE(); // index
stream->readUint32BE(); // flags, ignore
MIDITrackInfo trackInfo;
trackInfo.size = stream->size() - stream->pos();
assert(trackInfo.size > 0);
trackInfo.data = (byte *)malloc(trackInfo.size);
stream->read(trackInfo.data, trackInfo.size);
trackInfo.timeScale = 600; // the default
_trackInfo.push_back(trackInfo);
initCommon();
return true;
}
bool MidiParser_QT::loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
unloadMusic();
if (!parseStream(stream, disposeAfterUse))
return false;
initFromContainerTracks();
return true;
}
bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) {
unloadMusic();
if (!parseFile(fileName))
return false;
initFromContainerTracks();
return true;
}
void MidiParser_QT::parseNextEvent(EventInfo &info) {
uint32 delta = 0;
while (_queuedEvents.empty())
delta += readNextEvent();
info = _queuedEvents.pop();
info.delta = delta;
}
uint32 MidiParser_QT::readNextEvent() {
if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) {
// Manually insert end of track when we reach the end
EventInfo info;
info.event = 0xFF;
info.ext.type = 0x2F;
_queuedEvents.push(info);
return 0;
}
uint32 control = readUint32();
switch (control >> 28) {
case 0x0:
case 0x1:
// Rest
// We handle this by recursively adding up all the rests into the
// next event's delta
return readNextEvent() + (control & 0xFFFFFF);
case 0x2:
case 0x3:
// Note event
handleNoteEvent((control >> 24) & 0x1F, ((control >> 18) & 0x3F) + 32, (control >> 11) & 0x7F, control & 0x7FF);
break;
case 0x4:
case 0x5:
// Controller
handleControllerEvent((control >> 16) & 0xFF, (control >> 24) & 0x1F, (control >> 8) & 0xFF, control & 0xFF);
break;
case 0x6:
case 0x7:
// Marker
// Used for editing only, so we don't need to care about this
break;
case 0x9: {
// Extended note event
uint32 extra = readUint32();
handleNoteEvent((control >> 16) & 0xFFF, (control >> 8) & 0xFF, (extra >> 22) & 0x7F, extra & 0x3FFFFF);
break;
}
case 0xA: {
// Extended controller
uint32 extra = readUint32();
handleControllerEvent((extra >> 16) & 0x3FFF, (control >> 16) & 0xFFF, (extra >> 8) & 0xFF, extra & 0xFF);
break;
}
case 0xB:
// Knob
error("Encountered knob event in QuickTime MIDI");
break;
case 0x8:
case 0xC:
case 0xD:
case 0xE:
// Reserved
readUint32();
break;
case 0xF:
// General
handleGeneralEvent(control);
break;
}
return 0;
}
void MidiParser_QT::handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length) {
byte channel = getChannel(part);
EventInfo info;
info.event = 0x90 | channel;
info.basic.param1 = pitch;
info.basic.param2 = velocity;
info.length = (velocity == 0) ? 0 : length;
_queuedEvents.push(info);
}
void MidiParser_QT::handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart) {
byte channel = getChannel(part);
EventInfo info;
if (control == 0) {
// "Bank select"
// QuickTime docs don't list this, but IHNM Mac calls this anyway
// We have to ignore this.
return;
} else if (control == 32) {
// Pitch bend
info.event = 0xE0 | channel;
// Actually an 8.8 fixed point number
int16 value = (int16)((intPart << 8) | fracPart);
if (value < -0x200 || value > 0x1FF) {
warning("QuickTime MIDI pitch bend value (%d) out of range, clipping", value);
value = CLIP<int16>(value, -0x200, 0x1FF);
}
// Now convert the value to 'normal' MIDI values
value += 0x200;
value *= 16;
// param1 holds the low 7 bits, param2 holds the high 7 bits
info.basic.param1 = value & 0x7F;
info.basic.param2 = value >> 7;
_partMap[part].pitchBend = value;
} else {
// Regular controller
info.event = 0xB0 | channel;
info.basic.param1 = control;
info.basic.param2 = intPart;
// TODO: Parse more controls to hold their status
switch (control) {
case 7:
_partMap[part].volume = intPart;
break;
case 10:
_partMap[part].pan = intPart;
break;
}
}
_queuedEvents.push(info);
}
void MidiParser_QT::handleGeneralEvent(uint32 control) {
uint32 part = (control >> 16) & 0xFFF;
uint32 dataSize = ((control & 0xFFFF) - 2) * 4;
byte subType = READ_BE_UINT16(_position._playPos + dataSize) & 0x3FFF;
switch (subType) {
case 1:
// Note Request
// Currently we're only using the GM number from the request
assert(dataSize == 84);
// We have to remap channels because GM needs percussion to be on the
// percussion channel but QuickTime can have that anywhere.
definePart(part, READ_BE_UINT32(_position._playPos + 80));
break;
case 5: // Tune Difference
case 8: // MIDI Channel
case 10: // No-op
case 11: // Used Notes
// Should be safe to skip these
break;
default:
warning("Unhandled general event %d", subType);
}
_position._playPos += dataSize + 4;
}
void MidiParser_QT::definePart(uint32 part, uint32 instrument) {
if (_partMap.contains(part))
warning("QuickTime MIDI part %d being redefined", part);
PartStatus partStatus;
partStatus.instrument = instrument;
partStatus.volume = 127;
partStatus.pan = 64;
partStatus.pitchBend = 0x2000;
_partMap[part] = partStatus;
}
byte MidiParser_QT::getChannel(uint32 part) {
// If we already mapped it, just go with it
if (!_channelMap.contains(part)) {
byte newChannel = findFreeChannel(part);
_channelMap[part] = newChannel;
setupPart(part);
}
return _channelMap[part];
}
byte MidiParser_QT::findFreeChannel(uint32 part) {
if (_partMap[part].instrument != 0x4001) {
// Normal Instrument -> First Free Channel
if (allChannelsAllocated())
deallocateFreeChannel();
for (int i = 0; i < 16; i++)
if (i != 9 && !isChannelAllocated(i)) // 9 is reserved for Percussion
return i;
// Can't actually get here
}
// Drum Kit -> Percussion Channel
deallocateChannel(9);
return 9;
}
void MidiParser_QT::deallocateFreeChannel() {
for (int i = 0; i < 16; i++) {
if (i != 9 && !_activeNotes[i]) {
// TODO: Improve this by looking for the channel with the longest
// time since the last note.
deallocateChannel(i);
return;
}
}
error("Exceeded QuickTime MIDI channel polyphony");
}
void MidiParser_QT::deallocateChannel(byte channel) {
for (ChannelMap::iterator it = _channelMap.begin(); it != _channelMap.end(); it++) {
if (it->_value == channel) {
_channelMap.erase(it);
return;
}
}
}
bool MidiParser_QT::isChannelAllocated(byte channel) const {
for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++)
if (it->_value == channel)
return true;
return false;
}
bool MidiParser_QT::allChannelsAllocated() const {
// Less than 15? We definitely have room
if (_channelMap.size() < 15)
return false;
// 15? One of the allocated channels might be the percussion one
if (_channelMap.size() == 15)
for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++)
if (it->_value == 9)
return false;
// 16 -> definitely all allocated
return true;
}
void MidiParser_QT::setupPart(uint32 part) {
PartStatus &status = _partMap[part];
byte channel = _channelMap[part];
EventInfo info;
// First, the program change
if (channel != 9) {
// 9 is always percussion
info.event = 0xC0 | channel;
info.basic.param1 = status.instrument;
_queuedEvents.push(info);
}
// Volume
info.event = 0xB0 | channel;
info.basic.param1 = 7;
info.basic.param2 = status.volume;
_queuedEvents.push(info);
// Pan
info.event = 0xB0 | channel;
info.basic.param1 = 10;
info.basic.param2 = status.pan;
_queuedEvents.push(info);
// Pitch Bend
info.event = 0xE0 | channel;
info.basic.param1 = status.pitchBend & 0x7F;
info.basic.param2 = status.pitchBend >> 7;
_queuedEvents.push(info);
}
void MidiParser_QT::resetTracking() {
MidiParser::resetTracking();
_channelMap.clear();
_queuedEvents.clear();
_partMap.clear();
}
Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format, uint32 descSize) {
if (track->codecType == CODEC_TYPE_MIDI) {
debug(0, "MIDI Codec FourCC '%s'", tag2str(format));
_fd->readUint32BE(); // flags, ignore
descSize -= 4;
MIDISampleDesc *entry = new MIDISampleDesc(track, format);
entry->_requestSize = descSize;
entry->_requestData = (byte *)malloc(descSize);
_fd->read(entry->_requestData, descSize);
return entry;
}
return 0;
}
MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) :
Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) {
}
void MidiParser_QT::initFromContainerTracks() {
const Common::Array<Common::QuickTimeParser::Track *> &tracks = Common::QuickTimeParser::_tracks;
for (uint32 i = 0; i < tracks.size(); i++) {
if (tracks[i]->codecType == CODEC_TYPE_MIDI) {
assert(tracks[i]->sampleDescs.size() == 1);
if (tracks[i]->editCount != 1)
warning("Unhandled QuickTime MIDI edit lists, things may go awry");
MIDITrackInfo trackInfo;
trackInfo.data = readWholeTrack(tracks[i], trackInfo.size);
trackInfo.timeScale = tracks[i]->timeScale;
_trackInfo.push_back(trackInfo);
}
}
initCommon();
}
void MidiParser_QT::initCommon() {
// Now we have all our info needed in _trackInfo from whatever container
// form, we can fill in the MidiParser tracks.
_numTracks = _trackInfo.size();
assert(_numTracks > 0);
for (uint32 i = 0; i < _trackInfo.size(); i++)
MidiParser::_tracks[i] = _trackInfo[i].data;
_ppqn = _trackInfo[0].timeScale;
resetTracking();
setTempo(1000000);
setTrack(0);
}
byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize) {
// This just goes through all chunks and appends them together
Common::MemoryWriteStreamDynamic output;
uint32 curSample = 0;
// Read in the note request data first
MIDISampleDesc *entry = (MIDISampleDesc *)track->sampleDescs[0];
output.write(entry->_requestData, entry->_requestSize);
for (uint i = 0; i < track->chunkCount; i++) {
_fd->seek(track->chunkOffsets[i]);
uint32 sampleCount = 0;
for (uint32 j = 0; j < track->sampleToChunkCount; j++)
if (i >= track->sampleToChunk[j].first)
sampleCount = track->sampleToChunk[j].count;
for (uint32 j = 0; j < sampleCount; j++, curSample++) {
uint32 size = (track->sampleSize != 0) ? track->sampleSize : track->sampleSizes[curSample];
byte *data = new byte[size];
_fd->read(data, size);
output.write(data, size);
delete[] data;
}
}
trackSize = output.size();
return output.getData();
}
uint32 MidiParser_QT::readUint32() {
uint32 value = READ_BE_UINT32(_position._playPos);
_position._playPos += 4;
return value;
}
MidiParser *MidiParser::createParser_QT() {
return new MidiParser_QT();
}

134
audio/midiparser_qt.h Normal file
View File

@ -0,0 +1,134 @@
/* 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 AUDIO_MIDIPARSER_QT_H
#define AUDIO_MIDIPARSER_QT_H
#include "audio/midiparser.h"
#include "common/array.h"
#include "common/hashmap.h"
#include "common/queue.h"
#include "common/quicktime.h"
/**
* The QuickTime Music version of MidiParser.
*
* QuickTime Music is actually a superset of MIDI. It has its own custom
* instruments and supports more than 15 non-percussion channels. It also
* has custom control changes and a more advanced pitch bend (which we
* convert to GM pitch bend as best as possible). We then use the fallback
* GM instrument that each QuickTime instrument definition has to provide.
*
* Furthermore, Apple's documentation on this is terrible. You know
* documentation is bad when it contradicts itself three times on the same
* subject (like about setting the GM instrument field to percussion).
*
* This is as close to a proper QuickTime Music parser as we can currently
* implement using our MidiParser interface.
*/
class MidiParser_QT : public MidiParser, public Common::QuickTimeParser {
public:
MidiParser_QT() {}
~MidiParser_QT() {}
// MidiParser
bool loadMusic(byte *data, uint32 size);
void unloadMusic();
/**
* Load the MIDI from a 'Tune' resource
*/
bool loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
/**
* Load the MIDI from a QuickTime stream
*/
bool loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
/**
* Load the MIDI from a QuickTime file
*/
bool loadFromContainerFile(const Common::String &fileName);
protected:
// MidiParser
void parseNextEvent(EventInfo &info);
void resetTracking();
// QuickTimeParser
SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize);
private:
struct MIDITrackInfo {
byte *data;
uint32 size;
uint32 timeScale;
};
struct PartStatus {
uint32 instrument;
byte volume;
byte pan;
uint16 pitchBend;
};
class MIDISampleDesc : public SampleDesc {
public:
MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag);
~MIDISampleDesc() {}
byte *_requestData;
uint32 _requestSize;
};
uint32 readNextEvent();
void handleGeneralEvent(uint32 control);
void handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart);
void handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length);
void definePart(uint32 part, uint32 instrument);
void setupPart(uint32 part);
byte getChannel(uint32 part);
bool isChannelAllocated(byte channel) const;
byte findFreeChannel(uint32 part);
void deallocateFreeChannel();
void deallocateChannel(byte channel);
bool allChannelsAllocated() const;
byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize);
Common::Array<MIDITrackInfo> _trackInfo;
Common::Queue<EventInfo> _queuedEvents;
typedef Common::HashMap<uint, PartStatus> PartMap;
PartMap _partMap;
typedef Common::HashMap<uint, byte> ChannelMap;
ChannelMap _channelMap;
void initFromContainerTracks();
void initCommon();
uint32 readUint32();
};
#endif

View File

@ -56,8 +56,10 @@ void MidiParser_SMF::property(int prop, int value) {
switch (prop) {
case mpMalformedPitchBends:
_malformedPitchBends = (value > 0);
break;
default:
MidiParser::property(prop, value);
break;
}
}

View File

@ -47,8 +47,13 @@ protected:
uint32 readVLQ2(byte * &data);
void parseNextEvent(EventInfo &info);
virtual void resetTracking() {
MidiParser::resetTracking();
_loopCount = -1;
}
public:
MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _callbackProc(proc), _callbackData(data) {}
MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _callbackProc(proc), _callbackData(data), _loopCount(-1) {}
~MidiParser_XMIDI() { }
bool loadMusic(byte *data, uint32 size);

View File

@ -171,9 +171,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);
@ -491,7 +491,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);

View File

@ -54,7 +54,6 @@ private:
NUM_CHANNELS = 16
};
OSystem *_syst;
Common::Mutex _mutex;
const uint _sampleRate;

View File

@ -105,7 +105,7 @@ inline uint32 pow2Fixed(int32 val) {
}
#endif
} // End of namespace
} // End of anonymous namespace
namespace Audio {
@ -211,7 +211,7 @@ void MaxTrax::interrupt() {
goto endOfEventLoop;
case 0xA0: // SPECIAL
switch (curEvent->stopTime >> 8){
switch (curEvent->stopTime >> 8) {
case 0x01: // SPECIAL_SYNC
_playerCtx.syncCallBack(curEvent->stopTime & 0xFF);
break;
@ -1032,6 +1032,6 @@ void MaxTrax::outPutEvent(const Event &ev, int num) {}
void MaxTrax::outPutScore(const Score &sc, int num) {}
#endif // #ifndef NDEBUG
} // End of namespace Audio
} // End of namespace Audio
#endif // #if defined(AUDIO_MODS_MAXTRAX_H)

View File

@ -214,6 +214,6 @@ private:
static void outPutEvent(const Event &ev, int num = -1);
static void outPutScore(const Score &sc, int num = -1);
};
} // End of namespace Audio
} // End of namespace Audio
#endif // !defined(AUDIO_MODS_MAXTRAX_H)

View File

@ -132,7 +132,14 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
Channel &ch = _voice[voice];
int16 *p = buffer;
int neededSamples = nSamples;
assert(ch.offset.int_off < ch.length);
// NOTE: A Protracker (or other module format) player might actually
// push the offset past the sample length in its interrupt(), in which
// case the first mixBuffer() call should not mix anything, and the loop
// should be triggered.
// Thus, doing an assert(ch.offset.int_off < ch.length) here is wrong.
// An example where this happens is a certain Protracker module played
// by the OS/2 version of Hopkins FBI.
// Mix the generated samples into the output buffer
neededSamples -= mixBuffer<stereo>(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning);

View File

@ -90,6 +90,14 @@ private:
public:
ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo);
Modules::Module *getModule() {
// Ordinarily, the Module is not meant to be seen outside of
// this class, but occasionally, it's useful to be able to
// manipulate it directly. The Hopkins engine uses this to
// repair a broken song.
return &_module;
}
private:
void interrupt();
@ -462,8 +470,12 @@ void ProtrackerStream::interrupt() {
namespace Audio {
AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) {
return new Modules::ProtrackerStream(stream, offs, rate, stereo);
AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo, Modules::Module **module) {
Modules::ProtrackerStream *protrackerStream = new Modules::ProtrackerStream(stream, offs, rate, stereo);
if (module) {
*module = protrackerStream->getModule();
}
return (AudioStream *)protrackerStream;
}
} // End of namespace Audio

View File

@ -26,6 +26,7 @@
* - agos
* - parallaction
* - gob
* - hopkins
*/
#ifndef AUDIO_MODS_PROTRACKER_H
@ -35,6 +36,10 @@ namespace Common {
class SeekableReadStream;
}
namespace Modules {
class Module;
}
namespace Audio {
class AudioStream;
@ -48,9 +53,10 @@ class AudioStream;
* @param stream the ReadStream from which to read the ProTracker data
* @param rate TODO
* @param stereo TODO
* @param module can be used to return the Module object (rarely useful)
* @return a new AudioStream, or NULL, if an error occurred
*/
AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true);
AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true, Modules::Module **module = 0);
} // End of namespace Audio

View File

@ -1095,7 +1095,7 @@ int Tfmx::doSfx(uint16 sfxIndex, bool unlockChannel) {
return -1;
}
} // End of namespace Audio
} // End of namespace Audio
// some debugging functions
#if 0

View File

@ -273,6 +273,6 @@ private:
void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3);
};
} // End of namespace Audio
} // End of namespace Audio
#endif // !defined(AUDIO_MODS_TFMX_H)

View File

@ -4,6 +4,7 @@ MODULE_OBJS := \
audiostream.o \
fmopl.o \
mididrv.o \
midiparser_qt.o \
midiparser_smf.o \
midiparser_xmidi.o \
midiparser.o \

File diff suppressed because it is too large Load Diff

View File

@ -127,12 +127,54 @@ int MidiDriver_FluidSynth::open() {
_synth = new_fluid_synth(_settings);
// In theory, this ought to reduce CPU load... but it doesn't make any
// noticeable difference for me, so disable it for now.
if (ConfMan.getBool("fluidsynth_chorus_activate")) {
fluid_synth_set_chorus_on(_synth, 1);
// fluid_synth_set_interp_method(_synth, -1, FLUID_INTERP_LINEAR);
// fluid_synth_set_reverb_on(_synth, 0);
// fluid_synth_set_chorus_on(_synth, 0);
int chorusNr = ConfMan.getInt("fluidsynth_chorus_nr");
double chorusLevel = (double)ConfMan.getInt("fluidsynth_chorus_level") / 100.0;
double chorusSpeed = (double)ConfMan.getInt("fluidsynth_chorus_speed") / 100.0;
double chorusDepthMs = (double)ConfMan.getInt("fluidsynth_chorus_depth") / 10.0;
Common::String chorusWaveForm = ConfMan.get("fluidsynth_chorus_waveform");
int chorusType = FLUID_CHORUS_MOD_SINE;
if (chorusWaveForm == "sine") {
chorusType = FLUID_CHORUS_MOD_SINE;
} else {
chorusType = FLUID_CHORUS_MOD_TRIANGLE;
}
fluid_synth_set_chorus(_synth, chorusNr, chorusLevel, chorusSpeed, chorusDepthMs, chorusType);
} else {
fluid_synth_set_chorus_on(_synth, 0);
}
if (ConfMan.getBool("fluidsynth_reverb_activate")) {
fluid_synth_set_reverb_on(_synth, 1);
double reverbRoomSize = (double)ConfMan.getInt("fluidsynth_reverb_roomsize") / 100.0;
double reverbDamping = (double)ConfMan.getInt("fluidsynth_reverb_damping") / 100.0;
int reverbWidth = ConfMan.getInt("fluidsynth_reverb_width");
double reverbLevel = (double)ConfMan.getInt("fluidsynth_reverb_level") / 100.0;
fluid_synth_set_reverb(_synth, reverbRoomSize, reverbDamping, reverbWidth, reverbLevel);
} else {
fluid_synth_set_reverb_on(_synth, 0);
}
Common::String interpolation = ConfMan.get("fluidsynth_misc_interpolation");
int interpMethod = FLUID_INTERP_4THORDER;
if (interpolation == "none") {
interpMethod = FLUID_INTERP_NONE;
} else if (interpolation == "linear") {
interpMethod = FLUID_INTERP_LINEAR;
} else if (interpolation == "4th") {
interpMethod = FLUID_INTERP_4THORDER;
} else if (interpolation == "7th") {
interpMethod = FLUID_INTERP_7THORDER;
}
fluid_synth_set_interp_method(_synth, -1, interpMethod);
const char *soundfont = ConfMan.get("soundfont").c_str();

View File

@ -147,17 +147,10 @@ private:
TownsMidiOutputChannel *_out;
uint8 *_instrument;
uint8 _prg;
uint8 _chanIndex;
uint8 _effectLevel;
uint8 _priority;
uint8 _ctrlVolume;
uint8 _tl;
uint8 _pan;
uint8 _panEff;
uint8 _percS;
int8 _transpose;
uint8 _fld_1f;
int8 _detune;
int8 _modWheel;
uint8 _sustain;
@ -659,9 +652,8 @@ const uint16 TownsMidiOutputChannel::_freqLSB[] = {
0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B
};
TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _prg(0), _chanIndex(chanIndex),
_effectLevel(0), _priority(0), _ctrlVolume(0), _tl(0), _pan(0), _panEff(0), _transpose(0), _percS(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0),
_fld_1f(0), _detune(0), _modWheel(0), _allocated(false) {
TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _chanIndex(chanIndex),
_priority(0), _tl(0), _transpose(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), _detune(0), _modWheel(0), _allocated(false) {
_instrument = new uint8[30];
memset(_instrument, 0, 30);
}

View File

@ -25,6 +25,7 @@
#ifdef USE_MT32EMU
#include "audio/softsynth/mt32/mt32emu.h"
#include "audio/softsynth/mt32/ROMInfo.h"
#include "audio/softsynth/emumidi.h"
#include "audio/musicplugin.h"
@ -47,6 +48,50 @@
#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) {
debug(4, fmt, list);
}
// 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 +102,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 +133,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 +145,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 +204,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 +274,7 @@ void MidiDriver_MT32::close() {
_mixer->stopHandle(_mixerSoundHandle);
_synth->close();
delete _synth;
_synth = NULL;
deleteMuntStructures();
}
void MidiDriver_MT32::generateSamples(int16 *data, int len) {

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -16,64 +16,97 @@
*/
#include "mt32emu.h"
#if MT32EMU_USE_REVERBMODEL == 1
#include "AReverbModel.h"
using namespace MT32Emu;
// 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
// Default reverb settings for modes 0-2
namespace MT32Emu {
static const unsigned int NUM_ALLPASSES = 6;
static const unsigned int NUM_DELAYS = 5;
// 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_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};
// 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_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 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_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_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;
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_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;
RingBuffer::RingBuffer(Bit32u newsize) {
index = 0;
size = newsize;
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;
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++;
@ -235,3 +271,7 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out
outRight++;
}
}
}
#endif

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -21,66 +21,67 @@
namespace MT32Emu {
struct AReverbSettings {
const Bit32u *allpassSizes;
const Bit32u *delaySizes;
const float *decayTimes;
const float *wetLevels;
float filtVal;
float damp1;
float damp2;
const Bit32u * const allpassSizes;
const Bit32u * const combSizes;
const Bit32u * const outLPositions;
const Bit32u * const outRPositions;
const Bit32u * const filterFactor;
const Bit32u * const decayTimes;
const Bit32u * const wetLevels;
const Bit32u lpfAmp;
};
class RingBuffer {
protected:
float *buffer;
Bit32u size;
const Bit32u size;
Bit32u index;
public:
RingBuffer(Bit32u size);
RingBuffer(const Bit32u size);
virtual ~RingBuffer();
float next();
bool isEmpty();
bool isEmpty() const;
void mute();
};
class AllpassFilter : public RingBuffer {
public:
AllpassFilter(Bit32u size);
float process(float in);
AllpassFilter(const Bit32u size);
float process(const float in);
};
class Delay : public RingBuffer {
class CombFilter : public RingBuffer {
float feedbackFactor;
float filterFactor;
public:
Delay(Bit32u size);
float process(float in);
CombFilter(const Bit32u size);
void process(const float in);
float getOutputAt(const Bit32u outIndex) const;
void setFeedbackFactor(const float useFeedbackFactor);
void setFilterFactor(const float useFilterFactor);
};
class AReverbModel : public ReverbModel {
AllpassFilter **allpasses;
Delay **delays;
CombFilter **combs;
const AReverbSettings *currentSettings;
float decayTime;
const AReverbSettings &currentSettings;
float lpfAmp;
float wetLevel;
float filterhist1, filterhist2;
float combhist;
void mute();
public:
AReverbModel(const AReverbSettings *newSettings);
AReverbModel(const ReverbMode mode);
~AReverbModel();
void open(unsigned int sampleRate);
void open();
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
bool isActive() const;
static const AReverbSettings REVERB_MODE_0_SETTINGS;
static const AReverbSettings REVERB_MODE_1_SETTINGS;
static const AReverbSettings REVERB_MODE_2_SETTINGS;
};
// Default reverb settings for modes 0-2
}
#endif

View File

@ -0,0 +1,393 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mt32emu.h"
#if MT32EMU_USE_REVERBMODEL == 2
#include "BReverbModel.h"
// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
// and followed by three parallel comb filters
namespace MT32Emu {
// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
// so we can simply increase the input buffer size.
static const Bit32u PROCESS_DELAY = 1;
static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb.
static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
static const Bit32u MODE_0_LPF_AMP = 0x60;
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
static const Bit32u MODE_1_LPF_AMP = 0x60;
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
static const Bit32u MODE_2_LPF_AMP = 0x80;
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
static const Bit32u MODE_3_COMB_FACTOR[] = {0x68};
static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation.
static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) {
Bit8u mask = 0x80;
Bit32s res = 0;
for (int i = 0; i < 8; i++) {
Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0;
a >>= 1;
res += (mask & addMask) > 0 ? a + carry : 0;
mask >>= 1;
}
return res;
}
RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) {
buffer = new Bit16s[size];
}
RingBuffer::~RingBuffer() {
delete[] buffer;
buffer = NULL;
}
Bit32s RingBuffer::next() {
if (++index >= size) {
index = 0;
}
return buffer[index];
}
bool RingBuffer::isEmpty() const {
if (buffer == NULL) return true;
Bit16s *buf = buffer;
for (Bit32u i = 0; i < size; i++) {
if (*buf < -8 || *buf > 8) return false;
buf++;
}
return true;
}
void RingBuffer::mute() {
Bit16s *buf = buffer;
for (Bit32u i = 0; i < size; i++) {
*buf++ = 0;
}
}
AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
Bit32s AllpassFilter::process(const Bit32s in) {
// This model corresponds to the allpass filter implementation of the real CM-32L device
// found from sample analysis
Bit16s bufferOut = next();
// store input - feedback / 2
buffer[index] = in - (bufferOut >> 1);
// return buffer output + feedforward / 2
return bufferOut + (buffer[index] >> 1);
}
CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {}
void CombFilter::process(const Bit32s in) {
// This model corresponds to the comb filter implementation of the real CM-32L device
// the previously stored value
Bit32s last = buffer[index];
// prepare input + feedback
Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */);
// store input + feedback processed by a low-pass filter
buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn;
}
Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const {
return buffer[(size + index - outIndex) % size];
}
void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) {
feedbackFactor = useFeedbackFactor;
}
DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp)
: CombFilter(useSize, useFilterFactor), amp(useAmp) {}
void DelayWithLowPassFilter::process(const Bit32s in) {
// the previously stored value
Bit32s last = buffer[index];
// move to the next index
next();
// low-pass filter process
Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in;
// store lpfOut multiplied by LPF amp factor
buffer[index] = weirdMul(lpfOut, amp, 0xFF);
}
TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {}
void TapDelayCombFilter::process(const Bit32s in) {
// the previously stored value
Bit32s last = buffer[index];
// move to the next index
next();
// prepare input + feedback
// Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0);
// store input + feedback processed by a low-pass filter
buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn;
}
Bit32s TapDelayCombFilter::getLeftOutput() const {
return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
}
Bit32s TapDelayCombFilter::getRightOutput() const {
return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
}
void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
outL = useOutL;
outR = useOutR;
}
BReverbModel::BReverbModel(const ReverbMode mode)
: allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {}
BReverbModel::~BReverbModel() {
close();
}
void BReverbModel::open() {
if (currentSettings.numberOfAllpasses > 0) {
allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses];
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
}
}
combs = new CombFilter*[currentSettings.numberOfCombs];
if (tapDelayMode) {
*combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors);
} else {
combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
}
}
mute();
}
void BReverbModel::close() {
if (allpasses != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
if (allpasses[i] != NULL) {
delete allpasses[i];
allpasses[i] = NULL;
}
}
delete[] allpasses;
allpasses = NULL;
}
if (combs != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
if (combs[i] != NULL) {
delete combs[i];
combs[i] = NULL;
}
}
delete[] combs;
combs = NULL;
}
}
void BReverbModel::mute() {
if (allpasses != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
allpasses[i]->mute();
}
}
if (combs != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
combs[i]->mute();
}
}
}
void BReverbModel::setParameters(Bit8u time, Bit8u level) {
if (combs == NULL) return;
level &= 7;
time &= 7;
if (tapDelayMode) {
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
} else {
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
}
}
if (time == 0 && level == 0) {
dryAmp = wetLevel = 0;
} else {
dryAmp = currentSettings.dryAmps[level];
wetLevel = currentSettings.wetLevels[level];
}
}
bool BReverbModel::isActive() const {
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
if (!allpasses[i]->isEmpty()) return true;
}
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
if (!combs[i]->isEmpty()) return true;
}
return false;
}
void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
Bit32s dry, link, outL1, outR1;
for (unsigned long i = 0; i < numSamples; i++) {
if (tapDelayMode) {
dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f);
} else {
dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2;
}
// Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
dry = weirdMul(dry, dryAmp, 0xFF);
if (tapDelayMode) {
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
comb->process(dry);
*outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f;
*outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f;
} else {
// Get the last stored sample before processing in order not to loose it
link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
// Entrance LPF. Note, comb.process() differs a bit here.
combs[0]->process(dry);
// This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
link = link - 1;
link = allpasses[0]->process(link);
link = allpasses[1]->process(link);
link = allpasses[2]->process(link);
// If the output position is equal to the comb size, get it now in order not to loose it
outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
outL1 += outL1 >> 1;
combs[1]->process(link);
combs[2]->process(link);
combs[3]->process(link);
link = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
link += link >> 1;
link += outL1;
link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
*outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
outR1 += outR1 >> 1;
link = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
link += link >> 1;
link += outR1;
link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
*outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
}
inLeft++;
inRight++;
outLeft++;
outRight++;
}
}
}
#endif

View File

@ -0,0 +1,112 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_B_REVERB_MODEL_H
#define MT32EMU_B_REVERB_MODEL_H
namespace MT32Emu {
struct BReverbSettings {
const Bit32u numberOfAllpasses;
const Bit32u * const allpassSizes;
const Bit32u numberOfCombs;
const Bit32u * const combSizes;
const Bit32u * const outLPositions;
const Bit32u * const outRPositions;
const Bit32u * const filterFactors;
const Bit32u * const feedbackFactors;
const Bit32u * const dryAmps;
const Bit32u * const wetLevels;
const Bit32u lpfAmp;
};
class RingBuffer {
protected:
Bit16s *buffer;
const Bit32u size;
Bit32u index;
public:
RingBuffer(const Bit32u size);
virtual ~RingBuffer();
Bit32s next();
bool isEmpty() const;
void mute();
};
class AllpassFilter : public RingBuffer {
public:
AllpassFilter(const Bit32u size);
Bit32s process(const Bit32s in);
};
class CombFilter : public RingBuffer {
protected:
const Bit32u filterFactor;
Bit32u feedbackFactor;
public:
CombFilter(const Bit32u size, const Bit32u useFilterFactor);
virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure
Bit32s getOutputAt(const Bit32u outIndex) const;
void setFeedbackFactor(const Bit32u useFeedbackFactor);
};
class DelayWithLowPassFilter : public CombFilter {
Bit32u amp;
public:
DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp);
void process(const Bit32s in);
void setFeedbackFactor(const Bit32u) {}
};
class TapDelayCombFilter : public CombFilter {
Bit32u outL;
Bit32u outR;
public:
TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor);
void process(const Bit32s in);
Bit32s getLeftOutput() const;
Bit32s getRightOutput() const;
void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR);
};
class BReverbModel : public ReverbModel {
AllpassFilter **allpasses;
CombFilter **combs;
const BReverbSettings &currentSettings;
const bool tapDelayMode;
Bit32u dryAmp;
Bit32u wetLevel;
void mute();
public:
BReverbModel(const ReverbMode mode);
~BReverbModel();
void open();
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
bool isActive() const;
};
}
#endif

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -20,15 +20,14 @@
#include "mt32emu.h"
#include "DelayReverb.h"
using namespace MT32Emu;
namespace MT32Emu {
// CONFIRMED: The values below are found via analysis of digital samples. Checked with all time and level combinations.
// CONFIRMED: The values below are found via analysis of digital samples and tracing reverb RAM address / data lines. Checked with all time and level combinations.
// Obviously:
// rightDelay = (leftDelay - 2) * 2 + 2
// echoDelay = rightDelay - 1
// Leaving these separate in case it's useful for work on other reverb modes...
const Bit32u REVERB_TIMINGS[8][3]= {
static const Bit32u REVERB_TIMINGS[8][3]= {
// {leftDelay, rightDelay, feedbackDelay}
{402, 802, 801},
{626, 1250, 1249},
@ -40,14 +39,16 @@ const Bit32u REVERB_TIMINGS[8][3]= {
{8002, 16002, 16001}
};
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);
}
@ -55,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() {
@ -92,59 +88,55 @@ void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) {
void DelayReverb::recalcParameters() {
// Number of samples between impulse and eventual appearance on the left channel
delayLeft = REVERB_TIMINGS[time][0] * sampleRate / 32000;
delayLeft = REVERB_TIMINGS[time][0];
// Number of samples between impulse and eventual appearance on the right channel
delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000;
delayRight = REVERB_TIMINGS[time][1];
// Number of samples between a response and that response feeding back/echoing
delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000;
delayFeedback = REVERB_TIMINGS[time][2];
if (time < 6) {
feedback = REVERB_FEEDBACK;
if (level < 3 || time < 6) {
feedback = REVERB_FEEDBACK / 256.0f;
} else {
feedback = REVERB_FEEDBACK67;
feedback = REVERB_FEEDBACK67 / 256.0f;
}
// Fading speed, i.e. amplitude ratio of neighbor responses
fade = REVERB_FADE[level];
// Overall output amp
amp = (level == 0 && time == 0) ? 0.0f : REVERB_AMP[level] / 65536.0f;
}
void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
if (buf == NULL) {
return;
}
if (buf == NULL) return;
for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) {
// The ring buffer write index moves backwards; reads are all done with positive offsets.
Bit32u bufIxPrev = (bufIx + 1) % bufSize;
Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize;
Bit32u bufIxRight = (bufIx + delayRight) % bufSize;
Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize;
Bit32u bufIxPrev = (bufIx + 1) % BUFFER_SIZE;
Bit32u bufIxLeft = (bufIx + delayLeft) % BUFFER_SIZE;
Bit32u bufIxRight = (bufIx + delayRight) % BUFFER_SIZE;
Bit32u bufIxFeedback = (bufIx + delayFeedback) % BUFFER_SIZE;
// Attenuated input samples and feedback response are directly added to the current ring buffer location
float sample = fade * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
float lpfIn = amp * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
// Single-pole IIR filter found on real devices
buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE;
buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn;
outLeft[sampleIx] = buf[bufIxLeft];
outRight[sampleIx] = buf[bufIxRight];
bufIx = (bufSize + bufIx - 1) % bufSize;
bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE;
}
}
bool DelayReverb::isActive() const {
// Quick hack: Return true iff all samples in the left buffer are the same and
// all samples in the right buffers are the same (within the sample output threshold).
if (buf == NULL) {
return false;
}
float last = buf[0] * 8192.0f;
for (unsigned int i = 1; i < bufSize; i++) {
float s = (buf[i] * 8192.0f);
if (fabs(s - last) > 1.0f) {
return true;
}
if (buf == NULL) return false;
float *b = buf;
float max = 0.001f;
for (Bit32u i = 0; i < BUFFER_SIZE; i++) {
if ((*b < -max) || (*b > max)) return true;
b++;
}
return false;
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -25,17 +25,14 @@ private:
Bit8u time;
Bit8u level;
unsigned int sampleRate;
Bit32u bufSize;
Bit32u bufIx;
float *buf;
Bit32u delayLeft;
Bit32u delayRight;
Bit32u delayFeedback;
float fade;
float amp;
float feedback;
void recalcParameters();
@ -43,7 +40,7 @@ private:
public:
DelayReverb();
~DelayReverb();
void open(unsigned int sampleRate);
void open();
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -20,7 +20,7 @@
#include "freeverb.h"
using namespace MT32Emu;
namespace MT32Emu {
FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) {
freeverb = NULL;
@ -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);
}
@ -76,3 +74,5 @@ bool FreeverbModel::isActive() const {
// FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon...
return false;
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -32,7 +32,7 @@ class FreeverbModel : public ReverbModel {
public:
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
~FreeverbModel();
void open(unsigned int sampleRate);
void open();
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -79,11 +79,16 @@ LA32Ramp::LA32Ramp() :
void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
// CONFIRMED: From sample analysis, this appears to be very accurate.
// FIXME: We could use a table for this in future
if (increment == 0) {
largeIncrement = 0;
} else {
largeIncrement = (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f);
// Three bits in the fractional part, no need to interpolate
// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
Bit32u expArg = increment & 0x7F;
largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511];
largeIncrement <<= expArg >> 3;
largeIncrement += 64;
largeIncrement >>= 9;
}
descending = (increment & 0x80) != 0;
if (descending) {

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

View File

@ -0,0 +1,418 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
#include "mt32emu.h"
#include "mmath.h"
#include "LA32WaveGenerator.h"
#if MT32EMU_ACCURATE_WG == 0
namespace MT32Emu {
static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;
static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18;
static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18;
static const Bit32u MAX_CUTOFF_VALUE = 240 << 18;
static const LogSample SILENCE = {65535, LogSample::POSITIVE};
Bit16u LA32Utilites::interpolateExp(const Bit16u fract) {
Bit16u expTabIndex = fract >> 3;
Bit16u extraBits = ~fract & 7;
Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex];
Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]);
return expTabEntry2 + (((expTabEntry1 - expTabEntry2) * extraBits) >> 3);
}
Bit16s LA32Utilites::unlog(const LogSample &logSample) {
//Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f);
Bit32u intLogValue = logSample.logValue >> 12;
Bit16u fracLogValue = logSample.logValue & 4095;
Bit16s sample = interpolateExp(fracLogValue) >> intLogValue;
return logSample.sign == LogSample::POSITIVE ? sample : -sample;
}
void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) {
Bit32u logSampleValue = logSample1.logValue + logSample2.logValue;
logSample1.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
Bit32u LA32WaveGenerator::getSampleStep() {
// sampleStep = EXP2F(pitch / 4096.0f + 4.0f)
Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
sampleStep <<= pitch >> 12;
sampleStep >>= 8;
sampleStep &= ~1;
return sampleStep;
}
Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) {
// resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f);
Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095);
resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12;
return resonanceWaveLengthFactor;
}
Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) {
// Ratio of positive segment to wave length
Bit32u effectivePulseWidthValue = 0;
if (pulseWidth > 128) {
effectivePulseWidthValue = (pulseWidth - 128) << 6;
}
Bit32u highLinearLength = 0;
// highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH;
if (effectivePulseWidthValue < effectiveCutoffValue) {
Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue;
highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095);
highLinearLength <<= 7 + (expArg >> 12);
highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH;
}
return highLinearLength;
}
void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) {
// Assuming 12-bit multiplication used here
squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4);
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
phase = POSITIVE_RISING_SINE_SEGMENT;
return;
}
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
if (squareWavePosition < highLinearLength) {
phase = POSITIVE_LINEAR_SEGMENT;
return;
}
squareWavePosition -= highLinearLength;
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
phase = POSITIVE_FALLING_SINE_SEGMENT;
return;
}
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
resonanceSinePosition = squareWavePosition;
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
phase = NEGATIVE_FALLING_SINE_SEGMENT;
return;
}
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
if (squareWavePosition < lowLinearLength) {
phase = NEGATIVE_LINEAR_SEGMENT;
return;
}
squareWavePosition -= lowLinearLength;
phase = NEGATIVE_RISING_SINE_SEGMENT;
}
void LA32WaveGenerator::advancePosition() {
wavePosition += getSampleStep();
wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH;
Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0;
Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue);
Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue);
Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength;
computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor);
// resonancePhase computation hack
*(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3;
}
void LA32WaveGenerator::generateNextSquareWaveLogSample() {
Bit32u logSampleValue;
switch (phase) {
case POSITIVE_RISING_SINE_SEGMENT:
case NEGATIVE_FALLING_SINE_SEGMENT:
logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511];
break;
case POSITIVE_FALLING_SINE_SEGMENT:
case NEGATIVE_RISING_SINE_SEGMENT:
logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511];
break;
case POSITIVE_LINEAR_SEGMENT:
case NEGATIVE_LINEAR_SEGMENT:
default:
logSampleValue = 0;
break;
}
logSampleValue <<= 2;
logSampleValue += amp >> 10;
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9;
}
squareLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
Bit32u logSampleValue;
if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) {
logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511];
} else {
logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511];
}
logSampleValue <<= 2;
logSampleValue += amp >> 10;
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1;
// Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
// The window is synchronous sine here
logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
} else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) {
// The window is synchronous square sine here
logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3;
}
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
} else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
// For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13;
logSampleValue += Tables::getInstance().logsin9[sineIx] << 2;
}
// After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures
logSampleValue -= 1 << 12;
resonanceLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const {
Bit32u sawtoothCosinePosition = wavePosition + (1 << 18);
if ((sawtoothCosinePosition & (1 << 18)) > 0) {
logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511];
} else {
logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511];
}
logSample.logValue <<= 2;
logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const {
Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1;
logSampleValue += amp >> 10;
logSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE;
}
void LA32WaveGenerator::generateNextPCMWaveLogSamples() {
// This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc.
// The most probable cause is the factor in the interpolation formula is one bit less
// accurate than the sample position counter
pcmInterpolationFactor = (wavePosition & 255) >> 1;
Bit32u pcmWaveTableIx = wavePosition >> 8;
pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
if (pcmWaveInterpolated) {
pcmWaveTableIx++;
if (pcmWaveTableIx < pcmWaveLength) {
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
} else {
if (pcmWaveLooped) {
pcmWaveTableIx -= pcmWaveLength;
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
} else {
secondPCMLogSample = SILENCE;
}
}
} else {
secondPCMLogSample = SILENCE;
}
// pcmSampleStep = (Bit32u)EXP2F(pitch / 4096.0f + 3.0f);
Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
pcmSampleStep <<= pitch >> 12;
// Seeing the actual lengths of the PCM wave for pitches 00..12,
// the pcmPosition counter can be assumed to have 8-bit fractions
pcmSampleStep >>= 9;
wavePosition += pcmSampleStep;
if (wavePosition >= (pcmWaveLength << 8)) {
if (pcmWaveLooped) {
wavePosition -= pcmWaveLength << 8;
} else {
deactivate();
}
}
}
void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
sawtoothWaveform = useSawtoothWaveform;
pulseWidth = usePulseWidth;
resonance = useResonance;
wavePosition = 0;
squareWavePosition = 0;
phase = POSITIVE_RISING_SINE_SEGMENT;
resonanceSinePosition = 0;
resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT;
resonanceAmpSubtraction = (32 - resonance) << 10;
resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
pcmWaveAddress = NULL;
active = true;
}
void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
pcmWaveAddress = usePCMWaveAddress;
pcmWaveLength = usePCMWaveLength;
pcmWaveLooped = usePCMWaveLooped;
pcmWaveInterpolated = usePCMWaveInterpolated;
wavePosition = 0;
active = true;
}
void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) {
if (!active) {
return;
}
amp = useAmp;
pitch = usePitch;
if (isPCMWave()) {
generateNextPCMWaveLogSamples();
return;
}
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
// More research is needed to be sure that this is correct, however.
cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal;
generateNextSquareWaveLogSample();
generateNextResonanceWaveLogSample();
if (sawtoothWaveform) {
LogSample cosineLogSample;
generateNextSawtoothCosineLogSample(cosineLogSample);
LA32Utilites::addLogSamples(squareLogSample, cosineLogSample);
LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample);
}
advancePosition();
}
LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const {
if (!isActive()) {
return SILENCE;
}
if (isPCMWave()) {
return first ? firstPCMLogSample : secondPCMLogSample;
}
return first ? squareLogSample : resonanceLogSample;
}
void LA32WaveGenerator::deactivate() {
active = false;
}
bool LA32WaveGenerator::isActive() const {
return active;
}
bool LA32WaveGenerator::isPCMWave() const {
return pcmWaveAddress != NULL;
}
Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
return pcmInterpolationFactor;
}
void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) {
ringModulated = useRingModulated;
mixed = useMixed;
}
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
if (useMaster == MASTER) {
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
} else {
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
}
}
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
if (useMaster == MASTER) {
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
} else {
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
}
}
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
if (useMaster == MASTER) {
master.generateNextSample(amp, pitch, cutoff);
} else {
slave.generateNextSample(amp, pitch, cutoff);
}
}
Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) {
if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) {
return 0;
}
LogSample firstLogSample = wg.getOutputLogSample(true);
LogSample secondLogSample = wg.getOutputLogSample(false);
if (ringModulatingLogSample != NULL) {
LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample);
LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample);
}
Bit16s firstSample = LA32Utilites::unlog(firstLogSample);
Bit16s secondSample = LA32Utilites::unlog(secondLogSample);
if (wg.isPCMWave()) {
return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7));
}
return firstSample + secondSample;
}
Bit16s LA32PartialPair::nextOutSample() {
if (ringModulated) {
LogSample slaveFirstLogSample = slave.getOutputLogSample(true);
LogSample slaveSecondLogSample = slave.getOutputLogSample(false);
Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample);
if (!slave.isPCMWave()) {
sample += unlogAndMixWGOutput(master, &slaveSecondLogSample);
}
if (mixed) {
sample += unlogAndMixWGOutput(master, NULL);
}
return sample;
}
return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL);
}
void LA32PartialPair::deactivate(const PairType useMaster) {
if (useMaster == MASTER) {
master.deactivate();
} else {
slave.deactivate();
}
}
bool LA32PartialPair::isActive(const PairType useMaster) const {
return useMaster == MASTER ? master.isActive() : slave.isActive();
}
}
#endif // #if MT32EMU_ACCURATE_WG == 0

View File

@ -0,0 +1,246 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if MT32EMU_ACCURATE_WG == 0
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
#define MT32EMU_LA32_WAVE_GENERATOR_H
namespace MT32Emu {
/**
* LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions
* It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these:
* - interpolation of exponent table (obvious, a delta value has 4 bits)
* - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion)
* - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip)
* and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss
* Subtraction is supposed to be replaced by simple inversion
* As the logarithmic sine is always negative, all the logarithmic values are treated as decrements
*/
struct LogSample {
// 16-bit fixed point value, includes 12-bit fractional part
// 4-bit integer part allows to present any 16-bit sample in the log-space
// Obviously, the log value doesn't contain the sign of the resulting sample
Bit16u logValue;
enum {
POSITIVE,
NEGATIVE
} sign;
};
class LA32Utilites {
public:
static Bit16u interpolateExp(const Bit16u fract);
static Bit16s unlog(const LogSample &logSample);
static void addLogSamples(LogSample &logSample1, const LogSample &logSample2);
};
/**
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
* The output square wave is created by adding high / low linear segments in-between
* the rising and falling cosine segments. Basically, its very similar to the phase distortion synthesis.
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
*/
class LA32WaveGenerator {
//***************************************************************************
// The local copy of partial parameters below
//***************************************************************************
bool active;
// True means the resulting square wave is to be multiplied by the synchronous cosine
bool sawtoothWaveform;
// Logarithmic amp of the wave generator
Bit32u amp;
// Logarithmic frequency of the resulting wave
Bit16u pitch;
// Values in range [1..31]
// Value 1 correspong to the minimum resonance
Bit8u resonance;
// Processed value in range [0..255]
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
Bit8u pulseWidth;
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
Bit32u cutoffVal;
// Logarithmic PCM sample start address
const Bit16s *pcmWaveAddress;
// Logarithmic PCM sample length
Bit32u pcmWaveLength;
// true for looped logarithmic PCM samples
bool pcmWaveLooped;
// false for slave PCM partials in the structures with the ring modulation
bool pcmWaveInterpolated;
//***************************************************************************
// Internal variables below
//***************************************************************************
// Relative position within either the synth wave or the PCM sampled wave
// 0 - start of the positive rising sine segment of the square wave or start of the PCM sample
// 1048576 (2^20) - end of the negative rising sine segment of the square wave
// For PCM waves, the address of the currently playing sample equals (wavePosition / 256)
Bit32u wavePosition;
// Relative position within a square wave phase:
// 0 - start of the phase
// 262144 (2^18) - end of a sine phase in the square wave
Bit32u squareWavePosition;
// Relative position within the positive or negative wave segment:
// 0 - start of the corresponding positive or negative segment of the square wave
// 262144 (2^18) - corresponds to end of the first sine phase in the square wave
// The same increment sampleStep is used to indicate the current position
// since the length of the resonance wave is always equal to four square wave sine segments.
Bit32u resonanceSinePosition;
// The amp of the resonance sine wave grows with the resonance value
// As the resonance value cannot change while the partial is active, it is initialised once
Bit32u resonanceAmpSubtraction;
// The decay speed of resonance sine wave, depends on the resonance value
Bit32u resAmpDecayFactor;
// Fractional part of the pcmPosition
Bit32u pcmInterpolationFactor;
// Current phase of the square wave
enum {
POSITIVE_RISING_SINE_SEGMENT,
POSITIVE_LINEAR_SEGMENT,
POSITIVE_FALLING_SINE_SEGMENT,
NEGATIVE_FALLING_SINE_SEGMENT,
NEGATIVE_LINEAR_SEGMENT,
NEGATIVE_RISING_SINE_SEGMENT
} phase;
// Current phase of the resonance wave
enum {
POSITIVE_RISING_RESONANCE_SINE_SEGMENT,
POSITIVE_FALLING_RESONANCE_SINE_SEGMENT,
NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT,
NEGATIVE_RISING_RESONANCE_SINE_SEGMENT
} resonancePhase;
// Resulting log-space samples of the square and resonance waves
LogSample squareLogSample;
LogSample resonanceLogSample;
// Processed neighbour log-space samples of the PCM wave
LogSample firstPCMLogSample;
LogSample secondPCMLogSample;
//***************************************************************************
// Internal methods below
//***************************************************************************
Bit32u getSampleStep();
Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue);
Bit32u getHighLinearLength(Bit32u effectiveCutoffValue);
void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor);
void advancePosition();
void generateNextSquareWaveLogSample();
void generateNextResonanceWaveLogSample();
void generateNextSawtoothCosineLogSample(LogSample &logSample) const;
void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const;
void generateNextPCMWaveLogSamples();
public:
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards
LogSample getOutputLogSample(const bool first) const;
// Deactivate the WG engine
void deactivate();
// Return active state of the WG engine
bool isActive() const;
// Return true if the WG engine generates PCM wave samples
bool isPCMWave() const;
// Return current PCM interpolation factor
Bit32u getPCMInterpolationFactor() const;
};
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
class LA32PartialPair {
LA32WaveGenerator master;
LA32WaveGenerator slave;
bool ringModulated;
bool mixed;
static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample);
public:
enum PairType {
MASTER,
SLAVE
};
// ringModulated should be set to false for the structures with mixing or stereo output
// ringModulated should be set to true for the structures with ring modulation
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
void init(const bool ringModulated, const bool mixed);
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// Perform mixing / ring modulation and return the result
Bit16s nextOutSample();
// Deactivate the WG engine
void deactivate(const PairType master);
// Return active state of the WG engine
bool isActive(const PairType master) const;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
#endif // #if MT32EMU_ACCURATE_WG == 0

View File

@ -0,0 +1,347 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
#include "mt32emu.h"
#include "mmath.h"
#include "LegacyWaveGenerator.h"
#if MT32EMU_ACCURATE_WG == 1
namespace MT32Emu {
static const float MIDDLE_CUTOFF_VALUE = 128.0f;
static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f;
static const float MAX_CUTOFF_VALUE = 240.0f;
float LA32WaveGenerator::getPCMSample(unsigned int position) {
if (position >= pcmWaveLength) {
if (!pcmWaveLooped) {
return 0;
}
position = position % pcmWaveLength;
}
Bit16s pcmSample = pcmWaveAddress[position];
float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f);
return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue;
}
void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
this->sawtoothWaveform = sawtoothWaveform;
this->pulseWidth = pulseWidth;
this->resonance = resonance;
wavePos = 0.0f;
lastFreq = 0.0f;
pcmWaveAddress = NULL;
active = true;
}
void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) {
this->pcmWaveAddress = pcmWaveAddress;
this->pcmWaveLength = pcmWaveLength;
this->pcmWaveLooped = pcmWaveLooped;
this->pcmWaveInterpolated = pcmWaveInterpolated;
pcmPosition = 0.0f;
active = true;
}
float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) {
if (!active) {
return 0.0f;
}
this->amp = amp;
this->pitch = pitch;
float sample = 0.0f;
// SEMI-CONFIRMED: From sample analysis:
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
// This gives results within +/- 2 at the output (before any DAC bitshifting)
// when sustaining at levels 156 - 255 with no modifiers.
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
// positive amps, so negative still needs to be explored, as well as lower levels.
//
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
float amp = EXP2F(ampVal / -1024.0f / 4096.0f);
float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
if (isPCMWave()) {
// Render PCM waveform
int len = pcmWaveLength;
int intPCMPosition = (int)pcmPosition;
if (intPCMPosition >= len && !pcmWaveLooped) {
// We're now past the end of a non-looping PCM waveform so it's time to die.
deactivate();
return 0.0f;
}
float positionDelta = freq * 2048.0f / SAMPLE_RATE;
// Linear interpolation
float firstSample = getPCMSample(intPCMPosition);
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
if (pcmWaveInterpolated) {
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
} else {
sample = firstSample;
}
float newPCMPosition = pcmPosition + positionDelta;
if (pcmWaveLooped) {
newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength);
}
pcmPosition = newPCMPosition;
} else {
// Render synthesised waveform
wavePos *= lastFreq / freq;
lastFreq = freq;
float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f);
{
//static const float resAmpFactor = EXP2F(-7);
//resAmp = EXP2I(resonance << 10) * resAmpFactor;
}
// The cutoffModifier may not be supposed to be directly added to the cutoff -
// it may for example need to be multiplied in some way.
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
// More research is needed to be sure that this is correct, however.
float cutoffVal = cutoffRampVal / 262144.0f;
if (cutoffVal > MAX_CUTOFF_VALUE) {
cutoffVal = MAX_CUTOFF_VALUE;
}
// Wave length in samples
float waveLen = SAMPLE_RATE / freq;
// Init cosineLen
float cosineLen = 0.5f * waveLen;
if (cutoffVal > MIDDLE_CUTOFF_VALUE) {
cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis
}
// Start playing in center of first cosine segment
// relWavePos is shifted by a half of cosineLen
float relWavePos = wavePos + 0.5f * cosineLen;
if (relWavePos > waveLen) {
relWavePos -= waveLen;
}
// Ratio of positive segment to wave length
float pulseLen = 0.5f;
if (pulseWidth > 128) {
pulseLen = EXP2F((64 - pulseWidth) / 64.0f);
//static const float pulseLenFactor = EXP2F(-192 / 64);
//pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
}
pulseLen *= waveLen;
float hLen = pulseLen - cosineLen;
// Ignore pulsewidths too high for given freq
if (hLen < 0.0f) {
hLen = 0.0f;
}
// Ignore pulsewidths too high for given freq and cutoff
float lLen = waveLen - hLen - 2 * cosineLen;
if (lLen < 0.0f) {
lLen = 0.0f;
}
// Correct resAmp for cutoff in range 50..66
if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
}
// Produce filtered square wave with 2 cosine waves on slopes
// 1st cosine segment
if (relWavePos < cosineLen) {
sample = -cos(FLOAT_PI * relWavePos / cosineLen);
} else
// high linear segment
if (relWavePos < (cosineLen + hLen)) {
sample = 1.f;
} else
// 2nd cosine segment
if (relWavePos < (2 * cosineLen + hLen)) {
sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
} else {
// low linear segment
sample = -1.f;
}
if (cutoffVal < 128.0f) {
// Attenuate samples below cutoff 50
// Found by sample analysis
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
} else {
// Add resonance sine. Effective for cutoff > 50 only
float resSample = 1.0f;
// Resonance decay speed factor
float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2];
// Now relWavePos counts from the middle of first cosine
relWavePos = wavePos;
// negative segments
if (!(relWavePos < (cosineLen + hLen))) {
resSample = -resSample;
relWavePos -= cosineLen + hLen;
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
resAmpDecayFactor += 0.25f;
}
// Resonance sine WG
resSample *= sin(FLOAT_PI * relWavePos / cosineLen);
// Resonance sine amp
float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact
float resAmpFade = EXP2F(resAmpFadeLog2);
// Now relWavePos set negative to the left from center of any cosine
relWavePos = wavePos;
// negative segment
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
relWavePos -= waveLen;
} else
// positive segment
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
relWavePos -= cosineLen + hLen;
}
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
if (relWavePos < 0.5f * cosineLen) {
float syncSine = sin(FLOAT_PI * relWavePos / cosineLen);
if (relWavePos < 0.0f) {
// The window is synchronous square sine here
resAmpFade *= syncSine * syncSine;
} else {
// The window is synchronous sine here
resAmpFade *= syncSine;
}
}
sample += resSample * resAmp * resAmpFade;
}
// sawtooth waves
if (sawtoothWaveform) {
sample *= cos(FLOAT_2PI * wavePos / waveLen);
}
wavePos++;
// wavePos isn't supposed to be > waveLen
if (wavePos > waveLen) {
wavePos -= waveLen;
}
}
// Multiply sample with current TVA value
sample *= amp;
return sample;
}
void LA32WaveGenerator::deactivate() {
active = false;
}
bool LA32WaveGenerator::isActive() const {
return active;
}
bool LA32WaveGenerator::isPCMWave() const {
return pcmWaveAddress != NULL;
}
void LA32PartialPair::init(const bool ringModulated, const bool mixed) {
this->ringModulated = ringModulated;
this->mixed = mixed;
masterOutputSample = 0.0f;
slaveOutputSample = 0.0f;
}
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
if (useMaster == MASTER) {
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
} else {
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
}
}
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
if (useMaster == MASTER) {
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
} else {
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
}
}
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
if (useMaster == MASTER) {
masterOutputSample = master.generateNextSample(amp, pitch, cutoff);
} else {
slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff);
}
}
Bit16s LA32PartialPair::nextOutSample() {
float outputSample;
if (ringModulated) {
float ringModulatedSample = masterOutputSample * slaveOutputSample;
outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
} else {
outputSample = masterOutputSample + slaveOutputSample;
}
return Bit16s(outputSample * 8192.0f);
}
void LA32PartialPair::deactivate(const PairType useMaster) {
if (useMaster == MASTER) {
master.deactivate();
masterOutputSample = 0.0f;
} else {
slave.deactivate();
slaveOutputSample = 0.0f;
}
}
bool LA32PartialPair::isActive(const PairType useMaster) const {
return useMaster == MASTER ? master.isActive() : slave.isActive();
}
}
#endif // #if MT32EMU_ACCURATE_WG == 1

View File

@ -0,0 +1,146 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if MT32EMU_ACCURATE_WG == 1
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
#define MT32EMU_LA32_WAVE_GENERATOR_H
namespace MT32Emu {
/**
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
* The output square wave is created by adding high / low linear segments in-between
* the rising and falling cosine segments. Basically, its very similar to the phase distortion synthesis.
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
*/
class LA32WaveGenerator {
//***************************************************************************
// The local copy of partial parameters below
//***************************************************************************
bool active;
// True means the resulting square wave is to be multiplied by the synchronous cosine
bool sawtoothWaveform;
// Logarithmic amp of the wave generator
Bit32u amp;
// Logarithmic frequency of the resulting wave
Bit16u pitch;
// Values in range [1..31]
// Value 1 correspong to the minimum resonance
Bit8u resonance;
// Processed value in range [0..255]
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
Bit8u pulseWidth;
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
Bit32u cutoffVal;
// Logarithmic PCM sample start address
const Bit16s *pcmWaveAddress;
// Logarithmic PCM sample length
Bit32u pcmWaveLength;
// true for looped logarithmic PCM samples
bool pcmWaveLooped;
// false for slave PCM partials in the structures with the ring modulation
bool pcmWaveInterpolated;
//***************************************************************************
// Internal variables below
//***************************************************************************
float wavePos;
float lastFreq;
float pcmPosition;
float getPCMSample(unsigned int position);
public:
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// Deactivate the WG engine
void deactivate();
// Return active state of the WG engine
bool isActive() const;
// Return true if the WG engine generates PCM wave samples
bool isPCMWave() const;
};
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
class LA32PartialPair {
LA32WaveGenerator master;
LA32WaveGenerator slave;
bool ringModulated;
bool mixed;
float masterOutputSample;
float slaveOutputSample;
public:
enum PairType {
MASTER,
SLAVE
};
// ringModulated should be set to false for the structures with mixing or stereo output
// ringModulated should be set to true for the structures with ring modulation
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
void init(const bool ringModulated, const bool mixed);
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// Perform mixing / ring modulation and return the result
Bit16s nextOutSample();
// Deactivate the WG engine
void deactivate(const PairType master);
// Return active state of the WG engine
bool isActive(const PairType master) const;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
#endif // #if MT32EMU_ACCURATE_WG == 1

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -68,18 +68,16 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) {
activePartialCount = 0;
memset(patchCache, 0, sizeof(patchCache));
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
freePolys.push_front(new Poly(this));
freePolys.prepend(new Poly(this));
}
}
Part::~Part() {
while (!activePolys.empty()) {
delete activePolys.front();
activePolys.pop_front();
while (!activePolys.isEmpty()) {
delete activePolys.takeFirst();
}
while (!freePolys.empty()) {
delete freePolys.front();
freePolys.pop_front();
while (!freePolys.isEmpty()) {
delete freePolys.takeFirst();
}
}
@ -209,6 +207,7 @@ void RhythmPart::setTimbre(TimbreParam * /*timbre*/) {
void Part::setTimbre(TimbreParam *timbre) {
*timbreTemp = *timbre;
synth->newTimbreSet(partNum, timbre->common.name);
}
unsigned int RhythmPart::getAbsTimbreNum() const {
@ -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,16 +533,20 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
#if MT32EMU_MONITOR_PARTIALS > 1
synth->printPartialUsage();
#endif
synth->partStateChanged(partNum, true);
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;
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed
// applies to AllNotesOff.
poly->noteOff(holdpedal);
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.
if (poly->canSustain()) {
poly->noteOff(holdpedal);
}
}
}
@ -554,15 +554,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();
}
}
@ -580,8 +578,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)) {
@ -602,8 +599,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();
}
@ -615,7 +611,103 @@ void Part::partialDeactivated(Poly *poly) {
activePartialCount--;
if (!poly->isActive()) {
activePolys.remove(poly);
freePolys.push_front(poly);
freePolys.prepend(poly);
synth->polyStateChanged(partNum);
}
if (activePartialCount == 0) {
synth->partStateChanged(partNum, false);
}
}
//#define POLY_LIST_DEBUG
PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}
bool PolyList::isEmpty() const {
#ifdef POLY_LIST_DEBUG
if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) {
printf("PolyList: desynchronised firstPoly & lastPoly pointers\n");
}
#endif
return firstPoly == NULL && lastPoly == NULL;
}
Poly *PolyList::getFirst() const {
return firstPoly;
}
Poly *PolyList::getLast() const {
return lastPoly;
}
void PolyList::prepend(Poly *poly) {
#ifdef POLY_LIST_DEBUG
if (poly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n");
}
#endif
poly->setNext(firstPoly);
firstPoly = poly;
if (lastPoly == NULL) {
lastPoly = poly;
}
}
void PolyList::append(Poly *poly) {
#ifdef POLY_LIST_DEBUG
if (poly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n");
}
#endif
poly->setNext(NULL);
if (lastPoly != NULL) {
#ifdef POLY_LIST_DEBUG
if (lastPoly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in the lastPoly\n");
}
#endif
lastPoly->setNext(poly);
}
lastPoly = poly;
if (firstPoly == NULL) {
firstPoly = poly;
}
}
Poly *PolyList::takeFirst() {
Poly *oldFirst = firstPoly;
firstPoly = oldFirst->getNext();
if (firstPoly == NULL) {
#ifdef POLY_LIST_DEBUG
if (lastPoly != oldFirst) {
printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n");
}
#endif
lastPoly = NULL;
}
oldFirst->setNext(NULL);
return oldFirst;
}
void PolyList::remove(Poly * const polyToRemove) {
if (polyToRemove == firstPoly) {
takeFirst();
return;
}
for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) {
if (poly->getNext() == polyToRemove) {
if (polyToRemove == lastPoly) {
#ifdef POLY_LIST_DEBUG
if (lastPoly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in the lastPoly\n");
}
#endif
lastPoly = poly;
}
poly->setNext(polyToRemove->getNext());
polyToRemove->setNext(NULL);
break;
}
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -18,13 +18,27 @@
#ifndef MT32EMU_PART_H
#define MT32EMU_PART_H
#include <common/list.h>
namespace MT32Emu {
class PartialManager;
class Synth;
class PolyList {
private:
Poly *firstPoly;
Poly *lastPoly;
public:
PolyList();
bool isEmpty() const;
Poly *getFirst() const;
Poly *getLast() const;
void prepend(Poly *poly);
void append(Poly *poly);
Poly *takeFirst();
void remove(Poly * const poly);
};
class Part {
private:
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
@ -37,8 +51,8 @@ private:
unsigned int activePartialCount;
PatchCache patchCache[4];
Common::List<Poly *> freePolys;
Common::List<Poly *> activePolys;
PolyList freePolys;
PolyList activePolys;
void setPatch(const PatchParam *patch);
unsigned int midiKeyToKey(unsigned int midiKey);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -22,7 +22,7 @@
#include "mt32emu.h"
#include "mmath.h"
using namespace MT32Emu;
namespace MT32Emu {
#ifdef INACCURATE_SMOOTH_PAN
// Mok wanted an option for smoother panning, and we love Mok.
@ -35,7 +35,12 @@ static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f};
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0), tva(new TVA(this, &ampRamp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) {
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) {
// Initialisation of tva, tvp and tvf uses 'this' pointer
// and thus should not be in the initializer list to avoid a compiler warning
tva = new TVA(this, &ampRamp);
tvp = new TVP(this);
tvf = new TVF(this, &cutoffModifierRamp);
ownerPart = -1;
poly = NULL;
pair = NULL;
@ -81,26 +86,23 @@ void Partial::deactivate() {
ownerPart = -1;
if (poly != NULL) {
poly->partialDeactivated(this);
if (pair != NULL) {
pair->pair = NULL;
}
}
synth->partialStateChanged(this, tva->getPhase(), TVA_PHASE_DEAD);
#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;
}
}
@ -133,6 +135,25 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
stereoVolume.leftVol = panVal / 7.0f;
stereoVolume.rightVol = 1.0f - stereoVolume.leftVol;
// SEMI-CONFIRMED: From sample analysis:
// Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways.
// Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated.
// It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens.
// Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close.
// In this case that timbre can sound totally different depending of the way it is mixed up.
// Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4).
// Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently.
// Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one.
// The situation is better with 4-partial timbres since then a whole quarter is assigned for each poly. However, if a 3-partial timbre broke the normal
// whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently.
// This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02.
// For my personal taste, this behaviour rather enriches the sounding and should be emulated.
// Also, the current partial allocator model probably needs to be refined.
if (debugPartialNum & 8) {
stereoVolume.leftVol = -stereoVolume.leftVol;
stereoVolume.rightVol = -stereoVolume.rightVol;
}
if (patchCache->PCMPartial) {
pcmNum = patchCache->pcm;
if (synth->controlROMMap->pcmCount > 128) {
@ -144,37 +165,75 @@ 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
pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + synth->tables.pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth];
pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + Tables::getInstance().pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth];
if (pulseWidthVal < 0) {
pulseWidthVal = 0;
} else if (pulseWidthVal > 255) {
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) {
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;
}
@ -182,279 +241,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;
}
// 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((32772 - ampRampVal / 2048) / -2048.0f);
Bit16u pitch = tvp->nextPitch();
float freq = synth->tables.pitchToFreq[pitch];
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];
float nextSample = getPCMSample(intPCMPosition + 1);
sample = firstSample + (nextSample - firstSample) * (pcmPosition - intPCMPosition);
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;
// EXP2F(1.0f - (32 - res) / 4.0f);
float resAmp = synth->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
cosineLen *= synth->tables.cutoffToCosineLen[Bit32u((cutoffVal - 128.0f) * 8.0f)];
#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 += synth->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 *= synth->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 = -synth->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 = synth->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
sample *= synth->tables.cutoffToFilterAmp[Bit32u(cutoffVal * 8.0f)];
#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 *= synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095];
#endif
// Resonance sine amp
float resAmpFade = EXP2F(-synth->tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen)); // seems to be exact
// 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 + synth->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 *= synth->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);
}
@ -486,54 +297,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 == 1) {
mixBuffersRingMix(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated);
} else {
mixBuffersRing(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated);
}
}
}
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;
}
@ -555,3 +326,5 @@ void Partial::startDecayAll() {
tvp->startDecay();
tvf->startDecay();
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -44,12 +44,7 @@ private:
int structurePosition; // 0 or 1 of a structure pair
StereoVolume stereoVolume;
// Distance in (possibly fractional) samples from the start of the current pulse
float wavePos;
float lastFreq;
float myBuffer[MAX_SAMPLES_PER_RUN];
Bit16s myBuffer[MAX_SAMPLES_PER_RUN];
// Only used for PCM partials
int pcmNum;
@ -60,17 +55,16 @@ private:
// Range: 0-255
int pulseWidthVal;
float pcmPosition;
Poly *poly;
LA32Ramp ampRamp;
LA32Ramp cutoffModifierRamp;
float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len);
float *mixBuffersRing(float *buf1, float *buf2, unsigned long len);
// TODO: This should be owned by PartialPair
LA32PartialPair la32Pair;
float getPCMSample(unsigned int position);
Bit32u getAmpValue();
Bit32u getCutoffValue();
public:
const PatchCache *patchCache;
@ -90,7 +84,6 @@ public:
unsigned long debugGetSampleNum() const;
int getOwnerPart() const;
int getKey() const;
const Poly *getPoly() const;
bool isActive() const;
void activate(int part);
@ -111,7 +104,7 @@ public:
bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length);
// This function writes mono sample output to the provided buffer, and returns the number of samples written
unsigned long generateSamples(float *partialBuf, unsigned long length);
unsigned long generateSamples(Bit16s *partialBuf, unsigned long length);
};
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -20,7 +20,7 @@
#include "mt32emu.h"
#include "PartialManager.h"
using namespace MT32Emu;
namespace MT32Emu {
PartialManager::PartialManager(Synth *useSynth, Part **useParts) {
synth = useSynth;
@ -248,3 +248,5 @@ const Partial *PartialManager::getPartial(unsigned int partialNum) const {
}
return partialTable[partialNum];
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -29,6 +29,7 @@ Poly::Poly(Part *usePart) {
partials[i] = NULL;
}
state = POLY_Inactive;
next = NULL;
}
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
@ -58,6 +59,9 @@ bool Poly::noteOff(bool pedalHeld) {
return false;
}
if (pedalHeld) {
if (state == POLY_Held) {
return false;
}
state = POLY_Held;
} else {
startDecay();
@ -171,4 +175,12 @@ void Poly::partialDeactivated(Partial *partial) {
part->partialDeactivated(this);
}
Poly *Poly::getNext() {
return next;
}
void Poly::setNext(Poly *poly) {
next = poly;
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -41,6 +41,8 @@ private:
Partial *partials[4];
Poly *next;
public:
Poly(Part *part);
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
@ -60,6 +62,9 @@ public:
bool isActive() const;
void partialDeactivated(Partial *partial);
Poly *getNext();
void setNext(Poly *poly);
};
}

View File

@ -0,0 +1,111 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cstring>
#include "ROMInfo.h"
namespace MT32Emu {
// Known ROMs
static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, NULL};
static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL, NULL};
static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL, NULL};
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL, NULL};
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL, NULL};
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL, NULL};
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL, NULL};
static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL, NULL};
static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL, NULL};
static const ROMInfo * const ROM_INFOS[] = {
&CTRL_MT32_V1_04,
&CTRL_MT32_V1_05,
&CTRL_MT32_V1_06,
&CTRL_MT32_V1_07,
&CTRL_MT32_BLUER,
&CTRL_CM32L_V1_00,
&CTRL_CM32L_V1_02,
&PCM_MT32,
&PCM_CM32L,
NULL};
const ROMInfo* ROMInfo::getROMInfo(Common::File *file) {
size_t fileSize = file->size();
// We haven't added the SHA1 checksum code in ScummVM, as the file size
// suffices for our needs for now.
//const char *fileDigest = file->getSHA1();
for (int i = 0; ROM_INFOS[i] != NULL; i++) {
const ROMInfo *romInfo = ROM_INFOS[i];
if (fileSize == romInfo->fileSize /*&& !strcmp(fileDigest, romInfo->sha1Digest)*/) {
return romInfo;
}
}
return NULL;
}
void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
(void) romInfo;
}
static int getROMCount() {
int count;
for(count = 0; ROM_INFOS[count] != NULL; count++) {
}
return count;
}
const ROMInfo** ROMInfo::getROMInfoList(unsigned int types, unsigned int pairTypes) {
const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1];
const ROMInfo **currentROMInList = romInfoList;
for(int i = 0; ROM_INFOS[i] != NULL; i++) {
const ROMInfo *romInfo = ROM_INFOS[i];
if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) {
*currentROMInList++ = romInfo;
}
}
*currentROMInList = NULL;
return romInfoList;
}
void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
delete[] romInfoList;
}
const ROMImage* ROMImage::makeROMImage(Common::File *file) {
ROMImage *romImage = new ROMImage;
romImage->file = file;
romImage->romInfo = ROMInfo::getROMInfo(romImage->file);
return romImage;
}
void ROMImage::freeROMImage(const ROMImage *romImage) {
ROMInfo::freeROMInfo(romImage->romInfo);
delete romImage;
}
Common::File* ROMImage::getFile() const {
return file;
}
const ROMInfo* ROMImage::getROMInfo() const {
return romInfo;
}
}

View File

@ -0,0 +1,77 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_ROMINFO_H
#define MT32EMU_ROMINFO_H
//#include <cstddef>
#include "common/file.h"
namespace MT32Emu {
// Defines vital info about ROM file to be used by synth and applications
struct ROMInfo {
public:
size_t fileSize;
const char *sha1Digest;
enum Type {PCM, Control, Reverb} type;
const char *shortName;
const char *description;
enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType;
ROMInfo *pairROMInfo;
void *controlROMInfo;
// Returns a ROMInfo struct by inspecting the size and the SHA1 hash
static const ROMInfo* getROMInfo(Common::File *file);
// Currently no-op
static void freeROMInfo(const ROMInfo *romInfo);
// Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes
// (specified by bitmasks)
// Useful for GUI/console app to output information on what ROMs it supports
static const ROMInfo** getROMInfoList(unsigned int types, unsigned int pairTypes);
// Frees the list of ROMInfos given
static void freeROMInfoList(const ROMInfo **romInfos);
};
// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work
class ROMImage {
private:
Common::File *file;
const ROMInfo *romInfo;
public:
// Creates a ROMImage object given a ROMInfo and a File. Keeps a reference
// to the File and ROMInfo given, which must be freed separately by the user
// after the ROMImage is freed
static const ROMImage* makeROMImage(Common::File *file);
// Must only be done after all Synths using the ROMImage are deleted
static void freeROMImage(const ROMImage *romImage);
Common::File *getFile() const;
const ROMInfo *getROMInfo() const;
};
}
#endif

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -27,8 +27,10 @@
#include "mmath.h"
#include "PartialManager.h"
#if MT32EMU_USE_AREVERBMODEL == 1
#if MT32EMU_USE_REVERBMODEL == 1
#include "AReverbModel.h"
#elif MT32EMU_USE_REVERBMODEL == 2
#include "BReverbModel.h"
#else
#include "FreeverbModel.h"
#endif
@ -36,7 +38,7 @@
namespace MT32Emu {
const ControlROMMap ControlROMMaps[7] = {
static const ControlROMMap ControlROMMaps[7] = {
// ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax
{0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A},
{0x4014, 22, "\000 ver1.05 06 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A},
@ -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,49 @@ 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::partStateChanged(int partNum, bool isPartActive) {
reportHandler->onPartStateChanged(partNum, isPartActive);
}
void Synth::polyStateChanged(int partNum) {
reportHandler->onPolyStateChanged(partNum);
}
void Synth::partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase) {
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
if (getPartial(i) == partial) {
reportHandler->onPartialStateChanged(i, oldPartialPhase, newPartialPhase);
break;
}
}
}
void Synth::newTimbreSet(int partNum, char patchName[]) {
reportHandler->onProgramChanged(partNum, 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 +278,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 +345,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,26 +420,19 @@ 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
tables.init();
#if !MT32EMU_REDUCE_REVERB_MEMORY
for (int i = 0; i < 4; i++) {
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));
@ -441,12 +440,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();
@ -455,17 +452,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
@ -594,9 +589,6 @@ void Synth::close() {
parts[i] = NULL;
}
delete[] myProp.baseDir;
myProp.baseDir = NULL;
delete[] pcmWaves;
delete[] pcmROMData;
@ -627,6 +619,11 @@ void Synth::playMsg(Bit32u msg) {
return;
}
playMsgOnPart(part, code, note, velocity);
// This ensures minimum 1-sample delay between sequential MIDI events
// Without this, a sequence of NoteOn and immediately succeeding NoteOff messages is always silent
// Technically, it's also impossible to send events through the MIDI interface faster than about each millisecond
prerender();
}
void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) {
@ -1178,7 +1175,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.
@ -1216,7 +1213,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();
@ -1244,9 +1241,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
@ -1254,7 +1251,7 @@ void Synth::refreshSystemReverbParameters() {
if (reverbModel != NULL) {
reverbModel->close();
}
newReverbModel->open(myProp.sampleRate);
newReverbModel->open();
}
#endif
reverbModel = newReverbModel;
@ -1309,7 +1306,7 @@ void Synth::reset() {
#if MT32EMU_MONITOR_SYSEX > 0
printDebug("RESET");
#endif
report(ReportType_devReset, NULL);
reportHandler->onDeviceReset();
partialManager->deactivateAll();
mt32ram = mt32default;
for (int i = 0; i < 9; i++) {

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -26,6 +26,7 @@ class TableInitialiser;
class Partial;
class PartialManager;
class Part;
class ROMImage;
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
@ -57,71 +58,6 @@ enum DACInputMode {
DACInputMode_GENERATION2
};
enum ReportType {
// Errors
ReportType_errorControlROM = 1,
ReportType_errorPCMROM,
ReportType_errorSampleRate,
// Progress
ReportType_progressInit,
// HW spec
ReportType_availableSSE,
ReportType_available3DNow,
ReportType_usingSSE,
ReportType_using3DNow,
// General info
ReportType_lcdMessage,
ReportType_devReset,
ReportType_devReconfig,
ReportType_newReverbMode,
ReportType_newReverbTime,
ReportType_newReverbLevel
};
enum LoadResult {
LoadResult_OK,
LoadResult_NotFound,
LoadResult_Unreadable,
LoadResult_Invalid
};
struct SynthProperties {
// Sample rate to use in mixing
unsigned int sampleRate;
// Deprecated - ignored. Use Synth::setReverbEnabled() instead.
bool useReverb;
// Deprecated - ignored. Use Synth::setReverbOverridden() instead.
bool useDefaultReverb;
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
unsigned char reverbType;
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
unsigned char reverbTime;
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
unsigned char reverbLevel;
// The name of the directory in which the ROM and data files are stored (with trailing slash/backslash)
// Not used if "openFile" is set. May be NULL in any case.
const char *baseDir;
// This is used as the first argument to all callbacks
void *userData;
// Callback for reporting various errors and information. May be NULL
int (*report)(void *userData, ReportType type, const void *reportData);
// Callback for debug messages, in vprintf() format
void (*printDebug)(void *userData, const char *fmt, va_list list);
// Callback for providing an implementation of File, opened and ready for use
// May be NULL, in which case a default implementation will be used.
Common::File *(*openFile)(void *userData, const char *filename);
// Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted.
void (*closeFile)(void *userData, Common::File *file);
};
// This is the specification of the Callback routine used when calling the RecalcWaveforms
// function
typedef void (*recalcStatusCallback)(int percDone);
typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain);
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
@ -179,6 +115,13 @@ enum MemoryRegionType {
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
};
enum ReverbMode {
REVERB_MODE_ROOM,
REVERB_MODE_HALL,
REVERB_MODE_PLATE,
REVERB_MODE_TAP_DELAY
};
class MemoryRegion {
private:
Synth *synth;
@ -278,7 +221,7 @@ class ReverbModel {
public:
virtual ~ReverbModel() {}
// After construction or a close(), open() will be called at least once before any other call (with the exception of close()).
virtual void open(unsigned int sampleRate) = 0;
virtual void open() = 0;
// May be called multiple times without an open() in between.
virtual void close() = 0;
virtual void setParameters(Bit8u time, Bit8u level) = 0;
@ -286,6 +229,32 @@ public:
virtual bool isActive() const = 0;
};
class ReportHandler {
friend class Synth;
public:
virtual ~ReportHandler() {}
protected:
// Callback for debug messages, in vprintf() format
virtual void printDebug(const char *fmt, va_list list);
// Callbacks for reporting various errors and information
virtual void onErrorControlROM() {}
virtual void onErrorPCMROM() {}
virtual void showLCDMessage(const char *message);
virtual void onDeviceReset() {}
virtual void onDeviceReconfig() {}
virtual void onNewReverbMode(Bit8u /* mode */) {}
virtual void onNewReverbTime(Bit8u /* time */) {}
virtual void onNewReverbLevel(Bit8u /* level */) {}
virtual void onPartStateChanged(int /* partNum */, bool /* isActive */) {}
virtual void onPolyStateChanged(int /* partNum */) {}
virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {}
virtual void onProgramChanged(int /* partNum */, char * /* patchName */) {}
};
class Synth {
friend class Part;
friend class RhythmPart;
@ -314,14 +283,13 @@ 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];
Bit32u renderedSampleCount;
Tables tables;
MemParams mt32ram, mt32default;
@ -337,6 +305,9 @@ private:
bool isOpen;
bool isDefaultReportHandler;
ReportHandler *reportHandler;
PartialManager *partialManager;
Part *parts[9];
@ -369,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);
@ -384,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);
@ -399,24 +368,25 @@ 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 partStateChanged(int partNum, bool isPartActive);
void polyStateChanged(int partNum);
void partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase);
void newTimbreSet(int partNum, char patchName[]);
void printDebug(const char *fmt, ...);
public:
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
Synth();
// Optionally sets callbacks for reporting various errors, information and debug messages
Synth(ReportHandler *useReportHandler = NULL);
~Synth();
// Used to initialise the MT-32. Must be called before any other function.
// Returns true if initialization was sucessful, otherwise returns false.
bool open(SynthProperties &useProp);
// controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth.
bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage);
// Closes the MT-32 and deallocates any memory used by the synthesizer
void close(void);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -30,10 +30,13 @@ 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) {
if (newPhase != phase) {
partial->getSynth()->partialStateChanged(partial, phase, newPhase);
}
target = newTarget;
phase = newPhase;
ampRamp->startRamp(newTarget, newIncrement);
@ -43,6 +46,9 @@ void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
}
void TVA::end(int newPhase) {
if (newPhase != phase) {
partial->getSynth()->partialStateChanged(partial, phase, newPhase);
}
phase = newPhase;
playing = false;
#if MT32EMU_MONITOR_TVA >= 1
@ -154,7 +160,7 @@ void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartial
playing = true;
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
int key = partial->getPoly()->getKey();
int velocity = partial->getPoly()->getVelocity();
@ -215,7 +221,7 @@ void TVA::recalcSustain() {
return;
}
// We're sustaining. Recalculate all the values
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
newTarget += partialParam->tva.envLevel[3];
// Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp.
@ -241,7 +247,7 @@ int TVA::getPhase() const {
}
void TVA::nextPhase() {
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
if (phase >= TVA_PHASE_DEAD || !playing) {
partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false");
@ -274,7 +280,7 @@ void TVA::nextPhase() {
}
int newTarget;
int newIncrement = 0;
int newIncrement = 0; // Initialised to please compilers
int envPointIndex = phase;
if (!allLevelsZeroFromNowOn) {

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -117,7 +117,7 @@ void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int b
unsigned int key = partial->getPoly()->getKey();
unsigned int velocity = partial->getPoly()->getVelocity();
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
#if MT32EMU_MONITOR_TVF >= 1
@ -179,7 +179,7 @@ void TVF::startDecay() {
}
void TVF::nextPhase() {
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
int newPhase = phase + 1;
switch (newPhase) {

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -47,12 +47,11 @@ static Bit16u keyToPitchTable[] = {
TVP::TVP(const Partial *usePartial) :
partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) {
unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate;
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
maxCounter = sampleRate / 4000;
maxCounter = SAMPLE_RATE / 4000;
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
// This is how much to increment it by every maxCounter samples.
processTimerIncrement = 500000 * maxCounter / sampleRate;
processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
}
static Bit16s keyToPitch(unsigned int key) {
@ -171,9 +170,14 @@ void TVP::updatePitch() {
if (newPitch < 0) {
newPitch = 0;
}
// Note: Temporary #ifdef until we have proper "quirk" configuration
// This is about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning"
#ifndef MT32EMU_QUIRK_PITCH_ENVELOPE_OVERFLOW_MT32
if (newPitch > 59392) {
newPitch = 59392;
}
#endif
pitch = (Bit16u)newPitch;
// FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future.

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -22,18 +22,14 @@
#include "mt32emu.h"
#include "mmath.h"
using namespace MT32Emu;
namespace MT32Emu {
Tables::Tables() {
initialised = false;
const Tables &Tables::getInstance() {
static const Tables instance;
return instance;
}
void Tables::init() {
if (initialised) {
return;
}
initialised = true;
Tables::Tables() {
int lf;
for (lf = 0; lf <= 100; lf++) {
// CONFIRMED:KG: This matches a ROM table found by Mok
@ -76,44 +72,25 @@ void Tables::init() {
//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));
}
for (int i = 0; i < 65536; i++) {
// Aka (slightly slower): EXP2F(pitchVal / 4096.0f - 16.0f) * 32000.0f
pitchToFreq[i] = EXP2F(i / 4096.0f - 1.034215715f);
// 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 < 1024; i++) {
cutoffToCosineLen[i] = EXP2F(i / -128.0f);
}
// found from sample analysis
for (int i = 0; i < 1024; i++) {
cutoffToFilterAmp[i] = EXP2F(-0.125f * (128.0f - i / 8.0f));
}
// found from sample analysis
for (int i = 0; i < 32; i++) {
resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.0f);
}
// found from sample analysis
resAmpFadeFactor[7] = 1.0f / 8.0f;
resAmpFadeFactor[6] = 2.0f / 8.0f;
resAmpFadeFactor[5] = 3.0f / 8.0f;
resAmpFadeFactor[4] = 5.0f / 8.0f;
resAmpFadeFactor[3] = 8.0f / 8.0f;
resAmpFadeFactor[2] = 12.0f / 8.0f;
resAmpFadeFactor[1] = 16.0f / 8.0f;
resAmpFadeFactor[0] = 31.0f / 8.0f;
for (int i = 0; i < 5120; i++) {
sinf10[i] = sin(FLOAT_PI * i / 2048.0f);
}
static const Bit8u resAmpDecayFactorTable[] = {31, 16, 12, 8, 5, 3, 2, 1};
resAmpDecayFactor = resAmpDecayFactorTable;
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -20,14 +20,23 @@
namespace MT32Emu {
// 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;
class Synth;
class Tables {
bool initialised;
private:
Tables();
Tables(Tables &);
public:
static const Tables &getInstance();
// Constant LUTs
// CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope:
@ -47,16 +56,10 @@ public:
// CONFIRMED:
Bit8u pulseWidth100To255[101];
float pulseLenFactor[128];
float pitchToFreq[65536];
float cutoffToCosineLen[1024];
float cutoffToFilterAmp[1024];
float resAmpMax[32];
float resAmpFadeFactor[8];
float sinf10[5120];
Bit16u exp9[512];
Bit16u logsin9[512];
Tables();
void init();
const Bit8u *resAmpDecayFactor;
};
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by

View File

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

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@ -59,13 +59,6 @@
#define MT32EMU_MONITOR_TVA 0
#define MT32EMU_MONITOR_TVF 0
// 0: Use LUTs to speedup WG
// 1: Use precise float math
#define MT32EMU_ACCURATE_WG 0
#define MT32EMU_USE_EXTINT 0
// Configuration
// The maximum number of partials playing simultaneously
#define MT32EMU_MAX_PARTIALS 32
@ -77,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
{
@ -104,11 +102,14 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024;
#include "Tables.h"
#include "Poly.h"
#include "LA32Ramp.h"
#include "LA32WaveGenerator.h"
#include "LegacyWaveGenerator.h"
#include "TVA.h"
#include "TVP.h"
#include "TVF.h"
#include "Partial.h"
#include "Part.h"
#include "ROMInfo.h"
#include "Synth.h"
#endif

View File

@ -247,7 +247,7 @@ byte OPL::read(int port) {
}
void OPL::writeReg(int r, int v) {
byte tempReg = 0;
int tempReg = 0;
switch (_type) {
case Config::kOpl2:
case Config::kDualOpl2:
@ -257,12 +257,27 @@ void OPL::writeReg(int r, int v) {
// Backup old setup register
tempReg = _reg.normal;
// We need to set the register we want to write to via port 0x388
write(0x388, r);
// Do the real writing to the register
write(0x389, v);
// We directly allow writing to secondary OPL3 registers by using
// register values >= 0x100.
if (_type == Config::kOpl3 && r >= 0x100) {
// We need to set the register we want to write to via port 0x222,
// since we want to write to the secondary register set.
write(0x222, r);
// Do the real writing to the register
write(0x223, v);
} else {
// We need to set the register we want to write to via port 0x388
write(0x388, r);
// Do the real writing to the register
write(0x389, v);
}
// Restore the old register
write(0x388, tempReg);
if (_type == Config::kOpl3 && tempReg >= 0x100) {
write(0x222, tempReg & ~0x100);
} else {
write(0x388, tempReg);
}
break;
};
}

View File

@ -223,7 +223,7 @@ static int *ENV_CURVE;
/* multiple table */
#define ML(a) (int)(a * 2)
#define ML(a) (uint)(a * 2)
static const uint MUL_TABLE[16]= {
/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */
ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00),

View File

@ -512,7 +512,7 @@ void Filter::enable_filter(bool enable) {
enabled = enable;
}
void Filter::reset(){
void Filter::reset() {
fc = 0;
res = 0;

View File

@ -110,7 +110,7 @@ public:
/**
* Initialize the specified CD drive for audio playback.
* @param drive the drive id
* @return true if the CD drive was inited succesfully
* @return true if the CD drive was inited successfully
*/
virtual bool openCD(int drive) = 0;

View File

@ -51,6 +51,8 @@ DefaultEventManager::DefaultEventManager(Common::EventSource *boss) :
// Reset key repeat
_currentKeyDown.keycode = 0;
_currentKeyDown.ascii = 0;
_currentKeyDown.flags = 0;
#ifdef ENABLE_VKEYBD
_vk = new Common::VirtualKeyboard();

View File

@ -100,7 +100,7 @@ public:
* @param mode Mode to use while listing the directory.
* @param hidden Whether to include hidden files or not in the results.
*
* @return true if succesful, false otherwise (e.g. when the directory does not exist).
* @return true if successful, false otherwise (e.g. when the directory does not exist).
*/
virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const = 0;

View File

@ -226,6 +226,6 @@ void std_clearerr(FILE *handle);
int std_fflush(FILE *handle);
int std_ferror(FILE *handle);
} // End of namespace DS
} // End of namespace DS
#endif //_DS_FS_H

View File

@ -108,9 +108,18 @@ void GLTexture::allocBuffer(GLuint w, GLuint h) {
_realWidth = w;
_realHeight = h;
if (w <= _textureWidth && h <= _textureHeight && !_refresh)
// Already allocated a sufficiently large buffer
return;
if (!_refresh) {
if (npot_supported && _filter == GL_LINEAR) {
// Check if we already allocated a correctly-sized buffer
// This is so we don't need to duplicate the last row/column
if (w == _textureWidth && h == _textureHeight)
return;
} else {
// Check if we already have a large enough buffer
if (w <= _textureWidth && h <= _textureHeight)
return;
}
}
if (npot_supported) {
_textureWidth = w;
@ -151,12 +160,37 @@ void GLTexture::updateBuffer(const void *buf, int pitch, GLuint x, GLuint y, GLu
} else {
// Update the texture row by row
const byte *src = (const byte *)buf;
GLuint curY = y;
GLuint height = h;
do {
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
glTexSubImage2D(GL_TEXTURE_2D, 0, x, curY,
w, 1, _glFormat, _glType, src); CHECK_GL_ERROR();
++y;
curY++;
src += pitch;
} while (--h);
} while (--height);
}
// If we're in linear filter mode, repeat the last row/column if the real dimensions
// doesn't match the texture dimensions.
if (_filter == GL_LINEAR) {
if (_realWidth != _textureWidth && x + w == _realWidth) {
const byte *src = (const byte *)buf + (w - 1) * _bytesPerPixel;
GLuint curY = y;
GLuint height = h;
do {
glTexSubImage2D(GL_TEXTURE_2D, 0, x + w,
curY, 1, 1, _glFormat, _glType, src); CHECK_GL_ERROR();
curY++;
src += pitch;
} while (--height);
}
if (_realHeight != _textureHeight && y + h == _realHeight) {
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y + h,
w, 1, _glFormat, _glType, (const byte *)buf + pitch * (h - 1)); CHECK_GL_ERROR();
}
}
}
@ -177,10 +211,10 @@ void GLTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) {
// Calculate the screen rect where the texture will be drawn
const GLshort vertices[] = {
x, y,
x + w, y,
x, y + h,
x + w, y + h,
x, y,
(GLshort)(x + w), y,
x, (GLshort)(y + h),
(GLshort)(x + w), (GLshort)(y + h),
};
glVertexPointer(2, GL_SHORT, 0, vertices); CHECK_GL_ERROR();

View File

@ -460,6 +460,10 @@ void OpenGLSdlGraphicsManager::toggleFullScreen(int loop) {
_activeFullscreenMode = -2;
setFullscreenMode(!isFullscreen);
}
// HACK: We need to force a refresh here, since we change the
// fullscreen mode.
_transactionDetails.needRefresh = true;
endGFXTransaction();
// Ignore resize events for the next 10 frames

View File

@ -968,7 +968,7 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
// If the shake position changed, fill the dirty area with blackness
if (_currentShakePos != _newShakePos ||
(_mouseNeedsRedraw && _mouseBackup.y <= _currentShakePos)) {
SDL_Rect blackrect = {0, 0, _videoMode.screenWidth * _videoMode.scaleFactor, _newShakePos * _videoMode.scaleFactor};
SDL_Rect blackrect = {0, 0, (Uint16)(_videoMode.screenWidth * _videoMode.scaleFactor), (Uint16)(_newShakePos * _videoMode.scaleFactor)};
if (_videoMode.aspectRatioCorrection && !_overlayVisible)
blackrect.h = real2Aspect(blackrect.h - 1) + 1;

View File

@ -128,7 +128,7 @@ public:
* @param name name of the keymap to push
* @param transparent if true keymapper will iterate down the
* stack if it cannot find a key in the new map
* @return true if succesful
* @return true if successful
*/
bool pushKeymap(const String& name, bool transparent = false);

View File

@ -102,6 +102,7 @@ public:
void sysEx(const byte *msg, uint16 length);
private:
void loadSoundFont(const char *soundfont);
AUGraph _auGraph;
AudioUnit _synth;
};
@ -171,52 +172,8 @@ int MidiDriver_CORE::open() {
#endif
// Load custom soundfont, if specified
if (ConfMan.hasKey("soundfont")) {
const char *soundfont = ConfMan.get("soundfont").c_str();
// TODO: We should really check whether the file contains an
// actual soundfont...
#if USE_DEPRECATED_COREAUDIO_API
// Before 10.5, we need to use kMusicDeviceProperty_SoundBankFSSpec
FSRef fsref;
FSSpec fsSpec;
err = FSPathMakeRef ((const byte *)soundfont, &fsref, NULL);
if (err == noErr) {
err = FSGetCatalogInfo (&fsref, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
}
if (err == noErr) {
err = AudioUnitSetProperty (
_synth,
kMusicDeviceProperty_SoundBankFSSpec, kAudioUnitScope_Global,
0,
&fsSpec, sizeof(fsSpec)
);
}
#else
// kMusicDeviceProperty_SoundBankFSSpec is present on 10.6+, but broken
// kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)soundfont, strlen(soundfont), false);
if (url) {
err = AudioUnitSetProperty (
_synth,
kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global,
0,
&url, sizeof(url)
);
CFRelease(url);
} else {
warning("Failed to allocate CFURLRef from '%s'", soundfont);
}
#endif
if (err != noErr)
error("Failed loading custom sound font '%s' (error %ld)", soundfont, (long)err);
}
if (ConfMan.hasKey("soundfont"))
loadSoundFont(ConfMan.get("soundfont").c_str());
#ifdef COREAUDIO_DISABLE_REVERB
// Disable reverb mode, as that sucks up a lot of CPU power, which can
@ -242,6 +199,74 @@ bail:
return MERR_CANNOT_CONNECT;
}
void MidiDriver_CORE::loadSoundFont(const char *soundfont) {
// TODO: We should really check whether the file contains an
// actual soundfont...
OSStatus err = 0;
#if USE_DEPRECATED_COREAUDIO_API
FSRef fsref;
err = FSPathMakeRef((const byte *)soundfont, &fsref, NULL);
SInt32 version;
err = Gestalt(gestaltSystemVersion, &version);
if (err == noErr) {
if (version >= 0x1030) {
// Use kMusicDeviceProperty_SoundBankFSRef in >= 10.3
// HACK HACK HACK HACK SUPER HACK: Using the value of 1012 instead of
// kMusicDeviceProperty_SoundBankFSRef so this compiles with the 10.2
// SDK (which does not have that symbol).
if (err == noErr) {
err = AudioUnitSetProperty(
_synth,
/*kMusicDeviceProperty_SoundBankFSRef*/ 1012, kAudioUnitScope_Global,
0,
&fsref, sizeof(fsref)
);
}
} else {
// In 10.2, only kMusicDeviceProperty_SoundBankFSSpec is available
FSSpec fsSpec;
if (err == noErr)
err = FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
if (err == noErr) {
err = AudioUnitSetProperty(
_synth,
kMusicDeviceProperty_SoundBankFSSpec, kAudioUnitScope_Global,
0,
&fsSpec, sizeof(fsSpec)
);
}
}
}
#else
// kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement
// In addition, the File Manager API became deprecated starting in 10.8
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)soundfont, strlen(soundfont), false);
if (url) {
err = AudioUnitSetProperty(
_synth,
kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global,
0,
&url, sizeof(url)
);
CFRelease(url);
} else {
warning("Failed to allocate CFURLRef from '%s'", soundfont);
}
#endif // USE_DEPRECATED_COREAUDIO_API
if (err != noErr)
error("Failed loading custom sound font '%s' (error %ld)", soundfont, (long)err);
}
void MidiDriver_CORE::close() {
MidiDriver_MPU401::close();
if (_auGraph) {

View File

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

View File

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

View File

@ -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),
_overlay_texture(0),
@ -162,10 +162,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() {

View File

@ -234,7 +234,7 @@ private:
int _fingersDown;
void clipMouse(Common::Point &p);
void scaleMouse(Common::Point &p, int x, int y, bool deductDrawRect = true);
void scaleMouse(Common::Point &p, int x, int y, bool deductDrawRect = true, bool touchpadMode = false);
void updateEventScale();
void disableCursorPalette();

View File

@ -10,6 +10,7 @@ JAVA_FILES = \
ScummVMApplication.java \
ScummVMActivity.java \
EditableSurfaceView.java \
MouseHelper.java \
Unpacker.java
JAVA_FILES_PLUGIN = \
@ -47,15 +48,9 @@ APKBUILDER = $(ANDROID_SDK)/tools/apkbuilder
JAVAC ?= javac
JAVACFLAGS = -source 1.5 -target 1.5
# This is a bit silly. I want to compile against the 1.6 android.jar,
# to make the compiler check that I don't use something that requires
# a newer Android. However, in order to use android:installLocation,
# we need to give aapt a version >=8 android.jar - even though the
# result will work ok on 1.5+.
ANDROID_JAR = $(ANDROID_SDK)/platforms/android-4/android.jar
ANDROID_JAR8 = $(ANDROID_SDK)/platforms/android-8/android.jar
ANDROID_JAR = $(ANDROID_SDK)/platforms/android-14/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
@ -92,9 +87,9 @@ $(FILE_MANIFEST): $(FILE_MANIFEST_SRC)
@$(MKDIR) -p $(@D)
sed "s/@ANDROID_VERSIONCODE@/$(ANDROID_VERSIONCODE)/" < $< > $@
$(SRC_GEN): $(FILE_MANIFEST) $(filter %.xml,$(RESOURCES)) $(ANDROID_JAR8)
$(SRC_GEN): $(FILE_MANIFEST) $(filter %.xml,$(RESOURCES)) $(ANDROID_JAR)
@$(MKDIR) -p $(PATH_GEN_TOP)
$(AAPT) package -m -J $(PATH_GEN_TOP) -M $< -S $(PATH_RESOURCES) -I $(ANDROID_JAR8)
$(AAPT) package -m -J $(PATH_GEN_TOP) -M $< -S $(PATH_RESOURCES) -I $(ANDROID_JAR)
$(PATH_CLASSES_MAIN)/%.class: $(PATH_GEN)/%.java $(SRC_GEN)
@$(MKDIR) -p $(@D)
@ -127,13 +122,13 @@ $(PATH_STAGE_PREFIX).%/res/drawable/scummvm.png: $(PATH_RESOURCES)/drawable/scum
@$(MKDIR) -p $(@D)
$(CP) $< $@
$(FILE_RESOURCES_MAIN): $(FILE_MANIFEST) $(RESOURCES) $(ANDROID_JAR8) $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA)
$(FILE_RESOURCES_MAIN): $(FILE_MANIFEST) $(RESOURCES) $(ANDROID_JAR) $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA)
$(INSTALL) -d $(PATH_BUILD_ASSETS)
$(INSTALL) -c -m 644 $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(PATH_BUILD_ASSETS)/
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; \
@ -141,10 +136,10 @@ $(FILE_RESOURCES_MAIN): $(FILE_MANIFEST) $(RESOURCES) $(ANDROID_JAR8) $(DIST_FIL
zip -r ../`basename $$i` *; \
done
@$(RM) -rf $(PATH_BUILD_ASSETS)/tmp
$(AAPT) package -f -0 zip -M $< -S $(PATH_RESOURCES) -A $(PATH_BUILD_ASSETS) -I $(ANDROID_JAR8) -F $@
$(AAPT) package -f -0 zip -M $< -S $(PATH_RESOURCES) -A $(PATH_BUILD_ASSETS) -I $(ANDROID_JAR) -F $@
$(PATH_BUILD)/%/$(FILE_RESOURCES): $(PATH_BUILD)/%/AndroidManifest.xml $(PATH_STAGE_PREFIX).%/res/values/strings.xml $(PATH_STAGE_PREFIX).%/res/drawable/scummvm.png plugins/lib%.so $(ANDROID_JAR8)
$(AAPT) package -f -M $< -S $(PATH_STAGE_PREFIX).$*/res -I $(ANDROID_JAR8) -F $@
$(PATH_BUILD)/%/$(FILE_RESOURCES): $(PATH_BUILD)/%/AndroidManifest.xml $(PATH_STAGE_PREFIX).%/res/values/strings.xml $(PATH_STAGE_PREFIX).%/res/drawable/scummvm.png plugins/lib%.so $(ANDROID_JAR)
$(AAPT) package -f -M $< -S $(PATH_STAGE_PREFIX).$*/res -I $(ANDROID_JAR) -F $@
# Package installer won't delete old libscummvm.so on upgrade so
# replace it with a zero size file

View File

@ -97,7 +97,9 @@ JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) :
{
_input_stream = env->NewGlobalRef(is);
_buflen = 8192;
_buf = (jbyteArray)env->NewGlobalRef(env->NewByteArray(_buflen));
jobject buf = env->NewByteArray(_buflen);
_buf = (jbyteArray)env->NewGlobalRef(buf);
env->DeleteLocalRef(buf);
jclass cls = env->GetObjectClass(_input_stream);
MID_mark = env->GetMethodID(cls, "mark", "(I)V");
@ -112,6 +114,7 @@ JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) :
assert(MID_reset);
MID_skip = env->GetMethodID(cls, "skip", "(J)J");
assert(MID_skip);
env->DeleteLocalRef(cls);
// Mark start of stream, so we can reset back to it.
// readlimit is set to something bigger than anything we might
@ -142,7 +145,9 @@ uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) {
_buflen = dataSize;
env->DeleteGlobalRef(_buf);
_buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen)));
jobject buf = env->NewByteArray(_buflen);
_buf = static_cast<jbyteArray>(env->NewGlobalRef(buf));
env->DeleteLocalRef(buf);
}
jint ret = env->CallIntMethod(_input_stream, MID_read, _buf, 0, dataSize);
@ -290,6 +295,7 @@ AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) :
jclass cls = env->GetObjectClass(_assetfd);
MID_close = env->GetMethodID(cls, "close", "()V");
assert(MID_close);
env->DeleteLocalRef(cls);
jmethodID MID_getStartOffset =
env->GetMethodID(cls, "getStartOffset", "()J");
@ -311,8 +317,10 @@ AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) :
jclass fd_cls = env->GetObjectClass(javafd);
jfieldID FID_descriptor = env->GetFieldID(fd_cls, "descriptor", "I");
assert(FID_descriptor);
env->DeleteLocalRef(fd_cls);
_fd = env->GetIntField(javafd, FID_descriptor);
env->DeleteLocalRef(javafd);
}
AssetFdReadStream::~AssetFdReadStream() {
@ -382,6 +390,7 @@ AndroidAssetArchive::AndroidAssetArchive(jobject am) {
MID_list = env->GetMethodID(cls, "list",
"(Ljava/lang/String;)[Ljava/lang/String;");
assert(MID_list);
env->DeleteLocalRef(cls);
}
AndroidAssetArchive::~AndroidAssetArchive() {
@ -452,7 +461,9 @@ int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) con
member_list.push_back(getMember(thispath));
++count;
} else {
dirlist.push_back(thispath);
// AssetManager is ridiculously slow and we don't care
// about subdirectories at the moment, so ignore them.
// dirlist.push_back(thispath);
}
}
@ -481,8 +492,10 @@ Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const
env->ExceptionClear();
else if (afd != 0) {
// success :)
Common::SeekableReadStream *stream = new AssetFdReadStream(env, afd);
env->DeleteLocalRef(jpath);
return new AssetFdReadStream(env, afd);
env->DeleteLocalRef(afd);
return stream;
}
// ... and fallback to normal open() if that doesn't work
@ -498,7 +511,10 @@ Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const
return 0;
}
return new JavaInputStream(env, is);
Common::SeekableReadStream *stream = new JavaInputStream(env, is);
env->DeleteLocalRef(jpath);
env->DeleteLocalRef(is);
return stream;
}
#endif

View File

@ -59,6 +59,11 @@ enum {
JE_DOUBLE_TAP = 6,
JE_MULTI = 7,
JE_BALL = 8,
JE_LMB_DOWN = 9,
JE_LMB_UP = 10,
JE_RMB_DOWN = 11,
JE_RMB_UP = 12,
JE_MOUSE_MOVE = 13,
JE_QUIT = 0x1000
};
@ -272,7 +277,7 @@ void OSystem_Android::clipMouse(Common::Point &p) {
}
void OSystem_Android::scaleMouse(Common::Point &p, int x, int y,
bool deductDrawRect) {
bool deductDrawRect, bool touchpadMode) {
const GLESBaseTexture *tex;
if (_show_overlay)
@ -282,7 +287,7 @@ void OSystem_Android::scaleMouse(Common::Point &p, int x, int y,
const Common::Rect &r = tex->getDrawRect();
if (_touchpad_mode) {
if (touchpadMode) {
x = x * 100 / _touchpad_scale;
y = y * 100 / _touchpad_scale;
}
@ -327,11 +332,16 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
}
switch (arg2) {
// special case. we'll only get it's up event
case JKEYCODE_BACK:
e.kbd.keycode = Common::KEYCODE_ESCAPE;
e.kbd.ascii = Common::ASCII_ESCAPE;
lockMutex(_event_queue_lock);
e.type = Common::EVENT_KEYDOWN;
_event_queue.push(e);
e.type = Common::EVENT_KEYUP;
_event_queue.push(e);
unlockMutex(_event_queue_lock);
@ -554,7 +564,7 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
}
scaleMouse(e.mouse, arg3 - _touch_pt_scroll.x,
arg4 - _touch_pt_scroll.y, false);
arg4 - _touch_pt_scroll.y, false, true);
e.mouse += _touch_pt_down;
clipMouse(e.mouse);
} else {
@ -652,7 +662,7 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
if (_touchpad_mode) {
scaleMouse(e.mouse, arg1 - _touch_pt_dt.x,
arg2 - _touch_pt_dt.y, false);
arg2 - _touch_pt_dt.y, false, true);
e.mouse += _touch_pt_down;
clipMouse(e.mouse);
@ -757,6 +767,66 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
case JE_MOUSE_MOVE:
e.type = Common::EVENT_MOUSEMOVE;
scaleMouse(e.mouse, arg1, arg2);
clipMouse(e.mouse);
lockMutex(_event_queue_lock);
_event_queue.push(e);
unlockMutex(_event_queue_lock);
return;
case JE_LMB_DOWN:
e.type = Common::EVENT_LBUTTONDOWN;
scaleMouse(e.mouse, arg1, arg2);
clipMouse(e.mouse);
lockMutex(_event_queue_lock);
_event_queue.push(e);
unlockMutex(_event_queue_lock);
return;
case JE_LMB_UP:
e.type = Common::EVENT_LBUTTONUP;
scaleMouse(e.mouse, arg1, arg2);
clipMouse(e.mouse);
lockMutex(_event_queue_lock);
_event_queue.push(e);
unlockMutex(_event_queue_lock);
return;
case JE_RMB_DOWN:
e.type = Common::EVENT_RBUTTONDOWN;
scaleMouse(e.mouse, arg1, arg2);
clipMouse(e.mouse);
lockMutex(_event_queue_lock);
_event_queue.push(e);
unlockMutex(_event_queue_lock);
return;
case JE_RMB_UP:
e.type = Common::EVENT_RBUTTONUP;
scaleMouse(e.mouse, arg1, arg2);
clipMouse(e.mouse);
lockMutex(_event_queue_lock);
_event_queue.push(e);
unlockMutex(_event_queue_lock);
return;
case JE_QUIT:
e.type = Common::EVENT_QUIT;

View File

@ -603,10 +603,10 @@ void JNI::setPause(JNIEnv *env, jobject self, jboolean value) {
g_engine->pauseEngine(value);
if (value &&
/*if (value &&
g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) &&
g_engine->canSaveGameStateCurrently())
g_engine->saveGameState(0, "Android parachute");
g_engine->saveGameState(0, "Android parachute");*/
}
pause = value;

View File

@ -0,0 +1,129 @@
package org.scummvm.scummvm;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
/**
* Contains helper methods for mouse/hover events that were introduced in Android 4.0.
*/
public class MouseHelper {
private View.OnHoverListener _listener;
private ScummVM _scummvm;
private long _rmbGuardTime;
private boolean _rmbPressed;
private boolean _lmbPressed;
/**
* Class initialization fails when this throws an exception.
* Checking hover availability is done on static class initialization for Android 1.6 compatibility.
*/
static {
try {
Class.forName("android.view.View$OnHoverListener");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Calling this forces class initialization
*/
public static void checkHoverAvailable() {}
public MouseHelper(ScummVM scummvm) {
_scummvm = scummvm;
_listener = createListener();
}
private View.OnHoverListener createListener() {
return new View.OnHoverListener() {
@Override
public boolean onHover(View view, MotionEvent e) {
return onMouseEvent(e, true);
}
};
}
public void attach(SurfaceView main_surface) {
main_surface.setOnHoverListener(_listener);
}
public static boolean isMouse(MotionEvent e) {
if (e == null) {
return false;
}
InputDevice device = e.getDevice();
if (device == null) {
return false;
}
int sources = device.getSources();
return ((sources & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ||
((sources & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) ||
((sources & InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD);
}
public boolean onMouseEvent(MotionEvent e, boolean hover) {
_scummvm.pushEvent(ScummVMEvents.JE_MOUSE_MOVE, (int)e.getX(), (int)e.getY(), 0, 0, 0);
int buttonState = e.getButtonState();
boolean lmbDown = (buttonState & MotionEvent.BUTTON_PRIMARY) == MotionEvent.BUTTON_PRIMARY;
if (!hover && e.getAction() != MotionEvent.ACTION_UP && buttonState == 0) {
// On some device types, ButtonState is 0 even when tapping on the touchpad or using the stylus on the screen etc.
lmbDown = true;
}
if (lmbDown) {
if (!_lmbPressed) {
// left mouse button was pressed just now
_scummvm.pushEvent(ScummVMEvents.JE_LMB_DOWN, (int)e.getX(), (int)e.getY(), e.getButtonState(), 0, 0);
}
_lmbPressed = true;
} else {
if (_lmbPressed) {
// left mouse button was released just now
_scummvm.pushEvent(ScummVMEvents.JE_LMB_UP, (int)e.getX(), (int)e.getY(), e.getButtonState(), 0, 0);
}
_lmbPressed = false;
}
boolean rmbDown = (buttonState & MotionEvent.BUTTON_SECONDARY) == MotionEvent.BUTTON_SECONDARY;
if (rmbDown) {
if (!_rmbPressed) {
// right mouse button was pressed just now
_scummvm.pushEvent(ScummVMEvents.JE_RMB_DOWN, (int)e.getX(), (int)e.getY(), e.getButtonState(), 0, 0);
}
_rmbPressed = true;
} else {
if (_rmbPressed) {
// right mouse button was released just now
_scummvm.pushEvent(ScummVMEvents.JE_RMB_UP, (int)e.getX(), (int)e.getY(), e.getButtonState(), 0, 0);
_rmbGuardTime = System.currentTimeMillis();
}
_rmbPressed = false;
}
return true;
}
/**
* Checks whether right mouse button is pressed or was pressed just previously. This is used to prevent sending
* extra back key on right mouse click which is the default behaviour in some platforms.
*
* @return true if right mouse button is (or was in the last 200ms) pressed
*/
public boolean getRmbGuard() {
return _rmbPressed || _rmbGuardTime + 200 > System.currentTimeMillis();
}
}

View File

@ -18,6 +18,18 @@ import java.io.File;
public class ScummVMActivity extends Activity {
/* Establish whether the hover events are available */
private static boolean _hoverAvailable;
static {
try {
MouseHelper.checkHoverAvailable(); // this throws exception if we're on too old version
_hoverAvailable = true;
} catch (Throwable t) {
_hoverAvailable = false;
}
}
private class MyScummVM extends ScummVM {
private boolean usingSmallScreen() {
// Multiple screen sizes came in with Android 1.6. Have
@ -94,6 +106,7 @@ public class ScummVMActivity extends Activity {
private MyScummVM _scummvm;
private ScummVMEvents _events;
private MouseHelper _mouseHelper;
private Thread _scummvm_thread;
@Override
@ -147,11 +160,16 @@ public class ScummVMActivity extends Activity {
"ScummVM",
"--config=" + getFileStreamPath("scummvmrc").getPath(),
"--path=" + Environment.getExternalStorageDirectory().getPath(),
"--gui-theme=scummmodern",
"--savepath=" + savePath
});
_events = new ScummVMEvents(this, _scummvm);
Log.d(ScummVM.LOG_TAG, "Hover available: " + _hoverAvailable);
if (_hoverAvailable) {
_mouseHelper = new MouseHelper(_scummvm);
_mouseHelper.attach(main_surface);
}
_events = new ScummVMEvents(this, _scummvm, _mouseHelper);
main_surface.setOnKeyListener(_events);
main_surface.setOnTouchListener(_events);

View File

@ -2,7 +2,6 @@ package org.scummvm.scummvm;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.content.Context;
import android.view.KeyEvent;
import android.view.KeyCharacterMap;
@ -27,16 +26,23 @@ public class ScummVMEvents implements
public static final int JE_DOUBLE_TAP = 6;
public static final int JE_MULTI = 7;
public static final int JE_BALL = 8;
public static final int JE_LMB_DOWN = 9;
public static final int JE_LMB_UP = 10;
public static final int JE_RMB_DOWN = 11;
public static final int JE_RMB_UP = 12;
public static final int JE_MOUSE_MOVE = 13;
public static final int JE_QUIT = 0x1000;
final protected Context _context;
final protected ScummVM _scummvm;
final protected GestureDetector _gd;
final protected int _longPress;
final protected MouseHelper _mouseHelper;
public ScummVMEvents(Context context, ScummVM scummvm) {
public ScummVMEvents(Context context, ScummVM scummvm, MouseHelper mouseHelper) {
_context = context;
_scummvm = scummvm;
_mouseHelper = mouseHelper;
_gd = new GestureDetector(context, this);
_gd.setOnDoubleTapListener(this);
@ -64,7 +70,7 @@ public class ScummVMEvents implements
public void handleMessage(Message msg) {
if (msg.what == MSG_MENU_LONG_PRESS) {
InputMethodManager imm = (InputMethodManager)
_context.getSystemService(_context.INPUT_METHOD_SERVICE);
_context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null)
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
@ -73,9 +79,30 @@ public class ScummVMEvents implements
};
// OnKeyListener
@Override
final public boolean onKey(View v, int keyCode, KeyEvent e) {
final int action = e.getAction();
if (keyCode == 238) {
// this (undocumented) event is sent when ACTION_HOVER_ENTER or ACTION_HOVER_EXIT occurs
return false;
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (action != KeyEvent.ACTION_UP) {
// only send event from back button on up event, since down event is sent on right mouse click and
// cannot be caught (thus rmb click would send escape key first)
return true;
}
if (_mouseHelper != null) {
if (_mouseHelper.getRmbGuard()) {
// right mouse button was just clicked which sends an extra back button press
return true;
}
}
}
if (e.isSystem()) {
// filter what we handle
switch (keyCode) {
@ -160,7 +187,16 @@ public class ScummVMEvents implements
}
// OnTouchListener
@Override
final public boolean onTouch(View v, MotionEvent e) {
if (_mouseHelper != null) {
boolean isMouse = MouseHelper.isMouse(e);
if (isMouse) {
// mouse button is pressed
return _mouseHelper.onMouseEvent(e, false);
}
}
final int action = e.getAction();
// constants from APIv5:
@ -177,11 +213,13 @@ public class ScummVMEvents implements
}
// OnGestureListener
@Override
final public boolean onDown(MotionEvent e) {
_scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0);
return true;
}
@Override
final public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
//Log.d(ScummVM.LOG_TAG, String.format("onFling: %s -> %s (%.3f %.3f)",
@ -191,10 +229,12 @@ public class ScummVMEvents implements
return true;
}
@Override
final public void onLongPress(MotionEvent e) {
// disabled, interferes with drag&drop
}
@Override
final public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
_scummvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(),
@ -203,9 +243,11 @@ public class ScummVMEvents implements
return true;
}
@Override
final public void onShowPress(MotionEvent e) {
}
@Override
final public boolean onSingleTapUp(MotionEvent e) {
_scummvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(),
(int)(e.getEventTime() - e.getDownTime()), 0, 0);
@ -214,10 +256,12 @@ public class ScummVMEvents implements
}
// OnDoubleTapListener
@Override
final public boolean onDoubleTap(MotionEvent e) {
return true;
}
@Override
final public boolean onDoubleTapEvent(MotionEvent e) {
_scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(),
e.getAction(), 0, 0);
@ -225,6 +269,7 @@ public class ScummVMEvents implements
return true;
}
@Override
final public boolean onSingleTapConfirmed(MotionEvent e) {
return true;
}

View File

@ -391,4 +391,4 @@ void Rescale_320x256x1555_To_256x256x1555(u16 *dest, const u16 *src, int destStr
}
}
} // End of namespace DS
} // End of namespace DS

View File

@ -35,7 +35,7 @@ void asmCopy8Col(byte *dst, int dstPitch, const byte *src, int height);
void Rescale_320x256xPAL8_To_256x256x1555(u16 *dest, const u8 *src, int destStride, int srcStride, const u16 *palette);
void Rescale_320x256x1555_To_256x256x1555(u16 *dest, const u16 *src, int destStride, int srcStride);
} // End of namespace DS
} // End of namespace DS
#else

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