Import 0.5.0
This commit is contained in:
parent
419cf91dfe
commit
1f86fdea2d
|
@ -1,9 +1,13 @@
|
|||
* release 0.5.0
|
||||
- added CD-i widescreen mode (flashp*bob)
|
||||
- added support for DOS .prf music (Adlib, MT32)
|
||||
|
||||
* release 0.4.9
|
||||
- added option to match original inventory items order
|
||||
- added zoom from DOS version
|
||||
- added Sega CD tracks playback based on stb_vorbis (OGG 22khz)
|
||||
- fixed piege opcode 0x57
|
||||
- fixed repeating sounds volume
|
||||
- fixed volume of repeating sounds
|
||||
|
||||
* release 0.4.8
|
||||
- added detection for DOS version with .ABA files
|
||||
|
|
19
Makefile
19
Makefile
|
@ -6,18 +6,27 @@ MODPLUG_LIBS := -lmodplug
|
|||
TREMOR_LIBS := #-lvorbisidec -logg
|
||||
ZLIB_LIBS := -lz
|
||||
|
||||
CXXFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB
|
||||
LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
|
||||
|
||||
CXXFLAGS += -Wall -Wextra -Wno-unused-parameter -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB
|
||||
|
||||
SRCS = collision.cpp cpc_player.cpp cutscene.cpp decode_mac.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \
|
||||
menu.cpp mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp protection.cpp resource.cpp resource_aba.cpp \
|
||||
menu.cpp midi_parser.cpp mixer.cpp mod_player.cpp ogg_player.cpp \
|
||||
piege.cpp prf_player.cpp protection.cpp resource.cpp resource_aba.cpp \
|
||||
resource_mac.cpp scaler.cpp screenshot.cpp seq_player.cpp \
|
||||
sfx_player.cpp staticres.cpp systemstub_sdl.cpp unpack.cpp util.cpp video.cpp
|
||||
|
||||
#CXXFLAGS += -DUSE_STATIC_SCALER
|
||||
#SCALERS := scalers/scaler_nearest.cpp scalers/scaler_tv2x.cpp scalers/scaler_xbr.cpp
|
||||
|
||||
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o)
|
||||
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d)
|
||||
#CXXFLAGS += -DUSE_MIDI_DRIVER
|
||||
#MIDIDRIVERS := midi_driver_adlib.cpp midi_driver_mt32.cpp
|
||||
#MIDI_LIBS := -lmt32emu
|
||||
|
||||
LIBS = $(SDL_LIBS) $(MODPLUG_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
|
||||
LIBS = $(MIDI_LIBS) $(MODPLUG_LIBS) $(SDL_LIBS) $(TREMOR_LIBS) $(ZLIB_LIBS)
|
||||
|
||||
OBJS = $(SRCS:.cpp=.o) $(SCALERS:.cpp=.o) $(MIDIDRIVERS:.cpp=.o)
|
||||
DEPS = $(SRCS:.cpp=.d) $(SCALERS:.cpp=.d) $(MIDIDRIVERS:.cpp=.d)
|
||||
|
||||
rs: $(OBJS)
|
||||
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
|
||||
|
|
41
README.txt
41
README.txt
|
@ -1,6 +1,6 @@
|
|||
|
||||
REminiscence README
|
||||
Release version: 0.4.9
|
||||
Release version: 0.5.0
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
@ -21,43 +21,48 @@ release. Support for Amiga and Macintosh is still experimental.
|
|||
For the Macintosh release, the resource fork must be dumped as a file named
|
||||
'FLASHBACK.BIN' (MacBinary) or 'FLASHBACK.RSRC' (AppleDouble).
|
||||
|
||||
To hear music during polygonal cutscenes with the PC version, you need to copy
|
||||
the music/ directory of the Amiga version or use the .mod fileset from
|
||||
unexotica [4].
|
||||
|
||||
For speech with in-game dialogues, you need to copy the 'VOICE.VCE' file from
|
||||
the SegaCD version to the DATA directory.
|
||||
|
||||
The music/ directory of the Amiga version or the .mod fileset from
|
||||
unexotica [4] can be used as an alternative to the MIDI tracks from the
|
||||
DOS version. Simply copy the files to the DATA directory and set the
|
||||
'use_prf_music' option to false in the configuration file.
|
||||
|
||||
|
||||
Running:
|
||||
--------
|
||||
|
||||
By default, the engine tries to load the game data files from the 'DATA'
|
||||
directory, as the original game executable did. The savestates are saved in the
|
||||
current directory.
|
||||
By default, the engine loads the game data files from the 'DATA' directory,
|
||||
as the original game executable did. The savestates are saved in the current
|
||||
directory.
|
||||
|
||||
These paths can be changed using command line switches :
|
||||
These paths can be changed using command line switches:
|
||||
|
||||
Usage: rs [OPTIONS]...
|
||||
--datapath=PATH Path to data files (default 'DATA')
|
||||
--savepath=PATH Path to save files (default '.')
|
||||
--levelnum=NUM Level to start from (default '0')
|
||||
--fullscreen Fullscreen display
|
||||
--widescreen=MODE 16:9 display (adjacent,mirror,blur,none)
|
||||
--widescreen=MODE Widescreen display (adjacent,mirror,blur,cdi,none)
|
||||
--scaler=NAME@X Graphics scaler (default 'scale@3')
|
||||
--language=LANG Language (fr,en,de,sp,it,jp)
|
||||
--autosave Save game state automatically
|
||||
--mididriver=MIDI Driver (adlib, mt32)
|
||||
|
||||
The scaler option specifies the algorithm used to smoothen the image in
|
||||
addition to a scaling factor. External scalers are also supported, the suffix
|
||||
shall be used as the name. Eg. If you have scaler_xbr.dll, you can pass
|
||||
'--scaler xbr@2' to use that algorithm with a doubled window size (512x448).
|
||||
The scaler option specifies the algorithm used to smoothen the image and the
|
||||
scaling factor. External scalers are also supported, the suffix shall be used
|
||||
as the name. Eg. If you have scaler_xbr.dll, you can pass '--scaler xbr@2'
|
||||
to use that algorithm with a doubled window size (512x448).
|
||||
|
||||
The widescreen option accepts two modes :
|
||||
'adjacent' : left and right rooms bitmaps will be drawn
|
||||
'mirror' : the current room bitmap will be drawn mirrored
|
||||
The widescreen option accepts the modes below:
|
||||
|
||||
In-game hotkeys :
|
||||
adjacent draw left and right rooms bitmap
|
||||
mirror mirror the current room bitmap
|
||||
blur blur and stretch the current room bitmap
|
||||
cdi use bitmaps from the CD-i release ('flashp?.bob' files)
|
||||
|
||||
In-game keys:
|
||||
|
||||
Arrow Keys move Conrad
|
||||
Enter use the current inventory object
|
||||
|
|
|
@ -361,7 +361,7 @@ int Game::col_detectHitCallbackHelper(LivePGE *pge, int16_t msgNum) {
|
|||
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
|
||||
Object *obj = &on->objects[pge->first_obj_number];
|
||||
int i = pge->first_obj_number;
|
||||
while (pge->obj_type == obj->type && on->last_obj_number > i) {
|
||||
while (pge->obj_type == obj->type && on->num_objects > i) {
|
||||
if (obj->opcode2 == 0x6B) { // pge_isToggleable
|
||||
if (obj->opcode_arg2 == 0) {
|
||||
if (msgNum == 1 || msgNum == 2) return 0xFFFF;
|
||||
|
|
17
file.cpp
17
file.cpp
|
@ -24,6 +24,7 @@ struct File_impl {
|
|||
virtual void close() = 0;
|
||||
virtual uint32_t size() = 0;
|
||||
virtual void seek(int32_t off) = 0;
|
||||
virtual uint32_t tell() = 0;
|
||||
virtual uint32_t read(void *ptr, uint32_t len) = 0;
|
||||
virtual uint32_t write(const void *ptr, uint32_t len) = 0;
|
||||
};
|
||||
|
@ -57,6 +58,12 @@ struct StdioFile : File_impl {
|
|||
fseek(_fp, off, SEEK_SET);
|
||||
}
|
||||
}
|
||||
uint32_t tell() {
|
||||
if (_fp) {
|
||||
return ftell(_fp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
uint32_t read(void *ptr, uint32_t len) {
|
||||
if (_fp) {
|
||||
uint32_t r = fread(ptr, 1, len, _fp);
|
||||
|
@ -109,6 +116,12 @@ struct GzipFile : File_impl {
|
|||
gzseek(_fp, off, SEEK_SET);
|
||||
}
|
||||
}
|
||||
uint32_t tell() {
|
||||
if (_fp) {
|
||||
return gztell(_fp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
uint32_t read(void *ptr, uint32_t len) {
|
||||
if (_fp) {
|
||||
uint32_t r = gzread(_fp, ptr, len);
|
||||
|
@ -343,6 +356,10 @@ void File::seek(int32_t off) {
|
|||
_impl->seek(off);
|
||||
}
|
||||
|
||||
uint32_t File::tell() {
|
||||
return _impl->tell();
|
||||
}
|
||||
|
||||
uint32_t File::read(void *ptr, uint32_t len) {
|
||||
return _impl->read(ptr, len);
|
||||
}
|
||||
|
|
1
file.h
1
file.h
|
@ -25,6 +25,7 @@ struct File {
|
|||
bool ioErr() const;
|
||||
uint32_t size();
|
||||
void seek(int32_t off);
|
||||
uint32_t tell();
|
||||
uint32_t read(void *ptr, uint32_t len);
|
||||
uint8_t readByte();
|
||||
uint16_t readUint16LE();
|
||||
|
|
56
game.cpp
56
game.cpp
|
@ -9,13 +9,14 @@
|
|||
#include "file.h"
|
||||
#include "fs.h"
|
||||
#include "game.h"
|
||||
#include "screenshot.h"
|
||||
#include "seq_player.h"
|
||||
#include "systemstub.h"
|
||||
#include "util.h"
|
||||
|
||||
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, uint32_t cheats)
|
||||
Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, int midiDriver, uint32_t cheats)
|
||||
: _cut(&_res, stub, &_vid), _menu(&_res, stub, &_vid),
|
||||
_mix(fs, stub), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub, widescreenMode),
|
||||
_mix(fs, stub, midiDriver), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub, widescreenMode),
|
||||
_stub(stub), _fs(fs), _savePath(savePath) {
|
||||
_stateSlot = 1;
|
||||
_inp_demPos = 0;
|
||||
|
@ -1372,11 +1373,11 @@ void Game::drawObjectFrame(const uint8_t *bankDataPtr, const uint8_t *dataPtr, i
|
|||
|
||||
void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, uint8_t a, uint8_t b, uint8_t flags) {
|
||||
debug(DBG_GAME, "Game::drawCharacter(%p, %d, %d, 0x%X, 0x%X, 0x%X)", dataPtr, pos_x, pos_y, a, b, flags);
|
||||
bool var16 = false; // sprite_mirror_y
|
||||
bool sprite_mirror_y = false;
|
||||
if (b & 0x40) {
|
||||
b &= 0xBF;
|
||||
b &= ~0x40;
|
||||
SWAP(a, b);
|
||||
var16 = true;
|
||||
sprite_mirror_y = true;
|
||||
}
|
||||
uint16_t sprite_h = a;
|
||||
uint16_t sprite_w = b;
|
||||
|
@ -1392,7 +1393,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
|
|||
sprite_clipped_w = 256 - pos_x;
|
||||
if (flags & 2) {
|
||||
var14 = true;
|
||||
if (var16) {
|
||||
if (sprite_mirror_y) {
|
||||
src += (sprite_w - 1) * sprite_h;
|
||||
} else {
|
||||
src += sprite_w - 1;
|
||||
|
@ -1402,7 +1403,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
|
|||
} else {
|
||||
sprite_clipped_w = pos_x + sprite_w;
|
||||
if (!(flags & 2)) {
|
||||
if (var16) {
|
||||
if (sprite_mirror_y) {
|
||||
src -= sprite_h * pos_x;
|
||||
pos_x = 0;
|
||||
} else {
|
||||
|
@ -1411,7 +1412,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
|
|||
}
|
||||
} else {
|
||||
var14 = true;
|
||||
if (var16) {
|
||||
if (sprite_mirror_y) {
|
||||
src += sprite_h * (pos_x + sprite_w - 1);
|
||||
pos_x = 0;
|
||||
} else {
|
||||
|
@ -1434,7 +1435,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
|
|||
}
|
||||
} else {
|
||||
sprite_clipped_h = sprite_h + pos_y;
|
||||
if (var16) {
|
||||
if (sprite_mirror_y) {
|
||||
src -= pos_y;
|
||||
} else {
|
||||
src -= sprite_w * pos_y;
|
||||
|
@ -1446,7 +1447,7 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
|
|||
}
|
||||
|
||||
if (!var14 && (flags & 2)) {
|
||||
if (var16) {
|
||||
if (sprite_mirror_y) {
|
||||
src += sprite_h * (sprite_w - 1);
|
||||
} else {
|
||||
src += sprite_w - 1;
|
||||
|
@ -1459,13 +1460,13 @@ void Game::drawCharacter(const uint8_t *dataPtr, int16_t pos_x, int16_t pos_y, u
|
|||
debug(DBG_GAME, "dst_offset=0x%X src_offset=%ld", dst_offset, src - dataPtr);
|
||||
|
||||
if (!(flags & 2)) {
|
||||
if (var16) {
|
||||
if (sprite_mirror_y) {
|
||||
_vid.drawSpriteSub5(src, _vid._frontLayer + dst_offset, sprite_h, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
|
||||
} else {
|
||||
_vid.drawSpriteSub3(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
|
||||
}
|
||||
} else {
|
||||
if (var16) {
|
||||
if (sprite_mirror_y) {
|
||||
_vid.drawSpriteSub6(src, _vid._frontLayer + dst_offset, sprite_h, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
|
||||
} else {
|
||||
_vid.drawSpriteSub4(src, _vid._frontLayer + dst_offset, sprite_w, sprite_clipped_h, sprite_clipped_w, sprite_col_mask);
|
||||
|
@ -1536,7 +1537,7 @@ bool Game::hasLevelMap(int level, int room) const {
|
|||
if (_res._map) {
|
||||
return READ_LE_UINT32(_res._map + room * 6) != 0;
|
||||
} else if (_res._lev) {
|
||||
return READ_BE_UINT32(_res._lev + room * 4) != 0;
|
||||
return READ_BE_UINT32(_res._lev + room * 4) > 0x100;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1702,6 +1703,19 @@ void Game::loadLevelData() {
|
|||
_res.MAC_loadLevelData(_currentLevel);
|
||||
break;
|
||||
}
|
||||
if (0) {
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
if (hasLevelMap(_currentLevel, i)) {
|
||||
_currentRoom = i;
|
||||
loadLevelMap();
|
||||
uint8_t palette[256 * 3];
|
||||
_stub->getPalette(palette, 256);
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "DUMP/level%d_room%02d.bmp", _currentLevel, i);
|
||||
saveBMP(name, _vid._backLayer, palette, _vid._w, _vid._h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cut._id = lvl->cutscene_id;
|
||||
|
||||
|
@ -1747,6 +1761,22 @@ void Game::loadLevelData() {
|
|||
_validSaveState = false;
|
||||
|
||||
_mix.playMusic(Mixer::MUSIC_TRACK + lvl->track);
|
||||
|
||||
if (_widescreenMode == kWidescreenCDi) {
|
||||
char name[16];
|
||||
snprintf(name, sizeof(name), "flashp%d.bob", _currentLevel + 1);
|
||||
File f;
|
||||
if (f.open(name, "rb", _fs)) {
|
||||
const int w = f.readUint16LE();
|
||||
const int h = f.readUint16LE();
|
||||
if (w != kWidescreenBorderCDiW || h != kWidescreenBorderCDiH) {
|
||||
warning("Unsupported CDi border w:%d h:%d", w, h);
|
||||
} else {
|
||||
f.read(_res._scratchBuffer, 256 * 3 + w * h);
|
||||
_stub->copyWidescreenCDi(w, h, _res._scratchBuffer + 256 * 3, _res._scratchBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::drawIcon(uint8_t iconNum, int16_t x, int16_t y, uint8_t colMask) {
|
||||
|
|
2
game.h
2
game.h
|
@ -109,7 +109,7 @@ struct Game {
|
|||
bool _autoSave;
|
||||
uint32_t _saveTimestamp;
|
||||
|
||||
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, uint32_t cheats);
|
||||
Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, int midiDriver, uint32_t cheats);
|
||||
|
||||
void run();
|
||||
void displayTitleScreenAmiga();
|
||||
|
|
10
intern.h
10
intern.h
|
@ -118,6 +118,12 @@ enum WidescreenMode {
|
|||
kWidescreenAdjacentRooms,
|
||||
kWidescreenMirrorRoom,
|
||||
kWidescreenBlur,
|
||||
kWidescreenCDi,
|
||||
};
|
||||
|
||||
enum {
|
||||
kWidescreenBorderCDiW = 52,
|
||||
kWidescreenBorderCDiH = 224
|
||||
};
|
||||
|
||||
struct Options {
|
||||
|
@ -130,6 +136,7 @@ struct Options {
|
|||
bool use_seq_cutscenes;
|
||||
bool use_words_protection;
|
||||
bool use_white_tshirt;
|
||||
bool use_prf_music;
|
||||
bool play_asc_cutscene;
|
||||
bool play_caillou_cutscene;
|
||||
bool play_metro_cutscene;
|
||||
|
@ -230,9 +237,8 @@ struct Object {
|
|||
};
|
||||
|
||||
struct ObjectNode {
|
||||
uint16_t last_obj_number;
|
||||
Object *objects;
|
||||
uint16_t num_objects;
|
||||
Object *objects;
|
||||
};
|
||||
|
||||
struct ObjectOpcodeArgs {
|
||||
|
|
25
main.cpp
25
main.cpp
|
@ -26,6 +26,7 @@ static const char *USAGE =
|
|||
" --scaler=NAME@X Graphics scaler (default 'scale@3')\n"
|
||||
" --language=LANG Language (fr,en,de,sp,it,jp)\n"
|
||||
" --autosave Save game state automatically\n"
|
||||
" --mididriver=MIDI Driver (adlib, mt32)\n"
|
||||
;
|
||||
|
||||
static int detectVersion(FileSystem *fs) {
|
||||
|
@ -95,6 +96,7 @@ static void initOptions() {
|
|||
g_options.use_seq_cutscenes = true;
|
||||
g_options.use_words_protection = false;
|
||||
g_options.use_white_tshirt = false;
|
||||
g_options.use_prf_music = true;
|
||||
g_options.play_asc_cutscene = false;
|
||||
g_options.play_caillou_cutscene = false;
|
||||
g_options.play_metro_cutscene = false;
|
||||
|
@ -117,6 +119,7 @@ static void initOptions() {
|
|||
{ "use_seq_cutscenes", &g_options.use_seq_cutscenes },
|
||||
{ "use_words_protection", &g_options.use_words_protection },
|
||||
{ "use_white_tshirt", &g_options.use_white_tshirt },
|
||||
{ "use_prf_music", &g_options.use_prf_music },
|
||||
{ "play_asc_cutscene", &g_options.play_asc_cutscene },
|
||||
{ "play_caillou_cutscene", &g_options.play_caillou_cutscene },
|
||||
{ "play_metro_cutscene", &g_options.play_metro_cutscene },
|
||||
|
@ -179,6 +182,7 @@ static WidescreenMode parseWidescreen(const char *mode) {
|
|||
{ "adjacent", kWidescreenAdjacentRooms },
|
||||
{ "mirror", kWidescreenMirrorRoom },
|
||||
{ "blur", kWidescreenBlur },
|
||||
{ "cdi", kWidescreenCDi },
|
||||
{ 0, kWidescreenNone },
|
||||
};
|
||||
for (int i = 0; modes[i].name; ++i) {
|
||||
|
@ -200,6 +204,7 @@ int main(int argc, char *argv[]) {
|
|||
WidescreenMode widescreen = kWidescreenNone;
|
||||
ScalerParameters scalerParameters = ScalerParameters::defaults();
|
||||
int forcedLanguage = -1;
|
||||
int midiDriver = MODE_ADLIB;
|
||||
if (argc == 2) {
|
||||
// data path as the only command line argument
|
||||
struct stat st;
|
||||
|
@ -218,6 +223,7 @@ int main(int argc, char *argv[]) {
|
|||
{ "widescreen", required_argument, 0, 7 },
|
||||
{ "autosave", no_argument, 0, 8 },
|
||||
{ "cheats", required_argument, 0, 9 },
|
||||
{ "mididriver", required_argument, 0, 10 },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
int index;
|
||||
|
@ -271,6 +277,23 @@ int main(int argc, char *argv[]) {
|
|||
case 9:
|
||||
cheats = atoi(optarg);
|
||||
break;
|
||||
case 10: {
|
||||
static const struct {
|
||||
int mode;
|
||||
const char *str;
|
||||
} drivers[] = {
|
||||
{ MODE_ADLIB, "adlib" },
|
||||
{ MODE_MT32, "mt32" },
|
||||
{ -1, 0 }
|
||||
};
|
||||
for (int i = 0; drivers[i].str; ++i) {
|
||||
if (strcasecmp(drivers[i].str, optarg) == 0) {
|
||||
midiDriver = drivers[i].mode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printf(USAGE, argv[0]);
|
||||
return 0;
|
||||
|
@ -286,7 +309,7 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
const Language language = (forcedLanguage == -1) ? detectLanguage(&fs) : (Language)forcedLanguage;
|
||||
SystemStub *stub = SystemStub_SDL_create();
|
||||
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave, cheats);
|
||||
Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave, midiDriver, cheats);
|
||||
stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen, &scalerParameters);
|
||||
g->run();
|
||||
delete g;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
#ifndef MIDI_DRIVER_H__
|
||||
#define MIDI_DRIVER_H__
|
||||
|
||||
struct MidiDriver {
|
||||
virtual ~MidiDriver() {};
|
||||
|
||||
virtual int init() = 0;
|
||||
virtual void fini() = 0;
|
||||
|
||||
virtual void reset(int rate) = 0;
|
||||
|
||||
virtual void setInstrumentData(int channel, int num, const void *data) { // fmopl only
|
||||
}
|
||||
|
||||
virtual void noteOff(int channel, int note, int velocity) = 0;
|
||||
virtual void noteOn(int channel, int note, int velocity) = 0;
|
||||
virtual void controlChange(int channel, int type, int value) = 0;
|
||||
virtual void programChange(int channel, int num) = 0;
|
||||
virtual void pitchBend(int channel, int lsb, int msb) = 0;
|
||||
|
||||
virtual void readSamples(short *samples, int len) = 0;
|
||||
};
|
||||
|
||||
struct MidiDriverInfo {
|
||||
const char *name;
|
||||
MidiDriver *(*create)();
|
||||
};
|
||||
|
||||
#ifndef MIDI_DRIVER_SYMBOL
|
||||
#ifdef _WIN32
|
||||
#define MIDI_DRIVER_SYMBOL __declspec(dllexport)
|
||||
#else
|
||||
#define MIDI_DRIVER_SYMBOL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
MIDI_DRIVER_SYMBOL extern const MidiDriverInfo midi_driver_adlib;
|
||||
MIDI_DRIVER_SYMBOL extern const MidiDriverInfo midi_driver_mt32;
|
||||
|
||||
#endif /* MIDI_DRIVER_H__ */
|
|
@ -0,0 +1,369 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "midi_driver.h"
|
||||
extern "C" {
|
||||
#include "opl3.c"
|
||||
}
|
||||
|
||||
#define NUM_CHANNELS 11 /* rhythm mode */
|
||||
|
||||
static opl3_chip _opl3;
|
||||
|
||||
static const int kMusicVolume = 63; // 44;
|
||||
|
||||
/* Two-operator melodic and percussion mode */
|
||||
static const uint8_t _adlibOperatorIndexTable[] = { 0, 3, 1, 4, 2, 5, 6, 9, 7, 0xA, 8, 0xB, 0xC, 0xF, 0x10, 0x10, 0xE, 0xE, 0x11, 0x11, 0xD, 0xD };
|
||||
|
||||
static const uint8_t _adlibOperatorTable[] = { 0, 1, 2, 3, 4, 5, 8, 9, 0xA, 0xB, 0xC, 0xD, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 };
|
||||
|
||||
static const uint16_t _adlibFrequencyTable[] = {
|
||||
0x157, 0x16C, 0x181, 0x198, 0x1B1, 0x1CB, 0x1E6, 0x203, 0x222, 0x243, 0x266, 0x28A
|
||||
};
|
||||
|
||||
static struct {
|
||||
uint8_t midi_channel; // track_num
|
||||
uint8_t note;
|
||||
uint8_t hw_channel;
|
||||
const uint8_t *instrument;
|
||||
} _adlibPlayingNoteTable[NUM_CHANNELS];
|
||||
|
||||
static int16_t _adlibChannelPlayingTable[NUM_CHANNELS]; // could be a bitmask
|
||||
static int16_t _adlibChannelVolumeTable[NUM_CHANNELS];
|
||||
static uint8_t _regBD = 0x20;
|
||||
|
||||
static const uint8_t *_currentInstrumentData;
|
||||
|
||||
static uint16_t READ_LE_UINT16(const uint8_t *p) {
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
struct MidiDriver_adlib: MidiDriver {
|
||||
virtual int init() {
|
||||
return 0;
|
||||
}
|
||||
virtual void fini() {
|
||||
}
|
||||
|
||||
virtual void reset(int rate) {
|
||||
for (int i = 0; i < NUM_CHANNELS; ++i) {
|
||||
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
|
||||
_adlibChannelPlayingTable[i] = 0;
|
||||
_adlibChannelVolumeTable[i] = 10;
|
||||
}
|
||||
OPL3_Reset(&_opl3, rate);
|
||||
adlibReset();
|
||||
}
|
||||
|
||||
virtual void setInstrumentData(int channel, int num, const void *data) {
|
||||
// fprintf(stdout, "instrument channel:%d num:%d data:%p\n", channel, num, data);
|
||||
assert(data);
|
||||
_currentInstrumentData = (const uint8_t *)data;
|
||||
}
|
||||
|
||||
virtual void noteOff(int channel, int note, int velocity) {
|
||||
// fprintf(stdout, "noteOff %d channel %d\n", note, channel);
|
||||
for (int i = 0; i < NUM_CHANNELS; ++i) {
|
||||
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
|
||||
continue;
|
||||
}
|
||||
if (_adlibPlayingNoteTable[i].note != note) {
|
||||
//fprintf(stdout, "WARNING: noteOff: channel %d is playing note %d (expected %d)\n", channel, _adlibPlayingNoteTable[i].note, note);
|
||||
continue;
|
||||
}
|
||||
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
|
||||
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
|
||||
//fprintf(stdout, "free hw_channel %d\n", hw_channel);
|
||||
_adlibChannelPlayingTable[hw_channel] = 0;
|
||||
if (_adlibPlayingNoteTable[i].instrument != _currentInstrumentData) {
|
||||
// fprintf(stdout, "WARNING: noteOff: instrument changed for channel %d hw_channel %d\n", channel, hw_channel);
|
||||
}
|
||||
if (_adlibPlayingNoteTable[i].instrument[0] == 0) { //_currentInstrumentData[0] == 0) {
|
||||
adlibMelodicKeyOff(hw_channel, note);
|
||||
} else {
|
||||
adlibPercussionKeyOff(hw_channel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// fprintf(stdout, "WARNING: noteOff: channel %d note %d not found\n", channel, note);
|
||||
}
|
||||
virtual void noteOn(int channel, int note, int velocity) {
|
||||
if (_currentInstrumentData[0] == 0) {
|
||||
/* melodic channel */
|
||||
int i;
|
||||
for (i = 0; i < 6; ++i) {
|
||||
if (_adlibChannelPlayingTable[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 6) {
|
||||
// fprintf(stdout, "No free melodic channel found (A) 6 voices busy, track %d note %d\n", channel, note);
|
||||
return;
|
||||
}
|
||||
const int hw_channel = i;
|
||||
_adlibChannelPlayingTable[hw_channel] = 0xFF;
|
||||
for (i = 0; i < NUM_CHANNELS; ++i) {
|
||||
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == NUM_CHANNELS) {
|
||||
_adlibChannelPlayingTable[hw_channel] = 0;
|
||||
// fprintf(stdout, "No free channel found (B)\n");
|
||||
return;
|
||||
}
|
||||
_adlibPlayingNoteTable[i].midi_channel = channel;
|
||||
_adlibPlayingNoteTable[i].note = note;
|
||||
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
|
||||
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
|
||||
_adlibChannelVolumeTable[hw_channel] = velocity;
|
||||
adlibSetupInstrument(hw_channel, _currentInstrumentData);
|
||||
adlibMelodicKeyOn(hw_channel, velocity, note);
|
||||
} else {
|
||||
/* percussion channel */
|
||||
const int hw_channel = _currentInstrumentData[1]; // channel
|
||||
assert(hw_channel >= 6 && hw_channel <= 10);
|
||||
if (_adlibChannelPlayingTable[hw_channel] != 0) {
|
||||
// fprintf(stdout, "No free channel found (C) hw_channel %d for track %d note %d\n", hw_channel, channel, note);
|
||||
return;
|
||||
}
|
||||
_adlibChannelPlayingTable[hw_channel] = 0xFF;
|
||||
int i;
|
||||
for (i = 6; i < NUM_CHANNELS; ++i) {
|
||||
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == NUM_CHANNELS) {
|
||||
_adlibChannelPlayingTable[hw_channel] = 0;
|
||||
// fprintf(stdout, "No free channel found (D) hw_channel %d\n", hw_channel);
|
||||
return;
|
||||
}
|
||||
//fprintf(stdout, "Adding percussion channel %d note %d index %d track %d\n", hw_channel, note, i, channel);
|
||||
_adlibPlayingNoteTable[i].midi_channel = channel;
|
||||
_adlibPlayingNoteTable[i].note = note;
|
||||
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
|
||||
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
|
||||
_adlibChannelVolumeTable[hw_channel] = velocity;
|
||||
adlibSetupInstrument(hw_channel, _currentInstrumentData);
|
||||
adlibPercussionKeyOn(hw_channel, velocity, note);
|
||||
}
|
||||
}
|
||||
virtual void controlChange(int channel, int type, int value) {
|
||||
}
|
||||
virtual void programChange(int channel, int num) {
|
||||
}
|
||||
virtual void pitchBend(int channel, int lsb, int msb) {
|
||||
adlibPitchBend(channel, (msb << 7) | lsb);
|
||||
}
|
||||
|
||||
virtual void readSamples(int16_t *samples, int len) {
|
||||
OPL3_GenerateStream(&_opl3, samples, len);
|
||||
}
|
||||
|
||||
void adlibSetupInstrument(int channel, const uint8_t *arg0) {
|
||||
const int volume = (_adlibChannelVolumeTable[channel] * kMusicVolume) >> 6;
|
||||
// fprintf(stdout, "instrument channel:%d volume:%d\n", channel, volume);
|
||||
const int mode = arg0[0];
|
||||
const int hw_channel = arg0[1];
|
||||
const int num = (mode != 0) ? hw_channel : channel;
|
||||
const uint8_t modulatorOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2]];
|
||||
const uint8_t carrierOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2 + 1]];
|
||||
//fprintf(stdout, "operator modulator %d carrier %d\n", modulatorOperator, carrierOperator);
|
||||
if (mode == 0 || hw_channel == 6) { // else goto loc_F24_284E5
|
||||
// modulator
|
||||
const uint8_t *bx = arg0 + 6;
|
||||
uint16_t reg2x = 0;
|
||||
if (READ_LE_UINT16(bx + 0x12) != 0) { // amplitude vibrato
|
||||
reg2x |= 0x80;
|
||||
}
|
||||
if (READ_LE_UINT16(bx + 0x14) != 0) { // frequency vibrato
|
||||
reg2x |= 0x40;
|
||||
}
|
||||
if (READ_LE_UINT16(bx + 0x0A) != 0) { // sustaining sound
|
||||
reg2x |= 0x20;
|
||||
}
|
||||
if (READ_LE_UINT16(bx + 0x16) != 0) { // envelope scaling
|
||||
reg2x |= 0x10;
|
||||
}
|
||||
reg2x |= READ_LE_UINT16(bx + 2) & 15; // frequency multiplier
|
||||
writeRegister(0x20 + modulatorOperator, reg2x);
|
||||
uint16_t dx;
|
||||
if (READ_LE_UINT16(bx + 0x18) != 0) { // frequency modulation
|
||||
dx = READ_LE_UINT16(bx + 0x10) & 63;
|
||||
} else {
|
||||
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume; // output level
|
||||
dx = 63 - (((ax + 127) + ax) / 254);
|
||||
}
|
||||
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
|
||||
writeRegister(0x40 + modulatorOperator, reg4x);
|
||||
writeRegister(0x60 + modulatorOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
|
||||
writeRegister(0x80 + modulatorOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
|
||||
uint16_t ax = 1;
|
||||
if (READ_LE_UINT16(bx + 0x18) != 0) {
|
||||
ax = 0;
|
||||
}
|
||||
dx = (READ_LE_UINT16(bx + 4) << 1) | ax;
|
||||
ax = 0xC0 + ((mode != 0) ? hw_channel : channel);
|
||||
writeRegister(ax, dx);
|
||||
writeRegister(0xE0 + modulatorOperator, arg0[2] & 3); /* original writes to 0xE, typo */
|
||||
}
|
||||
// carrier
|
||||
const uint8_t *bx = arg0 + 0x20; // 26 + 6
|
||||
uint8_t reg2x = 0;
|
||||
if (READ_LE_UINT16(bx + 0x12) != 0) {
|
||||
reg2x |= 0x80;
|
||||
}
|
||||
if (READ_LE_UINT16(bx + 0x14) != 0) {
|
||||
reg2x |= 0x40;
|
||||
}
|
||||
if (READ_LE_UINT16(bx + 0x0A) != 0) {
|
||||
reg2x |= 0x20;
|
||||
}
|
||||
if (READ_LE_UINT16(bx + 0x16) != 0) {
|
||||
reg2x |= 0x10;
|
||||
}
|
||||
reg2x |= (READ_LE_UINT16(bx + 2) & 15);
|
||||
writeRegister(0x20 + carrierOperator, reg2x);
|
||||
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume;
|
||||
int dx = 63 - (((ax + 127) + ax) / 254);
|
||||
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
|
||||
writeRegister(0x40 + carrierOperator, reg4x);
|
||||
writeRegister(0x60 + carrierOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
|
||||
writeRegister(0x80 + carrierOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
|
||||
writeRegister(0xE0 + carrierOperator, arg0[3] & 3); /* original writes to 0xE, typo */
|
||||
}
|
||||
|
||||
void adlibPercussionKeyOn(int channel, int var6, int note) {
|
||||
// 6: Bass Drum
|
||||
// 7: Snare Drum
|
||||
// 8: Tom-Tom
|
||||
// 9: Cymbal
|
||||
// 10: Hi-Hat
|
||||
assert(channel >= 6 && channel <= 10);
|
||||
_regBD &= ~(1 << (10 - channel));
|
||||
writeRegister(0xBD, _regBD);
|
||||
int hw_channel = channel;
|
||||
if (channel == 9) {
|
||||
hw_channel = 8;
|
||||
} else if (channel == 10) {
|
||||
hw_channel = 7;
|
||||
}
|
||||
const uint16_t freq = _adlibFrequencyTable[note % 12];
|
||||
writeRegister(0xA0 + hw_channel, freq);
|
||||
const uint8_t regBx = ((note / 12) << 2) | ((freq & 0x300) >> 8);
|
||||
writeRegister(0xB0 + hw_channel, regBx);
|
||||
_regBD |= 1 << (10 - channel);
|
||||
writeRegister(0xBD, _regBD);
|
||||
}
|
||||
|
||||
void adlibMelodicKeyOn(int channel, int volume, int note) {
|
||||
writeRegister(0xB0 + channel, 0);
|
||||
const uint16_t freq = _adlibFrequencyTable[note % 12];
|
||||
writeRegister(0xA0 + channel, freq & 0xFF); /* Frequency (Low) */
|
||||
const uint8_t octave = (note / 12);
|
||||
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8); /* Key-On | Octave | Frequency (High) */
|
||||
writeRegister(0xB0 + channel, regBx);
|
||||
}
|
||||
|
||||
void adlibMelodicKeyOff(int channel, int note) {
|
||||
const uint16_t freq = _adlibFrequencyTable[note % 12];
|
||||
const uint8_t octave = (note / 12);
|
||||
const uint8_t regBx = (octave << 2) | ((freq & 0x300) >> 8); /* Octave | Frequency (High) */
|
||||
writeRegister(0xB0 + channel, regBx);
|
||||
}
|
||||
|
||||
void adlibPercussionKeyOff(int channel) {
|
||||
assert(channel >= 6 && channel <= 10);
|
||||
_regBD &= ~(1 << (10 - channel));
|
||||
writeRegister(0xBD, _regBD);
|
||||
}
|
||||
|
||||
void adlibPitchBend(int channel, int value) {
|
||||
//fprintf(stdout, "pitch bend channel:%d value:%d\n", channel, value);
|
||||
if (value == 0x2000) {
|
||||
return;
|
||||
}
|
||||
if (value & 0x2000) {
|
||||
value = -(value & 0x1FFF);
|
||||
}
|
||||
const int pitchBend = value;
|
||||
for (int i = 0; i < NUM_CHANNELS; ++i) {
|
||||
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
|
||||
continue;
|
||||
}
|
||||
const int note = _adlibPlayingNoteTable[i].note;
|
||||
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
|
||||
static const int kVarC = 0x2000; // / byte_1B73_2BE66;
|
||||
value = pitchBend / kVarC + note % 12;
|
||||
int octave = note / 12;
|
||||
if (value < 0) {
|
||||
--octave;
|
||||
value = -value;
|
||||
} else if (value > 12) {
|
||||
++octave;
|
||||
value -= 12;
|
||||
}
|
||||
--octave;
|
||||
const int var14 = ((pitchBend % kVarC) * 25) / kVarC;
|
||||
const uint16_t freq = var14 + _adlibFrequencyTable[value % 12];
|
||||
writeRegister(0xA0 + hw_channel, freq & 0xFF);
|
||||
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8);
|
||||
writeRegister(0xB0 + hw_channel, regBx);
|
||||
}
|
||||
}
|
||||
|
||||
void adlibReset() {
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
adlibMelodicKeyOff(i, 0);
|
||||
}
|
||||
for (int i = 6; i < 11; ++i) {
|
||||
adlibPercussionKeyOff(i);
|
||||
}
|
||||
writeRegister(8, 0x40);
|
||||
for (int i = 0; i < 18; ++i) {
|
||||
writeRegister(_adlibOperatorTable[i] + 0x40, 0x3F);
|
||||
}
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
writeRegister(0xB0 + i, 0);
|
||||
}
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
writeRegister(0xC0 + i, 0);
|
||||
}
|
||||
for (int i = 0; i < 18; ++i) {
|
||||
writeRegister(_adlibOperatorTable[i] + 0x60, 0);
|
||||
}
|
||||
for (int i = 0; i < 18; ++i) {
|
||||
writeRegister(_adlibOperatorTable[i] + 0x80, 0);
|
||||
}
|
||||
for (int i = 0; i < 18; ++i) {
|
||||
writeRegister(_adlibOperatorTable[i] + 0x20, 0);
|
||||
}
|
||||
for (int i = 0; i < 18; ++i) {
|
||||
writeRegister(_adlibOperatorTable[i] + 0xE0, 0);
|
||||
}
|
||||
writeRegister(1, 0x20);
|
||||
writeRegister(1, 0);
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
writeRegister(0xB0 + i, 0);
|
||||
writeRegister(0xA0 + i, 0);
|
||||
}
|
||||
writeRegister(0xBD, 0);
|
||||
_regBD = 0x20;
|
||||
}
|
||||
|
||||
void writeRegister(uint8_t port, uint8_t value) {
|
||||
//fprintf(stdout, "0x%02x:0x%02x\n", port, value);
|
||||
OPL3_WriteReg(&_opl3, port, value);
|
||||
}
|
||||
};
|
||||
|
||||
static MidiDriver *createMidiDriver() {
|
||||
return new MidiDriver_adlib;
|
||||
}
|
||||
|
||||
const MidiDriverInfo midi_driver_adlib = {
|
||||
"Adlib",
|
||||
createMidiDriver
|
||||
};
|
|
@ -0,0 +1,87 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "midi_driver.h"
|
||||
|
||||
#define MT32EMU_API_TYPE 1
|
||||
#include <mt32emu.h>
|
||||
|
||||
static const bool _isCM32L = false;
|
||||
|
||||
static const char *CM32L_CONTROL_FILENAME = "CM32L_CONTROL.ROM";
|
||||
static const char *CM32L_PCM_FILENAME = "CM32L_PCM.ROM";
|
||||
static const char *MT32_CONTROL_FILENAME = "MT32_CONTROL.ROM";
|
||||
static const char *MT32_PCM_FILENAME = "MT32_PCM.ROM";
|
||||
|
||||
static uint32_t msg(int cmd, int param1 = 0, int param2 = 0) {
|
||||
return (param2 << 16) | (param1 << 8) | cmd;
|
||||
}
|
||||
|
||||
struct MidiDriver_mt32emu: MidiDriver {
|
||||
virtual int init() {
|
||||
mt32emu_report_handler_i report = { 0 };
|
||||
_context = mt32emu_create_context(report, this);
|
||||
const char *control = _isCM32L ? CM32L_CONTROL_FILENAME : MT32_CONTROL_FILENAME;
|
||||
if (mt32emu_add_rom_file(_context, control) != MT32EMU_RC_ADDED_CONTROL_ROM) {
|
||||
fprintf(stdout, "WARNING: Failed to add MT32 rom file '%s'", control);
|
||||
return -1;
|
||||
}
|
||||
const char *pcm = _isCM32L ? CM32L_PCM_FILENAME : MT32_PCM_FILENAME;
|
||||
if (mt32emu_add_rom_file(_context, pcm) != MT32EMU_RC_ADDED_PCM_ROM) {
|
||||
fprintf(stdout, "WARNING: Failed to add MT32 rom file '%s'", pcm);
|
||||
return -1;
|
||||
}
|
||||
mt32emu_rom_info romInfo;
|
||||
mt32emu_get_rom_info(_context, &romInfo);
|
||||
fprintf(stdout, "mt32emu version %s\n", mt32emu_get_library_version_string());
|
||||
//fprintf(stdout, "control rom %s %s\n", romInfo.control_rom_id, romInfo.control_rom_description);
|
||||
//fprintf(stdout, "pcm rom %s %s\n", romInfo.pcm_rom_id, romInfo.pcm_rom_description);
|
||||
|
||||
mt32emu_set_midi_delay_mode(_context, MT32EMU_MDM_IMMEDIATE);
|
||||
return 0;
|
||||
}
|
||||
virtual void fini() {
|
||||
if (mt32emu_is_open(_context)) {
|
||||
mt32emu_close_synth(_context);
|
||||
}
|
||||
mt32emu_free_context(_context);
|
||||
}
|
||||
virtual void reset(int rate) {
|
||||
mt32emu_set_stereo_output_samplerate(_context, rate);
|
||||
if (mt32emu_is_open(_context)) {
|
||||
mt32emu_close_synth(_context);
|
||||
}
|
||||
mt32emu_open_synth(_context);
|
||||
}
|
||||
|
||||
virtual void noteOff(int channel, int note, int velocity) {
|
||||
mt32emu_play_msg(_context, msg(0x80 | channel, note, velocity));
|
||||
}
|
||||
virtual void noteOn(int channel, int note, int velocity) {
|
||||
mt32emu_play_msg(_context, msg(0x90 | channel, note, velocity));
|
||||
}
|
||||
virtual void controlChange(int channel, int type, int value) {
|
||||
mt32emu_play_msg(_context, msg(0xB0 | channel, type, value));
|
||||
}
|
||||
virtual void programChange(int channel, int num) {
|
||||
mt32emu_play_msg(_context, msg(0xC0 | channel, num));
|
||||
}
|
||||
virtual void pitchBend(int channel, int lsb, int msb) {
|
||||
mt32emu_play_msg(_context, msg(0xE0 | channel, lsb, msb));
|
||||
}
|
||||
|
||||
virtual void readSamples(int16_t *samples, int len) {
|
||||
mt32emu_render_bit16s(_context, samples, len);
|
||||
}
|
||||
|
||||
mt32emu_context _context;
|
||||
};
|
||||
|
||||
static MidiDriver *createMidiDriver() {
|
||||
return new MidiDriver_mt32emu;
|
||||
}
|
||||
|
||||
const MidiDriverInfo midi_driver_mt32 = {
|
||||
"MT32",
|
||||
createMidiDriver
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "file.h"
|
||||
#include "midi_parser.h"
|
||||
#include "util.h"
|
||||
|
||||
static uint32_t readVLQ(File *f) {
|
||||
uint32_t value = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const uint8_t b = f->readByte();
|
||||
value = (value << 7) | (b & 0x7F);
|
||||
if ((b & 0x80) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void loadTrk(File *f, int len, MidiTrack &track) {
|
||||
const uint32_t end = f->tell() + len;
|
||||
while (f->tell() < end) {
|
||||
MidiEvent ev;
|
||||
ev.timestamp = readVLQ(f);
|
||||
ev.command = f->readByte();
|
||||
switch (ev.command & 0xF0) {
|
||||
case MIDI_COMMAND_NOTE_OFF:
|
||||
case MIDI_COMMAND_NOTE_ON:
|
||||
ev.param1 = f->readByte();
|
||||
ev.param2 = f->readByte();
|
||||
//fprintf(stdout, "\t Note %s: note %d vel %d timestamp %d\n", (((ev.command & 0xF0) == MIDI_COMMAND_NOTE_OFF) ? "Off" : "On"), ev.param1, ev.param2, ev.timestamp);
|
||||
break;
|
||||
case MIDI_COMMAND_PROGRAM_CHANGE:
|
||||
ev.param1 = f->readByte();
|
||||
//fprintf(stdout, "\t Program %d timestamp %d\n", ev.param1, ev.timestamp);
|
||||
break;
|
||||
case MIDI_COMMAND_CHANNEL_PRESSURE:
|
||||
ev.param1 = f->readByte();
|
||||
//fprintf(stdout, "\t Channel pressure %d timestamp %d\n", ev.param1, ev.timestamp);
|
||||
break;
|
||||
case MIDI_COMMAND_PITCH_BEND:
|
||||
ev.param1 = f->readByte();
|
||||
ev.param2 = f->readByte();
|
||||
//fprintf(stdout, "\t Pitch Bend %d,%d\n", ev.param1, ev.param2);
|
||||
break;
|
||||
case MIDI_COMMAND_SYSEX:
|
||||
if (ev.command == 0xFF) {
|
||||
f->readByte(); /* type */
|
||||
const int len = readVLQ(f);
|
||||
f->seek(f->tell() + len);
|
||||
//fprintf(stdout, "\t SysEx timestamp %d\n", ev.timestamp);
|
||||
assert(ev.timestamp == 0);
|
||||
continue;
|
||||
}
|
||||
/* fall-through */
|
||||
default:
|
||||
warning("Unhandled MIDI command 0x%x", ev.command);
|
||||
break;
|
||||
}
|
||||
track.events.push(ev);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiParser::loadMid(File *f) {
|
||||
for (int i = 0; i < MAX_TRACKS; ++i) {
|
||||
_tracks[i].events = std::queue<MidiEvent>();
|
||||
}
|
||||
_tracksCount = 0;
|
||||
char tag[4];
|
||||
f->read(tag, 4);
|
||||
assert(memcmp(tag, "MThd", 4) == 0);
|
||||
const uint32_t len = f->readUint32BE();
|
||||
assert(len == 6);
|
||||
f->readByte();
|
||||
/* const int type = */ f->readByte();
|
||||
const int tracksCount = f->readUint16BE();
|
||||
assert(tracksCount <= MAX_TRACKS);
|
||||
/* const int ppqn = */ f->readUint16BE();
|
||||
//fprintf(stdout, "MThd type %d tracks %d ppqn %d len %d\n", type, tracksCount, ppqn, len);
|
||||
for (int i = 0; i < tracksCount; ++i) {
|
||||
f->read(tag, 4);
|
||||
assert(memcmp(tag, "MTrk", 4) == 0);
|
||||
const uint32_t len = f->readUint32BE();
|
||||
//fprintf(stdout, "Track #%d len %d\n", i, len);
|
||||
loadTrk(f, len, _tracks[i]);
|
||||
//fprintf(stdout, "Track #%d events %ld\n", i, track.events.size());
|
||||
}
|
||||
_tracksCount = tracksCount;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
#ifndef MIDI_PARSER_H__
|
||||
#define MIDI_PARSER_H__
|
||||
|
||||
#include <queue>
|
||||
#include <stdint.h>
|
||||
|
||||
#define MIDI_COMMAND_NOTE_OFF 0x80
|
||||
#define MIDI_COMMAND_NOTE_ON 0x90
|
||||
#define MIDI_COMMAND_PROGRAM_CHANGE 0xC0
|
||||
#define MIDI_COMMAND_CHANNEL_PRESSURE 0xD0 /* unused */
|
||||
#define MIDI_COMMAND_PITCH_BEND 0xE0
|
||||
#define MIDI_COMMAND_SYSEX 0xF0 /* unused */
|
||||
|
||||
struct MidiEvent {
|
||||
uint32_t timestamp;
|
||||
uint8_t command;
|
||||
uint8_t param1;
|
||||
uint8_t param2;
|
||||
};
|
||||
|
||||
struct MidiTrack {
|
||||
std::queue<MidiEvent> events;
|
||||
};
|
||||
|
||||
struct File;
|
||||
|
||||
#define MAX_TRACKS 16
|
||||
|
||||
struct MidiParser {
|
||||
void loadMid(File *f);
|
||||
|
||||
int _tracksCount;
|
||||
MidiTrack _tracks[MAX_TRACKS];
|
||||
};
|
||||
|
||||
#endif /* MIDI_PARSER_H__ */
|
19
mixer.cpp
19
mixer.cpp
|
@ -8,14 +8,16 @@
|
|||
#include "systemstub.h"
|
||||
#include "util.h"
|
||||
|
||||
Mixer::Mixer(FileSystem *fs, SystemStub *stub)
|
||||
: _stub(stub), _musicType(MT_NONE), _cpc(this, fs), _mod(this, fs), _ogg(this, fs), _sfx(this) {
|
||||
Mixer::Mixer(FileSystem *fs, SystemStub *stub, int midiDriver)
|
||||
: _stub(stub), _musicType(MT_NONE), _cpc(this, fs), _mod(this, fs), _ogg(this, fs), _prf(this, fs, midiDriver), _sfx(this) {
|
||||
_musicTrack = -1;
|
||||
_backgroundMusicType = MT_NONE;
|
||||
}
|
||||
|
||||
void Mixer::init() {
|
||||
memset(_channels, 0, sizeof(_channels));
|
||||
for (int i = 0; i < NUM_CHANNELS; ++i) {
|
||||
_channels[i].active = false;
|
||||
}
|
||||
_premixHook = 0;
|
||||
_stub->startAudio(Mixer::mixCallback, this);
|
||||
}
|
||||
|
@ -120,6 +122,14 @@ void Mixer::playMusic(int num) {
|
|||
_mod.play(num);
|
||||
if (_mod._playing) {
|
||||
_musicType = MT_MOD;
|
||||
return;
|
||||
}
|
||||
if (g_options.use_prf_music) {
|
||||
_prf.play(num);
|
||||
if (_prf._playing) {
|
||||
_musicType = MT_PRF;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +145,9 @@ void Mixer::stopMusic() {
|
|||
case MT_OGG:
|
||||
_ogg.pauseTrack();
|
||||
break;
|
||||
case MT_PRF:
|
||||
_prf.stop();
|
||||
break;
|
||||
case MT_SFX:
|
||||
_sfx.stop();
|
||||
break;
|
||||
|
|
5
mixer.h
5
mixer.h
|
@ -11,6 +11,7 @@
|
|||
#include "cpc_player.h"
|
||||
#include "mod_player.h"
|
||||
#include "ogg_player.h"
|
||||
#include "prf_player.h"
|
||||
#include "sfx_player.h"
|
||||
|
||||
struct MixerChunk {
|
||||
|
@ -49,6 +50,7 @@ struct Mixer {
|
|||
MT_NONE,
|
||||
MT_MOD,
|
||||
MT_OGG,
|
||||
MT_PRF,
|
||||
MT_SFX,
|
||||
MT_CPC,
|
||||
};
|
||||
|
@ -70,10 +72,11 @@ struct Mixer {
|
|||
CpcPlayer _cpc;
|
||||
ModPlayer _mod;
|
||||
OggPlayer _ogg;
|
||||
PrfPlayer _prf;
|
||||
SfxPlayer _sfx;
|
||||
int _musicTrack;
|
||||
|
||||
Mixer(FileSystem *fs, SystemStub *stub);
|
||||
Mixer(FileSystem *fs, SystemStub *stub, int midiDriver);
|
||||
void init();
|
||||
void free();
|
||||
void setPremixHook(PremixHook premixHook, void *userData);
|
||||
|
|
|
@ -165,7 +165,7 @@ void Game::pge_setupNextAnimFrame(LivePGE *pge, MessagePGE *le) {
|
|||
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
|
||||
Object *obj = &on->objects[pge->first_obj_number];
|
||||
int i = pge->first_obj_number;
|
||||
while (i < on->last_obj_number && pge->obj_type == obj->type) {
|
||||
while (i < on->num_objects && pge->obj_type == obj->type) {
|
||||
MessagePGE *next_le = le;
|
||||
while (next_le) {
|
||||
uint16_t msgNum = next_le->msg_num;
|
||||
|
@ -403,7 +403,7 @@ uint16_t Game::pge_processOBJ(LivePGE *pge) {
|
|||
ObjectNode *on = _res._objectNodesMap[init_pge->obj_node_number];
|
||||
Object *obj = &on->objects[pge->first_obj_number];
|
||||
int i = pge->first_obj_number;
|
||||
while (i < on->last_obj_number && pge->obj_type == obj->type) {
|
||||
while (i < on->num_objects && pge->obj_type == obj->type) {
|
||||
if (obj->opcode2 == 0x6B) return 0xFFFF;
|
||||
if (obj->opcode2 == 0x22 && obj->opcode_arg2 <= 4) return 0xFFFF;
|
||||
|
||||
|
|
|
@ -0,0 +1,421 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "file.h"
|
||||
#ifdef _WIN32
|
||||
#define MIDI_DRIVER_SYMBOL __declspec(dllimport)
|
||||
#else
|
||||
#define MIDI_DRIVER_SYMBOL
|
||||
#endif
|
||||
#include "midi_driver.h"
|
||||
#include "midi_parser.h"
|
||||
#include "mixer.h"
|
||||
#include "prf_player.h"
|
||||
#include "util.h"
|
||||
|
||||
static const struct {
|
||||
int mode;
|
||||
int hz;
|
||||
const MidiDriverInfo *info;
|
||||
} _midiDrivers[] = {
|
||||
#ifdef USE_MIDI_DRIVER
|
||||
{ MODE_ADLIB, TIMER_ADLIB_HZ, &midi_driver_adlib },
|
||||
{ MODE_MT32, TIMER_MT32_HZ, &midi_driver_mt32 },
|
||||
#endif
|
||||
{ -1, 0, 0 }
|
||||
};
|
||||
|
||||
static const int kMusicVolume = 63;
|
||||
|
||||
PrfPlayer::PrfPlayer(Mixer *mix, FileSystem *fs, int mode)
|
||||
: _playing(false), _mixer(mix), _fs(fs), _mode(mode), _driver(0) {
|
||||
for (int i = 0; _midiDrivers[i].info; ++i) {
|
||||
if (_midiDrivers[i].mode == mode) {
|
||||
_timerHz = _midiDrivers[i].hz;
|
||||
_driver = _midiDrivers[i].info->create();
|
||||
assert(_driver);
|
||||
if (_driver->init() < 0) {
|
||||
warning("Failed to initialize MIDI driver %s", _midiDrivers[i].info->name);
|
||||
_driver = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
fprintf(stdout, "WARNING: no midi driver for mode %d", mode);
|
||||
}
|
||||
|
||||
PrfPlayer::~PrfPlayer() {
|
||||
if (_driver) {
|
||||
_driver->fini();
|
||||
_driver = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PrfPlayer::play(int num) {
|
||||
_playing = false;
|
||||
memset(&_prfData, 0, sizeof(_prfData));
|
||||
memset(&_tracks, 0, sizeof(_tracks));
|
||||
if (num < _namesCount) {
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "%s.prf", (num == 1 && _mode == MODE_MT32) ? "opt" : _names[num]);
|
||||
File f;
|
||||
if (f.open(name, "rb", _fs)) {
|
||||
loadPrf(&f);
|
||||
if (_mode == MODE_ADLIB) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
memset(_adlibInstrumentData[i], 0, ADLIB_INSTRUMENT_DATA_LEN);
|
||||
if (_prfData.instruments[i][0]) {
|
||||
snprintf(name, sizeof(name), "%s.INS", _prfData.instruments[i]);
|
||||
if (f.open(name, "rb", _fs)) {
|
||||
loadIns(&f, i);
|
||||
} else {
|
||||
warning("Unable to open '%s'", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (f.open(_prfData.midi, "rb", _fs)) {
|
||||
_parser.loadMid(&f);
|
||||
play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrfPlayer::loadPrf(File *f) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
f->read(_prfData.instruments[i], INSTRUMENT_NAME_LEN);
|
||||
const int len = strlen(_prfData.instruments[i]);
|
||||
if (len > 8) {
|
||||
_prfData.instruments[i][8] = 0;
|
||||
debug(DBG_PRF, "Truncating instrument name to '%s'", _prfData.instruments[i]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_prfData.adlibNotes[i] = f->readUint16LE();
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_prfData.adlibVelocities[i] = f->readUint16LE();
|
||||
}
|
||||
_prfData.timerTicks = f->readUint32LE();
|
||||
_prfData.timerMod = f->readUint16LE();
|
||||
f->read(_prfData.midi, MIDI_FILENAME_LEN);
|
||||
_prfData.adlibDoNotesLookup = f->readUint16LE();
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_prfData.adlibPrograms[i] = f->readUint16LE();
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_prfData.mt32Programs[i] = f->readUint16LE();
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_prfData.mt32Velocities[i] = f->readUint16LE();
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_prfData.mt32Notes[i] = f->readUint16LE();
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_tracks[i].hw_channel_num = f->readByte();
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_tracks[i].mt32_program_num = f->readByte();
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_tracks[i].loop_flag = f->readByte();
|
||||
}
|
||||
_prfData.totalDurationTicks = f->readUint32LE();
|
||||
_prfData.mt32DoChannelsLookup = f->readByte();
|
||||
assert(f->tell() == 0x2F1);
|
||||
debug(DBG_PRF, "duration %d timer %d", _prfData.totalDurationTicks, _prfData.timerTicks);
|
||||
}
|
||||
|
||||
void PrfPlayer::loadIns(File *f, int i) {
|
||||
uint8_t *p = _adlibInstrumentData[i];
|
||||
p[0] = f->readByte();
|
||||
p[1] = f->readByte();
|
||||
f->read(&p[6], 26 * 2);
|
||||
f->seek(54 + 20);
|
||||
p[2] = f->readByte();
|
||||
f->readByte();
|
||||
p[3] = f->readByte();
|
||||
f->readByte();
|
||||
if (p[2] != 0 || p[3] != 0) {
|
||||
debug(DBG_PRF, "Wave Select Register 0x%02x 0x%02x was ignored", p[2], p[3]);
|
||||
}
|
||||
p[4] = 0;
|
||||
p[5] = 0;
|
||||
const int unk = f->readUint16LE();
|
||||
assert(unk == 1);
|
||||
assert(f->tell() == 80);
|
||||
}
|
||||
|
||||
void PrfPlayer::play() {
|
||||
if (!_driver) {
|
||||
return;
|
||||
}
|
||||
_driver->reset(_mixer->getSampleRate());
|
||||
for (int i = 0; i < _parser._tracksCount; ++i) {
|
||||
_tracks[i].instrument_num = i;
|
||||
if (_mode == MODE_MT32) {
|
||||
mt32ProgramChange(i, _tracks[i].mt32_program_num);
|
||||
mt32ControlChangeResetRPN(i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_tracks[i].instrument_num = i;
|
||||
}
|
||||
for (int i = 0; i < _parser._tracksCount; ++i) {
|
||||
PrfTrack *current_track = &_tracks[i];
|
||||
MidiTrack *track = &_parser._tracks[i];
|
||||
MidiEvent ev = track->events.front();
|
||||
current_track->counter = ev.timestamp;
|
||||
}
|
||||
_timerTick = _musicTick = 0;
|
||||
_samplesLeft = 0;
|
||||
_samplesPerTick = _mixer->getSampleRate() / _timerHz;
|
||||
_playing = true;
|
||||
_mixer->setPremixHook(mixCallback, this);
|
||||
}
|
||||
|
||||
void PrfPlayer::stop() {
|
||||
if (_playing) {
|
||||
_mixer->setPremixHook(0, 0);
|
||||
_playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PrfPlayer::mt32NoteOn(int track, int note, int velocity) {
|
||||
if (_prfData.mt32DoChannelsLookup != 0 && _tracks[track].hw_channel_num == 9) {
|
||||
note = _prfData.mt32Notes[_tracks[track].instrument_num];
|
||||
}
|
||||
_driver->noteOn(_tracks[track].hw_channel_num, note, (velocity * kMusicVolume) >> 6);
|
||||
}
|
||||
|
||||
void PrfPlayer::mt32NoteOff(int track, int note, int velocity) {
|
||||
if (_prfData.mt32DoChannelsLookup != 0 && _tracks[track].hw_channel_num == 9) {
|
||||
note = _prfData.mt32Notes[_tracks[track].instrument_num];
|
||||
}
|
||||
_driver->noteOff(_tracks[track].hw_channel_num, note, (velocity * kMusicVolume) >> 6);
|
||||
}
|
||||
|
||||
void PrfPlayer::mt32ProgramChange(int track, int num) {
|
||||
_driver->programChange(_tracks[track].hw_channel_num, num);
|
||||
}
|
||||
|
||||
void PrfPlayer::mt32PitchBend(int track, int lsb, int msb) {
|
||||
_driver->pitchBend(_tracks[track].hw_channel_num, lsb, msb);
|
||||
}
|
||||
|
||||
static const uint8_t MIDI_CONTROLLER_RPN_LSB = 0x64;
|
||||
static const uint8_t MIDI_CONTROLLER_RPN_MSB = 0x65;
|
||||
static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_LSB = 0x26;
|
||||
static const uint8_t MIDI_CONTROLLER_DATA_ENTRY_MSB = 0x06;
|
||||
|
||||
void PrfPlayer::mt32ControlChangeResetRPN(int track) {
|
||||
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_RPN_MSB, 0);
|
||||
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_RPN_LSB, 0);
|
||||
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_DATA_ENTRY_MSB, 1);
|
||||
_driver->controlChange(_tracks[track].hw_channel_num, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
|
||||
}
|
||||
|
||||
void PrfPlayer::adlibNoteOn(int track, int note, int velocity) {
|
||||
const int instrument_num = _tracks[track].instrument_num;
|
||||
_driver->setInstrumentData(track, instrument_num, _adlibInstrumentData[instrument_num]);
|
||||
_driver->noteOn(track, note, velocity);
|
||||
}
|
||||
|
||||
void PrfPlayer::adlibNoteOff(int track, int note, int velocity) {
|
||||
const int instrument_num = _tracks[track].instrument_num;
|
||||
_driver->setInstrumentData(track, instrument_num, _adlibInstrumentData[instrument_num]);
|
||||
_driver->noteOff(track, note, velocity);
|
||||
}
|
||||
|
||||
void PrfPlayer::handleTick() {
|
||||
for (int i = 0; i < _parser._tracksCount; ++i) {
|
||||
MidiTrack *track = &_parser._tracks[i];
|
||||
if (track->events.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
const int track_index = i;
|
||||
PrfTrack *current_track = &_tracks[i];
|
||||
if (current_track->counter != 0) {
|
||||
--current_track->counter;
|
||||
continue;
|
||||
}
|
||||
next_event:
|
||||
MidiEvent ev = track->events.front();
|
||||
track->events.pop();
|
||||
if (current_track->loop_flag) {
|
||||
track->events.push(ev);
|
||||
}
|
||||
switch (ev.command & 0xF0) {
|
||||
case MIDI_COMMAND_NOTE_OFF: {
|
||||
int note = ev.param1;
|
||||
int velocity = ev.param2;
|
||||
if (_mode == MODE_MT32) {
|
||||
note += _prfData.mt32Notes[current_track->instrument_num];
|
||||
velocity += _prfData.mt32Velocities[current_track->instrument_num];
|
||||
if (velocity < 0) {
|
||||
velocity = 0;
|
||||
} else if (velocity > 127) {
|
||||
velocity = 127;
|
||||
}
|
||||
mt32NoteOff(track_index, note, velocity);
|
||||
} else {
|
||||
if (_prfData.adlibDoNotesLookup) {
|
||||
note += _prfData.adlibNotes[current_track->instrument_num];
|
||||
} else {
|
||||
note += _prfData.adlibNotes[track_index];
|
||||
}
|
||||
if (_prfData.adlibDoNotesLookup) {
|
||||
velocity += _prfData.adlibVelocities[current_track->instrument_num];
|
||||
} else {
|
||||
velocity += _prfData.adlibVelocities[track_index];
|
||||
}
|
||||
if (velocity < 0) {
|
||||
velocity = 0;
|
||||
} else if (velocity > 127) {
|
||||
velocity = 127;
|
||||
}
|
||||
adlibNoteOff(track_index, note, velocity);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MIDI_COMMAND_NOTE_ON: {
|
||||
int note = ev.param1;
|
||||
int velocity = ev.param2;
|
||||
if (_mode == MODE_MT32) {
|
||||
note += _prfData.mt32Notes[current_track->instrument_num];
|
||||
if (velocity == 0) {
|
||||
mt32NoteOff(track_index, note, velocity);
|
||||
break;
|
||||
}
|
||||
velocity += _prfData.mt32Velocities[current_track->instrument_num];
|
||||
if (velocity < 0) {
|
||||
velocity = 0;
|
||||
} else if (velocity > 127) {
|
||||
velocity = 127;
|
||||
}
|
||||
mt32NoteOn(track_index, note, velocity);
|
||||
} else {
|
||||
assert(_mode == MODE_ADLIB);
|
||||
if (_prfData.adlibDoNotesLookup) {
|
||||
note += _prfData.adlibNotes[current_track->instrument_num];
|
||||
} else {
|
||||
note += _prfData.adlibNotes[track_index];
|
||||
}
|
||||
if (velocity == 0) {
|
||||
adlibNoteOff(track_index, note, velocity);
|
||||
break;
|
||||
}
|
||||
if (_prfData.adlibDoNotesLookup) {
|
||||
velocity += _prfData.adlibVelocities[current_track->instrument_num];
|
||||
} else {
|
||||
velocity += _prfData.adlibVelocities[track_index];
|
||||
}
|
||||
if (velocity < 0) {
|
||||
velocity = 0;
|
||||
} else if (velocity > 127) {
|
||||
velocity = 127;
|
||||
}
|
||||
adlibNoteOn(track_index, note, velocity);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MIDI_COMMAND_PROGRAM_CHANGE: {
|
||||
if (_mode == MODE_MT32) {
|
||||
for (int num = 0; num < 16; ++num) {
|
||||
if (_prfData.mt32Programs[num] == ev.param1) {
|
||||
mt32ProgramChange(track_index, _tracks[num].mt32_program_num);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(_mode == MODE_ADLIB);
|
||||
if (_prfData.adlibDoNotesLookup) {
|
||||
for (int num = 0; num < 16; ++num) {
|
||||
if (_prfData.adlibPrograms[num] == ev.param1) {
|
||||
current_track->instrument_num = num;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MIDI_COMMAND_CHANNEL_PRESSURE:
|
||||
break;
|
||||
case MIDI_COMMAND_PITCH_BEND: {
|
||||
if (_mode == MODE_MT32) {
|
||||
mt32PitchBend(track_index, ev.param1, ev.param2);
|
||||
} else {
|
||||
assert(_mode == MODE_ADLIB);
|
||||
_driver->pitchBend(track_index, ev.param1, ev.param2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
warning("Unhandled MIDI event command 0x%x", ev.command);
|
||||
break;
|
||||
}
|
||||
if (track->events.size() != 0) {
|
||||
ev = track->events.front();
|
||||
current_track->counter = ev.timestamp;
|
||||
if (current_track->counter == 0) {
|
||||
goto next_event;
|
||||
}
|
||||
--current_track->counter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PrfPlayer::end() const {
|
||||
return (_prfData.totalDurationTicks != 0) && _musicTick > _prfData.totalDurationTicks;
|
||||
}
|
||||
|
||||
int PrfPlayer::readSamples(int16_t *samples, int count) {
|
||||
const int total = count;
|
||||
while (count != 0) {
|
||||
if (_samplesLeft == 0) {
|
||||
//++_timerTick;
|
||||
//if (_timerTick == _prfData.timerTicks) {
|
||||
//fprintf(stdout, "musicTick #%d of %d\n", _musicTick, _prfData.totalDurationTicks);
|
||||
handleTick();
|
||||
//_timerTick = 0;
|
||||
if (_prfData.totalDurationTicks != 0) {
|
||||
++_musicTick;
|
||||
if (_musicTick == _prfData.totalDurationTicks + 1) {
|
||||
debug(DBG_PRF, "End of music");
|
||||
//break;
|
||||
}
|
||||
}
|
||||
//}
|
||||
_samplesLeft = _samplesPerTick * _prfData.timerTicks;
|
||||
}
|
||||
const int len = (count < _samplesLeft) ? count : _samplesLeft;
|
||||
_driver->readSamples(samples, len);
|
||||
_samplesLeft -= len;
|
||||
count -= len;
|
||||
samples += len * 2;
|
||||
}
|
||||
return total - count;
|
||||
}
|
||||
|
||||
bool PrfPlayer::mixCallback(void *param, int16_t *buf, int len) {
|
||||
return ((PrfPlayer *)param)->mix(buf, len);
|
||||
}
|
||||
|
||||
bool PrfPlayer::mix(int16_t *buf, int len) {
|
||||
int16_t *p = (int16_t *)alloca(len * sizeof(int16_t) * 2);
|
||||
if (p) {
|
||||
const int count = readSamples(p, len);
|
||||
/* stereo to mono */
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const int16_t l = *p++;
|
||||
const int16_t r = *p++;
|
||||
buf[i] = CLIP((l + r) / 2, -32768, 32767);
|
||||
}
|
||||
return count != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
|
||||
#ifndef PRF_PLAYER_H__
|
||||
#define PRF_PLAYER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "midi_parser.h"
|
||||
|
||||
static const int TIMER_ADLIB_HZ = 2082;
|
||||
static const int TIMER_MT32_HZ = 2242;
|
||||
|
||||
struct MidiDriver;
|
||||
|
||||
#define INSTRUMENT_NAME_LEN 30
|
||||
#define MIDI_FILENAME_LEN 20
|
||||
|
||||
struct PrfData {
|
||||
char instruments[16][INSTRUMENT_NAME_LEN];
|
||||
int16_t adlibNotes[16];
|
||||
int16_t adlibVelocities[16];
|
||||
uint32_t timerTicks;
|
||||
uint16_t timerMod;
|
||||
char midi[MIDI_FILENAME_LEN];
|
||||
uint16_t adlibDoNotesLookup;
|
||||
int16_t adlibPrograms[16];
|
||||
int16_t mt32Programs[16];
|
||||
int16_t mt32Velocities[16];
|
||||
int16_t mt32Notes[16];
|
||||
uint32_t totalDurationTicks;
|
||||
uint8_t mt32DoChannelsLookup;
|
||||
};
|
||||
|
||||
struct PrfTrack {
|
||||
uint8_t instrument_num;
|
||||
uint32_t counter;
|
||||
uint8_t hw_channel_num;
|
||||
uint8_t mt32_program_num;
|
||||
uint8_t loop_flag;
|
||||
};
|
||||
|
||||
#define ADLIB_INSTRUMENT_DATA_LEN 58
|
||||
|
||||
struct AdlibInstrument {
|
||||
uint8_t mode;
|
||||
uint8_t channel_num;
|
||||
uint8_t modulator_wave_select;
|
||||
uint8_t carrier_wave_select;
|
||||
uint8_t unk4;
|
||||
uint8_t unk5;
|
||||
struct {
|
||||
uint16_t key_scaling; // 0
|
||||
uint16_t frequency_multiplier; // 2
|
||||
uint16_t feedback_strength; // 4
|
||||
uint16_t attack_rate; // 6
|
||||
uint16_t sustain_level; // 8
|
||||
uint16_t sustain_sound; // 10
|
||||
uint16_t decay_rate; // 12
|
||||
uint16_t release_rate; // 14
|
||||
uint16_t output_level; // 16
|
||||
uint16_t amplitude_vibrato; // 18
|
||||
uint16_t frequency_vibrato; // 20
|
||||
uint16_t envelope_scaling; // 22
|
||||
uint16_t frequency_modulation; // 24
|
||||
} op[2]; /* modulator, carrier */
|
||||
};
|
||||
|
||||
enum {
|
||||
MODE_ADLIB,
|
||||
MODE_MT32,
|
||||
};
|
||||
|
||||
struct File;
|
||||
struct FileSystem;
|
||||
struct Mixer;
|
||||
|
||||
struct PrfPlayer {
|
||||
static const char *_names[];
|
||||
static const int _namesCount;
|
||||
|
||||
PrfPlayer(Mixer *mix, FileSystem *fs, int mode);
|
||||
~PrfPlayer();
|
||||
|
||||
void play(int num);
|
||||
|
||||
void loadPrf(File *f);
|
||||
void loadIns(File *f, int num);
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
|
||||
void mt32NoteOn(int track, int note, int velocity);
|
||||
void mt32NoteOff(int track, int note, int velocity);
|
||||
void mt32ProgramChange(int track, int num);
|
||||
void mt32PitchBend(int track, int lsb, int msb);
|
||||
void mt32ControlChangeResetRPN(int track);
|
||||
|
||||
void adlibNoteOn(int track, int note, int velocity);
|
||||
void adlibNoteOff(int track, int note, int velocity);
|
||||
|
||||
void handleTick();
|
||||
|
||||
bool end() const;
|
||||
int readSamples(int16_t *samples, int count);
|
||||
|
||||
static bool mixCallback(void *param, int16_t *buf, int len);
|
||||
bool mix(int16_t *buf, int len);
|
||||
|
||||
bool _playing;
|
||||
Mixer *_mixer;
|
||||
FileSystem *_fs;
|
||||
int _mode;
|
||||
int _timerHz;
|
||||
PrfTrack _tracks[16];
|
||||
MidiDriver *_driver;
|
||||
MidiParser _parser;
|
||||
PrfData _prfData;
|
||||
int _samplesLeft, _samplesPerTick;
|
||||
uint32_t _timerTick, _musicTick;
|
||||
uint8_t _adlibInstrumentData[16][ADLIB_INSTRUMENT_DATA_LEN];
|
||||
};
|
||||
|
||||
#endif /* PRF_PLAYER_H__ */
|
10
resource.cpp
10
resource.cpp
|
@ -917,9 +917,9 @@ void Resource::load_OBJ(File *f) {
|
|||
error("Unable to allocate ObjectNode num=%d", i);
|
||||
}
|
||||
f->seek(offsets[i] + 2);
|
||||
on->last_obj_number = f->readUint16LE();
|
||||
on->num_objects = objectsCount[iObj];
|
||||
debug(DBG_RES, "last=%d num=%d", on->last_obj_number, on->num_objects);
|
||||
on->num_objects = f->readUint16LE();
|
||||
debug(DBG_RES, "count=%d", on->num_objects, objectsCount[iObj]);
|
||||
assert(on->num_objects == objectsCount[iObj]);
|
||||
on->objects = (Object *)malloc(sizeof(Object) * on->num_objects);
|
||||
for (int j = 0; j < on->num_objects; ++j) {
|
||||
Object *obj = &on->objects[j];
|
||||
|
@ -1012,8 +1012,8 @@ void Resource::decodeOBJ(const uint8_t *tmp, int size) {
|
|||
error("Unable to allocate ObjectNode num=%d", i);
|
||||
}
|
||||
const uint8_t *objData = tmp + offsets[i];
|
||||
on->last_obj_number = _readUint16(objData); objData += 2;
|
||||
on->num_objects = objectsCount[iObj];
|
||||
on->num_objects = _readUint16(objData); objData += 2;
|
||||
assert(on->num_objects == objectsCount[iObj]);
|
||||
on->objects = (Object *)malloc(sizeof(Object) * on->num_objects);
|
||||
for (int j = 0; j < on->num_objects; ++j) {
|
||||
Object *obj = &on->objects[j];
|
||||
|
|
|
@ -353,7 +353,7 @@ struct Resource {
|
|||
void MAC_loadSounds();
|
||||
|
||||
int MAC_getPersoFrame(int anim) const {
|
||||
static const int data[] = {
|
||||
static const int16_t data[] = {
|
||||
0x000, 0x22E,
|
||||
0x28E, 0x2E9,
|
||||
0x4E9, 0x506,
|
||||
|
@ -371,7 +371,7 @@ struct Resource {
|
|||
return 0;
|
||||
}
|
||||
int MAC_getMonsterFrame(int anim) const {
|
||||
static const int data[] = {
|
||||
static const int16_t data[] = {
|
||||
0x22F, 0x28D, // junky - 94
|
||||
0x2EA, 0x385, // mercenai - 156
|
||||
0x387, 0x42F, // replican - 169
|
||||
|
|
3
rs.cfg
3
rs.cfg
|
@ -25,6 +25,9 @@ use_words_protection=false
|
|||
# white t-shirt for Conrad
|
||||
use_white_tshirt=false
|
||||
|
||||
# use .PRF music files
|
||||
use_prf_music=true
|
||||
|
||||
# enable playback of 'ASC' cutscene
|
||||
play_asc_cutscene=false
|
||||
|
||||
|
|
|
@ -6503,3 +6503,34 @@ const uint8_t Menu::_flagSp16x12[] = {
|
|||
0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03,
|
||||
0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8d, 0x03, 0x00, 0x8e, 0x03, 0x00
|
||||
};
|
||||
|
||||
const char *PrfPlayer::_names[] = {
|
||||
"introl3",
|
||||
"option3",
|
||||
"journal3",
|
||||
"chute3",
|
||||
"desinte3",
|
||||
"capture3",
|
||||
"voyage3",
|
||||
"telepor3",
|
||||
"planexp3",
|
||||
"end31",
|
||||
"lift3",
|
||||
"present3",
|
||||
"gameove3",
|
||||
"holo3",
|
||||
"memory3",
|
||||
"chutevi3",
|
||||
"reveil3",
|
||||
"misvali3",
|
||||
"taxi3",
|
||||
"donner3",
|
||||
"mission3",
|
||||
"objet3",
|
||||
"recharg3",
|
||||
"generat3",
|
||||
"pont3",
|
||||
"rechage3"
|
||||
};
|
||||
|
||||
const int PrfPlayer::_namesCount = ARRAYSIZE(_names);
|
||||
|
|
|
@ -74,6 +74,7 @@ struct SystemStub {
|
|||
virtual void copyWidescreenRight(int w, int h, const uint8_t *buf) = 0;
|
||||
virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf) = 0;
|
||||
virtual void copyWidescreenBlur(int w, int h, const uint8_t *buf) = 0;
|
||||
virtual void copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal) = 0;
|
||||
virtual void clearWidescreen() = 0;
|
||||
virtual void enableWidescreen(bool enable) = 0;
|
||||
virtual void fadeScreen() = 0;
|
||||
|
|
|
@ -72,6 +72,7 @@ struct SystemStub_SDL : SystemStub {
|
|||
virtual void copyWidescreenRight(int w, int h, const uint8_t *buf);
|
||||
virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf);
|
||||
virtual void copyWidescreenBlur(int w, int h, const uint8_t *buf);
|
||||
virtual void copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal);
|
||||
virtual void clearWidescreen();
|
||||
virtual void enableWidescreen(bool enable);
|
||||
virtual void fadeScreen();
|
||||
|
@ -478,6 +479,28 @@ void SystemStub_SDL::copyWidescreenBlur(int w, int h, const uint8_t *buf) {
|
|||
}
|
||||
}
|
||||
|
||||
void SystemStub_SDL::copyWidescreenCDi(int w, int h, const uint8_t *buf, const uint8_t *pal) {
|
||||
uint32_t *rgb = (uint32_t *)malloc(w * h * sizeof(uint32_t));
|
||||
if (rgb) {
|
||||
for (int i = 0; i < w * h; ++i) {
|
||||
const uint8_t *p = pal + buf[i] * 3;
|
||||
const uint32_t color = SDL_MapRGB(_fmt, p[0], p[1], p[2]);
|
||||
rgb[i] = color;
|
||||
}
|
||||
SDL_Rect r;
|
||||
r.y = 0;
|
||||
r.w = w;
|
||||
r.h = h;
|
||||
// left border
|
||||
r.x = 0;
|
||||
SDL_UpdateTexture(_widescreenTexture, &r, rgb, w * sizeof(uint32_t));
|
||||
// right border
|
||||
r.x = _screenW + w;
|
||||
SDL_UpdateTexture(_widescreenTexture, &r, rgb, w * sizeof(uint32_t));
|
||||
free(rgb);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStub_SDL::clearWidescreen() {
|
||||
clearTexture(_widescreenTexture, _screenH, _fmt);
|
||||
}
|
||||
|
@ -983,7 +1006,9 @@ void SystemStub_SDL::prepareGraphics() {
|
|||
_widescreenMode = kWidescreenNone;
|
||||
}
|
||||
}
|
||||
if (_widescreenMode != kWidescreenNone) {
|
||||
if (_widescreenMode == kWidescreenCDi) {
|
||||
windowW = (_screenW + kWidescreenBorderCDiW * 2) * _scaleFactor;
|
||||
} else if (_widescreenMode != kWidescreenNone) {
|
||||
windowW = windowH * 16 / 9;
|
||||
}
|
||||
_window = SDL_CreateWindow(_caption, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowW, windowH, flags);
|
||||
|
@ -996,11 +1021,15 @@ void SystemStub_SDL::prepareGraphics() {
|
|||
SDL_RenderSetLogicalSize(_renderer, windowW, windowH);
|
||||
_texture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, _texW, _texH);
|
||||
if (_widescreenMode != kWidescreenNone) {
|
||||
int w = _screenH * 16 / 9;
|
||||
// in blur mode, the background texture has the same dimensions as the game texture
|
||||
// SDL stretches the texture to 16:9
|
||||
const int w = (_widescreenMode == kWidescreenBlur) ? _screenW : _screenH * 16 / 9;
|
||||
const int h = _screenH;
|
||||
_widescreenTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, h);
|
||||
if (_widescreenMode == kWidescreenBlur) {
|
||||
w = _screenW;
|
||||
} else if (_widescreenMode == kWidescreenCDi) {
|
||||
w = _screenW + kWidescreenBorderCDiW * 2;
|
||||
}
|
||||
_widescreenTexture = SDL_CreateTexture(_renderer, kPixelFormat, SDL_TEXTUREACCESS_STREAMING, w, _screenH);
|
||||
clearTexture(_widescreenTexture, _screenH, _fmt);
|
||||
|
||||
// left and right borders
|
||||
|
@ -1053,7 +1082,7 @@ void SystemStub_SDL::setScaler(const ScalerParameters *parameters) {
|
|||
{ "tv2x", kScalerTypeInternal, &scaler_tv2x },
|
||||
{ "xbr", kScalerTypeInternal, &scaler_xbr },
|
||||
#endif
|
||||
{ 0, -1 }
|
||||
{ 0, -1, 0 }
|
||||
};
|
||||
bool found = false;
|
||||
for (int i = 0; scalers[i].name; ++i) {
|
||||
|
|
22
unpack.cpp
22
unpack.cpp
|
@ -15,13 +15,13 @@ struct UnpackCtx {
|
|||
const uint8_t *src;
|
||||
};
|
||||
|
||||
static bool nextBit(UnpackCtx *uc) {
|
||||
bool bit = (uc->bits & 1) != 0;
|
||||
static int nextBit(UnpackCtx *uc) {
|
||||
int bit = (uc->bits & 1);
|
||||
uc->bits >>= 1;
|
||||
if (uc->bits == 0) { // getnextlwd
|
||||
const uint32_t bits = READ_BE_UINT32(uc->src); uc->src -= 4;
|
||||
uc->crc ^= bits;
|
||||
bit = (bits & 1) != 0;
|
||||
bit = (bits & 1);
|
||||
uc->bits = (1 << 31) | (bits >> 1);
|
||||
}
|
||||
return bit;
|
||||
|
@ -30,10 +30,8 @@ static bool nextBit(UnpackCtx *uc) {
|
|||
template<int count>
|
||||
static uint32_t getBits(UnpackCtx *uc) { // rdd1bits
|
||||
uint32_t bits = 0;
|
||||
for (uint32_t mask = 1 << (count - 1); mask != 0; mask >>= 1) {
|
||||
if (nextBit(uc)) {
|
||||
bits |= mask;
|
||||
}
|
||||
for (int i = 0; i < count; ++i) {
|
||||
bits = (bits << 1) | nextBit(uc);
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
@ -44,10 +42,9 @@ static void copyLiteral(UnpackCtx *uc, int len) { // getd3chr
|
|||
len += uc->size;
|
||||
uc->size = 0;
|
||||
}
|
||||
for (int i = 0; i < len; ++i) {
|
||||
*(uc->dst - i) = (uint8_t)getBits<8>(uc);
|
||||
for (int i = 0; i < len; ++i, --uc->dst) {
|
||||
*(uc->dst) = (uint8_t)getBits<8>(uc);
|
||||
}
|
||||
uc->dst -= len;
|
||||
}
|
||||
|
||||
static void copyReference(UnpackCtx *uc, int len, int offset) { // copyd3bytes
|
||||
|
@ -56,10 +53,9 @@ static void copyReference(UnpackCtx *uc, int len, int offset) { // copyd3bytes
|
|||
len += uc->size;
|
||||
uc->size = 0;
|
||||
}
|
||||
for (int i = 0; i < len; ++i) {
|
||||
*(uc->dst - i) = *(uc->dst - i + offset);
|
||||
for (int i = 0; i < len; ++i, --uc->dst) {
|
||||
*(uc->dst) = *(uc->dst + offset);
|
||||
}
|
||||
uc->dst -= len;
|
||||
}
|
||||
|
||||
bool bytekiller_unpack(uint8_t *dst, int dstSize, const uint8_t *src, int srcSize) {
|
||||
|
|
3
util.h
3
util.h
|
@ -23,7 +23,8 @@ enum {
|
|||
DBG_MOD = 1 << 10,
|
||||
DBG_SFX = 1 << 11,
|
||||
DBG_FILE = 1 << 12,
|
||||
DBG_DEMO = 1 << 13
|
||||
DBG_DEMO = 1 << 13,
|
||||
DBG_PRF = 1 << 14
|
||||
};
|
||||
|
||||
extern uint16_t g_debugMask;
|
||||
|
|
21
video.cpp
21
video.cpp
|
@ -121,6 +121,7 @@ void Video::updateWidescreen() {
|
|||
_stub->copyWidescreenMirror(_w, _h, _backLayer);
|
||||
} else if (_widescreenMode == kWidescreenBlur) {
|
||||
_stub->copyWidescreenBlur(_w, _h, _backLayer);
|
||||
} else if (_widescreenMode == kWidescreenCDi) {
|
||||
} else {
|
||||
_stub->clearWidescreen();
|
||||
}
|
||||
|
@ -498,14 +499,14 @@ static void PC_drawTileMask(uint8_t *dst, int x0, int y0, int w, int h, uint8_t
|
|||
}
|
||||
}
|
||||
|
||||
static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, const bool isAmiga) {
|
||||
static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, const bool isAmiga, int level, int room) {
|
||||
int num = -1;
|
||||
uint8_t buf[256 * 32];
|
||||
int count = READ_BE_UINT16(src) - 1; src += 2;
|
||||
do {
|
||||
int d2 = READ_BE_UINT16(src); src += 2;
|
||||
const int d0 = (int16_t)READ_BE_UINT16(src); src += 2;
|
||||
const int d1 = (int16_t)READ_BE_UINT16(src); src += 2;
|
||||
int x_pos = (int16_t)READ_BE_UINT16(src); src += 2;
|
||||
int y_pos = (int16_t)READ_BE_UINT16(src); src += 2;
|
||||
if (d2 != 0xFFFF) {
|
||||
d2 &= ~(1 << 15);
|
||||
const int32_t offset = READ_BE_UINT32(data + d2 * 4);
|
||||
|
@ -526,13 +527,16 @@ static void decodeSgd(uint8_t *dst, const uint8_t *src, const uint8_t *data, con
|
|||
}
|
||||
}
|
||||
}
|
||||
if (level == 0 && room == 26 && d2 == 38 && y_pos == 176) {
|
||||
y_pos += 8;
|
||||
}
|
||||
const int w = (buf[0] + 1) >> 1;
|
||||
const int h = buf[1] + 1;
|
||||
const int planarSize = READ_BE_UINT16(buf + 2);
|
||||
if (isAmiga) {
|
||||
AMIGA_planar_mask(dst, d0, d1, w, h, buf + 4, buf + 4 + planarSize, planarSize);
|
||||
AMIGA_planar_mask(dst, x_pos, y_pos, w, h, buf + 4, buf + 4 + planarSize, planarSize);
|
||||
} else {
|
||||
PC_drawTileMask(dst, d0, d1, w, h, buf + 4, buf + 4 + planarSize, planarSize);
|
||||
PC_drawTileMask(dst, x_pos, y_pos, w, h, buf + 4, buf + 4 + planarSize, planarSize);
|
||||
}
|
||||
} while (--count >= 0);
|
||||
}
|
||||
|
@ -648,7 +652,7 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int
|
|||
const int d3 = isPC ? READ_LE_UINT16(a0) : READ_BE_UINT16(a0); a0 += 2;
|
||||
int d0 = d3 & 0x7FF;
|
||||
if (d0 != 0 && sgdBuf) {
|
||||
d0 -= 896;
|
||||
d0 -= 0x380;
|
||||
}
|
||||
if (d0 != 0) {
|
||||
const uint8_t *a2 = a5 + d0 * 32;
|
||||
|
@ -659,6 +663,9 @@ static void decodeLevHelper(uint8_t *dst, const uint8_t *src, int offset10, int
|
|||
mask = 0x10;
|
||||
} else if ((d3 & 0x8000) != 0) {
|
||||
mask = 0x80 + ((d3 >> 6) & 0x10);
|
||||
if (d3 & 0x4000) {
|
||||
mask = 0x90;
|
||||
}
|
||||
}
|
||||
if (isPC) {
|
||||
PC_drawTile(dst + y * 256 + x, a2, mask, xflip, yflip, 0);
|
||||
|
@ -718,7 +725,7 @@ void Video::AMIGA_decodeLev(int level, int room) {
|
|||
memset(_frontLayer, 0, _layerSize);
|
||||
if (tmp[1] != 0) {
|
||||
assert(_res->_sgd);
|
||||
decodeSgd(_frontLayer, tmp + offset10, _res->_sgd, _res->isAmiga());
|
||||
decodeSgd(_frontLayer, tmp + offset10, _res->_sgd, _res->isAmiga(), level, room);
|
||||
offset10 = 0;
|
||||
}
|
||||
decodeLevHelper(_frontLayer, tmp, offset10, offset12, buf, tmp[1] != 0, _res->isDOS());
|
||||
|
|
Loading…
Reference in New Issue