mirror of
https://github.com/libretro/scummvm.git
synced 2025-04-02 06:41:51 +00:00
Merge branch 'master' of https://github.com/scummvm/scummvm into mortevielle
Conflicts: engines/engines.mk
This commit is contained in:
commit
6e2d567bca
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
16
AUTHORS
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
67
NEWS
@ -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
52
README
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -268,7 +268,6 @@ static const int MSADPCMAdaptationTable[] = {
|
||||
768, 614, 512, 409, 307, 230, 230, 230
|
||||
};
|
||||
|
||||
|
||||
int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
|
||||
int32 predictor;
|
||||
|
||||
@ -290,40 +289,42 @@ int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
|
||||
int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int samples;
|
||||
byte data;
|
||||
int i = 0;
|
||||
int i;
|
||||
|
||||
samples = 0;
|
||||
for (samples = 0; samples < numSamples && !endOfData(); samples++) {
|
||||
if (_decodedSampleCount == 0) {
|
||||
if (_blockPos[0] == _blockAlign) {
|
||||
// read block header
|
||||
for (i = 0; i < _channels; i++) {
|
||||
_status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
|
||||
_status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
|
||||
_status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
|
||||
}
|
||||
|
||||
while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
|
||||
if (_blockPos[0] == _blockAlign) {
|
||||
// read block header
|
||||
for (i = 0; i < _channels; i++) {
|
||||
_status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
|
||||
_status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
|
||||
_status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
|
||||
for (i = 0; i < _channels; i++)
|
||||
_status.ch[i].delta = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_status.ch[i].sample1 = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample2 = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_decodedSamples[_decodedSampleCount++] = _status.ch[i].sample1;
|
||||
|
||||
_blockPos[0] = _channels * 7;
|
||||
} else {
|
||||
data = _stream->readByte();
|
||||
_blockPos[0]++;
|
||||
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
|
||||
_decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
|
||||
}
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_status.ch[i].delta = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
_status.ch[i].sample1 = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE();
|
||||
|
||||
for (i = 0; i < _channels; i++)
|
||||
buffer[samples++] = _status.ch[i].sample1;
|
||||
|
||||
_blockPos[0] = _channels * 7;
|
||||
}
|
||||
|
||||
for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
|
||||
data = _stream->readByte();
|
||||
_blockPos[0]++;
|
||||
buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
|
||||
buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
|
||||
}
|
||||
// (1 - (count - 1)) ensures that _decodedSamples acts as a FIFO of depth 2
|
||||
buffer[samples] = _decodedSamples[1 - (_decodedSampleCount - 1)];
|
||||
_decodedSampleCount--;
|
||||
}
|
||||
|
||||
return samples;
|
||||
@ -432,7 +433,7 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
|
||||
return samp;
|
||||
}
|
||||
|
||||
RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) {
|
||||
RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, ADPCMType type, int rate, int channels, uint32 blockAlign) {
|
||||
// If size is 0, report the entire size of the stream
|
||||
if (!size)
|
||||
size = stream->size();
|
||||
|
@ -51,7 +51,7 @@ class RewindableAudioStream;
|
||||
// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs
|
||||
// Usually, if the audio stream we're trying to play has the FourCC header
|
||||
// string intact, it's easy to discern which encoding is used
|
||||
enum typesADPCM {
|
||||
enum ADPCMType {
|
||||
kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
|
||||
kADPCMMSIma, // Microsoft IMA ADPCM
|
||||
kADPCMMS, // Microsoft ADPCM
|
||||
@ -76,9 +76,9 @@ enum typesADPCM {
|
||||
RewindableAudioStream *makeADPCMStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse,
|
||||
uint32 size, typesADPCM type,
|
||||
int rate = 22050,
|
||||
int channels = 2,
|
||||
uint32 size, ADPCMType type,
|
||||
int rate,
|
||||
int channels,
|
||||
uint32 blockAlign = 0);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
@ -206,12 +206,19 @@ public:
|
||||
if (blockAlign == 0)
|
||||
error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
|
||||
memset(&_status, 0, sizeof(_status));
|
||||
_decodedSampleCount = 0;
|
||||
}
|
||||
|
||||
virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); }
|
||||
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
protected:
|
||||
int16 decodeMS(ADPCMChannelStatus *c, byte);
|
||||
|
||||
private:
|
||||
uint8 _decodedSampleCount;
|
||||
int16 _decodedSamples[4];
|
||||
};
|
||||
|
||||
// Duck DK3 IMA ADPCM Decoder
|
||||
|
@ -48,7 +48,7 @@ class SeekableAudioStream;
|
||||
* successful. In that case, the stream's seek position will be set to the
|
||||
* start of the audio data, and size, rate and flags contain information
|
||||
* necessary for playback. Currently this function only supports uncompressed
|
||||
* raw PCM data as well as IMA ADPCM.
|
||||
* raw PCM.
|
||||
*/
|
||||
extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -137,7 +137,7 @@ OPL *Config::create(DriverId driver, OplType type) {
|
||||
return new MAME::OPL();
|
||||
else
|
||||
warning("MAME OPL emulator only supports OPL2 emulation");
|
||||
return 0;
|
||||
return 0;
|
||||
|
||||
#ifndef DISABLE_DOSBOX_OPL
|
||||
case kDOSBox:
|
||||
|
@ -129,7 +129,9 @@ public:
|
||||
|
||||
/**
|
||||
* Function to directly write to a specific OPL register.
|
||||
* This writes to *both* chips for a Dual OPL2.
|
||||
* This writes to *both* chips for a Dual OPL2. We allow
|
||||
* writing to secondary OPL registers by using register
|
||||
* values >= 0x100.
|
||||
*
|
||||
* @param r hardware register number to write to
|
||||
* @param v value, which will be written
|
||||
|
@ -194,7 +194,9 @@ public:
|
||||
enum {
|
||||
// PROP_TIMEDIV = 1,
|
||||
PROP_OLD_ADLIB = 2,
|
||||
PROP_CHANNEL_MASK = 3
|
||||
PROP_CHANNEL_MASK = 3,
|
||||
// HACK: Not so nice, but our SCUMM AdLib code is in audio/
|
||||
PROP_SCUMM_OPL3 = 4
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -46,6 +46,7 @@ _numTracks(0),
|
||||
_activeTrack(255),
|
||||
_abortParse(0) {
|
||||
memset(_activeNotes, 0, sizeof(_activeNotes));
|
||||
memset(_tracks, 0, sizeof(_tracks));
|
||||
_nextEvent.start = NULL;
|
||||
_nextEvent.delta = 0;
|
||||
_nextEvent.event = 0;
|
||||
|
@ -394,6 +394,7 @@ public:
|
||||
|
||||
static MidiParser *createParser_SMF();
|
||||
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
|
||||
static MidiParser *createParser_QT();
|
||||
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
|
||||
};
|
||||
|
||||
|
496
audio/midiparser_qt.cpp
Normal file
496
audio/midiparser_qt.cpp
Normal 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
134
audio/midiparser_qt.h
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -54,7 +54,6 @@ private:
|
||||
NUM_CHANNELS = 16
|
||||
};
|
||||
|
||||
OSystem *_syst;
|
||||
Common::Mutex _mutex;
|
||||
|
||||
const uint _sampleRate;
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -21,66 +21,67 @@
|
||||
namespace MT32Emu {
|
||||
|
||||
struct AReverbSettings {
|
||||
const Bit32u *allpassSizes;
|
||||
const Bit32u *delaySizes;
|
||||
const float *decayTimes;
|
||||
const float *wetLevels;
|
||||
float filtVal;
|
||||
float damp1;
|
||||
float damp2;
|
||||
const Bit32u * const allpassSizes;
|
||||
const Bit32u * const combSizes;
|
||||
const Bit32u * const outLPositions;
|
||||
const Bit32u * const outRPositions;
|
||||
const Bit32u * const filterFactor;
|
||||
const Bit32u * const decayTimes;
|
||||
const Bit32u * const wetLevels;
|
||||
const Bit32u lpfAmp;
|
||||
};
|
||||
|
||||
class RingBuffer {
|
||||
protected:
|
||||
float *buffer;
|
||||
Bit32u size;
|
||||
const Bit32u size;
|
||||
Bit32u index;
|
||||
|
||||
public:
|
||||
RingBuffer(Bit32u size);
|
||||
RingBuffer(const Bit32u size);
|
||||
virtual ~RingBuffer();
|
||||
float next();
|
||||
bool isEmpty();
|
||||
bool isEmpty() const;
|
||||
void mute();
|
||||
};
|
||||
|
||||
class AllpassFilter : public RingBuffer {
|
||||
public:
|
||||
AllpassFilter(Bit32u size);
|
||||
float process(float in);
|
||||
AllpassFilter(const Bit32u size);
|
||||
float process(const float in);
|
||||
};
|
||||
|
||||
class Delay : public RingBuffer {
|
||||
class CombFilter : public RingBuffer {
|
||||
float feedbackFactor;
|
||||
float filterFactor;
|
||||
|
||||
public:
|
||||
Delay(Bit32u size);
|
||||
float process(float in);
|
||||
CombFilter(const Bit32u size);
|
||||
void process(const float in);
|
||||
float getOutputAt(const Bit32u outIndex) const;
|
||||
void setFeedbackFactor(const float useFeedbackFactor);
|
||||
void setFilterFactor(const float useFilterFactor);
|
||||
};
|
||||
|
||||
class AReverbModel : public ReverbModel {
|
||||
AllpassFilter **allpasses;
|
||||
Delay **delays;
|
||||
CombFilter **combs;
|
||||
|
||||
const AReverbSettings *currentSettings;
|
||||
float decayTime;
|
||||
const AReverbSettings ¤tSettings;
|
||||
float lpfAmp;
|
||||
float wetLevel;
|
||||
float filterhist1, filterhist2;
|
||||
float combhist;
|
||||
void mute();
|
||||
|
||||
public:
|
||||
AReverbModel(const AReverbSettings *newSettings);
|
||||
AReverbModel(const ReverbMode mode);
|
||||
~AReverbModel();
|
||||
void open(unsigned int sampleRate);
|
||||
void open();
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
bool isActive() const;
|
||||
|
||||
static const AReverbSettings REVERB_MODE_0_SETTINGS;
|
||||
static const AReverbSettings REVERB_MODE_1_SETTINGS;
|
||||
static const AReverbSettings REVERB_MODE_2_SETTINGS;
|
||||
};
|
||||
|
||||
// Default reverb settings for modes 0-2
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
393
audio/softsynth/mt32/BReverbModel.cpp
Normal file
393
audio/softsynth/mt32/BReverbModel.cpp
Normal file
@ -0,0 +1,393 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mt32emu.h"
|
||||
|
||||
#if MT32EMU_USE_REVERBMODEL == 2
|
||||
|
||||
#include "BReverbModel.h"
|
||||
|
||||
// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
|
||||
// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
|
||||
// and followed by three parallel comb filters
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
|
||||
// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
|
||||
// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
|
||||
// so we can simply increase the input buffer size.
|
||||
static const Bit32u PROCESS_DELAY = 1;
|
||||
|
||||
static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
|
||||
static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
|
||||
|
||||
// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
|
||||
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
|
||||
|
||||
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
|
||||
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb.
|
||||
static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
|
||||
static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
|
||||
static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
|
||||
static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
|
||||
static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
|
||||
static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit32u MODE_0_LPF_AMP = 0x60;
|
||||
|
||||
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
|
||||
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
|
||||
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
|
||||
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
|
||||
static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
|
||||
static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
|
||||
static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit32u MODE_1_LPF_AMP = 0x60;
|
||||
|
||||
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
|
||||
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
|
||||
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
|
||||
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
|
||||
static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
|
||||
static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
|
||||
static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
|
||||
static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit32u MODE_2_LPF_AMP = 0x80;
|
||||
|
||||
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
|
||||
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
|
||||
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
|
||||
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
|
||||
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
|
||||
static const Bit32u MODE_3_COMB_FACTOR[] = {0x68};
|
||||
static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
|
||||
static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
|
||||
static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
|
||||
|
||||
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
|
||||
|
||||
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
|
||||
|
||||
// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
|
||||
// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation.
|
||||
static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) {
|
||||
Bit8u mask = 0x80;
|
||||
Bit32s res = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0;
|
||||
a >>= 1;
|
||||
res += (mask & addMask) > 0 ? a + carry : 0;
|
||||
mask >>= 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) {
|
||||
buffer = new Bit16s[size];
|
||||
}
|
||||
|
||||
RingBuffer::~RingBuffer() {
|
||||
delete[] buffer;
|
||||
buffer = NULL;
|
||||
}
|
||||
|
||||
Bit32s RingBuffer::next() {
|
||||
if (++index >= size) {
|
||||
index = 0;
|
||||
}
|
||||
return buffer[index];
|
||||
}
|
||||
|
||||
bool RingBuffer::isEmpty() const {
|
||||
if (buffer == NULL) return true;
|
||||
|
||||
Bit16s *buf = buffer;
|
||||
for (Bit32u i = 0; i < size; i++) {
|
||||
if (*buf < -8 || *buf > 8) return false;
|
||||
buf++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RingBuffer::mute() {
|
||||
Bit16s *buf = buffer;
|
||||
for (Bit32u i = 0; i < size; i++) {
|
||||
*buf++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
|
||||
|
||||
Bit32s AllpassFilter::process(const Bit32s in) {
|
||||
// This model corresponds to the allpass filter implementation of the real CM-32L device
|
||||
// found from sample analysis
|
||||
|
||||
Bit16s bufferOut = next();
|
||||
|
||||
// store input - feedback / 2
|
||||
buffer[index] = in - (bufferOut >> 1);
|
||||
|
||||
// return buffer output + feedforward / 2
|
||||
return bufferOut + (buffer[index] >> 1);
|
||||
}
|
||||
|
||||
CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {}
|
||||
|
||||
void CombFilter::process(const Bit32s in) {
|
||||
// This model corresponds to the comb filter implementation of the real CM-32L device
|
||||
|
||||
// the previously stored value
|
||||
Bit32s last = buffer[index];
|
||||
|
||||
// prepare input + feedback
|
||||
Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */);
|
||||
|
||||
// store input + feedback processed by a low-pass filter
|
||||
buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn;
|
||||
}
|
||||
|
||||
Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const {
|
||||
return buffer[(size + index - outIndex) % size];
|
||||
}
|
||||
|
||||
void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) {
|
||||
feedbackFactor = useFeedbackFactor;
|
||||
}
|
||||
|
||||
DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp)
|
||||
: CombFilter(useSize, useFilterFactor), amp(useAmp) {}
|
||||
|
||||
void DelayWithLowPassFilter::process(const Bit32s in) {
|
||||
// the previously stored value
|
||||
Bit32s last = buffer[index];
|
||||
|
||||
// move to the next index
|
||||
next();
|
||||
|
||||
// low-pass filter process
|
||||
Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in;
|
||||
|
||||
// store lpfOut multiplied by LPF amp factor
|
||||
buffer[index] = weirdMul(lpfOut, amp, 0xFF);
|
||||
}
|
||||
|
||||
TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {}
|
||||
|
||||
void TapDelayCombFilter::process(const Bit32s in) {
|
||||
// the previously stored value
|
||||
Bit32s last = buffer[index];
|
||||
|
||||
// move to the next index
|
||||
next();
|
||||
|
||||
// prepare input + feedback
|
||||
// Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
|
||||
Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0);
|
||||
|
||||
// store input + feedback processed by a low-pass filter
|
||||
buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn;
|
||||
}
|
||||
|
||||
Bit32s TapDelayCombFilter::getLeftOutput() const {
|
||||
return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||
}
|
||||
|
||||
Bit32s TapDelayCombFilter::getRightOutput() const {
|
||||
return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||
}
|
||||
|
||||
void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
|
||||
outL = useOutL;
|
||||
outR = useOutR;
|
||||
}
|
||||
|
||||
BReverbModel::BReverbModel(const ReverbMode mode)
|
||||
: allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {}
|
||||
|
||||
BReverbModel::~BReverbModel() {
|
||||
close();
|
||||
}
|
||||
|
||||
void BReverbModel::open() {
|
||||
if (currentSettings.numberOfAllpasses > 0) {
|
||||
allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses];
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
|
||||
}
|
||||
}
|
||||
combs = new CombFilter*[currentSettings.numberOfCombs];
|
||||
if (tapDelayMode) {
|
||||
*combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors);
|
||||
} else {
|
||||
combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
|
||||
for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
|
||||
}
|
||||
}
|
||||
mute();
|
||||
}
|
||||
|
||||
void BReverbModel::close() {
|
||||
if (allpasses != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
if (allpasses[i] != NULL) {
|
||||
delete allpasses[i];
|
||||
allpasses[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] allpasses;
|
||||
allpasses = NULL;
|
||||
}
|
||||
if (combs != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
if (combs[i] != NULL) {
|
||||
delete combs[i];
|
||||
combs[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] combs;
|
||||
combs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void BReverbModel::mute() {
|
||||
if (allpasses != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
allpasses[i]->mute();
|
||||
}
|
||||
}
|
||||
if (combs != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i]->mute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BReverbModel::setParameters(Bit8u time, Bit8u level) {
|
||||
if (combs == NULL) return;
|
||||
level &= 7;
|
||||
time &= 7;
|
||||
if (tapDelayMode) {
|
||||
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||
comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
|
||||
comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
|
||||
} else {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
|
||||
}
|
||||
}
|
||||
if (time == 0 && level == 0) {
|
||||
dryAmp = wetLevel = 0;
|
||||
} else {
|
||||
dryAmp = currentSettings.dryAmps[level];
|
||||
wetLevel = currentSettings.wetLevels[level];
|
||||
}
|
||||
}
|
||||
|
||||
bool BReverbModel::isActive() const {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
if (!allpasses[i]->isEmpty()) return true;
|
||||
}
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
if (!combs[i]->isEmpty()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||
Bit32s dry, link, outL1, outR1;
|
||||
|
||||
for (unsigned long i = 0; i < numSamples; i++) {
|
||||
if (tapDelayMode) {
|
||||
dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f);
|
||||
} else {
|
||||
dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2;
|
||||
}
|
||||
|
||||
// Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
|
||||
dry = weirdMul(dry, dryAmp, 0xFF);
|
||||
|
||||
if (tapDelayMode) {
|
||||
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||
comb->process(dry);
|
||||
*outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f;
|
||||
*outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f;
|
||||
} else {
|
||||
// Get the last stored sample before processing in order not to loose it
|
||||
link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
|
||||
|
||||
// Entrance LPF. Note, comb.process() differs a bit here.
|
||||
combs[0]->process(dry);
|
||||
|
||||
// This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
|
||||
link = link - 1;
|
||||
link = allpasses[0]->process(link);
|
||||
link = allpasses[1]->process(link);
|
||||
link = allpasses[2]->process(link);
|
||||
|
||||
// If the output position is equal to the comb size, get it now in order not to loose it
|
||||
outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
|
||||
outL1 += outL1 >> 1;
|
||||
|
||||
combs[1]->process(link);
|
||||
combs[2]->process(link);
|
||||
combs[3]->process(link);
|
||||
|
||||
link = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
|
||||
link += link >> 1;
|
||||
link += outL1;
|
||||
link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
|
||||
*outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
|
||||
|
||||
outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
|
||||
outR1 += outR1 >> 1;
|
||||
link = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
|
||||
link += link >> 1;
|
||||
link += outR1;
|
||||
link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
|
||||
*outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
|
||||
}
|
||||
|
||||
inLeft++;
|
||||
inRight++;
|
||||
outLeft++;
|
||||
outRight++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
112
audio/softsynth/mt32/BReverbModel.h
Normal file
112
audio/softsynth/mt32/BReverbModel.h
Normal file
@ -0,0 +1,112 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_B_REVERB_MODEL_H
|
||||
#define MT32EMU_B_REVERB_MODEL_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
struct BReverbSettings {
|
||||
const Bit32u numberOfAllpasses;
|
||||
const Bit32u * const allpassSizes;
|
||||
const Bit32u numberOfCombs;
|
||||
const Bit32u * const combSizes;
|
||||
const Bit32u * const outLPositions;
|
||||
const Bit32u * const outRPositions;
|
||||
const Bit32u * const filterFactors;
|
||||
const Bit32u * const feedbackFactors;
|
||||
const Bit32u * const dryAmps;
|
||||
const Bit32u * const wetLevels;
|
||||
const Bit32u lpfAmp;
|
||||
};
|
||||
|
||||
class RingBuffer {
|
||||
protected:
|
||||
Bit16s *buffer;
|
||||
const Bit32u size;
|
||||
Bit32u index;
|
||||
|
||||
public:
|
||||
RingBuffer(const Bit32u size);
|
||||
virtual ~RingBuffer();
|
||||
Bit32s next();
|
||||
bool isEmpty() const;
|
||||
void mute();
|
||||
};
|
||||
|
||||
class AllpassFilter : public RingBuffer {
|
||||
public:
|
||||
AllpassFilter(const Bit32u size);
|
||||
Bit32s process(const Bit32s in);
|
||||
};
|
||||
|
||||
class CombFilter : public RingBuffer {
|
||||
protected:
|
||||
const Bit32u filterFactor;
|
||||
Bit32u feedbackFactor;
|
||||
|
||||
public:
|
||||
CombFilter(const Bit32u size, const Bit32u useFilterFactor);
|
||||
virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure
|
||||
Bit32s getOutputAt(const Bit32u outIndex) const;
|
||||
void setFeedbackFactor(const Bit32u useFeedbackFactor);
|
||||
};
|
||||
|
||||
class DelayWithLowPassFilter : public CombFilter {
|
||||
Bit32u amp;
|
||||
|
||||
public:
|
||||
DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp);
|
||||
void process(const Bit32s in);
|
||||
void setFeedbackFactor(const Bit32u) {}
|
||||
};
|
||||
|
||||
class TapDelayCombFilter : public CombFilter {
|
||||
Bit32u outL;
|
||||
Bit32u outR;
|
||||
|
||||
public:
|
||||
TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor);
|
||||
void process(const Bit32s in);
|
||||
Bit32s getLeftOutput() const;
|
||||
Bit32s getRightOutput() const;
|
||||
void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR);
|
||||
};
|
||||
|
||||
class BReverbModel : public ReverbModel {
|
||||
AllpassFilter **allpasses;
|
||||
CombFilter **combs;
|
||||
|
||||
const BReverbSettings ¤tSettings;
|
||||
const bool tapDelayMode;
|
||||
Bit32u dryAmp;
|
||||
Bit32u wetLevel;
|
||||
void mute();
|
||||
|
||||
public:
|
||||
BReverbModel(const ReverbMode mode);
|
||||
~BReverbModel();
|
||||
void open();
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
bool isActive() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -25,17 +25,14 @@ private:
|
||||
Bit8u time;
|
||||
Bit8u level;
|
||||
|
||||
unsigned int sampleRate;
|
||||
Bit32u bufSize;
|
||||
Bit32u bufIx;
|
||||
|
||||
float *buf;
|
||||
|
||||
Bit32u delayLeft;
|
||||
Bit32u delayRight;
|
||||
Bit32u delayFeedback;
|
||||
|
||||
float fade;
|
||||
float amp;
|
||||
float feedback;
|
||||
|
||||
void recalcParameters();
|
||||
@ -43,7 +40,7 @@ private:
|
||||
public:
|
||||
DelayReverb();
|
||||
~DelayReverb();
|
||||
void open(unsigned int sampleRate);
|
||||
void open();
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -32,7 +32,7 @@ class FreeverbModel : public ReverbModel {
|
||||
public:
|
||||
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
|
||||
~FreeverbModel();
|
||||
void open(unsigned int sampleRate);
|
||||
void open();
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
|
418
audio/softsynth/mt32/LA32WaveGenerator.cpp
Normal file
418
audio/softsynth/mt32/LA32WaveGenerator.cpp
Normal file
@ -0,0 +1,418 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cmath>
|
||||
#include "mt32emu.h"
|
||||
#include "mmath.h"
|
||||
#include "LA32WaveGenerator.h"
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 0
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;
|
||||
static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18;
|
||||
static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18;
|
||||
static const Bit32u MAX_CUTOFF_VALUE = 240 << 18;
|
||||
static const LogSample SILENCE = {65535, LogSample::POSITIVE};
|
||||
|
||||
Bit16u LA32Utilites::interpolateExp(const Bit16u fract) {
|
||||
Bit16u expTabIndex = fract >> 3;
|
||||
Bit16u extraBits = ~fract & 7;
|
||||
Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex];
|
||||
Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]);
|
||||
return expTabEntry2 + (((expTabEntry1 - expTabEntry2) * extraBits) >> 3);
|
||||
}
|
||||
|
||||
Bit16s LA32Utilites::unlog(const LogSample &logSample) {
|
||||
//Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f);
|
||||
Bit32u intLogValue = logSample.logValue >> 12;
|
||||
Bit16u fracLogValue = logSample.logValue & 4095;
|
||||
Bit16s sample = interpolateExp(fracLogValue) >> intLogValue;
|
||||
return logSample.sign == LogSample::POSITIVE ? sample : -sample;
|
||||
}
|
||||
|
||||
void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) {
|
||||
Bit32u logSampleValue = logSample1.logValue + logSample2.logValue;
|
||||
logSample1.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getSampleStep() {
|
||||
// sampleStep = EXP2F(pitch / 4096.0f + 4.0f)
|
||||
Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||
sampleStep <<= pitch >> 12;
|
||||
sampleStep >>= 8;
|
||||
sampleStep &= ~1;
|
||||
return sampleStep;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) {
|
||||
// resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f);
|
||||
Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095);
|
||||
resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12;
|
||||
return resonanceWaveLengthFactor;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) {
|
||||
// Ratio of positive segment to wave length
|
||||
Bit32u effectivePulseWidthValue = 0;
|
||||
if (pulseWidth > 128) {
|
||||
effectivePulseWidthValue = (pulseWidth - 128) << 6;
|
||||
}
|
||||
|
||||
Bit32u highLinearLength = 0;
|
||||
// highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (effectivePulseWidthValue < effectiveCutoffValue) {
|
||||
Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue;
|
||||
highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095);
|
||||
highLinearLength <<= 7 + (expArg >> 12);
|
||||
highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
}
|
||||
return highLinearLength;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) {
|
||||
// Assuming 12-bit multiplication used here
|
||||
squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4);
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (squareWavePosition < highLinearLength) {
|
||||
phase = POSITIVE_LINEAR_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= highLinearLength;
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = POSITIVE_FALLING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
resonanceSinePosition = squareWavePosition;
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = NEGATIVE_FALLING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (squareWavePosition < lowLinearLength) {
|
||||
phase = NEGATIVE_LINEAR_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= lowLinearLength;
|
||||
phase = NEGATIVE_RISING_SINE_SEGMENT;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::advancePosition() {
|
||||
wavePosition += getSampleStep();
|
||||
wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
|
||||
Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0;
|
||||
Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue);
|
||||
Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue);
|
||||
Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength;
|
||||
computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor);
|
||||
|
||||
// resonancePhase computation hack
|
||||
*(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSquareWaveLogSample() {
|
||||
Bit32u logSampleValue;
|
||||
switch (phase) {
|
||||
case POSITIVE_RISING_SINE_SEGMENT:
|
||||
case NEGATIVE_FALLING_SINE_SEGMENT:
|
||||
logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511];
|
||||
break;
|
||||
case POSITIVE_FALLING_SINE_SEGMENT:
|
||||
case NEGATIVE_RISING_SINE_SEGMENT:
|
||||
logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511];
|
||||
break;
|
||||
case POSITIVE_LINEAR_SEGMENT:
|
||||
case NEGATIVE_LINEAR_SEGMENT:
|
||||
default:
|
||||
logSampleValue = 0;
|
||||
break;
|
||||
}
|
||||
logSampleValue <<= 2;
|
||||
logSampleValue += amp >> 10;
|
||||
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||
logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9;
|
||||
}
|
||||
|
||||
squareLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
|
||||
Bit32u logSampleValue;
|
||||
if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) {
|
||||
logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511];
|
||||
} else {
|
||||
logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511];
|
||||
}
|
||||
logSampleValue <<= 2;
|
||||
logSampleValue += amp >> 10;
|
||||
|
||||
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||
Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1;
|
||||
// Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
|
||||
logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
|
||||
|
||||
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||
if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
|
||||
// The window is synchronous sine here
|
||||
logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
|
||||
} else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) {
|
||||
// The window is synchronous square sine here
|
||||
logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3;
|
||||
}
|
||||
|
||||
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||
// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
|
||||
logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
|
||||
} else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
|
||||
// For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
|
||||
Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13;
|
||||
logSampleValue += Tables::getInstance().logsin9[sineIx] << 2;
|
||||
}
|
||||
|
||||
// After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures
|
||||
logSampleValue -= 1 << 12;
|
||||
|
||||
resonanceLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const {
|
||||
Bit32u sawtoothCosinePosition = wavePosition + (1 << 18);
|
||||
if ((sawtoothCosinePosition & (1 << 18)) > 0) {
|
||||
logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511];
|
||||
} else {
|
||||
logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511];
|
||||
}
|
||||
logSample.logValue <<= 2;
|
||||
logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const {
|
||||
Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1;
|
||||
logSampleValue += amp >> 10;
|
||||
logSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
|
||||
logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextPCMWaveLogSamples() {
|
||||
// This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc.
|
||||
// The most probable cause is the factor in the interpolation formula is one bit less
|
||||
// accurate than the sample position counter
|
||||
pcmInterpolationFactor = (wavePosition & 255) >> 1;
|
||||
Bit32u pcmWaveTableIx = wavePosition >> 8;
|
||||
pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
if (pcmWaveInterpolated) {
|
||||
pcmWaveTableIx++;
|
||||
if (pcmWaveTableIx < pcmWaveLength) {
|
||||
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
} else {
|
||||
if (pcmWaveLooped) {
|
||||
pcmWaveTableIx -= pcmWaveLength;
|
||||
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
} else {
|
||||
secondPCMLogSample = SILENCE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
secondPCMLogSample = SILENCE;
|
||||
}
|
||||
// pcmSampleStep = (Bit32u)EXP2F(pitch / 4096.0f + 3.0f);
|
||||
Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||
pcmSampleStep <<= pitch >> 12;
|
||||
// Seeing the actual lengths of the PCM wave for pitches 00..12,
|
||||
// the pcmPosition counter can be assumed to have 8-bit fractions
|
||||
pcmSampleStep >>= 9;
|
||||
wavePosition += pcmSampleStep;
|
||||
if (wavePosition >= (pcmWaveLength << 8)) {
|
||||
if (pcmWaveLooped) {
|
||||
wavePosition -= pcmWaveLength << 8;
|
||||
} else {
|
||||
deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
|
||||
sawtoothWaveform = useSawtoothWaveform;
|
||||
pulseWidth = usePulseWidth;
|
||||
resonance = useResonance;
|
||||
|
||||
wavePosition = 0;
|
||||
|
||||
squareWavePosition = 0;
|
||||
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||
|
||||
resonanceSinePosition = 0;
|
||||
resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT;
|
||||
resonanceAmpSubtraction = (32 - resonance) << 10;
|
||||
resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
|
||||
|
||||
pcmWaveAddress = NULL;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
|
||||
pcmWaveAddress = usePCMWaveAddress;
|
||||
pcmWaveLength = usePCMWaveLength;
|
||||
pcmWaveLooped = usePCMWaveLooped;
|
||||
pcmWaveInterpolated = usePCMWaveInterpolated;
|
||||
|
||||
wavePosition = 0;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
amp = useAmp;
|
||||
pitch = usePitch;
|
||||
|
||||
if (isPCMWave()) {
|
||||
generateNextPCMWaveLogSamples();
|
||||
return;
|
||||
}
|
||||
|
||||
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||
// More research is needed to be sure that this is correct, however.
|
||||
cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal;
|
||||
|
||||
generateNextSquareWaveLogSample();
|
||||
generateNextResonanceWaveLogSample();
|
||||
if (sawtoothWaveform) {
|
||||
LogSample cosineLogSample;
|
||||
generateNextSawtoothCosineLogSample(cosineLogSample);
|
||||
LA32Utilites::addLogSamples(squareLogSample, cosineLogSample);
|
||||
LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample);
|
||||
}
|
||||
advancePosition();
|
||||
}
|
||||
|
||||
LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const {
|
||||
if (!isActive()) {
|
||||
return SILENCE;
|
||||
}
|
||||
if (isPCMWave()) {
|
||||
return first ? firstPCMLogSample : secondPCMLogSample;
|
||||
}
|
||||
return first ? squareLogSample : resonanceLogSample;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::deactivate() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isActive() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isPCMWave() const {
|
||||
return pcmWaveAddress != NULL;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
|
||||
return pcmInterpolationFactor;
|
||||
}
|
||||
|
||||
void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) {
|
||||
ringModulated = useRingModulated;
|
||||
mixed = useMixed;
|
||||
}
|
||||
|
||||
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
} else {
|
||||
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||
} else {
|
||||
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||
if (useMaster == MASTER) {
|
||||
master.generateNextSample(amp, pitch, cutoff);
|
||||
} else {
|
||||
slave.generateNextSample(amp, pitch, cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) {
|
||||
if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) {
|
||||
return 0;
|
||||
}
|
||||
LogSample firstLogSample = wg.getOutputLogSample(true);
|
||||
LogSample secondLogSample = wg.getOutputLogSample(false);
|
||||
if (ringModulatingLogSample != NULL) {
|
||||
LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample);
|
||||
LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample);
|
||||
}
|
||||
Bit16s firstSample = LA32Utilites::unlog(firstLogSample);
|
||||
Bit16s secondSample = LA32Utilites::unlog(secondLogSample);
|
||||
if (wg.isPCMWave()) {
|
||||
return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7));
|
||||
}
|
||||
return firstSample + secondSample;
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::nextOutSample() {
|
||||
if (ringModulated) {
|
||||
LogSample slaveFirstLogSample = slave.getOutputLogSample(true);
|
||||
LogSample slaveSecondLogSample = slave.getOutputLogSample(false);
|
||||
Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample);
|
||||
if (!slave.isPCMWave()) {
|
||||
sample += unlogAndMixWGOutput(master, &slaveSecondLogSample);
|
||||
}
|
||||
if (mixed) {
|
||||
sample += unlogAndMixWGOutput(master, NULL);
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL);
|
||||
}
|
||||
|
||||
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||
if (useMaster == MASTER) {
|
||||
master.deactivate();
|
||||
} else {
|
||||
slave.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 0
|
246
audio/softsynth/mt32/LA32WaveGenerator.h
Normal file
246
audio/softsynth/mt32/LA32WaveGenerator.h
Normal file
@ -0,0 +1,246 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 0
|
||||
|
||||
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
#define MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
/**
|
||||
* LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions
|
||||
* It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these:
|
||||
* - interpolation of exponent table (obvious, a delta value has 4 bits)
|
||||
* - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion)
|
||||
* - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip)
|
||||
* and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss
|
||||
* Subtraction is supposed to be replaced by simple inversion
|
||||
* As the logarithmic sine is always negative, all the logarithmic values are treated as decrements
|
||||
*/
|
||||
struct LogSample {
|
||||
// 16-bit fixed point value, includes 12-bit fractional part
|
||||
// 4-bit integer part allows to present any 16-bit sample in the log-space
|
||||
// Obviously, the log value doesn't contain the sign of the resulting sample
|
||||
Bit16u logValue;
|
||||
enum {
|
||||
POSITIVE,
|
||||
NEGATIVE
|
||||
} sign;
|
||||
};
|
||||
|
||||
class LA32Utilites {
|
||||
public:
|
||||
static Bit16u interpolateExp(const Bit16u fract);
|
||||
static Bit16s unlog(const LogSample &logSample);
|
||||
static void addLogSamples(LogSample &logSample1, const LogSample &logSample2);
|
||||
};
|
||||
|
||||
/**
|
||||
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||
* The output square wave is created by adding high / low linear segments in-between
|
||||
* the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
|
||||
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||
*/
|
||||
class LA32WaveGenerator {
|
||||
//***************************************************************************
|
||||
// The local copy of partial parameters below
|
||||
//***************************************************************************
|
||||
|
||||
bool active;
|
||||
|
||||
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||
bool sawtoothWaveform;
|
||||
|
||||
// Logarithmic amp of the wave generator
|
||||
Bit32u amp;
|
||||
|
||||
// Logarithmic frequency of the resulting wave
|
||||
Bit16u pitch;
|
||||
|
||||
// Values in range [1..31]
|
||||
// Value 1 correspong to the minimum resonance
|
||||
Bit8u resonance;
|
||||
|
||||
// Processed value in range [0..255]
|
||||
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||
Bit8u pulseWidth;
|
||||
|
||||
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||
Bit32u cutoffVal;
|
||||
|
||||
// Logarithmic PCM sample start address
|
||||
const Bit16s *pcmWaveAddress;
|
||||
|
||||
// Logarithmic PCM sample length
|
||||
Bit32u pcmWaveLength;
|
||||
|
||||
// true for looped logarithmic PCM samples
|
||||
bool pcmWaveLooped;
|
||||
|
||||
// false for slave PCM partials in the structures with the ring modulation
|
||||
bool pcmWaveInterpolated;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal variables below
|
||||
//***************************************************************************
|
||||
|
||||
// Relative position within either the synth wave or the PCM sampled wave
|
||||
// 0 - start of the positive rising sine segment of the square wave or start of the PCM sample
|
||||
// 1048576 (2^20) - end of the negative rising sine segment of the square wave
|
||||
// For PCM waves, the address of the currently playing sample equals (wavePosition / 256)
|
||||
Bit32u wavePosition;
|
||||
|
||||
// Relative position within a square wave phase:
|
||||
// 0 - start of the phase
|
||||
// 262144 (2^18) - end of a sine phase in the square wave
|
||||
Bit32u squareWavePosition;
|
||||
|
||||
// Relative position within the positive or negative wave segment:
|
||||
// 0 - start of the corresponding positive or negative segment of the square wave
|
||||
// 262144 (2^18) - corresponds to end of the first sine phase in the square wave
|
||||
// The same increment sampleStep is used to indicate the current position
|
||||
// since the length of the resonance wave is always equal to four square wave sine segments.
|
||||
Bit32u resonanceSinePosition;
|
||||
|
||||
// The amp of the resonance sine wave grows with the resonance value
|
||||
// As the resonance value cannot change while the partial is active, it is initialised once
|
||||
Bit32u resonanceAmpSubtraction;
|
||||
|
||||
// The decay speed of resonance sine wave, depends on the resonance value
|
||||
Bit32u resAmpDecayFactor;
|
||||
|
||||
// Fractional part of the pcmPosition
|
||||
Bit32u pcmInterpolationFactor;
|
||||
|
||||
// Current phase of the square wave
|
||||
enum {
|
||||
POSITIVE_RISING_SINE_SEGMENT,
|
||||
POSITIVE_LINEAR_SEGMENT,
|
||||
POSITIVE_FALLING_SINE_SEGMENT,
|
||||
NEGATIVE_FALLING_SINE_SEGMENT,
|
||||
NEGATIVE_LINEAR_SEGMENT,
|
||||
NEGATIVE_RISING_SINE_SEGMENT
|
||||
} phase;
|
||||
|
||||
// Current phase of the resonance wave
|
||||
enum {
|
||||
POSITIVE_RISING_RESONANCE_SINE_SEGMENT,
|
||||
POSITIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||
NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||
NEGATIVE_RISING_RESONANCE_SINE_SEGMENT
|
||||
} resonancePhase;
|
||||
|
||||
// Resulting log-space samples of the square and resonance waves
|
||||
LogSample squareLogSample;
|
||||
LogSample resonanceLogSample;
|
||||
|
||||
// Processed neighbour log-space samples of the PCM wave
|
||||
LogSample firstPCMLogSample;
|
||||
LogSample secondPCMLogSample;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal methods below
|
||||
//***************************************************************************
|
||||
|
||||
Bit32u getSampleStep();
|
||||
Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue);
|
||||
Bit32u getHighLinearLength(Bit32u effectiveCutoffValue);
|
||||
|
||||
void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor);
|
||||
void advancePosition();
|
||||
|
||||
void generateNextSquareWaveLogSample();
|
||||
void generateNextResonanceWaveLogSample();
|
||||
void generateNextSawtoothCosineLogSample(LogSample &logSample) const;
|
||||
|
||||
void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const;
|
||||
void generateNextPCMWaveLogSamples();
|
||||
|
||||
public:
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards
|
||||
LogSample getOutputLogSample(const bool first) const;
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate();
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive() const;
|
||||
|
||||
// Return true if the WG engine generates PCM wave samples
|
||||
bool isPCMWave() const;
|
||||
|
||||
// Return current PCM interpolation factor
|
||||
Bit32u getPCMInterpolationFactor() const;
|
||||
};
|
||||
|
||||
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||
class LA32PartialPair {
|
||||
LA32WaveGenerator master;
|
||||
LA32WaveGenerator slave;
|
||||
bool ringModulated;
|
||||
bool mixed;
|
||||
|
||||
static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample);
|
||||
|
||||
public:
|
||||
enum PairType {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||
// ringModulated should be set to true for the structures with ring modulation
|
||||
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||
void init(const bool ringModulated, const bool mixed);
|
||||
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Perform mixing / ring modulation and return the result
|
||||
Bit16s nextOutSample();
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate(const PairType master);
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive(const PairType master) const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 0
|
347
audio/softsynth/mt32/LegacyWaveGenerator.cpp
Normal file
347
audio/softsynth/mt32/LegacyWaveGenerator.cpp
Normal file
@ -0,0 +1,347 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cmath>
|
||||
#include "mt32emu.h"
|
||||
#include "mmath.h"
|
||||
#include "LegacyWaveGenerator.h"
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 1
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const float MIDDLE_CUTOFF_VALUE = 128.0f;
|
||||
static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f;
|
||||
static const float MAX_CUTOFF_VALUE = 240.0f;
|
||||
|
||||
float LA32WaveGenerator::getPCMSample(unsigned int position) {
|
||||
if (position >= pcmWaveLength) {
|
||||
if (!pcmWaveLooped) {
|
||||
return 0;
|
||||
}
|
||||
position = position % pcmWaveLength;
|
||||
}
|
||||
Bit16s pcmSample = pcmWaveAddress[position];
|
||||
float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f);
|
||||
return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
this->sawtoothWaveform = sawtoothWaveform;
|
||||
this->pulseWidth = pulseWidth;
|
||||
this->resonance = resonance;
|
||||
|
||||
wavePos = 0.0f;
|
||||
lastFreq = 0.0f;
|
||||
|
||||
pcmWaveAddress = NULL;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) {
|
||||
this->pcmWaveAddress = pcmWaveAddress;
|
||||
this->pcmWaveLength = pcmWaveLength;
|
||||
this->pcmWaveLooped = pcmWaveLooped;
|
||||
this->pcmWaveInterpolated = pcmWaveInterpolated;
|
||||
|
||||
pcmPosition = 0.0f;
|
||||
active = true;
|
||||
}
|
||||
|
||||
float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) {
|
||||
if (!active) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
this->amp = amp;
|
||||
this->pitch = pitch;
|
||||
|
||||
float sample = 0.0f;
|
||||
|
||||
// SEMI-CONFIRMED: From sample analysis:
|
||||
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
|
||||
// This gives results within +/- 2 at the output (before any DAC bitshifting)
|
||||
// when sustaining at levels 156 - 255 with no modifiers.
|
||||
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
|
||||
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
|
||||
// positive amps, so negative still needs to be explored, as well as lower levels.
|
||||
//
|
||||
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
|
||||
|
||||
float amp = EXP2F(ampVal / -1024.0f / 4096.0f);
|
||||
float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
|
||||
|
||||
if (isPCMWave()) {
|
||||
// Render PCM waveform
|
||||
int len = pcmWaveLength;
|
||||
int intPCMPosition = (int)pcmPosition;
|
||||
if (intPCMPosition >= len && !pcmWaveLooped) {
|
||||
// We're now past the end of a non-looping PCM waveform so it's time to die.
|
||||
deactivate();
|
||||
return 0.0f;
|
||||
}
|
||||
float positionDelta = freq * 2048.0f / SAMPLE_RATE;
|
||||
|
||||
// Linear interpolation
|
||||
float firstSample = getPCMSample(intPCMPosition);
|
||||
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
|
||||
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
|
||||
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
|
||||
if (pcmWaveInterpolated) {
|
||||
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
|
||||
} else {
|
||||
sample = firstSample;
|
||||
}
|
||||
|
||||
float newPCMPosition = pcmPosition + positionDelta;
|
||||
if (pcmWaveLooped) {
|
||||
newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength);
|
||||
}
|
||||
pcmPosition = newPCMPosition;
|
||||
} else {
|
||||
// Render synthesised waveform
|
||||
wavePos *= lastFreq / freq;
|
||||
lastFreq = freq;
|
||||
|
||||
float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f);
|
||||
{
|
||||
//static const float resAmpFactor = EXP2F(-7);
|
||||
//resAmp = EXP2I(resonance << 10) * resAmpFactor;
|
||||
}
|
||||
|
||||
// The cutoffModifier may not be supposed to be directly added to the cutoff -
|
||||
// it may for example need to be multiplied in some way.
|
||||
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||
// More research is needed to be sure that this is correct, however.
|
||||
float cutoffVal = cutoffRampVal / 262144.0f;
|
||||
if (cutoffVal > MAX_CUTOFF_VALUE) {
|
||||
cutoffVal = MAX_CUTOFF_VALUE;
|
||||
}
|
||||
|
||||
// Wave length in samples
|
||||
float waveLen = SAMPLE_RATE / freq;
|
||||
|
||||
// Init cosineLen
|
||||
float cosineLen = 0.5f * waveLen;
|
||||
if (cutoffVal > MIDDLE_CUTOFF_VALUE) {
|
||||
cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis
|
||||
}
|
||||
|
||||
// Start playing in center of first cosine segment
|
||||
// relWavePos is shifted by a half of cosineLen
|
||||
float relWavePos = wavePos + 0.5f * cosineLen;
|
||||
if (relWavePos > waveLen) {
|
||||
relWavePos -= waveLen;
|
||||
}
|
||||
|
||||
// Ratio of positive segment to wave length
|
||||
float pulseLen = 0.5f;
|
||||
if (pulseWidth > 128) {
|
||||
pulseLen = EXP2F((64 - pulseWidth) / 64.0f);
|
||||
//static const float pulseLenFactor = EXP2F(-192 / 64);
|
||||
//pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
|
||||
}
|
||||
pulseLen *= waveLen;
|
||||
|
||||
float hLen = pulseLen - cosineLen;
|
||||
|
||||
// Ignore pulsewidths too high for given freq
|
||||
if (hLen < 0.0f) {
|
||||
hLen = 0.0f;
|
||||
}
|
||||
|
||||
// Ignore pulsewidths too high for given freq and cutoff
|
||||
float lLen = waveLen - hLen - 2 * cosineLen;
|
||||
if (lLen < 0.0f) {
|
||||
lLen = 0.0f;
|
||||
}
|
||||
|
||||
// Correct resAmp for cutoff in range 50..66
|
||||
if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
|
||||
resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
|
||||
}
|
||||
|
||||
// Produce filtered square wave with 2 cosine waves on slopes
|
||||
|
||||
// 1st cosine segment
|
||||
if (relWavePos < cosineLen) {
|
||||
sample = -cos(FLOAT_PI * relWavePos / cosineLen);
|
||||
} else
|
||||
|
||||
// high linear segment
|
||||
if (relWavePos < (cosineLen + hLen)) {
|
||||
sample = 1.f;
|
||||
} else
|
||||
|
||||
// 2nd cosine segment
|
||||
if (relWavePos < (2 * cosineLen + hLen)) {
|
||||
sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
|
||||
} else {
|
||||
|
||||
// low linear segment
|
||||
sample = -1.f;
|
||||
}
|
||||
|
||||
if (cutoffVal < 128.0f) {
|
||||
|
||||
// Attenuate samples below cutoff 50
|
||||
// Found by sample analysis
|
||||
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
|
||||
} else {
|
||||
|
||||
// Add resonance sine. Effective for cutoff > 50 only
|
||||
float resSample = 1.0f;
|
||||
|
||||
// Resonance decay speed factor
|
||||
float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2];
|
||||
|
||||
// Now relWavePos counts from the middle of first cosine
|
||||
relWavePos = wavePos;
|
||||
|
||||
// negative segments
|
||||
if (!(relWavePos < (cosineLen + hLen))) {
|
||||
resSample = -resSample;
|
||||
relWavePos -= cosineLen + hLen;
|
||||
|
||||
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||
resAmpDecayFactor += 0.25f;
|
||||
}
|
||||
|
||||
// Resonance sine WG
|
||||
resSample *= sin(FLOAT_PI * relWavePos / cosineLen);
|
||||
|
||||
// Resonance sine amp
|
||||
float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact
|
||||
float resAmpFade = EXP2F(resAmpFadeLog2);
|
||||
|
||||
// Now relWavePos set negative to the left from center of any cosine
|
||||
relWavePos = wavePos;
|
||||
|
||||
// negative segment
|
||||
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
|
||||
relWavePos -= waveLen;
|
||||
} else
|
||||
|
||||
// positive segment
|
||||
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
|
||||
relWavePos -= cosineLen + hLen;
|
||||
}
|
||||
|
||||
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||
if (relWavePos < 0.5f * cosineLen) {
|
||||
float syncSine = sin(FLOAT_PI * relWavePos / cosineLen);
|
||||
if (relWavePos < 0.0f) {
|
||||
// The window is synchronous square sine here
|
||||
resAmpFade *= syncSine * syncSine;
|
||||
} else {
|
||||
// The window is synchronous sine here
|
||||
resAmpFade *= syncSine;
|
||||
}
|
||||
}
|
||||
|
||||
sample += resSample * resAmp * resAmpFade;
|
||||
}
|
||||
|
||||
// sawtooth waves
|
||||
if (sawtoothWaveform) {
|
||||
sample *= cos(FLOAT_2PI * wavePos / waveLen);
|
||||
}
|
||||
|
||||
wavePos++;
|
||||
|
||||
// wavePos isn't supposed to be > waveLen
|
||||
if (wavePos > waveLen) {
|
||||
wavePos -= waveLen;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply sample with current TVA value
|
||||
sample *= amp;
|
||||
return sample;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::deactivate() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isActive() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isPCMWave() const {
|
||||
return pcmWaveAddress != NULL;
|
||||
}
|
||||
|
||||
void LA32PartialPair::init(const bool ringModulated, const bool mixed) {
|
||||
this->ringModulated = ringModulated;
|
||||
this->mixed = mixed;
|
||||
masterOutputSample = 0.0f;
|
||||
slaveOutputSample = 0.0f;
|
||||
}
|
||||
|
||||
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
} else {
|
||||
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||
} else {
|
||||
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||
if (useMaster == MASTER) {
|
||||
masterOutputSample = master.generateNextSample(amp, pitch, cutoff);
|
||||
} else {
|
||||
slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::nextOutSample() {
|
||||
float outputSample;
|
||||
if (ringModulated) {
|
||||
float ringModulatedSample = masterOutputSample * slaveOutputSample;
|
||||
outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
|
||||
} else {
|
||||
outputSample = masterOutputSample + slaveOutputSample;
|
||||
}
|
||||
return Bit16s(outputSample * 8192.0f);
|
||||
}
|
||||
|
||||
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||
if (useMaster == MASTER) {
|
||||
master.deactivate();
|
||||
masterOutputSample = 0.0f;
|
||||
} else {
|
||||
slave.deactivate();
|
||||
slaveOutputSample = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 1
|
146
audio/softsynth/mt32/LegacyWaveGenerator.h
Normal file
146
audio/softsynth/mt32/LegacyWaveGenerator.h
Normal file
@ -0,0 +1,146 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if MT32EMU_ACCURATE_WG == 1
|
||||
|
||||
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
#define MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
/**
|
||||
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||
* The output square wave is created by adding high / low linear segments in-between
|
||||
* the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
|
||||
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||
*/
|
||||
class LA32WaveGenerator {
|
||||
//***************************************************************************
|
||||
// The local copy of partial parameters below
|
||||
//***************************************************************************
|
||||
|
||||
bool active;
|
||||
|
||||
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||
bool sawtoothWaveform;
|
||||
|
||||
// Logarithmic amp of the wave generator
|
||||
Bit32u amp;
|
||||
|
||||
// Logarithmic frequency of the resulting wave
|
||||
Bit16u pitch;
|
||||
|
||||
// Values in range [1..31]
|
||||
// Value 1 correspong to the minimum resonance
|
||||
Bit8u resonance;
|
||||
|
||||
// Processed value in range [0..255]
|
||||
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||
Bit8u pulseWidth;
|
||||
|
||||
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||
Bit32u cutoffVal;
|
||||
|
||||
// Logarithmic PCM sample start address
|
||||
const Bit16s *pcmWaveAddress;
|
||||
|
||||
// Logarithmic PCM sample length
|
||||
Bit32u pcmWaveLength;
|
||||
|
||||
// true for looped logarithmic PCM samples
|
||||
bool pcmWaveLooped;
|
||||
|
||||
// false for slave PCM partials in the structures with the ring modulation
|
||||
bool pcmWaveInterpolated;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal variables below
|
||||
//***************************************************************************
|
||||
|
||||
float wavePos;
|
||||
float lastFreq;
|
||||
float pcmPosition;
|
||||
|
||||
float getPCMSample(unsigned int position);
|
||||
|
||||
public:
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate();
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive() const;
|
||||
|
||||
// Return true if the WG engine generates PCM wave samples
|
||||
bool isPCMWave() const;
|
||||
};
|
||||
|
||||
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||
class LA32PartialPair {
|
||||
LA32WaveGenerator master;
|
||||
LA32WaveGenerator slave;
|
||||
bool ringModulated;
|
||||
bool mixed;
|
||||
float masterOutputSample;
|
||||
float slaveOutputSample;
|
||||
|
||||
public:
|
||||
enum PairType {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||
// ringModulated should be set to true for the structures with ring modulation
|
||||
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||
void init(const bool ringModulated, const bool mixed);
|
||||
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Perform mixing / ring modulation and return the result
|
||||
Bit16s nextOutSample();
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate(const PairType master);
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive(const PairType master) const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
#endif // #if MT32EMU_ACCURATE_WG == 1
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -68,18 +68,16 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) {
|
||||
activePartialCount = 0;
|
||||
memset(patchCache, 0, sizeof(patchCache));
|
||||
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
|
||||
freePolys.push_front(new Poly(this));
|
||||
freePolys.prepend(new Poly(this));
|
||||
}
|
||||
}
|
||||
|
||||
Part::~Part() {
|
||||
while (!activePolys.empty()) {
|
||||
delete activePolys.front();
|
||||
activePolys.pop_front();
|
||||
while (!activePolys.isEmpty()) {
|
||||
delete activePolys.takeFirst();
|
||||
}
|
||||
while (!freePolys.empty()) {
|
||||
delete freePolys.front();
|
||||
freePolys.pop_front();
|
||||
while (!freePolys.isEmpty()) {
|
||||
delete freePolys.takeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -18,13 +18,27 @@
|
||||
#ifndef MT32EMU_PART_H
|
||||
#define MT32EMU_PART_H
|
||||
|
||||
#include <common/list.h>
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class PartialManager;
|
||||
class Synth;
|
||||
|
||||
class PolyList {
|
||||
private:
|
||||
Poly *firstPoly;
|
||||
Poly *lastPoly;
|
||||
|
||||
public:
|
||||
PolyList();
|
||||
bool isEmpty() const;
|
||||
Poly *getFirst() const;
|
||||
Poly *getLast() const;
|
||||
void prepend(Poly *poly);
|
||||
void append(Poly *poly);
|
||||
Poly *takeFirst();
|
||||
void remove(Poly * const poly);
|
||||
};
|
||||
|
||||
class Part {
|
||||
private:
|
||||
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
|
||||
@ -37,8 +51,8 @@ private:
|
||||
|
||||
unsigned int activePartialCount;
|
||||
PatchCache patchCache[4];
|
||||
Common::List<Poly *> freePolys;
|
||||
Common::List<Poly *> activePolys;
|
||||
PolyList freePolys;
|
||||
PolyList activePolys;
|
||||
|
||||
void setPatch(const PatchParam *patch);
|
||||
unsigned int midiKeyToKey(unsigned int midiKey);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -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, &Ramp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) {
|
||||
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) {
|
||||
// Initialisation of tva, tvp and tvf uses 'this' pointer
|
||||
// and thus should not be in the initializer list to avoid a compiler warning
|
||||
tva = new TVA(this, &Ramp);
|
||||
tvp = new TVP(this);
|
||||
tvf = new TVF(this, &cutoffModifierRamp);
|
||||
ownerPart = -1;
|
||||
poly = NULL;
|
||||
pair = NULL;
|
||||
@ -81,26 +86,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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -44,12 +44,7 @@ private:
|
||||
int structurePosition; // 0 or 1 of a structure pair
|
||||
StereoVolume stereoVolume;
|
||||
|
||||
// Distance in (possibly fractional) samples from the start of the current pulse
|
||||
float wavePos;
|
||||
|
||||
float lastFreq;
|
||||
|
||||
float myBuffer[MAX_SAMPLES_PER_RUN];
|
||||
Bit16s myBuffer[MAX_SAMPLES_PER_RUN];
|
||||
|
||||
// Only used for PCM partials
|
||||
int pcmNum;
|
||||
@ -60,17 +55,16 @@ private:
|
||||
// Range: 0-255
|
||||
int pulseWidthVal;
|
||||
|
||||
float pcmPosition;
|
||||
|
||||
Poly *poly;
|
||||
|
||||
LA32Ramp ampRamp;
|
||||
LA32Ramp cutoffModifierRamp;
|
||||
|
||||
float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len);
|
||||
float *mixBuffersRing(float *buf1, float *buf2, unsigned long len);
|
||||
// TODO: This should be owned by PartialPair
|
||||
LA32PartialPair la32Pair;
|
||||
|
||||
float getPCMSample(unsigned int position);
|
||||
Bit32u getAmpValue();
|
||||
Bit32u getCutoffValue();
|
||||
|
||||
public:
|
||||
const PatchCache *patchCache;
|
||||
@ -90,7 +84,6 @@ public:
|
||||
unsigned long debugGetSampleNum() const;
|
||||
|
||||
int getOwnerPart() const;
|
||||
int getKey() const;
|
||||
const Poly *getPoly() const;
|
||||
bool isActive() const;
|
||||
void activate(int part);
|
||||
@ -111,7 +104,7 @@ public:
|
||||
bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length);
|
||||
|
||||
// This function writes mono sample output to the provided buffer, and returns the number of samples written
|
||||
unsigned long generateSamples(float *partialBuf, unsigned long length);
|
||||
unsigned long generateSamples(Bit16s *partialBuf, unsigned long length);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -29,6 +29,7 @@ Poly::Poly(Part *usePart) {
|
||||
partials[i] = NULL;
|
||||
}
|
||||
state = POLY_Inactive;
|
||||
next = NULL;
|
||||
}
|
||||
|
||||
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -41,6 +41,8 @@ private:
|
||||
|
||||
Partial *partials[4];
|
||||
|
||||
Poly *next;
|
||||
|
||||
public:
|
||||
Poly(Part *part);
|
||||
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
|
||||
@ -60,6 +62,9 @@ public:
|
||||
bool isActive() const;
|
||||
|
||||
void partialDeactivated(Partial *partial);
|
||||
|
||||
Poly *getNext();
|
||||
void setNext(Poly *poly);
|
||||
};
|
||||
|
||||
}
|
||||
|
111
audio/softsynth/mt32/ROMInfo.cpp
Normal file
111
audio/softsynth/mt32/ROMInfo.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cstring>
|
||||
#include "ROMInfo.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Known ROMs
|
||||
static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL, NULL};
|
||||
|
||||
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL, NULL};
|
||||
|
||||
static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL, NULL};
|
||||
static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL, NULL};
|
||||
|
||||
static const ROMInfo * const ROM_INFOS[] = {
|
||||
&CTRL_MT32_V1_04,
|
||||
&CTRL_MT32_V1_05,
|
||||
&CTRL_MT32_V1_06,
|
||||
&CTRL_MT32_V1_07,
|
||||
&CTRL_MT32_BLUER,
|
||||
&CTRL_CM32L_V1_00,
|
||||
&CTRL_CM32L_V1_02,
|
||||
&PCM_MT32,
|
||||
&PCM_CM32L,
|
||||
NULL};
|
||||
|
||||
const ROMInfo* ROMInfo::getROMInfo(Common::File *file) {
|
||||
size_t fileSize = file->size();
|
||||
// We haven't added the SHA1 checksum code in ScummVM, as the file size
|
||||
// suffices for our needs for now.
|
||||
//const char *fileDigest = file->getSHA1();
|
||||
for (int i = 0; ROM_INFOS[i] != NULL; i++) {
|
||||
const ROMInfo *romInfo = ROM_INFOS[i];
|
||||
if (fileSize == romInfo->fileSize /*&& !strcmp(fileDigest, romInfo->sha1Digest)*/) {
|
||||
return romInfo;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
|
||||
(void) romInfo;
|
||||
}
|
||||
|
||||
static int getROMCount() {
|
||||
int count;
|
||||
for(count = 0; ROM_INFOS[count] != NULL; count++) {
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
const ROMInfo** ROMInfo::getROMInfoList(unsigned int types, unsigned int pairTypes) {
|
||||
const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1];
|
||||
const ROMInfo **currentROMInList = romInfoList;
|
||||
for(int i = 0; ROM_INFOS[i] != NULL; i++) {
|
||||
const ROMInfo *romInfo = ROM_INFOS[i];
|
||||
if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) {
|
||||
*currentROMInList++ = romInfo;
|
||||
}
|
||||
}
|
||||
*currentROMInList = NULL;
|
||||
return romInfoList;
|
||||
}
|
||||
|
||||
void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
|
||||
delete[] romInfoList;
|
||||
}
|
||||
|
||||
const ROMImage* ROMImage::makeROMImage(Common::File *file) {
|
||||
ROMImage *romImage = new ROMImage;
|
||||
romImage->file = file;
|
||||
romImage->romInfo = ROMInfo::getROMInfo(romImage->file);
|
||||
return romImage;
|
||||
}
|
||||
|
||||
void ROMImage::freeROMImage(const ROMImage *romImage) {
|
||||
ROMInfo::freeROMInfo(romImage->romInfo);
|
||||
delete romImage;
|
||||
}
|
||||
|
||||
|
||||
Common::File* ROMImage::getFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
const ROMInfo* ROMImage::getROMInfo() const {
|
||||
return romInfo;
|
||||
}
|
||||
|
||||
}
|
77
audio/softsynth/mt32/ROMInfo.h
Normal file
77
audio/softsynth/mt32/ROMInfo.h
Normal file
@ -0,0 +1,77 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_ROMINFO_H
|
||||
#define MT32EMU_ROMINFO_H
|
||||
|
||||
//#include <cstddef>
|
||||
#include "common/file.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Defines vital info about ROM file to be used by synth and applications
|
||||
|
||||
struct ROMInfo {
|
||||
public:
|
||||
size_t fileSize;
|
||||
const char *sha1Digest;
|
||||
enum Type {PCM, Control, Reverb} type;
|
||||
const char *shortName;
|
||||
const char *description;
|
||||
enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType;
|
||||
ROMInfo *pairROMInfo;
|
||||
void *controlROMInfo;
|
||||
|
||||
// Returns a ROMInfo struct by inspecting the size and the SHA1 hash
|
||||
static const ROMInfo* getROMInfo(Common::File *file);
|
||||
|
||||
// Currently no-op
|
||||
static void freeROMInfo(const ROMInfo *romInfo);
|
||||
|
||||
// Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes
|
||||
// (specified by bitmasks)
|
||||
// Useful for GUI/console app to output information on what ROMs it supports
|
||||
static const ROMInfo** getROMInfoList(unsigned int types, unsigned int pairTypes);
|
||||
|
||||
// Frees the list of ROMInfos given
|
||||
static void freeROMInfoList(const ROMInfo **romInfos);
|
||||
};
|
||||
|
||||
// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work
|
||||
|
||||
class ROMImage {
|
||||
private:
|
||||
Common::File *file;
|
||||
const ROMInfo *romInfo;
|
||||
|
||||
public:
|
||||
|
||||
// Creates a ROMImage object given a ROMInfo and a File. Keeps a reference
|
||||
// to the File and ROMInfo given, which must be freed separately by the user
|
||||
// after the ROMImage is freed
|
||||
static const ROMImage* makeROMImage(Common::File *file);
|
||||
|
||||
// Must only be done after all Synths using the ROMImage are deleted
|
||||
static void freeROMImage(const ROMImage *romImage);
|
||||
|
||||
Common::File *getFile() const;
|
||||
const ROMInfo *getROMInfo() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -27,8 +27,10 @@
|
||||
#include "mmath.h"
|
||||
#include "PartialManager.h"
|
||||
|
||||
#if MT32EMU_USE_AREVERBMODEL == 1
|
||||
#if MT32EMU_USE_REVERBMODEL == 1
|
||||
#include "AReverbModel.h"
|
||||
#elif MT32EMU_USE_REVERBMODEL == 2
|
||||
#include "BReverbModel.h"
|
||||
#else
|
||||
#include "FreeverbModel.h"
|
||||
#endif
|
||||
@ -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++) {
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -47,12 +47,11 @@ static Bit16u keyToPitchTable[] = {
|
||||
|
||||
TVP::TVP(const Partial *usePartial) :
|
||||
partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) {
|
||||
unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate;
|
||||
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
|
||||
maxCounter = sampleRate / 4000;
|
||||
maxCounter = SAMPLE_RATE / 4000;
|
||||
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
|
||||
// This is how much to increment it by every maxCounter samples.
|
||||
processTimerIncrement = 500000 * maxCounter / sampleRate;
|
||||
processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
|
||||
}
|
||||
|
||||
static Bit16s keyToPitch(unsigned int key) {
|
||||
@ -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.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -2,13 +2,17 @@ MODULE := audio/softsynth/mt32
|
||||
|
||||
MODULE_OBJS := \
|
||||
AReverbModel.o \
|
||||
BReverbModel.o \
|
||||
DelayReverb.o \
|
||||
FreeverbModel.o \
|
||||
LA32Ramp.o \
|
||||
LA32WaveGenerator.o \
|
||||
LegacyWaveGenerator.o \
|
||||
Part.o \
|
||||
Partial.o \
|
||||
PartialManager.o \
|
||||
Poly.o \
|
||||
ROMInfo.o \
|
||||
Synth.o \
|
||||
TVA.o \
|
||||
TVF.o \
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
@ -59,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
|
||||
|
@ -247,7 +247,7 @@ byte OPL::read(int port) {
|
||||
}
|
||||
|
||||
void OPL::writeReg(int r, int v) {
|
||||
byte tempReg = 0;
|
||||
int tempReg = 0;
|
||||
switch (_type) {
|
||||
case Config::kOpl2:
|
||||
case Config::kDualOpl2:
|
||||
@ -257,12 +257,27 @@ void OPL::writeReg(int r, int v) {
|
||||
// Backup old setup register
|
||||
tempReg = _reg.normal;
|
||||
|
||||
// We need to set the register we want to write to via port 0x388
|
||||
write(0x388, r);
|
||||
// Do the real writing to the register
|
||||
write(0x389, v);
|
||||
// We directly allow writing to secondary OPL3 registers by using
|
||||
// register values >= 0x100.
|
||||
if (_type == Config::kOpl3 && r >= 0x100) {
|
||||
// We need to set the register we want to write to via port 0x222,
|
||||
// since we want to write to the secondary register set.
|
||||
write(0x222, r);
|
||||
// Do the real writing to the register
|
||||
write(0x223, v);
|
||||
} else {
|
||||
// We need to set the register we want to write to via port 0x388
|
||||
write(0x388, r);
|
||||
// Do the real writing to the register
|
||||
write(0x389, v);
|
||||
}
|
||||
|
||||
// Restore the old register
|
||||
write(0x388, tempReg);
|
||||
if (_type == Config::kOpl3 && tempReg >= 0x100) {
|
||||
write(0x222, tempReg & ~0x100);
|
||||
} else {
|
||||
write(0x388, tempReg);
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ static int *ENV_CURVE;
|
||||
|
||||
|
||||
/* multiple table */
|
||||
#define ML(a) (int)(a * 2)
|
||||
#define ML(a) (uint)(a * 2)
|
||||
static const uint MUL_TABLE[16]= {
|
||||
/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */
|
||||
ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00),
|
||||
|
@ -512,7 +512,7 @@ void Filter::enable_filter(bool enable) {
|
||||
enabled = enable;
|
||||
}
|
||||
|
||||
void Filter::reset(){
|
||||
void Filter::reset() {
|
||||
fc = 0;
|
||||
|
||||
res = 0;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -128,7 +128,7 @@ public:
|
||||
* @param name name of the keymap to push
|
||||
* @param transparent if true keymapper will iterate down the
|
||||
* stack if it cannot find a key in the new map
|
||||
* @return true if succesful
|
||||
* @return true if successful
|
||||
*/
|
||||
bool pushKeymap(const String& name, bool transparent = false);
|
||||
|
||||
|
@ -102,6 +102,7 @@ public:
|
||||
void sysEx(const byte *msg, uint16 length);
|
||||
|
||||
private:
|
||||
void loadSoundFont(const char *soundfont);
|
||||
AUGraph _auGraph;
|
||||
AudioUnit _synth;
|
||||
};
|
||||
@ -171,52 +172,8 @@ int MidiDriver_CORE::open() {
|
||||
#endif
|
||||
|
||||
// Load custom soundfont, if specified
|
||||
if (ConfMan.hasKey("soundfont")) {
|
||||
const char *soundfont = ConfMan.get("soundfont").c_str();
|
||||
|
||||
// TODO: We should really check whether the file contains an
|
||||
// actual soundfont...
|
||||
|
||||
#if USE_DEPRECATED_COREAUDIO_API
|
||||
// Before 10.5, we need to use kMusicDeviceProperty_SoundBankFSSpec
|
||||
FSRef fsref;
|
||||
FSSpec fsSpec;
|
||||
err = FSPathMakeRef ((const byte *)soundfont, &fsref, NULL);
|
||||
|
||||
if (err == noErr) {
|
||||
err = FSGetCatalogInfo (&fsref, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
|
||||
}
|
||||
|
||||
if (err == noErr) {
|
||||
err = AudioUnitSetProperty (
|
||||
_synth,
|
||||
kMusicDeviceProperty_SoundBankFSSpec, kAudioUnitScope_Global,
|
||||
0,
|
||||
&fsSpec, sizeof(fsSpec)
|
||||
);
|
||||
}
|
||||
#else
|
||||
// kMusicDeviceProperty_SoundBankFSSpec is present on 10.6+, but broken
|
||||
// kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement
|
||||
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)soundfont, strlen(soundfont), false);
|
||||
|
||||
if (url) {
|
||||
err = AudioUnitSetProperty (
|
||||
_synth,
|
||||
kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global,
|
||||
0,
|
||||
&url, sizeof(url)
|
||||
);
|
||||
|
||||
CFRelease(url);
|
||||
} else {
|
||||
warning("Failed to allocate CFURLRef from '%s'", soundfont);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err != noErr)
|
||||
error("Failed loading custom sound font '%s' (error %ld)", soundfont, (long)err);
|
||||
}
|
||||
if (ConfMan.hasKey("soundfont"))
|
||||
loadSoundFont(ConfMan.get("soundfont").c_str());
|
||||
|
||||
#ifdef COREAUDIO_DISABLE_REVERB
|
||||
// Disable reverb mode, as that sucks up a lot of CPU power, which can
|
||||
@ -242,6 +199,74 @@ bail:
|
||||
return MERR_CANNOT_CONNECT;
|
||||
}
|
||||
|
||||
void MidiDriver_CORE::loadSoundFont(const char *soundfont) {
|
||||
// TODO: We should really check whether the file contains an
|
||||
// actual soundfont...
|
||||
|
||||
OSStatus err = 0;
|
||||
|
||||
#if USE_DEPRECATED_COREAUDIO_API
|
||||
FSRef fsref;
|
||||
err = FSPathMakeRef((const byte *)soundfont, &fsref, NULL);
|
||||
|
||||
SInt32 version;
|
||||
err = Gestalt(gestaltSystemVersion, &version);
|
||||
|
||||
if (err == noErr) {
|
||||
if (version >= 0x1030) {
|
||||
// Use kMusicDeviceProperty_SoundBankFSRef in >= 10.3
|
||||
|
||||
// HACK HACK HACK HACK SUPER HACK: Using the value of 1012 instead of
|
||||
// kMusicDeviceProperty_SoundBankFSRef so this compiles with the 10.2
|
||||
// SDK (which does not have that symbol).
|
||||
if (err == noErr) {
|
||||
err = AudioUnitSetProperty(
|
||||
_synth,
|
||||
/*kMusicDeviceProperty_SoundBankFSRef*/ 1012, kAudioUnitScope_Global,
|
||||
0,
|
||||
&fsref, sizeof(fsref)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// In 10.2, only kMusicDeviceProperty_SoundBankFSSpec is available
|
||||
FSSpec fsSpec;
|
||||
|
||||
if (err == noErr)
|
||||
err = FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
|
||||
|
||||
if (err == noErr) {
|
||||
err = AudioUnitSetProperty(
|
||||
_synth,
|
||||
kMusicDeviceProperty_SoundBankFSSpec, kAudioUnitScope_Global,
|
||||
0,
|
||||
&fsSpec, sizeof(fsSpec)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement
|
||||
// In addition, the File Manager API became deprecated starting in 10.8
|
||||
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)soundfont, strlen(soundfont), false);
|
||||
|
||||
if (url) {
|
||||
err = AudioUnitSetProperty(
|
||||
_synth,
|
||||
kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global,
|
||||
0,
|
||||
&url, sizeof(url)
|
||||
);
|
||||
|
||||
CFRelease(url);
|
||||
} else {
|
||||
warning("Failed to allocate CFURLRef from '%s'", soundfont);
|
||||
}
|
||||
#endif // USE_DEPRECATED_COREAUDIO_API
|
||||
|
||||
if (err != noErr)
|
||||
error("Failed loading custom sound font '%s' (error %ld)", soundfont, (long)err);
|
||||
}
|
||||
|
||||
void MidiDriver_CORE::close() {
|
||||
MidiDriver_MPU401::close();
|
||||
if (_auGraph) {
|
||||
|
@ -88,12 +88,9 @@ int MidiDriver_SEQ::open() {
|
||||
|
||||
device = ::open((device_name), O_RDWR, 0);
|
||||
|
||||
if ((device_name == NULL) || (device < 0)) {
|
||||
if (device_name == NULL)
|
||||
warning("Opening /dev/null (no music will be heard) ");
|
||||
else
|
||||
warning("Cannot open rawmidi device %s - using /dev/null (no music will be heard) ",
|
||||
device_name);
|
||||
if (device < 0) {
|
||||
warning("Cannot open rawmidi device %s - using /dev/null (no music will be heard)",
|
||||
device_name);
|
||||
device = (::open(("/dev/null"), O_RDWR, 0));
|
||||
if (device < 0)
|
||||
error("Cannot open /dev/null to dump midi output");
|
||||
@ -145,7 +142,7 @@ void MidiDriver_SEQ::send(uint32 b) {
|
||||
buf[position++] = 0;
|
||||
break;
|
||||
default:
|
||||
warning("MidiDriver_SEQ::send: unknown : %08x", (int)b);
|
||||
warning("MidiDriver_SEQ::send: unknown: %08x", (int)b);
|
||||
break;
|
||||
}
|
||||
if (write(device, buf, position) == -1)
|
||||
|
@ -153,7 +153,7 @@ void SdlMixerManager::suspendAudio() {
|
||||
int SdlMixerManager::resumeAudio() {
|
||||
if (!_audioSuspended)
|
||||
return -2;
|
||||
if (SDL_OpenAudio(&_obtained, NULL) < 0){
|
||||
if (SDL_OpenAudio(&_obtained, NULL) < 0) {
|
||||
return -1;
|
||||
}
|
||||
SDL_PauseAudio(0);
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
129
backends/platform/android/org/scummvm/scummvm/MouseHelper.java
Normal file
129
backends/platform/android/org/scummvm/scummvm/MouseHelper.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -391,4 +391,4 @@ void Rescale_320x256x1555_To_256x256x1555(u16 *dest, const u16 *src, int destStr
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace DS
|
||||
} // End of namespace DS
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user