From 1f86fdea2d5a961d867f10e97b76fbdc7a125fda Mon Sep 17 00:00:00 2001 From: Gregory Montoir Date: Fri, 31 Mar 2023 21:23:51 +0800 Subject: [PATCH] Import 0.5.0 --- CHANGES.txt | 6 +- Makefile | 19 +- README.txt | 41 ++-- collision.cpp | 2 +- file.cpp | 17 ++ file.h | 1 + game.cpp | 56 ++++-- game.h | 2 +- intern.h | 10 +- main.cpp | 25 ++- midi_driver.h | 41 ++++ midi_driver_adlib.cpp | 369 ++++++++++++++++++++++++++++++++++++ midi_driver_mt32.cpp | 87 +++++++++ midi_parser.cpp | 90 +++++++++ midi_parser.h | 37 ++++ mixer.cpp | 19 +- mixer.h | 5 +- piege.cpp | 4 +- prf_player.cpp | 421 ++++++++++++++++++++++++++++++++++++++++++ prf_player.h | 121 ++++++++++++ resource.cpp | 10 +- resource.h | 4 +- rs.cfg | 3 + staticres.cpp | 31 ++++ systemstub.h | 1 + systemstub_sdl.cpp | 39 +++- unpack.cpp | 22 +-- util.h | 3 +- video.cpp | 21 ++- 29 files changed, 1426 insertions(+), 81 deletions(-) create mode 100644 midi_driver.h create mode 100644 midi_driver_adlib.cpp create mode 100644 midi_driver_mt32.cpp create mode 100644 midi_parser.cpp create mode 100644 midi_parser.h create mode 100644 prf_player.cpp create mode 100644 prf_player.h diff --git a/CHANGES.txt b/CHANGES.txt index de31d51..3b083a5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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 diff --git a/Makefile b/Makefile index 18fb80d..25712f2 100644 --- a/Makefile +++ b/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) diff --git a/README.txt b/README.txt index 4d09469..b430d7a 100644 --- a/README.txt +++ b/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 diff --git a/collision.cpp b/collision.cpp index aabd86d..617bc17 100644 --- a/collision.cpp +++ b/collision.cpp @@ -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; diff --git a/file.cpp b/file.cpp index 4942d83..bf958be 100644 --- a/file.cpp +++ b/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); } diff --git a/file.h b/file.h index 1876cd4..3e26646 100644 --- a/file.h +++ b/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(); diff --git a/game.cpp b/game.cpp index 5f13110..4ea05e8 100644 --- a/game.cpp +++ b/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) { diff --git a/game.h b/game.h index c526c3f..908ef2d 100644 --- a/game.h +++ b/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(); diff --git a/intern.h b/intern.h index 82e2fee..a0fd2c0 100644 --- a/intern.h +++ b/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 { diff --git a/main.cpp b/main.cpp index e8bfd83..e54295d 100644 --- a/main.cpp +++ b/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; diff --git a/midi_driver.h b/midi_driver.h new file mode 100644 index 0000000..614413c --- /dev/null +++ b/midi_driver.h @@ -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__ */ diff --git a/midi_driver_adlib.cpp b/midi_driver_adlib.cpp new file mode 100644 index 0000000..2babaab --- /dev/null +++ b/midi_driver_adlib.cpp @@ -0,0 +1,369 @@ + +#include +#include +#include +#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 +}; diff --git a/midi_driver_mt32.cpp b/midi_driver_mt32.cpp new file mode 100644 index 0000000..0b61d12 --- /dev/null +++ b/midi_driver_mt32.cpp @@ -0,0 +1,87 @@ + +#include +#include +#include "midi_driver.h" + +#define MT32EMU_API_TYPE 1 +#include + +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 +}; diff --git a/midi_parser.cpp b/midi_parser.cpp new file mode 100644 index 0000000..272cbef --- /dev/null +++ b/midi_parser.cpp @@ -0,0 +1,90 @@ + +#include +#include +#include +#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(); + } + _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; +} diff --git a/midi_parser.h b/midi_parser.h new file mode 100644 index 0000000..f3f1690 --- /dev/null +++ b/midi_parser.h @@ -0,0 +1,37 @@ + +#ifndef MIDI_PARSER_H__ +#define MIDI_PARSER_H__ + +#include +#include + +#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 events; +}; + +struct File; + +#define MAX_TRACKS 16 + +struct MidiParser { + void loadMid(File *f); + + int _tracksCount; + MidiTrack _tracks[MAX_TRACKS]; +}; + +#endif /* MIDI_PARSER_H__ */ diff --git a/mixer.cpp b/mixer.cpp index 25278c1..bd4f846 100644 --- a/mixer.cpp +++ b/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; diff --git a/mixer.h b/mixer.h index 63d0834..664a7eb 100644 --- a/mixer.h +++ b/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); diff --git a/piege.cpp b/piege.cpp index 3ee9f8d..a353880 100644 --- a/piege.cpp +++ b/piege.cpp @@ -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; diff --git a/prf_player.cpp b/prf_player.cpp new file mode 100644 index 0000000..a117a34 --- /dev/null +++ b/prf_player.cpp @@ -0,0 +1,421 @@ + +#include +#include +#include +#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; +} diff --git a/prf_player.h b/prf_player.h new file mode 100644 index 0000000..a6a4333 --- /dev/null +++ b/prf_player.h @@ -0,0 +1,121 @@ + +#ifndef PRF_PLAYER_H__ +#define PRF_PLAYER_H__ + +#include +#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__ */ diff --git a/resource.cpp b/resource.cpp index 1a25940..de0faea 100644 --- a/resource.cpp +++ b/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]; diff --git a/resource.h b/resource.h index 35d63da..60f6009 100644 --- a/resource.h +++ b/resource.h @@ -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 diff --git a/rs.cfg b/rs.cfg index 2e90fd8..28706a9 100644 --- a/rs.cfg +++ b/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 diff --git a/staticres.cpp b/staticres.cpp index 4da0d96..b797ea0 100644 --- a/staticres.cpp +++ b/staticres.cpp @@ -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); diff --git a/systemstub.h b/systemstub.h index cb8af31..ba75087 100644 --- a/systemstub.h +++ b/systemstub.h @@ -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; diff --git a/systemstub_sdl.cpp b/systemstub_sdl.cpp index 19df072..b60f8d3 100644 --- a/systemstub_sdl.cpp +++ b/systemstub_sdl.cpp @@ -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) { diff --git a/unpack.cpp b/unpack.cpp index 1b10db8..a272305 100644 --- a/unpack.cpp +++ b/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 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) { diff --git a/util.h b/util.h index 2eca991..ccca611 100644 --- a/util.h +++ b/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; diff --git a/video.cpp b/video.cpp index 92e1afe..f310093 100644 --- a/video.cpp +++ b/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());