diff options
Diffstat (limited to 'engines/scumm')
47 files changed, 2227 insertions, 942 deletions
diff --git a/engines/scumm/actor.cpp b/engines/scumm/actor.cpp index e4057d1f13..20b929dfd4 100644 --- a/engines/scumm/actor.cpp +++ b/engines/scumm/actor.cpp @@ -1066,7 +1066,7 @@ static int checkXYInBoxBounds(int boxnum, int x, int y, int &destX, int &destY) int dist; // MM C64: This fixes the trunk bug (#3070065), as well - // as the fruit bowl, however im not sure if its + // as the fruit bowl, however im not sure if its // the proper solution or not. if( g_scumm->_game.version == 0 ) yDist = ABS(y - destY); diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp index dce6c9c144..8558da397e 100644 --- a/engines/scumm/charset.cpp +++ b/engines/scumm/charset.cpp @@ -49,34 +49,28 @@ void ScummEngine::loadCJKFont() { _newLineCharacter = 0; if (_game.version <= 5 && _game.platform == Common::kPlatformFMTowns && _language == Common::JA_JPN) { // FM-TOWNS v3 / v5 Kanji -#ifdef DISABLE_TOWNS_DUAL_LAYER_MODE +#if defined(DISABLE_TOWNS_DUAL_LAYER_MODE) || !defined(USE_RGB_COLOR) + GUIErrorMessage("FM-Towns Kanji font drawing requires dual graphics layer support which is disabled in this build"); error("FM-Towns Kanji font drawing requires dual graphics layer support which is disabled in this build"); #else // use FM-TOWNS font rom, since game files don't have kanji font resources - _cjkFont = Graphics::FontSJIS::createFont(Common::kPlatformFMTowns); + _cjkFont = Graphics::FontSJIS::createFont(_game.platform); if (!_cjkFont) error("SCUMM::Font: Could not open file 'FMT_FNT.ROM'"); _textSurfaceMultiplier = 2; _useCJKMode = true; #endif } else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine && _language == Common::JA_JPN) { - int numChar = 3418; - _2byteWidth = 12; - _2byteHeight = 12; +#ifdef USE_RGB_COLOR // use PC-Engine System Card, since game files don't have kanji font resources - if (!fp.open("pce.cdbios")) { - error("SCUMM::Font: Could not open System Card pce.cdbios"); - } else { - _useCJKMode = true; - debug(2, "Loading PC-Engine System Card"); - - // A 0x200 byte header can be present at the beginning of the syscard. Seek past it too. - fp.seek((fp.size() & 0x200) ? 0x30200 : 0x30000); + _cjkFont = Graphics::FontSJIS::createFont(_game.platform); + if (!_cjkFont) + error("SCUMM::Font: Could not open file 'pce.cdbios'"); - _2byteFontPtr = new byte[_2byteWidth * _2byteHeight * numChar / 8]; - fp.read(_2byteFontPtr, _2byteWidth * _2byteHeight * numChar / 8); - fp.close(); - } + _cjkFont->setDrawingMode(Graphics::FontSJIS::kShadowMode); + _2byteWidth = _2byteHeight = 12; + _useCJKMode = true; +#endif } else if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) { int numChar = 1413; _2byteWidth = 16; @@ -160,71 +154,16 @@ void ScummEngine::loadCJKFont() { } } -static int SJIStoPCEChunk(int f, int s) { //converts sjis code to pce font offset - // rangeTbl maps SJIS char-codes to the PCE System Card font rom. - // Each pair {<upperBound>,<lowerBound>} in the array represents a SJIS range. - const int rangeCnt = 45; - static const uint16 rangeTbl[rangeCnt][2] = { - // Symbols - {0x8140,0x817E},{0x8180,0x81AC}, - // 0-9 - {0x824F,0x8258}, - // Latin upper - {0x8260,0x8279}, - // Latin lower - {0x8281,0x829A}, - // Kana - {0x829F,0x82F1},{0x8340,0x837E},{0x8380,0x8396}, - // Greek upper - {0x839F,0x83B6}, - // Greek lower - {0x83BF,0x83D6}, - // Cyrillic upper - {0x8440,0x8460}, - // Cyrillic lower - {0x8470,0x847E},{0x8480,0x8491}, - // Kanji - {0x889F,0x88FC}, - {0x8940,0x897E},{0x8980,0x89FC}, - {0x8A40,0x8A7E},{0x8A80,0x8AFC}, - {0x8B40,0x8B7E},{0x8B80,0x8BFC}, - {0x8C40,0x8C7E},{0x8C80,0x8CFC}, - {0x8D40,0x8D7E},{0x8D80,0x8DFC}, - {0x8E40,0x8E7E},{0x8E80,0x8EFC}, - {0x8F40,0x8F7E},{0x8F80,0x8FFC}, - {0x9040,0x907E},{0x9080,0x90FC}, - {0x9140,0x917E},{0x9180,0x91FC}, - {0x9240,0x927E},{0x9280,0x92FC}, - {0x9340,0x937E},{0x9380,0x93FC}, - {0x9440,0x947E},{0x9480,0x94FC}, - {0x9540,0x957E},{0x9580,0x95FC}, - {0x9640,0x967E},{0x9680,0x96FC}, - {0x9740,0x977E},{0x9780,0x97FC}, - {0x9840,0x9872} - }; - - int ch = (f << 8) | (s & 0xFF); - int offset = 0; - for (int i = 0; i < rangeCnt; ++i) { - if (ch >= rangeTbl[i][0] && ch <= rangeTbl[i][1]) - return offset + ch - rangeTbl[i][0]; - offset += rangeTbl[i][1] - rangeTbl[i][0] + 1; - } - - debug(4, "Invalid Char: 0x%x", ch); - return 0; -} - byte *ScummEngine::get2byteCharPtr(int idx) { + if (_game.platform == Common::kPlatformFMTowns || _game.platform == Common::kPlatformPCEngine) + return 0; + switch (_language) { case Common::KO_KOR: idx = ((idx % 256) - 0xb0) * 94 + (idx / 256) - 0xa1; break; case Common::JA_JPN: - if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine) { - idx = SJIStoPCEChunk((idx % 256), (idx / 256)); - return _2byteFontPtr + (_2byteWidth * _2byteHeight / 8) * idx; - } else if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) { + if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) { // init pointer to charset resource if (_2byteFontPtr[0] == 0xFF) { int charsetId = 5; @@ -313,7 +252,7 @@ CharsetRenderer::~CharsetRenderer() { CharsetRendererCommon::CharsetRendererCommon(ScummEngine *vm) : CharsetRenderer(vm), _bytesPerPixel(0), _fontHeight(0), _numChars(0) { - _shadowMode = kNoShadowMode; + _shadowMode = false; _shadowColor = 0; } @@ -361,17 +300,9 @@ void CharsetRendererV3::setCurID(int32 id) { } int CharsetRendererCommon::getFontHeight() { - if (_vm->_useCJKMode) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - static const uint8 sjisFontHeightM1[] = { 0, 8, 9, 8, 9, 8, 9, 0, 0, 0 }; - static const uint8 sjisFontHeightM2[] = { 0, 8, 9, 9, 9, 8, 9, 9, 9, 8 }; - static const uint8 sjisFontHeightI4[] = { 0, 8, 9, 9, 9, 8, 8, 8, 8, 8 }; - const uint8 *htbl = (_vm->_game.id == GID_MONKEY) ? sjisFontHeightM1 : ((_vm->_game.id == GID_INDY4) ? sjisFontHeightI4 : sjisFontHeightM2); - return (_vm->_game.version == 3) ? 8 : htbl[_curId]; - } else { - return MAX(_vm->_2byteHeight + 1, _fontHeight); - } - } else + if (_vm->_useCJKMode) + return MAX(_vm->_2byteHeight + 1, _fontHeight); + else return _fontHeight; } @@ -379,57 +310,16 @@ int CharsetRendererCommon::getFontHeight() { int CharsetRendererClassic::getCharWidth(uint16 chr) { int spacing = 0; - if (_vm->_useCJKMode) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - if ((chr & 0xff00) == 0xfd00) { - chr &= 0xff; - } else if (chr >= 256) { - spacing = 8; - } else if (useTownsFontRomCharacter(chr)) { - spacing = 4; - } + if (_vm->_useCJKMode && chr >= 0x80) + return _vm->_2byteWidth / 2; - if (spacing) { - if (_vm->_game.id == GID_MONKEY) { - spacing++; - if (_curId == 2) - spacing++; - } else if (_vm->_game.id != GID_INDY4 && _curId == 1) { - spacing++; - } - } - - } else if (chr >= 0x80) { - return _vm->_2byteWidth / 2; - } - } - - if (!spacing) { - int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); - if (offs) { - spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2]; - } - } + int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); + if (offs) + spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2]; return spacing; } -bool CharsetRendererClassic::useTownsFontRomCharacter(uint16 chr) { -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_game.platform != Common::kPlatformFMTowns || !_vm->_useCJKMode) - return false; - - if (chr < 128) { - if (((_vm->_game.id == GID_MONKEY2 && _curId != 0) || (_vm->_game.id == GID_INDY4 && _curId != 3)) && (chr > 31 && chr != 94 && chr != 95 && chr != 126 && chr != 127)) - return true; - return false; - } - return true; -#else - return false; -#endif -} - int CharsetRenderer::getStringWidth(int arg, const byte *text) { int pos = 0; int width = 1; @@ -607,22 +497,51 @@ void CharsetRenderer::addLinebreaks(int a, byte *str, int pos, int maxwidth) { int CharsetRendererV3::getCharWidth(uint16 chr) { int spacing = 0; - if (_vm->_useCJKMode) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - if (chr >= 256) - spacing = 8; - else if (chr >= 128) - spacing = 4; - } else if (chr & 0x80) { - spacing = _vm->_2byteWidth / 2; + if (_vm->_useCJKMode && (chr & 0x80)) + spacing = _vm->_2byteWidth / 2; + + if (!spacing) + spacing = *(_widthTable + chr); + + return spacing; +} + +void CharsetRendererV3::enableShadow(bool enable) { + _shadowColor = 0; + _shadowMode = enable; +} + +void CharsetRendererV3::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { + int y, x; + byte bits = 0; + uint8 col = _color; + int pitch = s.pitch - width * bitDepth; + byte *dst2 = dst + s.pitch; + + for (y = 0; y < height && y + drawTop < s.h; y++) { + for (x = 0; x < width; x++) { + if ((x % 8) == 0) + bits = *src++; + if ((bits & revBitMask(x % 8)) && y + drawTop >= 0) { + if (_shadowMode) + dst[1] = dst2[0] = dst2[1] = _shadowColor; + dst[0] = col; + } + dst += bitDepth; + dst2 += bitDepth; } - } - if (!spacing) { - spacing = *(_widthTable + chr); + dst += pitch; + dst2 += pitch; } +} - return spacing; +int CharsetRendererV3::getDrawWidthIntern(uint16 chr) { + return getCharWidth(chr); +} + +int CharsetRendererV3::getDrawHeightIntern(uint16) { + return 8; } void CharsetRendererV3::setColor(byte color) { @@ -661,43 +580,6 @@ void CharsetRendererPCE::setColor(byte color) { } #endif -void CharsetRendererCommon::enableShadow(bool enable) { - if (enable) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - _shadowColor = 8; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - _shadowColor = _vm->_game.version == 5 ? _vm->_townsCharsetColorMap[0] : 0x88; - if (_vm->_cjkFont) { - if (_vm->_game.version == 5) { - if (((_vm->_game.id == GID_MONKEY) && (_curId == 2 || _curId == 4 || _curId == 6)) || - ((_vm->_game.id == GID_MONKEY2) && (_curId != 1 && _curId != 5 && _curId != 9)) || - ((_vm->_game.id == GID_INDY4) && (_curId == 2 || _curId == 3 || _curId == 4))) { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kOutlineMode); - } else { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kDefaultMode); - } - _vm->_cjkFont->toggleFlippedMode((_vm->_game.id == GID_MONKEY || _vm->_game.id == GID_MONKEY2) && _curId == 3); - } else { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kShadowMode); - } - } -#endif - _shadowMode = kFMTOWNSShadowMode; - } else { - _shadowColor = 0; - _shadowMode = kNormalShadowMode; - } - } else { -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_cjkFont) { - _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kDefaultMode); - _vm->_cjkFont->toggleFlippedMode(false); - } -#endif - _shadowMode = kNoShadowMode; - } -} - void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { // WORKAROUND for bug #1509509: Indy3 Mac does not show black // characters (such as in the grail diary) if ignoreCharsetMask @@ -720,33 +602,19 @@ void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { if (chr == '@') return; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_useCJKMode && chr > 127) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - charPtr = 0; - width = _vm->_cjkFont->getCharWidth(chr); - height = _vm->_cjkFont->getFontHeight(); - } else { - width = _vm->_2byteWidth; - height = _vm->_2byteHeight; - charPtr = _vm->get2byteCharPtr(chr); - } - } else -#endif - { - charPtr = _fontPtr + chr * 8; - width = getCharWidth(chr); - height = 8; - } + charPtr = (_vm->_useCJKMode && chr > 127) ? _vm->get2byteCharPtr(chr) : _fontPtr + chr * 8; + width = getDrawWidthIntern(chr); + height = getDrawHeightIntern(chr); + setDrawCharIntern(chr); + + origWidth = width; + origHeight = height; // Clip at the right side (to avoid drawing "outside" the screen bounds). if (_left + origWidth > _right + 1) return; - origWidth = width; - origHeight = height; - - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) { width++; height++; } @@ -768,28 +636,17 @@ void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { _textScreenID = vs->number; } - if ( -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - (_vm->_game.platform != Common::kPlatformFMTowns) && -#endif - (ignoreCharsetMask || !vs->hasTwoBuffers)) { + if ((ignoreCharsetMask || !vs->hasTwoBuffers)) { dst = vs->getPixels(_left, drawTop); - if (charPtr) - drawBits1(*vs, dst, charPtr, drawTop, origWidth, origHeight, vs->format.bytesPerPixel); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - else if (_vm->_cjkFont) - _vm->_cjkFont->drawChar(vs, chr, _left, drawTop, _color, _shadowColor); -#endif + drawBits1(*vs, dst, charPtr, drawTop, origWidth, origHeight, vs->format.bytesPerPixel); } else { dst = (byte *)_vm->_textSurface.getBasePtr(_left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier); - if (charPtr) - drawBits1(_vm->_textSurface, dst, charPtr, drawTop, origWidth, origHeight, _vm->_textSurface.format.bytesPerPixel, (_vm->_textSurfaceMultiplier == 2 && !is2byte)); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - else if (_vm->_cjkFont) - _vm->_cjkFont->drawChar(_vm->_textSurface, chr, _left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier, _color, _shadowColor); -#endif - if (is2byte) - origWidth /= _vm->_textSurfaceMultiplier; + drawBits1(_vm->_textSurface, dst, charPtr, drawTop, origWidth, origHeight, _vm->_textSurface.format.bytesPerPixel); + } + + if (is2byte) { + origWidth /= _vm->_textSurfaceMultiplier; + height /= _vm->_textSurfaceMultiplier; } if (_str.left > _left) @@ -799,39 +656,21 @@ void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) { if (_str.right < _left) { _str.right = _left; - if (_shadowMode != kNoShadowMode) + if (_shadowMode) _str.right++; } - if (_str.bottom < _top + height / _vm->_textSurfaceMultiplier) - _str.bottom = _top + height / _vm->_textSurfaceMultiplier; + if (_str.bottom < _top + height) + _str.bottom = _top + height; } void CharsetRendererV3::drawChar(int chr, Graphics::Surface &s, int x, int y) { - const byte *charPtr; - byte *dst; - int width, height; - int is2byte = (chr >= 0x80 && _vm->_useCJKMode) ? 1 : 0; - if (is2byte) { -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_game.platform == Common::kPlatformFMTowns) { - _vm->_cjkFont->drawChar(s, chr, x * _vm->_textSurfaceMultiplier, y * _vm->_textSurfaceMultiplier, _color, _shadowColor); - return; - } - else -#endif - { - charPtr = _vm->get2byteCharPtr(chr); - width = _vm->_2byteWidth; - height = _vm->_2byteHeight; - } - } else { - charPtr = _fontPtr + chr * 8; -// width = height = 8; - width = getCharWidth(chr); - height = 8; - } - dst = (byte *)s.pixels + y * s.pitch + x; + const byte *charPtr = (_vm->_useCJKMode && chr > 127) ? _vm->get2byteCharPtr(chr) : _fontPtr + chr * 8; + int width = getDrawWidthIntern(chr); + int height = getDrawHeightIntern(chr); + setDrawCharIntern(chr); + + byte *dst = (byte *)s.pixels + y * s.pitch + x; drawBits1(s, dst, charPtr, y, width, height, s.format.bytesPerPixel); } @@ -850,29 +689,6 @@ void CharsetRenderer::translateColor() { } } -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE -void CharsetRenderer::processTownsCharsetColors(uint8 bytesPerPixel) { - if (_vm->_game.platform == Common::kPlatformFMTowns) { - for (int i = 0; i < (1 << bytesPerPixel); i++) { - uint8 c = _vm->_charsetColorMap[i]; - - if (c > 16) { - uint8 t = (_vm->_currentPalette[c * 3] < 32) ? 4 : 12; - t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 2 : 10); - t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 1 : 9); - c = t; - } - - if (c == 0) - c = _vm->_townsOverrideShadowColor; - - c = ((c & 0x0f) << 4) | (c & 0x0f); - _vm->_townsCharsetColorMap[i] = c; - } - } -} -#endif - void CharsetRenderer::saveLoadWithSerializer(Serializer *ser) { static const SaveLoadEntry charsetRendererEntries[] = { MKLINE_OLD(CharsetRenderer, _curId, sleByte, VER(73), VER(73)), @@ -890,10 +706,7 @@ void CharsetRenderer::saveLoadWithSerializer(Serializer *ser) { } void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { - int width, height, origWidth, origHeight; - int offsX, offsY; VirtScreen *vs; - const byte *charPtr; bool is2byte = (chr >= 256 && _vm->_useCJKMode); assertRange(1, _curId, _vm->_numCharsets - 1, "charset"); @@ -908,64 +721,8 @@ void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { _vm->_charsetColorMap[1] = _color; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - processTownsCharsetColors(_bytesPerPixel); - bool noSjis = false; - - if (_vm->_game.platform == Common::kPlatformFMTowns && _vm->_useCJKMode) { - if ((chr & 0x00ff) == 0x00fd) { - chr >>= 8; - noSjis = true; - } - } - - if (useTownsFontRomCharacter(chr) && !noSjis) { - charPtr = 0; - _vm->_cjkChar = chr; - enableShadow(true); - - width = getCharWidth(chr); - // For whatever reason MI1 uses a different font width - // for alignment calculation and for drawing when - // charset 2 is active. This fixes some subtle glitches. - if (_vm->_game.id == GID_MONKEY && _curId == 2) - width--; - origWidth = width; - - origHeight = height = getFontHeight(); - offsX = offsY = 0; - } else if (_vm->_useCJKMode && (chr >= 128) && !noSjis) { - enableShadow(true); - origWidth = width = _vm->_2byteWidth; - origHeight = height = _vm->_2byteHeight; - charPtr = _vm->get2byteCharPtr(chr); - offsX = offsY = 0; - if (_shadowMode != kNoShadowMode) { - width++; - height++; - } - } else -#endif - { - uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); - assert(charOffs < 0x14000); - if (!charOffs) - return; - charPtr = _fontPtr + charOffs; - - width = origWidth = charPtr[0]; - height = origHeight = charPtr[1]; - - if (_disableOffsX) { - offsX = 0; - } else { - offsX = (signed char)charPtr[2]; - } - - offsY = (signed char)charPtr[3]; - - charPtr += 4; // Skip over char header - } + if (!prepareDraw(chr)) + return; if (_firstChar) { _str.left = 0; @@ -974,12 +731,12 @@ void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { _str.bottom = 0; } - _top += offsY; - _left += offsX; + _top += _offsY; + _left += _offsX; - if (_left + origWidth > _right + 1 || _left < 0) { - _left += origWidth; - _top -= offsY; + if (_left + _origWidth > _right + 1 || _left < 0) { + _left += _origWidth; + _top -= _offsY; return; } @@ -1001,33 +758,29 @@ void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) { int drawTop = _top - vs->topline; - _vm->markRectAsDirty(vs->number, _left, _left + width, drawTop, drawTop + height); + _vm->markRectAsDirty(vs->number, _left, _left + _width, drawTop, drawTop + _height); // This check for kPlatformFMTowns and kMainVirtScreen is at least required for the chat with // the navigator's head in front of the ghost ship in Monkey Island 1 - if (!ignoreCharsetMask -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - || (_vm->_game.platform == Common::kPlatformFMTowns && vs->number == kMainVirtScreen) -#endif - ) { + if (!ignoreCharsetMask || (_vm->_game.platform == Common::kPlatformFMTowns && vs->number == kMainVirtScreen)) { _hasMask = true; _textScreenID = vs->number; } - printCharIntern(is2byte, charPtr, origWidth, origHeight, width, height, vs, ignoreCharsetMask); + printCharIntern(is2byte, _charPtr, _origWidth, _origHeight, _width, _height, vs, ignoreCharsetMask); - _left += origWidth; + _left += _origWidth; if (_str.right < _left) { _str.right = _left; - if (_vm->_game.platform != Common::kPlatformFMTowns && _shadowMode != kNoShadowMode) + if (_vm->_game.platform != Common::kPlatformFMTowns && _shadowMode) _str.right++; } - if (_str.bottom < _top + origHeight) - _str.bottom = _top + origHeight; + if (_str.bottom < _top + _origHeight) + _str.bottom = _top + _origHeight; - _top -= offsY; + _top -= _offsY; } void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, int origWidth, int origHeight, int width, int height, VirtScreen *vs, bool ignoreCharsetMask) { @@ -1065,11 +818,7 @@ void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, } else { Graphics::Surface dstSurface; Graphics::Surface backSurface; - if ((ignoreCharsetMask || !vs->hasTwoBuffers) -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - && (_vm->_game.platform != Common::kPlatformFMTowns) -#endif - ) { + if ((ignoreCharsetMask || !vs->hasTwoBuffers)) { dstSurface = *vs; dstPtr = vs->getPixels(_left, drawTop); } else { @@ -1088,16 +837,7 @@ void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, drawTop = _top - _vm->_screenTop; } -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (!charPtr && _vm->_cjkFont) { - _vm->_cjkFont->drawChar(dstSurface, _vm->_cjkChar, _left * _vm->_textSurfaceMultiplier, (_top - _vm->_screenTop) * _vm->_textSurfaceMultiplier, _vm->_townsCharsetColorMap[1], _shadowColor); - } else -#endif - if (is2byte) { - drawBits1(dstSurface, dstPtr, charPtr, drawTop, origWidth, origHeight, dstSurface.format.bytesPerPixel); - } else { - drawBitsN(dstSurface, dstPtr, charPtr, *_fontPtr, drawTop, origWidth, origHeight, _vm->_textSurfaceMultiplier == 2); - } + drawBitsN(dstSurface, dstPtr, charPtr, *_fontPtr, drawTop, origWidth, origHeight); if (_blitAlso && vs->hasTwoBuffers) { // FIXME: Revisiting this code, I think the _blitAlso mode is likely broken @@ -1136,54 +876,36 @@ void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, } } -void CharsetRendererClassic::drawChar(int chr, Graphics::Surface &s, int x, int y) { - const byte *charPtr; - byte *dst; - int width, height; - int is2byte = (chr >= 0x80 && _vm->_useCJKMode) ? 1 : 0; - - if (is2byte) { - enableShadow(true); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (_vm->_game.platform == Common::kPlatformFMTowns) { - _vm->_cjkFont->drawChar(s, chr, x * _vm->_textSurfaceMultiplier, y * _vm->_textSurfaceMultiplier, _color, _shadowColor); - return; - } else -#endif - { - charPtr = _vm->get2byteCharPtr(chr); - width = _vm->_2byteWidth; - height = _vm->_2byteHeight; - } - } else { - uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); - assert(charOffs < 0x10000); - if (!charOffs) - return; - charPtr = _fontPtr + charOffs; +bool CharsetRendererClassic::prepareDraw(uint16 chr) { + uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); + assert(charOffs < 0x14000); + if (!charOffs) + return false; + _charPtr = _fontPtr + charOffs; - width = charPtr[0]; - height = charPtr[1]; + _width = _origWidth = _charPtr[0]; + _height = _origHeight = _charPtr[1]; - charPtr += 4; // Skip over char header + if (_disableOffsX) { + _offsX = 0; + } else { + _offsX = (signed char)_charPtr[2]; } - dst = (byte *)s.pixels + y * s.pitch + x; + _offsY = (signed char)_charPtr[3]; - if (is2byte) { - drawBits1(s, dst, charPtr, y, width, height, s.format.bytesPerPixel); - } else { - drawBitsN(s, dst, charPtr, *_fontPtr, y, width, height); - } + _charPtr += 4; // Skip over char header + return true; } -void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height, -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - bool scale2x) { -#else - bool) { -#endif +void CharsetRendererClassic::drawChar(int chr, Graphics::Surface &s, int x, int y) { + if (!prepareDraw(chr)) + return; + byte *dst = (byte *)s.pixels + y * s.pitch + x; + drawBitsN(s, dst, _charPtr, *_fontPtr, y, _width, _height); +} +void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height) { int y, x; int color; byte numbits, bits; @@ -1195,38 +917,13 @@ void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, co numbits = 8; byte *cmap = _vm->_charsetColorMap; -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - byte *dst2 = dst; - - if (_vm->_game.platform == Common::kPlatformFMTowns) - cmap = _vm->_townsCharsetColorMap; - if (scale2x) { - dst2 += s.pitch; - pitch <<= 1; - } -#endif - for (y = 0; y < height && y + drawTop < s.h; y++) { for (x = 0; x < width; x++) { color = (bits >> (8 - bpp)) & 0xFF; - if (color && y + drawTop >= 0) { + if (color && y + drawTop >= 0) *dst = cmap[color]; - -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (scale2x) - dst[1] = dst2[0] = dst2[1] = dst[0]; -#endif - } dst++; - -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - if (scale2x) { - dst++; - dst2 += 2; - } -#endif - bits <<= bpp; numbits -= bpp; if (numbits == 0) { @@ -1235,52 +932,93 @@ void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, co } } dst += pitch; + } +} + +CharsetRendererTownsV3::CharsetRendererTownsV3(ScummEngine *vm) : CharsetRendererV3(vm), _sjisCurChar(0) { +} + +int CharsetRendererTownsV3::getCharWidth(uint16 chr) { + int spacing = 0; + + if (_vm->_useCJKMode) { + if (chr >= 256) + spacing = 8; + else if (chr >= 128) + spacing = 4; + } + + if (!spacing) + spacing = *(_widthTable + chr); + + return spacing; +} + +int CharsetRendererTownsV3::getFontHeight() { + return _vm->_useCJKMode ? 8 : _fontHeight; +} + +void CharsetRendererTownsV3::enableShadow(bool enable) { + _shadowColor = 8; + _shadowMode = enable; + #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - dst2 += pitch; + _shadowColor = 0x88; +#ifdef USE_RGB_COLOR + if (_vm->_cjkFont) + _vm->_cjkFont->setDrawingMode(enable ? Graphics::FontSJIS::kFMTownsShadowMode : Graphics::FontSJIS::kDefaultMode); +#endif #endif - } } -void CharsetRendererCommon::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, +void CharsetRendererTownsV3::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - bool scale2x) { +#ifdef USE_RGB_COLOR + if (_sjisCurChar) { + assert(_vm->_cjkFont); + _vm->_cjkFont->drawChar(_vm->_textSurface, _sjisCurChar, _left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier, _color, _shadowColor); + return; + } +#endif + + dst = (byte *)_vm->_textSurface.getBasePtr(_left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier); + int sfPitch = _vm->_textSurface.pitch; + int sfHeight = _vm->_textSurface.h; + bool scale2x = (_vm->_textSurfaceMultiplier == 2 && !(_sjisCurChar >= 256 && _vm->_useCJKMode)); #else - bool) { + int sfPitch = s.pitch; + int sfHeight = s.h; #endif int y, x; byte bits = 0; uint8 col = _color; - int pitch = s.pitch - width * bitDepth; - byte *dst2 = dst + s.pitch; + int pitch = sfPitch - width * bitDepth; + byte *dst2 = dst + sfPitch; #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE byte *dst3 = dst2; byte *dst4 = dst2; if (scale2x) { - dst3 = dst2 + s.pitch; - dst4 = dst3 + s.pitch; + dst3 = dst2 + sfPitch; + dst4 = dst3 + sfPitch; pitch <<= 1; } - if (_vm->_game.platform == Common::kPlatformFMTowns && _vm->_game.version == 5) - col = _vm->_townsCharsetColorMap[1]; #endif - for (y = 0; y < height && y + drawTop < s.h; y++) { + for (y = 0; y < height && y + drawTop < sfHeight; y++) { for (x = 0; x < width; x++) { if ((x % 8) == 0) bits = *src++; if ((bits & revBitMask(x % 8)) && y + drawTop >= 0) { if (bitDepth == 2) { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) { WRITE_UINT16(dst + 2, _vm->_16BitPalette[_shadowColor]); - WRITE_UINT16(dst + s.pitch, _vm->_16BitPalette[_shadowColor]); - if (_shadowMode != kFMTOWNSShadowMode) - WRITE_UINT16(dst + s.pitch + 2, _vm->_16BitPalette[_shadowColor]); + WRITE_UINT16(dst + sfPitch, _vm->_16BitPalette[_shadowColor]); } WRITE_UINT16(dst, _vm->_16BitPalette[_color]); } else { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) { #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE if (scale2x) { dst[2] = dst[3] = dst2[2] = dst2[3] = _shadowColor; @@ -1289,8 +1027,6 @@ void CharsetRendererCommon::drawBits1(const Graphics::Surface &s, byte *dst, con #endif { dst[1] = dst2[0] = _shadowColor; - if (_shadowMode != kFMTOWNSShadowMode) - dst2[1] = _shadowColor; } } dst[0] = col; @@ -1321,31 +1057,64 @@ void CharsetRendererCommon::drawBits1(const Graphics::Surface &s, byte *dst, con #endif } } +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE +int CharsetRendererTownsV3::getDrawWidthIntern(uint16 chr) { +#ifdef USE_RGB_COLOR + if (_vm->_useCJKMode && chr > 127) { + assert(_vm->_cjkFont); + return _vm->_cjkFont->getCharWidth(chr); + } +#endif + return CharsetRendererV3::getDrawWidthIntern(chr); +} + +int CharsetRendererTownsV3::getDrawHeightIntern(uint16 chr) { +#ifdef USE_RGB_COLOR + if (_vm->_useCJKMode && chr > 127) { + assert(_vm->_cjkFont); + return _vm->_cjkFont->getFontHeight(); + } +#endif + return CharsetRendererV3::getDrawHeightIntern(chr); +} + +void CharsetRendererTownsV3::setDrawCharIntern(uint16 chr) { + _sjisCurChar = (_vm->_useCJKMode && chr > 127) ? chr : 0; +} +#endif #ifdef USE_RGB_COLOR -void CharsetRendererPCE::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scalex) { +void CharsetRendererPCE::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { + if (_sjisCurChar) { + assert(_vm->_cjkFont); + uint16 col1 = _color; + uint16 col2 = _shadowColor; + + if (s.format.bytesPerPixel == 2) { + col1 = _vm->_16BitPalette[col1]; + col2 = _vm->_16BitPalette[col2]; + } + + _vm->_cjkFont->drawChar(dst, _sjisCurChar, s.pitch, s.format.bytesPerPixel, col1, col2, -1, -1); + return; + } + int y, x; - int bitCount = 0; byte bits = 0; - const bool resetLineBitCount = (_vm->_language != Common::JA_JPN || width != 12); - for (y = 0; y < height && y + drawTop < s.h; y++) { - if (resetLineBitCount) - bitCount = 0; + int bitCount = 0; for (x = 0; x < width; x++) { if ((bitCount % 8) == 0) bits = *src++; if ((bits & revBitMask(bitCount % 8)) && y + drawTop >= 0) { if (bitDepth == 2) { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) WRITE_UINT16(dst + s.pitch + 2, _vm->_16BitPalette[_shadowColor]); - } WRITE_UINT16(dst, _vm->_16BitPalette[_color]); } else { - if (_shadowMode != kNoShadowMode) { + if (_shadowMode) *(dst + s.pitch + 1) = _shadowColor; - } *dst = _color; } } @@ -1356,6 +1125,22 @@ void CharsetRendererPCE::drawBits1(const Graphics::Surface &s, byte *dst, const dst += s.pitch - width * bitDepth; } } + +int CharsetRendererPCE::getDrawWidthIntern(uint16 chr) { + if (_vm->_useCJKMode && chr > 127) + return _vm->_2byteWidth; + return CharsetRendererV3::getDrawWidthIntern(chr); +} + +int CharsetRendererPCE::getDrawHeightIntern(uint16 chr) { + if (_vm->_useCJKMode && chr > 127) + return _vm->_2byteHeight; + return CharsetRendererV3::getDrawHeightIntern(chr); +} + +void CharsetRendererPCE::setDrawCharIntern(uint16 chr) { + _sjisCurChar = (_vm->_useCJKMode && chr > 127) ? chr : 0; +} #endif #ifdef ENABLE_SCUMM_7_8 @@ -1530,7 +1315,7 @@ void CharsetRendererNES::printChar(int chr, bool ignoreCharsetMask) { if (_str.right < _left) { _str.right = _left; - if (_shadowMode != kNoShadowMode) + if (_shadowMode) _str.right++; } @@ -1553,7 +1338,204 @@ void CharsetRendererNES::drawChar(int chr, Graphics::Surface &s, int x, int y) { drawBits1(s, dst, charPtr, y, width, height, s.format.bytesPerPixel); } -void CharsetRendererNES::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scalex) { +#ifdef USE_RGB_COLOR +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE +CharsetRendererTownsClassic::CharsetRendererTownsClassic(ScummEngine *vm) : CharsetRendererClassic(vm), _sjisCurChar(0) { +} + +int CharsetRendererTownsClassic::getCharWidth(uint16 chr) { + int spacing = 0; + + if (_vm->_useCJKMode) { + if ((chr & 0xff00) == 0xfd00) { + chr &= 0xff; + } else if (chr >= 256) { + spacing = 8; + } else if (useFontRomCharacter(chr)) { + spacing = 4; + } + + if (spacing) { + if (_vm->_game.id == GID_MONKEY) { + spacing++; + if (_curId == 2) + spacing++; + } else if (_vm->_game.id != GID_INDY4 && _curId == 1) { + spacing++; + } + } + } + + if (!spacing) { + int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4); + if (offs) + spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2]; + } + + return spacing; +} + +int CharsetRendererTownsClassic::getFontHeight() { + static const uint8 sjisFontHeightM1[] = { 0, 8, 9, 8, 9, 8, 9, 0, 0, 0 }; + static const uint8 sjisFontHeightM2[] = { 0, 8, 9, 9, 9, 8, 9, 9, 9, 8 }; + static const uint8 sjisFontHeightI4[] = { 0, 8, 9, 9, 9, 8, 8, 8, 8, 8 }; + const uint8 *htbl = (_vm->_game.id == GID_MONKEY) ? sjisFontHeightM1 : ((_vm->_game.id == GID_INDY4) ? sjisFontHeightI4 : sjisFontHeightM2); + return _vm->_useCJKMode ? htbl[_curId] : _fontHeight; +} + +void CharsetRendererTownsClassic::drawBitsN(const Graphics::Surface&, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height) { + if (_sjisCurChar) { + assert(_vm->_cjkFont); + _vm->_cjkFont->drawChar(_vm->_textSurface, _sjisCurChar, _left * _vm->_textSurfaceMultiplier, (_top - _vm->_screenTop) * _vm->_textSurfaceMultiplier, _vm->_townsCharsetColorMap[1], _shadowColor); + return; + } + + bool scale2x = (_vm->_textSurfaceMultiplier == 2); + dst = (byte *)_vm->_textSurface.pixels + (_top - _vm->_screenTop) * _vm->_textSurface.pitch * _vm->_textSurfaceMultiplier + _left * _vm->_textSurfaceMultiplier; + + int y, x; + int color; + byte numbits, bits; + + int pitch = _vm->_textSurface.pitch - width; + + assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8); + bits = *src++; + numbits = 8; + byte *cmap = _vm->_charsetColorMap; + byte *dst2 = dst; + + if (_vm->_game.platform == Common::kPlatformFMTowns) + cmap = _vm->_townsCharsetColorMap; + if (scale2x) { + dst2 += _vm->_textSurface.pitch; + pitch <<= 1; + } + + for (y = 0; y < height && y + drawTop < _vm->_textSurface.h; y++) { + for (x = 0; x < width; x++) { + color = (bits >> (8 - bpp)) & 0xFF; + + if (color && y + drawTop >= 0) { + *dst = cmap[color]; + if (scale2x) + dst[1] = dst2[0] = dst2[1] = dst[0]; + } + dst++; + + if (scale2x) { + dst++; + dst2 += 2; + } + + bits <<= bpp; + numbits -= bpp; + if (numbits == 0) { + bits = *src++; + numbits = 8; + } + } + dst += pitch; + dst2 += pitch; + } +} + +bool CharsetRendererTownsClassic::prepareDraw(uint16 chr) { + processCharsetColors(); + bool noSjis = false; + + if (_vm->_game.platform == Common::kPlatformFMTowns && _vm->_useCJKMode) { + if ((chr & 0x00ff) == 0x00fd) { + chr >>= 8; + noSjis = true; + } + } + + if (useFontRomCharacter(chr) && !noSjis) { + setupShadowMode(); + _charPtr = 0; + _sjisCurChar = chr; + + _width = getCharWidth(chr); + // For whatever reason MI1 uses a different font width + // for alignment calculation and for drawing when + // charset 2 is active. This fixes some subtle glitches. + if (_vm->_game.id == GID_MONKEY && _curId == 2) + _width--; + _origWidth = _width; + + _origHeight = _height = getFontHeight(); + _offsX = _offsY = 0; + } else if (_vm->_useCJKMode && (chr >= 128) && !noSjis) { + setupShadowMode(); + _origWidth = _width = _vm->_2byteWidth; + _origHeight = _height = _vm->_2byteHeight; + _charPtr = _vm->get2byteCharPtr(chr); + _offsX = _offsY = 0; + if (_shadowMode) { + _width++; + _height++; + } + } else { + _sjisCurChar = 0; + return CharsetRendererClassic::prepareDraw(chr); + } + return true; +} + +void CharsetRendererTownsClassic::setupShadowMode() { + _shadowMode = true; + _shadowColor = _vm->_townsCharsetColorMap[0]; + assert(_vm->_cjkFont); + + if (((_vm->_game.id == GID_MONKEY) && (_curId == 2 || _curId == 4 || _curId == 6)) || + ((_vm->_game.id == GID_MONKEY2) && (_curId != 1 && _curId != 5 && _curId != 9)) || + ((_vm->_game.id == GID_INDY4) && (_curId == 2 || _curId == 3 || _curId == 4))) { + _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kOutlineMode); + } else { + _vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kDefaultMode); + } + + _vm->_cjkFont->toggleFlippedMode((_vm->_game.id == GID_MONKEY || _vm->_game.id == GID_MONKEY2) && _curId == 3); +} + +bool CharsetRendererTownsClassic::useFontRomCharacter(uint16 chr) { + if (!_vm->_useCJKMode) + return false; + + // Some SCUMM 5 games contain hard coded logic to determine whether to use + // the SCUMM fonts or the FM-Towns font rom to draw a character. For the other + // games we will simply check for a character greater 127. + if (chr < 128) { + if (((_vm->_game.id == GID_MONKEY2 && _curId != 0) || (_vm->_game.id == GID_INDY4 && _curId != 3)) && (chr > 31 && chr != 94 && chr != 95 && chr != 126 && chr != 127)) + return true; + return false; + } + return true; +} + +void CharsetRendererTownsClassic::processCharsetColors() { + for (int i = 0; i < (1 << _bytesPerPixel); i++) { + uint8 c = _vm->_charsetColorMap[i]; + + if (c > 16) { + uint8 t = (_vm->_currentPalette[c * 3] < 32) ? 4 : 12; + t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 2 : 10); + t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 1 : 9); + c = t; + } + + if (c == 0) + c = _vm->_townsOverrideShadowColor; + + c = ((c & 0x0f) << 4) | (c & 0x0f); + _vm->_townsCharsetColorMap[i] = c; + } +} +#endif +#endif + +void CharsetRendererNES::drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth) { for (int i = 0; i < 8; i++) { byte c0 = src[i]; byte c1 = src[i + 8]; diff --git a/engines/scumm/charset.h b/engines/scumm/charset.h index 4c657b475e..b23ec996f5 100644 --- a/engines/scumm/charset.h +++ b/engines/scumm/charset.h @@ -25,6 +25,7 @@ #include "common/scummsys.h" #include "common/rect.h" #include "graphics/sjis.h" +#include "scumm/scumm.h" #include "scumm/gfx.h" #include "scumm/saveload.h" @@ -78,10 +79,6 @@ public: void addLinebreaks(int a, byte *str, int pos, int maxwidth); void translateColor(); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - void processTownsCharsetColors(uint8 bytesPerPixel); -#endif - virtual void setCurID(int32 id) = 0; int getCurID() { return _curId; } @@ -101,31 +98,26 @@ protected: int _fontHeight; int _numChars; - enum ShadowMode { - kNoShadowMode, - kFMTOWNSShadowMode, - kNormalShadowMode - }; byte _shadowColor; - ShadowMode _shadowMode; - - void enableShadow(bool enable); - virtual void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scale2x = false); - + bool _shadowMode; public: CharsetRendererCommon(ScummEngine *vm); void setCurID(int32 id); - int getFontHeight(); + virtual int getFontHeight(); }; class CharsetRendererClassic : public CharsetRendererCommon { protected: - void drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height, bool scale2x = false); + virtual void drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height); + void printCharIntern(bool is2byte, const byte *charPtr, int origWidth, int origHeight, int width, int height, VirtScreen *vs, bool ignoreCharsetMask); + virtual bool prepareDraw(uint16 chr); - void printCharIntern(bool is2byte, const byte *charPtr, int origWidth, int origHeight, int width, int height, VirtScreen *vs, bool ignoreCharsetMask); + int _width, _height, _origWidth, _origHeight; + int _offsX, _offsY; + const byte *_charPtr; public: CharsetRendererClassic(ScummEngine *vm) : CharsetRendererCommon(vm) {} @@ -134,18 +126,34 @@ public: void drawChar(int chr, Graphics::Surface &s, int x, int y); int getCharWidth(uint16 chr); +}; + +#ifdef USE_RGB_COLOR +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE +class CharsetRendererTownsClassic : public CharsetRendererClassic { +public: + CharsetRendererTownsClassic(ScummEngine *vm); + + int getCharWidth(uint16 chr); + int getFontHeight(); - // Some SCUMM 5 games contain hard coded logic to determine whether to use - // the SCUMM fonts or the FM-Towns font rom to draw a character. For the other - // games we will simply check for a character greater 127. - bool useTownsFontRomCharacter(uint16 chr); +private: + void drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height); + bool prepareDraw(uint16 chr); + void setupShadowMode(); + bool useFontRomCharacter(uint16 chr); + void processCharsetColors(); + + uint16 _sjisCurChar; }; +#endif +#endif class CharsetRendererNES : public CharsetRendererCommon { protected: byte *_trTable; - void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scale2x = false); + void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); public: CharsetRendererNES(ScummEngine *vm) : CharsetRendererCommon(vm) {} @@ -160,6 +168,12 @@ public: class CharsetRendererV3 : public CharsetRendererCommon { protected: + virtual void enableShadow(bool enable); + virtual void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); + virtual int getDrawWidthIntern(uint16 chr); + virtual int getDrawHeightIntern(uint16 chr); + virtual void setDrawCharIntern(uint16 chr) {} + const byte *_widthTable; public: @@ -169,16 +183,40 @@ public: void drawChar(int chr, Graphics::Surface &s, int x, int y); void setCurID(int32 id); void setColor(byte color); + virtual int getCharWidth(uint16 chr); +}; + +class CharsetRendererTownsV3 : public CharsetRendererV3 { +public: + CharsetRendererTownsV3(ScummEngine *vm); + int getCharWidth(uint16 chr); + int getFontHeight(); + +private: + void enableShadow(bool enable); + void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE + int getDrawWidthIntern(uint16 chr); + int getDrawHeightIntern(uint16 chr); + void setDrawCharIntern(uint16 chr); +#endif + uint16 _sjisCurChar; }; #ifdef USE_RGB_COLOR class CharsetRendererPCE : public CharsetRendererV3 { -protected: - void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth, bool scale2x = false); +private: + void drawBits1(const Graphics::Surface &s, byte *dst, const byte *src, int drawTop, int width, int height, uint8 bitDepth); + + int getDrawWidthIntern(uint16 chr); + int getDrawHeightIntern(uint16 chr); + void setDrawCharIntern(uint16 chr); + + uint16 _sjisCurChar; public: - CharsetRendererPCE(ScummEngine *vm) : CharsetRendererV3(vm) {} + CharsetRendererPCE(ScummEngine *vm) : CharsetRendererV3(vm), _sjisCurChar(0) {} void setColor(byte color); }; diff --git a/engines/scumm/costume.cpp b/engines/scumm/costume.cpp index 75cde5e33a..4ca4988605 100644 --- a/engines/scumm/costume.cpp +++ b/engines/scumm/costume.cpp @@ -73,10 +73,10 @@ static const int v1MMNESLookup[25] = { }; static const byte v0ActorTalkArray[0x19] = { - 0x00, 0x06, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x00, 0x46, 0x06, - 0x06, 0x06, 0x06, 0xFF, 0xFF, - 0x06, 0xC0, 0x06, 0x06, 0x00, + 0x00, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x00, 0x46, 0x06, + 0x06, 0x06, 0x06, 0xFF, 0xFF, + 0x06, 0xC0, 0x06, 0x06, 0x00, 0xC0, 0xC0, 0x00, 0x06, 0x06 }; @@ -1417,7 +1417,7 @@ byte C64CostumeLoader::increaseAnims(Actor *a) { if (A->_moving && _vm->_currentRoom != 1 && _vm->_currentRoom != 44) { if (a->_cost.soundPos == 0) a->_cost.soundCounter++; - + // Is this the correct location? // 0x073C if (v0ActorTalkArray[a->_number] & 0x3F) diff --git a/engines/scumm/cursor.cpp b/engines/scumm/cursor.cpp index e9b5260eca..a8adb4d5c5 100644 --- a/engines/scumm/cursor.cpp +++ b/engines/scumm/cursor.cpp @@ -551,7 +551,7 @@ void ScummEngine_v5::setBuiltinCursor(int idx) { uint16 color; const uint16 *src = _cursorImages[_currentCursor]; - if (_bytesPerPixelOutput == 2) { + if (_outputPixelFormat.bytesPerPixel == 2) { if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine) { byte r, g, b; colorPCEToRGB(default_pce_cursor_colors[idx], &r, &g, &b); @@ -577,14 +577,14 @@ void ScummEngine_v5::setBuiltinCursor(int idx) { _cursor.width = 16 * _textSurfaceMultiplier; _cursor.height = 16 * _textSurfaceMultiplier; - int scl = _bytesPerPixelOutput * _textSurfaceMultiplier; + int scl = _outputPixelFormat.bytesPerPixel * _textSurfaceMultiplier; for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (src[i] & (1 << j)) { byte *dst1 = _grabbedCursor + 16 * scl * i * _textSurfaceMultiplier + (15 - j) * scl; byte *dst2 = (_textSurfaceMultiplier == 2) ? dst1 + 16 * scl : dst1; - if (_bytesPerPixelOutput == 2) { + if (_outputPixelFormat.bytesPerPixel == 2) { for (int b = 0; b < scl; b += 2) { *((uint16*)dst1) = *((uint16*)dst2) = color; dst1 += 2; diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index e5c5906404..d3514645d3 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -142,6 +142,14 @@ Common::String ScummEngine_v70he::generateFilename(const int room) const { Common::String result; char id = 0; + Common::String bPattern = _filenamePattern.pattern; + + // Special cases for Blue's games, which share common (b) files + if (_game.id == GID_BIRTHDAY && !(_game.features & GF_DEMO)) + bPattern = "Blue'sBirthday"; + else if (_game.id == GID_TREASUREHUNT) + bPattern = "Blue'sTreasureHunt"; + switch (_filenamePattern.genMethod) { case kGenHEMac: case kGenHEMacNoParens: @@ -154,13 +162,7 @@ Common::String ScummEngine_v70he::generateFilename(const int room) const { switch (disk) { case 2: id = 'b'; - // Special cases for Blue's games, which share common (b) files - if (_game.id == GID_BIRTHDAY && !(_game.features & GF_DEMO)) - result = "Blue'sBirthday.(b)"; - else if (_game.id == GID_TREASUREHUNT) - result = "Blue'sTreasureHunt.(b)"; - else - result = Common::String::format("%s.(b)", _filenamePattern.pattern); + result = bPattern + ".(b)"; break; case 1: id = 'a'; @@ -185,10 +187,11 @@ Common::String ScummEngine_v70he::generateFilename(const int room) const { // For mac they're stored in game binary result = _filenamePattern.pattern; } else { + Common::String pattern = id == 'b' ? bPattern : _filenamePattern.pattern; if (_filenamePattern.genMethod == kGenHEMac) - result = Common::String::format("%s (%c)", _filenamePattern.pattern, id); + result = Common::String::format("%s (%c)", pattern.c_str(), id); else - result = Common::String::format("%s %c", _filenamePattern.pattern, id); + result = Common::String::format("%s %c", pattern.c_str(), id); } } @@ -272,7 +275,7 @@ static BaseScummFile *openDiskImage(const Common::FSNode &node, const GameFilena GameSettings gs; memset(&gs, 0, sizeof(GameSettings)); gs.gameid = gfp->gameid; - gs.id = (Common::String(gfp->gameid) == "maniac" ? GID_MANIAC : GID_ZAK); + gs.id = (Common::String(gfp->gameid) == "maniac" ? GID_MANIAC : GID_ZAK); gs.platform = gfp->platform; // determine second disk file name @@ -454,7 +457,7 @@ static void composeFileHashMap(DescMap &fileMD5Map, const Common::FSList &fslist matched = true; break; } - + if (!matched) continue; @@ -515,7 +518,7 @@ static void detectGames(const Common::FSList &fslist, Common::List<DetectorResul if (d.md5.empty()) { Common::SeekableReadStream *tmp = 0; bool isDiskImg = (file.hasSuffix(".d64") || file.hasSuffix(".dsk") || file.hasSuffix(".prg")); - + if (isDiskImg) { tmp = openDiskImage(d.node, gfp); diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h index e510c46cf2..11901f7565 100644 --- a/engines/scumm/detection_tables.h +++ b/engines/scumm/detection_tables.h @@ -243,11 +243,11 @@ static const GameSettings gameVariantsTable[] = { {"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_TOWNS, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS}, {"monkey", "SEGA", 0, GID_MONKEY, 5, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO_NOSPEECH | GUIO_NOMIDI}, - {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, - {"monkey2", "FM-TOWNS", 0, GID_MONKEY2, 5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32}, + {"monkey2", "", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, + {"monkey2", "FM-TOWNS", 0, GID_MONKEY2, 5, 0, MDT_PCSPK | MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32}, - {"atlantis", "", 0, GID_INDY4, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NONE}, - {"atlantis", "Floppy", 0, GID_INDY4, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, + {"atlantis", "", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NONE}, + {"atlantis", "Floppy", 0, GID_INDY4, 5, 0, MDT_PCSPK | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH}, {"atlantis", "FM-TOWNS", 0, GID_INDY4, 5, 0, MDT_TOWNS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, Common::kPlatformFMTowns, GUIO_MIDITOWNS | GUIO_MIDIADLIB | GUIO_MIDIMT32}, {"tentacle", "", 0, GID_TENTACLE, 6, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM, GF_USE_KEY, UNK, GUIO_NONE}, diff --git a/engines/scumm/gfx.cpp b/engines/scumm/gfx.cpp index 1b913e16b4..f22547f193 100644 --- a/engines/scumm/gfx.cpp +++ b/engines/scumm/gfx.cpp @@ -251,7 +251,7 @@ GdiV2::~GdiV2() { } #ifdef USE_RGB_COLOR -Gdi16Bit::Gdi16Bit(ScummEngine *vm) : Gdi(vm) { +GdiHE16bit::GdiHE16bit(ScummEngine *vm) : GdiHE(vm) { } #endif @@ -652,16 +652,13 @@ void ScummEngine::drawStripToScreen(VirtScreen *vs, int x, int width, int top, i assert(0 == (width & 3)); // Compose the text over the game graphics -#ifdef USE_ARM_GFX_ASM - asmDrawStripToScreen(height, width, text, src, _compositeBuf, vs->pitch, width, _textSurface.pitch); -#else #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE if (_game.platform == Common::kPlatformFMTowns) { towns_drawStripToScreen(vs, x, y, x, top, width, height); - return; + return; } else -#endif - if (_bytesPerPixelOutput == 2) { +#endif + if (_outputPixelFormat.bytesPerPixel == 2) { const byte *srcPtr = (const byte *)src; const byte *textPtr = (byte *)_textSurface.getBasePtr(x * m, y * m); byte *dstPtr = _compositeBuf; @@ -682,7 +679,11 @@ void ScummEngine::drawStripToScreen(VirtScreen *vs, int x, int width, int top, i srcPtr += vsPitch; textPtr += _textSurface.pitch - width * m; } - } else { + } +#ifdef USE_ARM_GFX_ASM + asmDrawStripToScreen(height, width, text, src, _compositeBuf, vs->pitch, width, _textSurface.pitch); +#else + else { // We blit four pixels at a time, for improved performance. const uint32 *src32 = (const uint32 *)src; uint32 *dst32 = (uint32 *)_compositeBuf; @@ -721,11 +722,11 @@ void ScummEngine::drawStripToScreen(VirtScreen *vs, int x, int width, int top, i if (_renderMode == Common::kRenderHercA || _renderMode == Common::kRenderHercG) { ditherHerc(_compositeBuf, _herculesBuf, width, &x, &y, &width, &height); - src = _herculesBuf + x + y * Common::kHercW; - pitch = Common::kHercW; + src = _herculesBuf + x + y * kHercWidth; + pitch = kHercWidth; // center image on the screen - x += (Common::kHercW - _screenWidth * 2) / 2; // (720 - 320*2)/2 = 40 + x += (kHercWidth - _screenWidth * 2) / 2; // (720 - 320*2)/2 = 40 } else if (_useCJKMode && m == 2) { pitch *= m; x *= m; @@ -818,10 +819,10 @@ void ditherHerc(byte *src, byte *hercbuf, int srcPitch, int *x, int *y, int *wid int dsty = yo*2 - yo/4; for (int y1 = 0; y1 < heighto;) { - assert(dsty < Common::kHercH); + assert(dsty < kHercHeight); srcptr = src + y1 * srcPitch; - dstptr = hercbuf + dsty * Common::kHercW + xo * 2; + dstptr = hercbuf + dsty * kHercWidth + xo * 2; const int idx1 = (dsty % 7) % 2; for (int x1 = 0; x1 < widtho; x1++) { @@ -1023,7 +1024,7 @@ void ScummEngine::restoreBackground(Common::Rect rect, byte backColor) { if (rect.left > vs->w) return; - + // Convert 'rect' to local (virtual screen) coordinates rect.top -= vs->topline; rect.bottom -= vs->topline; @@ -1067,7 +1068,7 @@ void ScummEngine::restoreBackground(Common::Rect rect, byte backColor) { fill(mask, _textSurface.pitch, backColor, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel); } #endif - + if (_game.features & GF_16BIT_COLOR) fill(screenBuf, vs->pitch, _16BitPalette[backColor], width, height, vs->format.bytesPerPixel); else @@ -1127,7 +1128,7 @@ void ScummEngine::clearTextSurface() { fill((byte*)_textSurface.pixels, _textSurface.pitch, #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE _game.platform == Common::kPlatformFMTowns ? 0 : -#endif +#endif CHARSET_MASK_TRANSPARENCY, _textSurface.w, _textSurface.h, _textSurface.format.bytesPerPixel); } @@ -1344,12 +1345,12 @@ void ScummEngine::drawBox(int x, int y, int x2, int y2, int color) { color = ((color & 0x0f) << 4) | (color & 0x0f); byte *mask = (byte *)_textSurface.getBasePtr(x * _textSurfaceMultiplier, (y - _screenTop + vs->topline) * _textSurfaceMultiplier); fill(mask, _textSurface.pitch, color, width * _textSurfaceMultiplier, height * _textSurfaceMultiplier, _textSurface.format.bytesPerPixel); - + if (_game.id == GID_MONKEY2 || _game.id == GID_INDY4 || ((_game.id == GID_INDY3 || _game.id == GID_ZAK) && vs->number != kTextVirtScreen) || (_game.id == GID_LOOM && vs->number == kMainVirtScreen)) return; } #endif - + fill(backbuff, vs->pitch, color, width, height, vs->format.bytesPerPixel); } } @@ -3673,7 +3674,7 @@ void Gdi::unkDecode11(byte *dst, int dstPitch, const byte *src, int height) cons #undef READ_BIT_256 #ifdef USE_RGB_COLOR -void Gdi16Bit::writeRoomColor(byte *dst, byte color) const { +void GdiHE16bit::writeRoomColor(byte *dst, byte color) const { WRITE_UINT16(dst, READ_LE_UINT16(_vm->_hePalettes + 2048 + color * 2)); } #endif @@ -4009,7 +4010,7 @@ void ScummEngine::scrollEffect(int dir) { y = 1 + step; while (y < vs->h) { moveScreen(0, -step, vs->h); -#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE if (_townsScreen) { towns_drawStripToScreen(vs, 0, vs->topline + vs->h - step, 0, y - step, vs->w, step); } else @@ -4022,7 +4023,7 @@ void ScummEngine::scrollEffect(int dir) { vs->w * m, step * m); _system->updateScreen(); } - + waitForTimer(delay); y += step; } @@ -4045,7 +4046,7 @@ void ScummEngine::scrollEffect(int dir) { vs->w * m, step * m); _system->updateScreen(); } - + waitForTimer(delay); y += step; } @@ -4092,7 +4093,7 @@ void ScummEngine::scrollEffect(int dir) { 0, 0, step, vs->h); _system->updateScreen(); - } + } waitForTimer(delay); x += step; diff --git a/engines/scumm/gfx.h b/engines/scumm/gfx.h index 6da07efd18..4b44ddc376 100644 --- a/engines/scumm/gfx.h +++ b/engines/scumm/gfx.h @@ -32,6 +32,11 @@ namespace Scumm { class ScummEngine; +enum HerculesDimensions { + kHercWidth = 720, + kHercHeight = 350 +}; + /** Camera modes */ enum { kNormalCameraMode = 1, @@ -430,11 +435,11 @@ public: }; #ifdef USE_RGB_COLOR -class Gdi16Bit : public Gdi { +class GdiHE16bit : public GdiHE { protected: virtual void writeRoomColor(byte *dst, byte color) const; public: - Gdi16Bit(ScummEngine *vm); + GdiHE16bit(ScummEngine *vm); }; #endif @@ -443,21 +448,21 @@ public: // switching graphics layers on and off). class TownsScreen { public: - TownsScreen(OSystem *system, int width, int height, int bpp); + TownsScreen(OSystem *system, int width, int height, Graphics::PixelFormat &format); ~TownsScreen(); void setupLayer(int layer, int width, int height, int numCol, void *srcPal = 0); void clearLayer(int layer); void fillLayerRect(int layer, int x, int y, int w, int h, int col); //void copyRectToLayer(int layer, int x, int y, int w, int h, const uint8 *src); - + uint8 *getLayerPixels(int layer, int x, int y); int getLayerPitch(int layer); int getLayerHeight(int layer); int getLayerBpp(int layer); int getLayerScaleW(int layer); int getLayerScaleH(int layer); - + void addDirtyRect(int x, int y, int w, int h); void toggleLayers(int flag); void update(); @@ -484,16 +489,16 @@ private: uint8 **bltInternY; uint16 *bltTmpPal; } _layers[2]; - + uint8 *_outBuffer; int _height; int _width; int _pitch; - int _bpp; - + Graphics::PixelFormat _pixelFormat; + int _numDirtyRects; - Common::List<Common::Rect> _dirtyRects; + Common::List<Common::Rect> _dirtyRects; OSystem *_system; }; #endif // DISABLE_TOWNS_DUAL_LAYER_MODE diff --git a/engines/scumm/gfx_towns.cpp b/engines/scumm/gfx_towns.cpp index cdccd3e193..6a3f50a1af 100644 --- a/engines/scumm/gfx_towns.cpp +++ b/engines/scumm/gfx_towns.cpp @@ -47,13 +47,13 @@ void ScummEngine::towns_drawStripToScreen(VirtScreen *vs, int dstX, int dstY, in int dp2 = _townsScreen->getLayerPitch(1) - width * m * _townsScreen->getLayerBpp(1); int sp1 = vs->pitch - (width * vs->format.bytesPerPixel); int sp2 = _textSurface.pitch - width * m; - + if (vs->number == kMainVirtScreen || _game.id == GID_INDY3 || _game.id == GID_ZAK) { for (int h = 0; h < height; ++h) { - if (_bytesPerPixelOutput == 2) { + if (_outputPixelFormat.bytesPerPixel == 2) { for (int w = 0; w < width; ++w) { *(uint16*)dst1 = _16BitPalette[*src1++]; - dst1 += _bytesPerPixelOutput; + dst1 += _outputPixelFormat.bytesPerPixel; } src1 += sp1; @@ -63,13 +63,13 @@ void ScummEngine::towns_drawStripToScreen(VirtScreen *vs, int dstX, int dstY, in src1 += vs->pitch; dst1 += _townsScreen->getLayerPitch(0); } - + for (int sH = 0; sH < m; ++sH) { memcpy(dst2, src2, width * m); src2 += _textSurface.pitch; dst2 += _townsScreen->getLayerPitch(1); } - } + } } else { dst1 = dst2; for (int h = 0; h < height; ++h) { @@ -81,7 +81,7 @@ void ScummEngine::towns_drawStripToScreen(VirtScreen *vs, int dstX, int dstY, in dst1 = dst2; uint8 *src3 = src2; - + if (m == 2) { dst2 += _townsScreen->getLayerPitch(1); src3 += _townsScreen->getLayerPitch(1); @@ -95,7 +95,7 @@ void ScummEngine::towns_drawStripToScreen(VirtScreen *vs, int dstX, int dstY, in dst1++; } - src1 += sp1; + src1 += sp1; src2 = src3 + sp2; dst1 = dst2 + dp2; dst2 += dp2; @@ -197,8 +197,8 @@ const uint8 ScummEngine::_townsLayer2Mask[] = { #define DIRTY_RECTS_MAX 20 #define FULL_REDRAW (DIRTY_RECTS_MAX + 1) -TownsScreen::TownsScreen(OSystem *system, int width, int height, int bpp) : - _system(system), _width(width), _height(height), _bpp(bpp), _pitch(width * bpp) { +TownsScreen::TownsScreen(OSystem *system, int width, int height, Graphics::PixelFormat &format) : + _system(system), _width(width), _height(height), _pixelFormat(format), _pitch(width * format.bytesPerPixel) { memset(&_layers[0], 0, sizeof(TownsScreenLayer)); memset(&_layers[1], 0, sizeof(TownsScreenLayer)); _outBuffer = new byte[_pitch * _height]; @@ -231,7 +231,7 @@ void TownsScreen::setupLayer(int layer, int width, int height, int numCol, void if (width > _width || height > _height) error("TownsScreen::setupLayer(): Layer width/height must be equal or less than screen width/height"); - + l->scaleW = _width / width; l->scaleH = _height / height; @@ -247,7 +247,7 @@ void TownsScreen::setupLayer(int layer, int width, int height, int numCol, void l->pitch = width * l->bpp; l->palette = (uint8*)pal; - if (l->palette && _bpp == 1) + if (l->palette && _pixelFormat.bytesPerPixel == 1) warning("TownsScreen::setupLayer(): Layer palette usage requires 16 bit graphics setting.\nLayer palette will be ignored."); delete[] l->pixels; @@ -267,14 +267,14 @@ void TownsScreen::setupLayer(int layer, int width, int height, int numCol, void l->bltInternY[i] = l->pixels + (i / l->scaleH) * l->pitch; delete[] l->bltTmpPal; - l->bltTmpPal = (l->bpp == 1 && _bpp == 2) ? new uint16[l->numCol] : 0; - + l->bltTmpPal = (l->bpp == 1 && _pixelFormat.bytesPerPixel == 2) ? new uint16[l->numCol] : 0; + l->enabled = true; _layers[0].onBottom = true; _layers[1].onBottom = _layers[0].enabled ? false : true; l->ready = true; } - + void TownsScreen::clearLayer(int layer) { if (layer < 0 || layer > 1) return; @@ -300,10 +300,10 @@ void TownsScreen::fillLayerRect(int layer, int x, int y, int w, int h, int col) assert(x >= 0 && y >= 0 && ((x + w) * l->bpp) <= (l->pitch) && (y + h) <= (l->height)); uint8 *pos = l->pixels + y * l->pitch + x * l->bpp; - + for (int i = 0; i < h; ++i) { if (l->bpp == 2) { - for (int ii = 0; ii < w; ++ii) { + for (int ii = 0; ii < w; ++ii) { *(uint16*)pos = col; pos += 2; } @@ -359,8 +359,8 @@ int TownsScreen::getLayerScaleH(int layer) { void TownsScreen::addDirtyRect(int x, int y, int w, int h) { if (w <= 0 || h <= 0 || _numDirtyRects > DIRTY_RECTS_MAX) - return; - + return; + if (_numDirtyRects == DIRTY_RECTS_MAX) { // full redraw _dirtyRects.clear(); @@ -383,25 +383,25 @@ void TownsScreen::addDirtyRect(int x, int y, int w, int h) { y = r->top; skip = true; } - + if (x2 > r->left && x2 < r->right && y > r->top && y < r->bottom) { x2 = r->right; y = r->top; skip = true; } - + if (x2 > r->left && x2 < r->right && y2 > r->top && y2 < r->bottom) { x2 = r->right; y2 = r->bottom; skip = true; } - + if (x > r->left && x < r->right && y2 > r->top && y2 < r->bottom) { x = r->left; y2 = r->bottom; skip = true; - } - + } + if (skip) { r->left = x; r->top = y; @@ -449,20 +449,22 @@ void TownsScreen::updateOutputBuffer() { if (!l->enabled || !l->ready) continue; - uint8 *dst = _outBuffer + r->top * _pitch + r->left * _bpp; - int ptch = _pitch - (r->right - r->left + 1) * _bpp; + uint8 *dst = _outBuffer + r->top * _pitch + r->left * _pixelFormat.bytesPerPixel; + int ptch = _pitch - (r->right - r->left + 1) * _pixelFormat.bytesPerPixel; - if (_bpp == 2 && l->bpp == 1) { + if (_pixelFormat.bytesPerPixel == 2 && l->bpp == 1) { + if (!l->palette) + error("void TownsScreen::updateOutputBuffer(): No palette assigned to 8 bit layer %d", i); for (int ic = 0; ic < l->numCol; ic++) l->bltTmpPal[ic] = calc16BitColor(&l->palette[ic * 3]); } for (int y = r->top; y <= r->bottom; ++y) { - if (l->bpp == _bpp && l->scaleW == 1 && l->onBottom && l->numCol & 0xff00) { - memcpy(dst, &l->bltInternY[y][l->bltInternX[r->left]], (r->right + 1 - r->left) * _bpp); + if (l->bpp == _pixelFormat.bytesPerPixel && l->scaleW == 1 && l->onBottom && l->numCol & 0xff00) { + memcpy(dst, &l->bltInternY[y][l->bltInternX[r->left]], (r->right + 1 - r->left) * _pixelFormat.bytesPerPixel); dst += _pitch; - } else if (_bpp == 2) { + } else if (_pixelFormat.bytesPerPixel == 2) { for (int x = r->left; x <= r->right; ++x) { uint8 *src = &l->bltInternY[y][l->bltInternX[x]]; if (l->bpp == 1) { @@ -484,13 +486,13 @@ void TownsScreen::updateOutputBuffer() { uint8 col = l->bltInternY[y][l->bltInternX[x]]; if (col || l->onBottom) { if (l->numCol == 16) - col = (col >> 4) & (col & 0x0f); + col = (col >> 4) & (col & 0x0f); *dst = col; } dst++; } dst += ptch; - } + } } } } @@ -498,17 +500,13 @@ void TownsScreen::updateOutputBuffer() { void TownsScreen::outputToScreen() { for (Common::List<Common::Rect>::iterator i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) - _system->copyRectToScreen(_outBuffer + i->top * _pitch + i->left * _bpp, _pitch, i->left, i->top, i->right - i->left + 1, i->bottom - i->top + 1); + _system->copyRectToScreen(_outBuffer + i->top * _pitch + i->left * _pixelFormat.bytesPerPixel, _pitch, i->left, i->top, i->right - i->left + 1, i->bottom - i->top + 1); _dirtyRects.clear(); _numDirtyRects = 0; } uint16 TownsScreen::calc16BitColor(const uint8 *palEntry) { - uint16 ar = (palEntry[0] & 0xf8) << 7; - uint16 ag = (palEntry[1] & 0xf8) << 2; - uint16 ab = (palEntry[2] >> 3); - uint16 col = ar | ag | ab; - return col; + return _pixelFormat.RGBToColor(palEntry[0], palEntry[1], palEntry[2]); } #undef DIRTY_RECTS_MAX diff --git a/engines/scumm/he/animation_he.cpp b/engines/scumm/he/animation_he.cpp index 74183c24d3..40e99c26a8 100644 --- a/engines/scumm/he/animation_he.cpp +++ b/engines/scumm/he/animation_he.cpp @@ -26,27 +26,42 @@ #include "scumm/he/intern_he.h" #include "audio/audiostream.h" +#include "video/smk_decoder.h" + +#ifdef USE_BINK +#include "video/bink_decoder.h" +#endif namespace Scumm { -MoviePlayer::MoviePlayer(ScummEngine_v90he *vm, Audio::Mixer *mixer) - : SmackerDecoder(mixer), _vm(vm), _mixer(mixer) { +MoviePlayer::MoviePlayer(ScummEngine_v90he *vm, Audio::Mixer *mixer) : _vm(vm) { +#ifdef USE_BINK + if (_vm->_game.heversion >= 100 && (_vm->_game.features & GF_16BIT_COLOR)) + _video = new Video::BinkDecoder(); + else +#endif + _video = new Video::SmackerDecoder(mixer); _flags = 0; _wizResNum = 0; } +MoviePlayer::~MoviePlayer() { + delete _video; +} + int MoviePlayer::getImageNum() { - if (!isVideoLoaded()) + if (!_video->isVideoLoaded()) return 0; + return _wizResNum; } int MoviePlayer::load(const char *filename, int flags, int image) { - if (isVideoLoaded()) - close(); + if (_video->isVideoLoaded()) + _video->close(); - if (!loadFile(filename)) { + if (!_video->loadFile(filename)) { warning("Failed to load video file %s", filename); return -1; } @@ -54,7 +69,7 @@ int MoviePlayer::load(const char *filename, int flags, int image) { debug(1, "Playing video %s", filename); if (flags & 2) - _vm->_wiz->createWizEmptyImage(image, 0, 0, getWidth(), getHeight()); + _vm->_wiz->createWizEmptyImage(image, 0, 0, _video->getWidth(), _video->getHeight()); _flags = flags; _wizResNum = image; @@ -62,34 +77,59 @@ int MoviePlayer::load(const char *filename, int flags, int image) { } void MoviePlayer::copyFrameToBuffer(byte *dst, int dstType, uint x, uint y, uint pitch) { - uint h = getHeight(); - uint w = getWidth(); + uint h = _video->getHeight(); + uint w = _video->getWidth(); + + const Graphics::Surface *surface = _video->decodeNextFrame(); + + if (!surface) + return; - const Graphics::Surface *surface = decodeNextFrame(); byte *src = (byte *)surface->pixels; - if (hasDirtyPalette()) - _vm->setPaletteFromPtr(getPalette(), 256); + if (_video->hasDirtyPalette()) + _vm->setPaletteFromPtr(_video->getPalette(), 256); if (_vm->_game.features & GF_16BIT_COLOR) { - dst += y * pitch + x * 2; - do { - for (uint i = 0; i < w; i++) { - uint16 color = READ_LE_UINT16(_vm->_hePalettes + _vm->_hePaletteSlot + 768 + src[i] * 2); - switch (dstType) { - case kDstScreen: - WRITE_UINT16(dst + i * 2, color); - break; - case kDstResource: - WRITE_LE_UINT16(dst + i * 2, color); - break; - default: - error("copyFrameToBuffer: Unknown dstType %d", dstType); + if (surface->format.bytesPerPixel == 1) { + dst += y * pitch + x * 2; + do { + for (uint i = 0; i < w; i++) { + uint16 color = READ_LE_UINT16(_vm->_hePalettes + _vm->_hePaletteSlot + 768 + src[i] * 2); + switch (dstType) { + case kDstScreen: + WRITE_UINT16(dst + i * 2, color); + break; + case kDstResource: + WRITE_LE_UINT16(dst + i * 2, color); + break; + default: + error("copyFrameToBuffer: Unknown dstType %d", dstType); + } } - } - dst += pitch; - src += w; - } while (--h); + dst += pitch; + src += w; + } while (--h); + } else { + dst += y * pitch + x * 2; + do { + for (uint i = 0; i < w; i++) { + uint16 color = *((uint16 *)src + i); + switch (dstType) { + case kDstScreen: + WRITE_UINT16(dst + i * 2, color); + break; + case kDstResource: + WRITE_LE_UINT16(dst + i * 2, color); + break; + default: + error("copyFrameToBuffer: Unknown dstType %d", dstType); + } + } + dst += pitch; + src += surface->pitch; + } while (--h); + } } else { dst += y * pitch + x; do { @@ -101,7 +141,7 @@ void MoviePlayer::copyFrameToBuffer(byte *dst, int dstType, uint x, uint y, uint } void MoviePlayer::handleNextFrame() { - if (!isVideoLoaded()) + if (!_video->isVideoLoaded()) return; VirtScreen *pvs = &_vm->_virtscr[kMainVirtScreen]; @@ -115,17 +155,37 @@ void MoviePlayer::handleNextFrame() { } else if (_flags & 1) { copyFrameToBuffer(pvs->getBackPixels(0, 0), kDstScreen, 0, 0, pvs->pitch); - Common::Rect imageRect(getWidth(), getHeight()); + Common::Rect imageRect(_video->getWidth(), _video->getHeight()); _vm->restoreBackgroundHE(imageRect); } else { copyFrameToBuffer(pvs->getPixels(0, 0), kDstScreen, 0, 0, pvs->pitch); - Common::Rect imageRect(getWidth(), getHeight()); + Common::Rect imageRect(_video->getWidth(), _video->getHeight()); _vm->markRectAsDirty(kMainVirtScreen, imageRect); } - if (endOfVideo()) - close(); + if (_video->endOfVideo()) + _video->close(); +} + +void MoviePlayer::close() { + _video->close(); +} + +int MoviePlayer::getWidth() const { + return _video->getWidth(); +} + +int MoviePlayer::getHeight() const { + return _video->getHeight(); +} + +int MoviePlayer::getFrameCount() const { + return _video->getFrameCount(); +} + +int MoviePlayer::getCurFrame() const { + return _video->endOfVideo() ? -1 : _video->getCurFrame() + 1; } } // End of namespace Scumm diff --git a/engines/scumm/he/animation_he.h b/engines/scumm/he/animation_he.h index b3405fead0..7fa31a195d 100644 --- a/engines/scumm/he/animation_he.h +++ b/engines/scumm/he/animation_he.h @@ -23,34 +23,41 @@ #if !defined(SCUMM_HE_ANIMATION_H) && defined(ENABLE_HE) #define SCUMM_HE_ANIMATION_H -#include "video/smk_decoder.h" - #include "audio/mixer.h" +namespace Video { + class VideoDecoder; +} + namespace Scumm { class ScummEngine_v90he; -class MoviePlayer : public Video::SmackerDecoder { - ScummEngine_v90he *_vm; - - Audio::Mixer *_mixer; - - Audio::SoundHandle _bgSound; - Audio::AudioStream *_bgSoundStream; - - char baseName[40]; - uint32 _flags; - uint32 _wizResNum; - +class MoviePlayer { public: MoviePlayer(ScummEngine_v90he *vm, Audio::Mixer *mixer); + ~MoviePlayer(); int getImageNum(); int load(const char *filename, int flags, int image = 0); void copyFrameToBuffer(byte *dst, int dstType, uint x, uint y, uint pitch); void handleNextFrame(); + + void close(); + int getWidth() const; + int getHeight() const; + int getFrameCount() const; + int getCurFrame() const; + +private: + ScummEngine_v90he *_vm; + + Video::VideoDecoder *_video; + + char baseName[40]; + uint32 _flags; + uint32 _wizResNum; }; } // End of namespace Scumm diff --git a/engines/scumm/he/logic_he.cpp b/engines/scumm/he/logic_he.cpp index a7d808e316..af56bca2ee 100644 --- a/engines/scumm/he/logic_he.cpp +++ b/engines/scumm/he/logic_he.cpp @@ -1018,7 +1018,7 @@ int LogicHEsoccer::op_1007(int32 *args) { // Returns the square root of the sum of the squares of the arguments static inline double sqrtSquare(double a1, double a2, double a3) { return sqrt(a1 * a1 + a2 * a2 + a3 * a3); -} +} int LogicHEsoccer::op_1008(int32 *args) { // TODO: Used during a match (kicking?) diff --git a/engines/scumm/he/resource_he.cpp b/engines/scumm/he/resource_he.cpp index 4565bb9f26..39240e347f 100644 --- a/engines/scumm/he/resource_he.cpp +++ b/engines/scumm/he/resource_he.cpp @@ -166,7 +166,7 @@ bool MacResExtractor::extractResource(int id, CachedCursor *cc) { } Common::SeekableReadStream *dataStream = _resMgr->getResource('crsr', id + 1000); - + if (!dataStream) return false; diff --git a/engines/scumm/he/resource_he.h b/engines/scumm/he/resource_he.h index 9978869ffc..6996ce81eb 100644 --- a/engines/scumm/he/resource_he.h +++ b/engines/scumm/he/resource_he.h @@ -61,7 +61,7 @@ private: ResExtractor::CachedCursor *findCachedCursor(int id); ResExtractor::CachedCursor *getCachedCursorSlot(); - + CachedCursor _cursorCache[MAX_CACHED_CURSORS]; }; diff --git a/engines/scumm/he/script_v100he.cpp b/engines/scumm/he/script_v100he.cpp index e057ab524a..5a9172ff8a 100644 --- a/engines/scumm/he/script_v100he.cpp +++ b/engines/scumm/he/script_v100he.cpp @@ -2933,7 +2933,7 @@ void ScummEngine_v100he::o100_getVideoData() { break; case 73: pop(); - push(_moviePlay->endOfVideo() ? -1 : (_moviePlay->getCurFrame() + 1)); + push(_moviePlay->getCurFrame()); break; case 84: pop(); diff --git a/engines/scumm/he/script_v60he.cpp b/engines/scumm/he/script_v60he.cpp index fb070b3e27..dbeee567bf 100644 --- a/engines/scumm/he/script_v60he.cpp +++ b/engines/scumm/he/script_v60he.cpp @@ -94,6 +94,12 @@ int ScummEngine_v60he::convertFilePath(byte *dst, int dstSize) { debug(1, "convertFilePath: original filePath is %s", dst); int len = resStrLen(dst); + + // Switch all \ to / for portablity + for (int i = 0; i < len; i++) + if (dst[i] == '\\') + dst[i] = '/'; + if (_game.platform == Common::kPlatformMacintosh) { // Remove : prefix in HE71 games if (dst[0] == ':') { @@ -107,12 +113,6 @@ int ScummEngine_v60he::convertFilePath(byte *dst, int dstSize) { if (dst[i] == ':') dst[i] = '/'; } - } else { - // Switch all \ to / for portablity - for (int i = 0; i < len; i++) { - if (dst[i] == '\\') - dst[i] = '/'; - } } // Strip path @@ -744,7 +744,7 @@ void ScummEngine_v60he::o60_closeFile() { _hOutFileTable[slot] = 0; } - delete _hInFileTable[slot]; + delete _hInFileTable[slot]; _hInFileTable[slot] = 0; } } diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp index 8f16bf0f3a..5af4035930 100644 --- a/engines/scumm/he/script_v72he.cpp +++ b/engines/scumm/he/script_v72he.cpp @@ -1446,7 +1446,7 @@ void ScummEngine_v72he::o72_openFile() { _hOutFileTable[slot]->write(initialData, initialSize); delete[] initialData; } - + } break; default: error("o72_openFile(): wrong open file mode %d", mode); diff --git a/engines/scumm/he/script_v90he.cpp b/engines/scumm/he/script_v90he.cpp index 6b632d8ff2..66a0a34d16 100644 --- a/engines/scumm/he/script_v90he.cpp +++ b/engines/scumm/he/script_v90he.cpp @@ -1460,7 +1460,7 @@ void ScummEngine_v90he::o90_getVideoData() { break; case 52: // Get current frame pop(); - push(_moviePlay->endOfVideo() ? -1 : (_moviePlay->getCurFrame() + 1)); + push(_moviePlay->getCurFrame()); break; case 63: // Get image number pop(); diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index 7d971f5ca4..27a72c2afe 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -26,6 +26,7 @@ #include "common/util.h" #include "common/system.h" +#include "common/endian.h" #include "scumm/imuse/imuse.h" #include "scumm/imuse/imuse_internal.h" @@ -43,30 +44,31 @@ namespace Scumm { //////////////////////////////////////// IMuseInternal::IMuseInternal() : -_native_mt32(false), -_enable_gs(false), -_sc55(false), -_midi_adlib(NULL), -_midi_native(NULL), -_sysex(NULL), -_paused(false), -_initialized(false), -_tempoFactor(0), -_player_limit(ARRAYSIZE(_players)), -_recycle_players(false), -_queue_end(0), -_queue_pos(0), -_queue_sound(0), -_queue_adding(0), -_queue_marker(0), -_queue_cleared(0), -_master_volume(0), -_music_volume(0), -_trigger_count(0), -_snm_trigger_index(0) { - memset(_channel_volume,0,sizeof(_channel_volume)); - memset(_channel_volume_eff,0,sizeof(_channel_volume_eff)); - memset(_volchan_table,0,sizeof(_volchan_table)); + _native_mt32(false), + _enable_gs(false), + _sc55(false), + _midi_adlib(NULL), + _midi_native(NULL), + _sysex(NULL), + _paused(false), + _initialized(false), + _tempoFactor(0), + _player_limit(ARRAYSIZE(_players)), + _recycle_players(false), + _queue_end(0), + _queue_pos(0), + _queue_sound(0), + _queue_adding(0), + _queue_marker(0), + _queue_cleared(0), + _master_volume(0), + _music_volume(0), + _trigger_count(0), + _snm_trigger_index(0), + _pcSpeaker(false) { + memset(_channel_volume, 0, sizeof(_channel_volume)); + memset(_channel_volume_eff, 0, sizeof(_channel_volume_eff)); + memset(_volchan_table, 0, sizeof(_volchan_table)); } IMuseInternal::~IMuseInternal() { @@ -99,9 +101,15 @@ IMuseInternal::~IMuseInternal() { } } -byte *IMuseInternal::findStartOfSound(int sound, int ct) { +byte *IMuseInternal::findStartOfSound(int sound, int ct) { int32 size, pos; - static uint32 id[] = { 'MThd', 'FORM', 'MDhd', 'MDpg' }; + + static const uint32 id[] = { + MKTAG('M', 'T', 'h', 'd'), + MKTAG('F', 'O', 'R', 'M'), + MKTAG('M', 'D', 'h', 'd'), + MKTAG('M', 'D', 'p', 'g') + }; byte *ptr = g_scumm->_res->_types[rtSound][sound]._address; @@ -112,7 +120,7 @@ byte *IMuseInternal::findStartOfSound(int sound, int ct) { // Check for old-style headers first, like 'RO' int trFlag = (kMThd | kFORM); - if (ptr[0] == 'R' && ptr[1] == 'O'&& ptr[2] != 'L') + if (ptr[0] == 'R' && ptr[1] == 'O' && ptr[2] != 'L') return ct == trFlag ? ptr : 0; if (ptr[4] == 'S' && ptr[5] == 'O') return ct == trFlag ? ptr + 4 : 0; @@ -146,22 +154,22 @@ bool IMuseInternal::isMT32(int sound) { uint32 tag = READ_BE_UINT32(ptr); switch (tag) { - case MKTAG('A','D','L',' '): - case MKTAG('A','S','F','X'): // Special AD class for old AdLib sound effects - case MKTAG('S','P','K',' '): + case MKTAG('A', 'D', 'L', ' '): + case MKTAG('A', 'S', 'F', 'X'): // Special AD class for old AdLib sound effects + case MKTAG('S', 'P', 'K', ' '): return false; - case MKTAG('A','M','I',' '): - case MKTAG('R','O','L',' '): + case MKTAG('A', 'M', 'I', ' '): + case MKTAG('R', 'O', 'L', ' '): return true; - case MKTAG('M','A','C',' '): // Occurs in the Mac version of FOA and MI2 + case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2 return true; - case MKTAG('G','M','D',' '): + case MKTAG('G', 'M', 'D', ' '): return false; - case MKTAG('M','I','D','I'): // Occurs in Sam & Max + case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max // HE games use Roland music if (ptr[8] == 'H' && ptr[9] == 'S') return true; @@ -188,20 +196,20 @@ bool IMuseInternal::isMIDI(int sound) { uint32 tag = READ_BE_UINT32(ptr); switch (tag) { - case MKTAG('A','D','L',' '): - case MKTAG('A','S','F','X'): // Special AD class for old AdLib sound effects - case MKTAG('S','P','K',' '): + case MKTAG('A', 'D', 'L', ' '): + case MKTAG('A', 'S', 'F', 'X'): // Special AD class for old AdLib sound effects + case MKTAG('S', 'P', 'K', ' '): return false; - case MKTAG('A','M','I',' '): - case MKTAG('R','O','L',' '): + case MKTAG('A', 'M', 'I', ' '): + case MKTAG('R', 'O', 'L', ' '): return true; - case MKTAG('M','A','C',' '): // Occurs in the Mac version of FOA and MI2 + case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2 return true; - case MKTAG('G','M','D',' '): - case MKTAG('M','I','D','I'): // Occurs in Sam & Max + case MKTAG('G', 'M', 'D', ' '): + case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max return true; } @@ -374,7 +382,8 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { for (i = 0; i < ARRAYSIZE(_parts); ++i) _parts[i].saveLoadWithSerializer(ser); - { // Load/save the instrument definitions, which were revamped with V11. + { + // Load/save the instrument definitions, which were revamped with V11. Part *part = &_parts[0]; if (ser->getVersion() >= VER(11)) { for (i = ARRAYSIZE(_parts); i; --i, ++part) { @@ -460,6 +469,10 @@ uint32 IMuseInternal::property(int prop, uint32 value) { case IMuse::PROP_GAME_ID: _game_id = value; break; + + case IMuse::PROP_PC_SPEAKER: + _pcSpeaker = (value != 0); + break; } return 0; @@ -515,7 +528,7 @@ void IMuseInternal::stopAllSounds() { int IMuseInternal::getSoundStatus(int sound) const { Common::StackLock lock(_mutex, "IMuseInternal::getSoundStatus()"); - return getSoundStatus_internal (sound, true); + return getSoundStatus_internal(sound, true); } int IMuseInternal::getMusicTimer() { @@ -558,7 +571,7 @@ bool IMuseInternal::startSound_internal(int sound, int offset) { int i; ImTrigger *trigger = _snm_triggers; for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trigger) { - if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus_internal (trigger->sound,true)) + if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus_internal(trigger->sound, true)) return false; } @@ -656,9 +669,7 @@ int IMuseInternal::getSoundStatus_internal(int sound, bool ignoreFadeouts) const return (sound == -1) ? 0 : get_queue_sound_status(sound); } -int32 IMuseInternal::doCommand_internal - (int a, int b, int c, int d, int e, int f, int g, int h) -{ +int32 IMuseInternal::doCommand_internal(int a, int b, int c, int d, int e, int f, int g, int h) { int args[8]; args[0] = a; args[1] = b; @@ -726,7 +737,7 @@ int32 IMuseInternal::doCommand_internal(int numargs, int a[]) { } return -1; case 13: - return getSoundStatus_internal (a[1], true); + return getSoundStatus_internal(a[1], true); case 14: // Sam and Max: Parameter fade player = findActivePlayer(a[1]); @@ -772,8 +783,7 @@ int32 IMuseInternal::doCommand_internal(int numargs, int a[]) { a[0] = 0; for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) { if (_snm_triggers[i].sound == a[1] && _snm_triggers[i].id && - (a[3] == -1 || _snm_triggers[i].id == a[3])) - { + (a[3] == -1 || _snm_triggers[i].id == a[3])) { ++a[0]; } } @@ -945,7 +955,7 @@ void IMuseInternal::handle_marker(uint id, byte data) { _trigger_count--; _queue_cleared = false; _queue_end = (_queue_end + 1) % ARRAYSIZE(_cmd_queue); - + while (_queue_end != _queue_pos && _cmd_queue[_queue_end].array[0] == COMMAND_ID && !_queue_cleared) { p = _cmd_queue[_queue_end].array; doCommand_internal(p[1], p[2], p[3], p[4], p[5], p[6], p[7], 0); @@ -995,9 +1005,9 @@ int IMuseInternal::get_queue_sound_status(int sound) const { i = (i + 1) % ARRAYSIZE(_cmd_queue); } - for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) { + for (i = 0; i < ARRAYSIZE(_deferredCommands); ++i) { if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 && - _deferredCommands[i].b == sound) { + _deferredCommands[i].b == sound) { return 2; } } @@ -1206,7 +1216,7 @@ int32 IMuseInternal::ImSetTrigger(int sound, int id, int a, int b, int c, int d, // NOTE: We ONLY do this if the sound that will trigger the command is actually // playing. Otherwise, there's a problem when exiting and re-entering the // Bumpusville mansion. Ref Bug #780918. - if (trig->command[0] == 8 && getSoundStatus_internal(trig->command[1],true) && getSoundStatus_internal(sound,true)) + if (trig->command[0] == 8 && getSoundStatus_internal(trig->command[1], true) && getSoundStatus_internal(sound, true)) stopSound_internal(trig->command[1]); return 0; } @@ -1239,8 +1249,7 @@ int32 IMuseInternal::ImFireAllTriggers(int sound) { return (count > 0) ? 0 : -1; } -int IMuseInternal::set_channel_volume(uint chan, uint vol) -{ +int IMuseInternal::set_channel_volume(uint chan, uint vol) { if (chan >= 8 || vol > 127) return -1; @@ -1420,7 +1429,7 @@ void IMuseInternal::initMT32(MidiDriver *midi) { // Display a welcome message on MT-32 displays. memcpy(&buffer[0], "\x41\x10\x16\x12\x20\x00\x00", 7); memcpy(&buffer[7], " ", 20); - memcpy(buffer + 7 +(20 - len) / 2, info, len); + memcpy(buffer + 7 + (20 - len) / 2, info, len); byte checksum = 0; for (int i = 4; i < 27; ++i) checksum -= buffer[i]; @@ -1466,9 +1475,9 @@ void IMuseInternal::initGM(MidiDriver *midi) { // Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation for (i = 0; i < 16; ++i) { - midi->send(( 127 << 16) | (0 << 8) | (0xB0 | i)); - midi->send(( 1 << 16) | (32 << 8) | (0xB0 | i)); - midi->send(( 0 << 16) | (0 << 8) | (0xC0 | i)); + midi->send((127 << 16) | (0 << 8) | (0xB0 | i)); + midi->send((1 << 16) | (32 << 8) | (0xB0 | i)); + midi->send((0 << 16) | (0 << 8) | (0xC0 | i)); } debug(2, "GS Program Change: CM-64/32L Map Selected"); @@ -1489,7 +1498,7 @@ void IMuseInternal::initGM(MidiDriver *midi) { // Set Channels 1-16 Reverb to 64, which is the // equivalent of MT-32 default Reverb Level 5 for (i = 0; i < 16; ++i) - midi->send(( 64 << 16) | (91 << 8) | (0xB0 | i)); + midi->send((64 << 16) | (91 << 8) | (0xB0 | i)); debug(2, "GM Controller 91 Change: Channels 1-16 Reverb Level is 64"); // Set Channels 1-16 Pitch Bend Sensitivity to @@ -1630,8 +1639,8 @@ void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) { hipart = NULL; for (i = 32, part = _parts; i; i--, part++) { if (part->_player && part->_player->getMidiDriver() == midi && - !part->_percussion && part->_on && - !part->_mc && part->_pri_eff >= hipri) { + !part->_percussion && part->_on && + !part->_mc && part->_pri_eff >= hipri) { hipri = part->_pri_eff; hipart = part; } @@ -1661,16 +1670,35 @@ void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) { } } -void IMuseInternal::setGlobalAdLibInstrument(byte slot, byte *data) { +void IMuseInternal::setGlobalInstrument(byte slot, byte *data) { if (slot < 32) { - _global_adlib_instruments[slot].adlib(data); + if (_pcSpeaker) + _global_instruments[slot].pcspk(data); + else + _global_instruments[slot].adlib(data); } } -void IMuseInternal::copyGlobalAdLibInstrument(byte slot, Instrument *dest) { +void IMuseInternal::copyGlobalInstrument(byte slot, Instrument *dest) { if (slot >= 32) return; - _global_adlib_instruments[slot].copy_to(dest); + + // Both the AdLib code and the PC Speaker code use an all zero instrument + // as default in the original, thus we do the same. + // PC Speaker instrument size is 23, while AdLib instrument size is 30. + // Thus we just use a 30 byte instrument data array as default. + const byte defaultInstr[30] = { 0 }; + + if (_global_instruments[slot].isValid()) { + // In case we have an valid instrument set up, copy it to the part. + _global_instruments[slot].copy_to(dest); + } else if (_pcSpeaker) { + debug(0, "Trying to use non-existant global PC Speaker instrument %d", slot); + dest->pcspk(defaultInstr); + } else { + debug(0, "Trying to use non-existant global AdLib instrument %d", slot); + dest->adlib(defaultInstr); + } } diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h index 8014b13409..23449e470b 100644 --- a/engines/scumm/imuse/imuse.h +++ b/engines/scumm/imuse/imuse.h @@ -37,7 +37,7 @@ class Player; class ScummEngine; class Serializer; -typedef void (*sysexfunc) (Player *, const byte *, uint16); +typedef void (*sysexfunc)(Player *, const byte *, uint16); /** * iMuse implementation interface. @@ -55,7 +55,8 @@ public: PROP_GS, PROP_LIMIT_PLAYERS, PROP_RECYCLE_PLAYERS, - PROP_GAME_ID + PROP_GAME_ID, + PROP_PC_SPEAKER }; public: @@ -66,7 +67,7 @@ public: virtual int32 doCommand(int numargs, int args[]) = 0; virtual int clear_queue() = 0; virtual uint32 property(int prop, uint32 value) = 0; - virtual void addSysexHandler (byte mfgID, sysexfunc handler) = 0; + virtual void addSysexHandler(byte mfgID, sysexfunc handler) = 0; public: virtual void startSoundWithNoteOffset(int sound, int offset) = 0; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 8808a3655a..3b0d36e119 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -135,7 +135,7 @@ struct ImTrigger { int sound; byte id; uint16 expire; - int command [8]; + int command[8]; ImTrigger() { memset(this, 0, sizeof(ImTrigger)); } }; @@ -153,12 +153,12 @@ struct CommandQueue { ////////////////////////////////////////////////// class Player : public MidiDriver_BASE { -/* - * External SysEx handler functions shall each be defined in - * a separate file. This header file shall be included at the - * top of the file immediately following this special #define: - * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction - */ + /* + * External SysEx handler functions shall each be defined in + * a separate file. This header file shall be included at the + * top of the file immediately following this special #define: + * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction + */ #ifdef SYSEX_CALLBACK_FUNCTION friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16); #endif @@ -244,7 +244,7 @@ public: void clear(); void clearLoop(); void fixAfterLoad(); - Part * getActivePart(uint8 part); + Part *getActivePart(uint8 part); uint getBeatIndex(); int8 getDetune() const { return _detune; } byte getEffectiveVolume() const { return _vol_eff; } @@ -252,7 +252,7 @@ public: MidiDriver *getMidiDriver() const { return _midi; } int getParam(int param, byte chan); int8 getPan() const { return _pan; } - Part * getPart(uint8 part); + Part *getPart(uint8 part); byte getPriority() const { return _priority; } uint getTicksPerBeat() const { return TICKS_PER_BEAT; } int8 getTranspose() const { return _transpose; } @@ -342,6 +342,7 @@ struct Part : public Serializable { void off(); void set_instrument(uint b); void set_instrument(byte *data); + void set_instrument_pcspk(byte *data); void load_global_instrument(byte b); void set_transpose(int8 transpose); @@ -375,12 +376,12 @@ class IMuseInternal : public IMuse { friend class Player; friend struct Part; -/* - * External SysEx handler functions shall each be defined in - * a separate file. This header file shall be included at the - * top of the file immediately following this special #define: - * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction - */ + /* + * External SysEx handler functions shall each be defined in + * a separate file. This header file shall be included at the + * top of the file immediately following this special #define: + * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction + */ #ifdef SYSEX_CALLBACK_FUNCTION friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16); #endif @@ -433,7 +434,8 @@ protected: Player _players[8]; Part _parts[32]; - Instrument _global_adlib_instruments[32]; + bool _pcSpeaker; + Instrument _global_instruments[32]; CommandQueue _cmd_queue[64]; DeferredCommand _deferredCommands[4]; @@ -449,8 +451,8 @@ protected: enum ChunkType { kMThd = 1, kFORM = 2, - kMDhd = 4, // Used in MI2 and INDY4. Contain certain start parameters (priority, volume, etc. ) for the player. - kMDpg = 8 // These chunks exist in DOTT and SAMNMAX. They don't get processed, however. + kMDhd = 4, // Used in MI2 and INDY4. Contain certain start parameters (priority, volume, etc. ) for the player. + kMDpg = 8 // These chunks exist in DOTT and SAMNMAX. They don't get processed, however. }; byte *findStartOfSound(int sound, int ct = (kMThd | kFORM)); @@ -498,8 +500,8 @@ protected: int setImuseMasterVolume(uint vol); void reallocateMidiChannels(MidiDriver *midi); - void setGlobalAdLibInstrument(byte slot, byte *data); - void copyGlobalAdLibInstrument(byte slot, Instrument *dest); + void setGlobalInstrument(byte slot, byte *data); + void copyGlobalInstrument(byte slot, Instrument *dest); bool isNativeMT32() { return _native_mt32; } protected: diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp index 5df8407a96..73e7704469 100644 --- a/engines/scumm/imuse/imuse_part.cpp +++ b/engines/scumm/imuse/imuse_part.cpp @@ -193,14 +193,18 @@ void Part::set_onoff(bool on) { } } -void Part::set_instrument(byte * data) { - _instrument.adlib(data); +void Part::set_instrument(byte *data) { + if (_se->_pcSpeaker) + _instrument.pcspk(data); + else + _instrument.adlib(data); + if (clearToTransmit()) _instrument.send(_mc); } void Part::load_global_instrument(byte slot) { - _player->_se->copyGlobalAdLibInstrument(slot, &_instrument); + _player->_se->copyGlobalInstrument(slot, &_instrument); if (clearToTransmit()) _instrument.send(_mc); } @@ -234,7 +238,7 @@ void Part::noteOn(byte note, byte velocity) { // should be implemented as a class static var. As it is, using // a function level static var in most cases is arcane and evil. static byte prev_vol_eff = 128; - if (_vol_eff != prev_vol_eff){ + if (_vol_eff != prev_vol_eff) { mc->volume(_vol_eff); prev_vol_eff = _vol_eff; } diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp index 0b084f3116..73be2174cd 100644 --- a/engines/scumm/imuse/imuse_player.cpp +++ b/engines/scumm/imuse/imuse_player.cpp @@ -79,7 +79,7 @@ Player::Player() : _isMT32(false), _isMIDI(false), _se(0), - _vol_chan(0){ + _vol_chan(0) { } Player::~Player() { @@ -120,7 +120,7 @@ bool Player::startSound(int sound, MidiDriver *midi) { _midi = NULL; return false; } - + debugC(DEBUG_IMUSE, "Starting music %d", sound); return true; } @@ -133,7 +133,7 @@ bool Player::isFadingOut() const { int i; for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) { if (_parameterFaders[i].param == ParameterFader::pfVolume && - _parameterFaders[i].end == 0) { + _parameterFaders[i].end == 0) { return true; } } @@ -194,7 +194,7 @@ int Player::start_seq_sound(int sound, bool reset_vars) { _parser->property(MidiParser::mpSmartJump, 1); _parser->loadMusic(ptr, 0); _parser->setTrack(_track_index); - + ptr = _se->findStartOfSound(sound, IMuseInternal::kMDhd); setSpeed(reset_vars ? (ptr ? (READ_BE_UINT32(&ptr[4]) && ptr[15] ? ptr[15] : 128) : 128) : _speed); @@ -226,7 +226,7 @@ void Player::loadStartParameters(int sound) { _pan = ptr[4]; _transpose = ptr[5]; _detune = ptr[6]; - setSpeed(ptr[7]); + setSpeed(ptr[7]); } } } @@ -371,11 +371,13 @@ void Player::sysEx(const byte *p, uint16 len) { if (a != IMUSE_SYSEX_ID) { if (a == ROLAND_SYSEX_ID) { // Roland custom instrument definition. - part = getPart(p[0] & 0x0F); - if (part) { - part->_instrument.roland(p - 1); - if (part->clearToTransmit()) - part->_instrument.send(part->_mc); + if (_isMIDI || _isMT32) { + part = getPart(p[0] & 0x0F); + if (part) { + part->_instrument.roland(p - 1); + if (part->clearToTransmit()) + part->_instrument.send(part->_mc); + } } } else if (a == YM2612_SYSEX_ID) { // FM-TOWNS custom instrument definition @@ -399,13 +401,13 @@ void Player::sysEx(const byte *p, uint16 len) { if (!_scanning) { for (a = 0; a < len + 1 && a < 19; ++a) { - sprintf((char *)&buf[a*3], " %02X", p[a]); + sprintf((char *)&buf[a * 3], " %02X", p[a]); } // next for if (a < len + 1) { - buf[a*3] = buf[a*3+1] = buf[a*3+2] = '.'; + buf[a * 3] = buf[a * 3 + 1] = buf[a * 3 + 2] = '.'; ++a; } // end if - buf[a*3] = '\0'; + buf[a * 3] = '\0'; debugC(DEBUG_IMUSE, "[%02d] SysEx:%s", _id, buf); } @@ -814,7 +816,7 @@ int Player::query_part_param(int param, byte chan) { return part->_vol; case 16: // FIXME: Need to know where this occurs... -error("Trying to cast instrument (%d, %d) -- please tell Fingolfin", param, chan); + error("Trying to cast instrument (%d, %d) -- please tell Fingolfin", param, chan); // In old versions of the code, this used to return part->_program. // This was changed in revision 2.29 of imuse.cpp (where this code used // to reside). @@ -845,9 +847,8 @@ void Player::onTimer() { uint beat_index = target_tick / TICKS_PER_BEAT + 1; uint tick_index = target_tick % TICKS_PER_BEAT; - if (_loop_counter &&(beat_index > _loop_from_beat || - (beat_index == _loop_from_beat && tick_index >= _loop_from_tick))) - { + if (_loop_counter && (beat_index > _loop_from_beat || + (beat_index == _loop_from_beat && tick_index >= _loop_from_tick))) { _loop_counter--; jump(_track_index, _loop_to_beat, _loop_to_tick); } @@ -891,15 +892,15 @@ int Player::addParameterFader(int param, int target, int time) { // target = target * 128 / 100; break; - case 127: - { // FIXME? I *think* this clears all parameter faders. - ParameterFader *ptr = &_parameterFaders[0]; - int i; - for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) - ptr->param = 0; - return 0; - } - break; + case 127: { + // FIXME? I *think* this clears all parameter faders. + ParameterFader *ptr = &_parameterFaders[0]; + int i; + for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr) + ptr->param = 0; + return 0; + } + break; default: debug(0, "Player::addParameterFader(%d, %d, %d): Unknown parameter", param, target, time); @@ -1085,7 +1086,7 @@ void Player::saveLoadWithSerializer(Serializer *ser) { } ser->saveLoadEntries(this, playerEntries); ser->saveLoadArrayOf(_parameterFaders, ARRAYSIZE(_parameterFaders), - sizeof(ParameterFader), parameterFaderEntries); + sizeof(ParameterFader), parameterFaderEntries); return; } diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp index 955700fc2b..11bb4e7605 100644 --- a/engines/scumm/imuse/instrument.cpp +++ b/engines/scumm/imuse/instrument.cpp @@ -114,14 +114,15 @@ roland_to_gm_map[] = { // { "trickle4 ", ??? } }; +// This emulates the percussion bank setup LEC used with the MT-32, +// where notes 24 - 34 were assigned instruments without reverb. +// It also fixes problems on GS devices that map sounds to these +// notes by default. const byte Instrument::_gmRhythmMap[35] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47, - 65, 48, 56}; - // This emulates the percussion bank setup LEC used with the MT-32, - // where notes 24 - 34 were assigned instruments without reverb. - // It also fixes problems on GS devices that map sounds to these - // notes by default. + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47, + 65, 48, 56 +}; class Instrument_Program : public InstrumentInternal { private: @@ -136,15 +137,16 @@ public: void copy_to(Instrument *dest) { dest->program(_program, _mt32); } bool is_valid() { return (_program < 128) && - ((_native_mt32 == _mt32) || _native_mt32 - ? (MidiDriver::_gmToMt32[_program] < 128) - : (MidiDriver::_mt32ToGm[_program] < 128)); } + ((_native_mt32 == _mt32) || _native_mt32 + ? (MidiDriver::_gmToMt32[_program] < 128) + : (MidiDriver::_mt32ToGm[_program] < 128)); + } }; class Instrument_AdLib : public InstrumentInternal { private: -#include "common/pack-start.h" // START STRUCT PACKING +#include "common/pack-start.h" // START STRUCT PACKING struct AdLibInstrument { byte flags_1; @@ -159,13 +161,17 @@ private: byte waveform_2; byte feedback; byte flags_a; - struct { byte a,b,c,d,e,f,g,h; } extra_a; + struct { + byte a, b, c, d, e, f, g, h; + } extra_a; byte flags_b; - struct { byte a,b,c,d,e,f,g,h; } extra_b; + struct { + byte a, b, c, d, e, f, g, h; + } extra_b; byte duration; } PACKED_STRUCT; -#include "common/pack-end.h" // END STRUCT PACKING +#include "common/pack-end.h" // END STRUCT PACKING AdLibInstrument _instrument; @@ -181,7 +187,7 @@ public: class Instrument_Roland : public InstrumentInternal { private: -#include "common/pack-start.h" // START STRUCT PACKING +#include "common/pack-start.h" // START STRUCT PACKING struct RolandInstrument { byte roland_id; @@ -242,11 +248,11 @@ private: byte checksum; } PACKED_STRUCT; -#include "common/pack-end.h" // END STRUCT PACKING +#include "common/pack-end.h" // END STRUCT PACKING RolandInstrument _instrument; - char _instrument_name [11]; + char _instrument_name[11]; uint8 getEquivalentGM(); @@ -259,6 +265,19 @@ public: bool is_valid() { return (_native_mt32 ? true : (_instrument_name[0] != '\0')); } }; +class Instrument_PcSpk : public InstrumentInternal { +public: + Instrument_PcSpk(const byte *data); + Instrument_PcSpk(Serializer *s); + void saveOrLoad(Serializer *s); + void send(MidiChannel *mc); + void copy_to(Instrument *dest) { dest->pcspk((byte *)&_instrument); } + bool is_valid() { return true; } + +private: + byte _instrument[23]; +}; + //////////////////////////////////////// // // Instrument class members @@ -299,7 +318,15 @@ void Instrument::roland(const byte *instrument) { _instrument = new Instrument_Roland(instrument); } -void Instrument::saveOrLoad (Serializer *s) { +void Instrument::pcspk(const byte *instrument) { + clear(); + if (!instrument) + return; + _type = itPcSpk; + _instrument = new Instrument_PcSpk(instrument); +} + +void Instrument::saveOrLoad(Serializer *s) { if (s->isSaving()) { s->saveByte(_type); if (_instrument) @@ -319,6 +346,9 @@ void Instrument::saveOrLoad (Serializer *s) { case itRoland: _instrument = new Instrument_Roland(s); break; + case itPcSpk: + _instrument = new Instrument_PcSpk(s); + break; default: warning("No known instrument classification #%d", (int)_type); _type = itNone; @@ -333,8 +363,8 @@ void Instrument::saveOrLoad (Serializer *s) { //////////////////////////////////////// Instrument_Program::Instrument_Program(byte program, bool mt32) : -_program (program), -_mt32 (mt32) { + _program(program), + _mt32(mt32) { if (program > 127) _program = 255; } @@ -413,7 +443,7 @@ Instrument_Roland::Instrument_Roland(const byte *data) { Instrument_Roland::Instrument_Roland(Serializer *s) { _instrument_name[0] = '\0'; if (!s->isSaving()) - saveOrLoad (s); + saveOrLoad(s); else memset(&_instrument, 0, sizeof(_instrument)); } @@ -470,4 +500,32 @@ uint8 Instrument_Roland::getEquivalentGM() { return 255; } +//////////////////////////////////////// +// +// Instrument_PcSpk class members +// +//////////////////////////////////////// + +Instrument_PcSpk::Instrument_PcSpk(const byte *data) { + memcpy(_instrument, data, sizeof(_instrument)); +} + +Instrument_PcSpk::Instrument_PcSpk(Serializer *s) { + if (!s->isSaving()) + saveOrLoad(s); + else + memset(_instrument, 0, sizeof(_instrument)); +} + +void Instrument_PcSpk::saveOrLoad(Serializer *s) { + if (s->isSaving()) + s->saveBytes(_instrument, sizeof(_instrument)); + else + s->loadBytes(_instrument, sizeof(_instrument)); +} + +void Instrument_PcSpk::send(MidiChannel *mc) { + mc->sysEx_customInstrument('SPK ', (byte *)&_instrument); +} + } // End of namespace Scumm diff --git a/engines/scumm/imuse/instrument.h b/engines/scumm/imuse/instrument.h index 3555d319e6..a855c64155 100644 --- a/engines/scumm/imuse/instrument.h +++ b/engines/scumm/imuse/instrument.h @@ -39,7 +39,6 @@ public: virtual void send(MidiChannel *mc) = 0; virtual void copy_to(Instrument *dest) = 0; virtual bool is_valid() = 0; - virtual operator int() { return 255; } }; class Instrument { @@ -52,10 +51,11 @@ public: itNone = 0, itProgram = 1, itAdLib = 2, - itRoland = 3 + itRoland = 3, + itPcSpk = 4 }; - Instrument() : _type (0), _instrument (0) { } + Instrument() : _type(0), _instrument(0) { } ~Instrument() { delete _instrument; } static void nativeMT32(bool native); static const byte _gmRhythmMap[35]; @@ -71,6 +71,7 @@ public: void program(byte program, bool mt32); void adlib(const byte *instrument); void roland(const byte *instrument); + void pcspk(const byte *instrument); byte getType() { return _type; } bool isValid() { return (_instrument ? _instrument->is_valid() : false); } diff --git a/engines/scumm/imuse/pcspk.cpp b/engines/scumm/imuse/pcspk.cpp new file mode 100644 index 0000000000..01e2ab3b7d --- /dev/null +++ b/engines/scumm/imuse/pcspk.cpp @@ -0,0 +1,835 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "scumm/imuse/pcspk.h" + +#include "common/util.h" + +namespace Scumm { + +PcSpkDriver::PcSpkDriver(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _pcSpk(mixer->getOutputRate()) { +} + +PcSpkDriver::~PcSpkDriver() { + close(); +} + +int PcSpkDriver::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + for (uint i = 0; i < 6; ++i) + _channels[i].init(this, i); + _activeChannel = 0; + _effectTimer = 0; + _randBase = 1; + + // We need to take care we only send note frequencies, when the internal + // settings actually changed, thus we need some extra state to keep track + // of that. + _lastActiveChannel = 0; + _lastActiveOut = 0; + + // We set the output sound type to music here to allow sound volume + // adjustment. The drawback here is that we can not control the music and + // sfx separately here. But the AdLib output has the same issue so it + // should not be that bad. + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + return 0; +} + +void PcSpkDriver::close() { + if (!_isOpen) + return; + _isOpen = false; + + _mixer->stopHandle(_mixerSoundHandle); +} + +void PcSpkDriver::send(uint32 d) { + assert((d & 0x0F) < 6); + _channels[(d & 0x0F)].send(d); +} + +void PcSpkDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { + assert(channel < 6); + if (type == 'SPK ') + _channels[channel].sysEx_customInstrument(type, instr); +} + +MidiChannel *PcSpkDriver::allocateChannel() { + for (uint i = 0; i < 6; ++i) { + if (_channels[i].allocate()) + return &_channels[i]; + } + + return 0; +} + +void PcSpkDriver::generateSamples(int16 *buf, int len) { + _pcSpk.readBuffer(buf, len); +} + +void PcSpkDriver::onTimer() { + if (!_activeChannel) + return; + + for (uint i = 0; i < 6; ++i) { + OutputChannel &out = _channels[i]._out; + + if (!out.active) + continue; + + if (out.length == 0 || --out.length != 0) { + if (out.unkB && out.unkC) { + out.unkA += out.unkB; + if (out.instrument) + out.unkE = ((int8)out.instrument[out.unkA] * out.unkC) >> 4; + } + + ++_effectTimer; + if (_effectTimer > 3) { + _effectTimer = 0; + + if (out.effectEnvelopeA.state) + updateEffectGenerator(_channels[i], out.effectEnvelopeA, out.effectDefA); + if (out.effectEnvelopeB.state) + updateEffectGenerator(_channels[i], out.effectEnvelopeB, out.effectDefB); + } + } else { + out.active = 0; + updateNote(); + return; + } + } + + if (_activeChannel->_tl) { + output((_activeChannel->_out.note << 7) + _activeChannel->_pitchBend + _activeChannel->_out.unk60 + _activeChannel->_out.unkE); + } else { + _pcSpk.stop(); + _lastActiveChannel = 0; + _lastActiveOut = 0; + } +} + +void PcSpkDriver::updateNote() { + uint8 priority = 0; + _activeChannel = 0; + for (uint i = 0; i < 6; ++i) { + if (_channels[i]._allocated && _channels[i]._out.active && _channels[i]._priority >= priority) { + priority = _channels[i]._priority; + _activeChannel = &_channels[i]; + } + } + + if (_activeChannel == 0 || _activeChannel->_tl == 0) { + _pcSpk.stop(); + _lastActiveChannel = 0; + _lastActiveOut = 0; + } else { + output(_activeChannel->_pitchBend + (_activeChannel->_out.note << 7)); + } +} + +void PcSpkDriver::output(uint16 out) { + byte v1 = (out >> 7) & 0xFF; + byte v2 = (out >> 2) & 0x1E; + + byte shift = _outputTable1[v1]; + uint16 indexBase = _outputTable2[v1] << 5; + uint16 frequency = _frequencyTable[(indexBase + v2) / 2] >> shift; + + // Only output in case the active channel changed or the frequency changed. + // This is not faithful to the original. Since our timings differ we would + // get distorted sound otherwise though. + if (_lastActiveChannel != _activeChannel || _lastActiveOut != out) { + _pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1); + _lastActiveChannel = _activeChannel; + _lastActiveOut = out; + } +} + +void PcSpkDriver::MidiChannel_PcSpk::init(PcSpkDriver *owner, byte channel) { + _owner = owner; + _channel = channel; + _allocated = false; + memset(&_out, 0, sizeof(_out)); +} + +bool PcSpkDriver::MidiChannel_PcSpk::allocate() { + if (_allocated) + return false; + + memset(&_out, 0, sizeof(_out)); + memset(_instrument, 0, sizeof(_instrument)); + _out.effectDefA.envelope = &_out.effectEnvelopeA; + _out.effectDefB.envelope = &_out.effectEnvelopeB; + + _allocated = true; + return true; +} + +MidiDriver *PcSpkDriver::MidiChannel_PcSpk::device() { + return _owner; +} + +byte PcSpkDriver::MidiChannel_PcSpk::getNumber() { + return _channel; +} + +void PcSpkDriver::MidiChannel_PcSpk::release() { + _out.active = 0; + _allocated = false; + _owner->updateNote(); +} + +void PcSpkDriver::MidiChannel_PcSpk::send(uint32 b) { + uint8 type = b & 0xF0; + uint8 p1 = (b >> 8) & 0xFF; + uint8 p2 = (b >> 16) & 0xFF; + + switch (type) { + case 0x80: + noteOff(p1); + break; + + case 0x90: + if (p2) + noteOn(p1, p2); + else + noteOff(p1); + break; + + case 0xB0: + controlChange(p1, p2); + break; + + case 0xE0: + pitchBend((p1 | (p2 << 7)) - 0x2000); + break; + + default: + break; + } +} + +void PcSpkDriver::MidiChannel_PcSpk::noteOff(byte note) { + if (!_allocated) + return; + + if (_sustain) { + if (_out.note == note) + _out.sustainNoteOff = 1; + } else { + if (_out.note == note) { + _out.active = 0; + _owner->updateNote(); + } + } +} + +void PcSpkDriver::MidiChannel_PcSpk::noteOn(byte note, byte velocity) { + if (!_allocated) + return; + + _out.note = note; + _out.sustainNoteOff = 0; + _out.length = _instrument[0]; + + if (_instrument[4] * 256 < ARRAYSIZE(PcSpkDriver::_outInstrumentData)) + _out.instrument = _owner->_outInstrumentData + _instrument[4] * 256; + else + _out.instrument = 0; + + _out.unkA = 0; + _out.unkB = _instrument[1]; + _out.unkC = _instrument[2]; + _out.unkE = 0; + _out.unk60 = 0; + _out.active = 1; + + // In case we get a note on event on the last active channel, we reset the + // last active channel, thus we assure the frequency is correctly set, even + // when the same note was sent. + if (_owner->_lastActiveChannel == this) { + _owner->_lastActiveChannel = 0; + _owner->_lastActiveOut = 0; + } + _owner->updateNote(); + + _out.unkC += PcSpkDriver::getEffectModifier(_instrument[3] + ((velocity & 0xFE) << 4)); + if (_out.unkC > 63) + _out.unkC = 63; + + if ((_instrument[5] & 0x80) != 0) + _owner->setupEffects(*this, _out.effectEnvelopeA, _out.effectDefA, _instrument[5], _instrument + 6); + + if ((_instrument[14] & 0x80) != 0) + _owner->setupEffects(*this, _out.effectEnvelopeB, _out.effectDefB, _instrument[14], _instrument + 15); +} + +void PcSpkDriver::MidiChannel_PcSpk::programChange(byte program) { + // Nothing to implement here, the iMuse code takes care of passing us the + // instrument data. +} + +void PcSpkDriver::MidiChannel_PcSpk::pitchBend(int16 bend) { + _pitchBend = (bend * _pitchBendFactor) >> 6; +} + +void PcSpkDriver::MidiChannel_PcSpk::controlChange(byte control, byte value) { + switch (control) { + case 1: + if (_out.effectEnvelopeA.state && _out.effectDefA.useModWheel) + _out.effectEnvelopeA.modWheelState = (value >> 2); + if (_out.effectEnvelopeB.state && _out.effectDefB.useModWheel) + _out.effectEnvelopeB.modWheelState = (value >> 2); + break; + + case 7: + _tl = value; + if (_owner->_activeChannel == this) { + if (_tl == 0) { + _owner->_lastActiveChannel = 0; + _owner->_lastActiveOut = 0; + _owner->_pcSpk.stop(); + } else { + _owner->output((_out.note << 7) + _pitchBend + _out.unk60 + _out.unkE); + } + } + break; + + case 64: + _sustain = value; + if (!value && _out.sustainNoteOff) { + _out.active = 0; + _owner->updateNote(); + } + break; + + case 123: + _out.active = 0; + _owner->updateNote(); + break; + + default: + break; + } +} + +void PcSpkDriver::MidiChannel_PcSpk::pitchBendFactor(byte value) { + _pitchBendFactor = value; +} + +void PcSpkDriver::MidiChannel_PcSpk::priority(byte value) { + _priority = value; +} + +void PcSpkDriver::MidiChannel_PcSpk::sysEx_customInstrument(uint32 type, const byte *instr) { + memcpy(_instrument, instr, sizeof(_instrument)); +} + +uint8 PcSpkDriver::getEffectModifier(uint16 level) { + uint8 base = level / 32; + uint8 index = level % 32; + + if (index == 0) + return 0; + + return (base * (index + 1)) >> 5; +} + +int16 PcSpkDriver::getEffectModLevel(int16 level, int8 mod) { + if (!mod) { + return 0; + } else if (mod == 31) { + return level; + } else if (level < -63 || level > 63) { + return (mod * (level + 1)) >> 6; + } else if (mod < 0) { + if (level < 0) + return getEffectModifier(((-level) << 5) - mod); + else + return -getEffectModifier((level << 5) - mod); + } else { + if (level < 0) + return -getEffectModifier(((-level) << 5) + mod); + else + return getEffectModifier(((-level) << 5) + mod); + } +} + +int16 PcSpkDriver::getRandMultipy(int16 input) { + if (_randBase & 1) + _randBase = (_randBase >> 1) ^ 0xB8; + else + _randBase >>= 1; + + return (_randBase * input) >> 8; +} + +void PcSpkDriver::setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data) { + def.phase = 0; + def.useModWheel = flags & 0x40; + env.loop = flags & 0x20; + def.type = flags & 0x1F; + + env.modWheelSensitivity = 31; + if (def.useModWheel) + env.modWheelState = chan._modWheel >> 2; + else + env.modWheelState = 31; + + switch (def.type) { + case 0: + env.maxLevel = 767; + env.startLevel = 383; + break; + + case 1: + env.maxLevel = 31; + env.startLevel = 15; + break; + + case 2: + env.maxLevel = 63; + env.startLevel = chan._out.unkB; + break; + + case 3: + env.maxLevel = 63; + env.startLevel = chan._out.unkC; + break; + + case 4: + env.maxLevel = 3; + env.startLevel = chan._instrument[4]; + break; + + case 5: + env.maxLevel = 62; + env.startLevel = 31; + env.modWheelState = 0; + break; + + case 6: + env.maxLevel = 31; + env.startLevel = 0; + env.modWheelSensitivity = 0; + break; + + default: + break; + } + + startEffect(env, data); +} + +void PcSpkDriver::startEffect(EffectEnvelope &env, const byte *data) { + env.state = 1; + env.currentLevel = 0; + env.modWheelLast = 31; + env.duration = data[0] * 63; + + env.stateTargetLevels[0] = data[1]; + env.stateTargetLevels[1] = data[3]; + env.stateTargetLevels[2] = data[5]; + env.stateTargetLevels[3] = data[6]; + + env.stateModWheelLevels[0] = data[2]; + env.stateModWheelLevels[1] = data[4]; + env.stateModWheelLevels[2] = 0; + env.stateModWheelLevels[3] = data[7]; + + initNextEnvelopeState(env); +} + +void PcSpkDriver::initNextEnvelopeState(EffectEnvelope &env) { + uint8 lastState = env.state - 1; + + uint16 stepCount = _effectEnvStepTable[getEffectModifier(((env.stateTargetLevels[lastState] & 0x7F) << 5) + env.modWheelSensitivity)]; + if (env.stateTargetLevels[lastState] & 0x80) + stepCount = getRandMultipy(stepCount); + if (!stepCount) + stepCount = 1; + + env.stateNumSteps = env.stateStepCounter = stepCount; + + int16 totalChange = 0; + if (lastState != 2) { + totalChange = getEffectModLevel(env.maxLevel, (env.stateModWheelLevels[lastState] & 0x7F) - 31); + if (env.stateModWheelLevels[lastState] & 0x80) + totalChange = getRandMultipy(totalChange); + + if (totalChange + env.startLevel > env.maxLevel) + totalChange = env.maxLevel - env.startLevel; + else if (totalChange + env.startLevel < 0) + totalChange = -env.startLevel; + + totalChange -= env.currentLevel; + } + + env.changePerStep = totalChange / stepCount; + if (totalChange < 0) { + totalChange = -totalChange; + env.dir = -1; + } else { + env.dir = 1; + } + env.changePerStepRem = totalChange % stepCount; + env.changeCountRem = 0; +} + +void PcSpkDriver::updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def) { + if (advanceEffectEnvelope(env, def) & 1) { + switch (def.type) { + case 0: case 1: + chan._out.unk60 = def.phase << 4; + break; + + case 2: + chan._out.unkB = (def.phase & 0xFF) + chan._instrument[1]; + break; + + case 3: + chan._out.unkC = (def.phase & 0xFF) + chan._instrument[2]; + break; + + case 4: + if ((chan._instrument[4] + (def.phase & 0xFF)) * 256 < ARRAYSIZE(_outInstrumentData)) + chan._out.instrument = _outInstrumentData + (chan._instrument[4] + (def.phase & 0xFF)) * 256; + else + chan._out.instrument = 0; + break; + + case 5: + env.modWheelState = (def.phase & 0xFF); + break; + + case 6: + env.modWheelSensitivity = (def.phase & 0xFF); + break; + + default: + break; + } + } +} + +uint8 PcSpkDriver::advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def) { + if (env.duration != 0) { + env.duration -= 17; + if (env.duration <= 0) { + env.state = 0; + return 0; + } + } + + uint8 changedFlags = 0; + int16 newLevel = env.currentLevel + env.changePerStep; + env.changeCountRem += env.changePerStepRem; + if (env.changeCountRem >= env.stateNumSteps) { + env.changeCountRem -= env.stateNumSteps; + newLevel += env.dir; + } + + if (env.currentLevel != newLevel || env.modWheelLast != env.modWheelState) { + env.currentLevel = newLevel; + env.modWheelLast = env.modWheelState; + + int16 newPhase = getEffectModLevel(newLevel, env.modWheelState); + if (def.phase != newPhase) { + changedFlags |= 1; + def.phase = newPhase; + } + } + + --env.stateStepCounter; + if (!env.stateStepCounter) { + ++env.state; + if (env.state > 4) { + if (env.loop) { + env.state = 1; + changedFlags |= 2; + } else { + env.state = 0; + return changedFlags; + } + } + + initNextEnvelopeState(env); + } + + return changedFlags; +} + +const byte PcSpkDriver::_outInstrumentData[1024] = { + 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15, + 0x18, 0x1B, 0x1E, 0x21, 0x24, 0x27, 0x2A, 0x2D, + 0x30, 0x33, 0x36, 0x39, 0x3B, 0x3E, 0x41, 0x43, + 0x46, 0x49, 0x4B, 0x4E, 0x50, 0x52, 0x55, 0x57, + 0x59, 0x5B, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x67, + 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x72, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7B, + 0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, + 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7D, 0x7D, + 0x7C, 0x7B, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76, + 0x75, 0x74, 0x72, 0x71, 0x70, 0x6E, 0x6C, 0x6B, + 0x69, 0x67, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5B, + 0x59, 0x57, 0x55, 0x52, 0x50, 0x4E, 0x4B, 0x49, + 0x46, 0x43, 0x41, 0x3E, 0x3B, 0x39, 0x36, 0x33, + 0x30, 0x2D, 0x2A, 0x27, 0x24, 0x21, 0x1E, 0x1B, + 0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03, + 0x00, 0xFD, 0xFA, 0xF7, 0xF4, 0xF1, 0xEE, 0xEB, + 0xE8, 0xE5, 0xE2, 0xDF, 0xDC, 0xD9, 0xD6, 0xD3, + 0xD0, 0xCD, 0xCA, 0xC7, 0xC5, 0xC2, 0xBF, 0xBD, + 0xBA, 0xB7, 0xB5, 0xB2, 0xB0, 0xAE, 0xAB, 0xA9, + 0xA7, 0xA5, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x99, + 0x97, 0x95, 0x94, 0x92, 0x90, 0x8F, 0x8E, 0x8C, + 0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x85, + 0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83, + 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, + 0x8B, 0x8C, 0x8E, 0x8F, 0x90, 0x92, 0x94, 0x95, + 0x97, 0x99, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA5, + 0xA7, 0xA9, 0xAB, 0xAE, 0xB0, 0xB2, 0xB5, 0xB7, + 0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC7, 0xCA, 0xCD, + 0xD0, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE2, 0xE5, + 0xE8, 0xEB, 0xEE, 0xF1, 0xF4, 0xF7, 0xFA, 0xFD, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, + 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE, + 0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, 0xE9, 0xEB, + 0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99, + 0x24, 0x5E, 0x0D, 0x1C, 0x06, 0xB7, 0x47, 0xDE, + 0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6, + 0x1F, 0x03, 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F, + 0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B, + 0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32, + 0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, 0x36, 0xFD, + 0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC, + 0xAD, 0x4F, 0x14, 0xF2, 0x44, 0x40, 0x66, 0xD0, + 0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22, + 0xF6, 0x22, 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA, + 0xB0, 0xCA, 0x99, 0x02, 0xB9, 0x72, 0x9D, 0x49, + 0x2C, 0x80, 0x7E, 0xC5, 0x99, 0xD5, 0xE9, 0x80, + 0xB2, 0xEA, 0xC9, 0xCC, 0x53, 0xBF, 0x67, 0xD6, + 0xBF, 0x14, 0xD6, 0x7E, 0x2D, 0xDC, 0x8E, 0x66, + 0x83, 0xEF, 0x57, 0x49, 0x61, 0xFF, 0x69, 0x8F, + 0x61, 0xCD, 0xD1, 0x1E, 0x9D, 0x9C, 0x16, 0x72, + 0x72, 0xE6, 0x1D, 0xF0, 0x84, 0x4F, 0x4A, 0x77, + 0x02, 0xD7, 0xE8, 0x39, 0x2C, 0x53, 0xCB, 0xC9, + 0x12, 0x1E, 0x33, 0x74, 0x9E, 0x0C, 0xF4, 0xD5, + 0xD4, 0x9F, 0xD4, 0xA4, 0x59, 0x7E, 0x35, 0xCF, + 0x32, 0x22, 0xF4, 0xCC, 0xCF, 0xD3, 0x90, 0x2D, + 0x48, 0xD3, 0x8F, 0x75, 0xE6, 0xD9, 0x1D, 0x2A, + 0xE5, 0xC0, 0xF7, 0x2B, 0x78, 0x81, 0x87, 0x44, + 0x0E, 0x5F, 0x50, 0x00, 0xD4, 0x61, 0x8D, 0xBE, + 0x7B, 0x05, 0x15, 0x07, 0x3B, 0x33, 0x82, 0x1F, + 0x18, 0x70, 0x92, 0xDA, 0x64, 0x54, 0xCE, 0xB1, + 0x85, 0x3E, 0x69, 0x15, 0xF8, 0x46, 0x6A, 0x04, + 0x96, 0x73, 0x0E, 0xD9, 0x16, 0x2F, 0x67, 0x68, + 0xD4, 0xF7, 0x4A, 0x4A, 0xD0, 0x57, 0x68, 0x76 +}; + +const byte PcSpkDriver::_outputTable1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 +}; + +const byte PcSpkDriver::_outputTable2[] = { + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 0, 1, 2, 3, + 4, 5, 6, 7 +}; + +const uint16 PcSpkDriver::_effectEnvStepTable[] = { + 1, 2, 4, 5, + 6, 7, 8, 9, + 10, 12, 14, 16, + 18, 21, 24, 30, + 36, 50, 64, 82, + 100, 136, 160, 192, + 240, 276, 340, 460, + 600, 860, 1200, 1600 +}; + +const uint16 PcSpkDriver::_frequencyTable[] = { + 0x8E84, 0x8E00, 0x8D7D, 0x8CFA, + 0x8C78, 0x8BF7, 0x8B76, 0x8AF5, + 0x8A75, 0x89F5, 0x8976, 0x88F7, + 0x8879, 0x87FB, 0x877D, 0x8700, + 0x8684, 0x8608, 0x858C, 0x8511, + 0x8496, 0x841C, 0x83A2, 0x8328, + 0x82AF, 0x8237, 0x81BF, 0x8147, + 0x80D0, 0x8059, 0x7FE3, 0x7F6D, + 0x7EF7, 0x7E82, 0x7E0D, 0x7D99, + 0x7D25, 0x7CB2, 0x7C3F, 0x7BCC, + 0x7B5A, 0x7AE8, 0x7A77, 0x7A06, + 0x7995, 0x7925, 0x78B5, 0x7846, + 0x77D7, 0x7768, 0x76FA, 0x768C, + 0x761F, 0x75B2, 0x7545, 0x74D9, + 0x746D, 0x7402, 0x7397, 0x732C, + 0x72C2, 0x7258, 0x71EF, 0x7186, + 0x711D, 0x70B5, 0x704D, 0x6FE5, + 0x6F7E, 0x6F17, 0x6EB0, 0x6E4A, + 0x6DE5, 0x6D7F, 0x6D1A, 0x6CB5, + 0x6C51, 0x6BED, 0x6B8A, 0x6B26, + 0x6AC4, 0x6A61, 0x69FF, 0x699D, + 0x693C, 0x68DB, 0x687A, 0x681A, + 0x67BA, 0x675A, 0x66FA, 0x669B, + 0x663D, 0x65DF, 0x6581, 0x6523, + 0x64C6, 0x6469, 0x640C, 0x63B0, + 0x6354, 0x62F8, 0x629D, 0x6242, + 0x61E7, 0x618D, 0x6133, 0x60D9, + 0x6080, 0x6027, 0x5FCE, 0x5F76, + 0x5F1E, 0x5EC6, 0x5E6E, 0x5E17, + 0x5DC1, 0x5D6A, 0x5D14, 0x5CBE, + 0x5C68, 0x5C13, 0x5BBE, 0x5B6A, + 0x5B15, 0x5AC1, 0x5A6E, 0x5A1A, + 0x59C7, 0x5974, 0x5922, 0x58CF, + 0x587D, 0x582C, 0x57DA, 0x5789, + 0x5739, 0x56E8, 0x5698, 0x5648, + 0x55F9, 0x55A9, 0x555A, 0x550B, + 0x54BD, 0x546F, 0x5421, 0x53D3, + 0x5386, 0x5339, 0x52EC, 0x52A0, + 0x5253, 0x5207, 0x51BC, 0x5170, + 0x5125, 0x50DA, 0x5090, 0x5046, + 0x4FFB, 0x4FB2, 0x4F68, 0x4F1F, + 0x4ED6, 0x4E8D, 0x4E45, 0x4DFC, + 0x4DB5, 0x4D6D, 0x4D25, 0x4CDE, + 0x4C97, 0x4C51, 0x4C0A, 0x4BC4, + 0x4B7E, 0x4B39, 0x4AF3, 0x4AAE, + 0x4A69, 0x4A24, 0x49E0, 0x499C, + 0x4958, 0x4914, 0x48D1, 0x488E, + 0x484B, 0x4808, 0x47C6, 0x4783 +}; + +} // End of namespace Scumm + diff --git a/engines/scumm/imuse/pcspk.h b/engines/scumm/imuse/pcspk.h new file mode 100644 index 0000000000..e77ac8c1bf --- /dev/null +++ b/engines/scumm/imuse/pcspk.h @@ -0,0 +1,161 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SCUMM_IMUSE_PCSPK_H +#define SCUMM_IMUSE_PCSPK_H + +#include "audio/softsynth/emumidi.h" +#include "audio/softsynth/pcspk.h" + +namespace Scumm { + +class PcSpkDriver : public MidiDriver_Emulated { +public: + PcSpkDriver(Audio::Mixer *mixer); + ~PcSpkDriver(); + + virtual int open(); + virtual void close(); + + virtual void send(uint32 d); + virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr); + + virtual MidiChannel *allocateChannel(); + virtual MidiChannel *getPercussionChannel() { return 0; } + + bool isStereo() const { return _pcSpk.isStereo(); } + int getRate() const { return _pcSpk.getRate(); } +protected: + void generateSamples(int16 *buf, int len); + void onTimer(); + +private: + Audio::PCSpeaker _pcSpk; + int _effectTimer; + uint8 _randBase; + + void updateNote(); + void output(uint16 out); + + static uint8 getEffectModifier(uint16 level); + int16 getEffectModLevel(int16 level, int8 mod); + int16 getRandMultipy(int16 input); + + struct EffectEnvelope { + uint8 state; + int16 currentLevel; + int16 duration; + int16 maxLevel; + int16 startLevel; + uint8 loop; + uint8 stateTargetLevels[4]; + uint8 stateModWheelLevels[4]; + uint8 modWheelSensitivity; + uint8 modWheelState; + uint8 modWheelLast; + int16 stateNumSteps; + int16 stateStepCounter; + int16 changePerStep; + int8 dir; + int16 changePerStepRem; + int16 changeCountRem; + }; + + struct EffectDefinition { + int16 phase; + uint8 type; + uint8 useModWheel; + EffectEnvelope *envelope; + }; + + struct OutputChannel { + uint8 active; + uint8 note; + uint8 sustainNoteOff; + uint8 length; + const uint8 *instrument; + uint8 unkA; + uint8 unkB; + uint8 unkC; + int16 unkE; + EffectEnvelope effectEnvelopeA; + EffectDefinition effectDefA; + EffectEnvelope effectEnvelopeB; + EffectDefinition effectDefB; + int16 unk60; + }; + + struct MidiChannel_PcSpk : public MidiChannel { + virtual MidiDriver *device(); + virtual byte getNumber(); + virtual void release(); + + virtual void send(uint32 b); + virtual void noteOff(byte note); + virtual void noteOn(byte note, byte velocity); + virtual void programChange(byte program); + virtual void pitchBend(int16 bend); + virtual void controlChange(byte control, byte value); + virtual void pitchBendFactor(byte value); + virtual void priority(byte value); + virtual void sysEx_customInstrument(uint32 type, const byte *instr); + + void init(PcSpkDriver *owner, byte channel); + bool allocate(); + + PcSpkDriver *_owner; + bool _allocated; + byte _channel; + + OutputChannel _out; + uint8 _instrument[23]; + uint8 _programNr; + uint8 _priority; + uint8 _tl; + uint8 _modWheel; + uint8 _sustain; + uint8 _pitchBendFactor; + int16 _pitchBend; + }; + + void setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data); + void startEffect(EffectEnvelope &env, const byte *data); + void initNextEnvelopeState(EffectEnvelope &env); + void updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def); + uint8 advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def); + + MidiChannel_PcSpk _channels[6]; + MidiChannel_PcSpk *_activeChannel; + + MidiChannel_PcSpk *_lastActiveChannel; + uint16 _lastActiveOut; + + static const byte _outInstrumentData[1024]; + static const byte _outputTable1[]; + static const byte _outputTable2[]; + static const uint16 _effectEnvStepTable[]; + static const uint16 _frequencyTable[]; +}; + +} // End of namespace Scumm + +#endif + diff --git a/engines/scumm/imuse/sysex_samnmax.cpp b/engines/scumm/imuse/sysex_samnmax.cpp index 4c4219e7bb..a4f525da56 100644 --- a/engines/scumm/imuse/sysex_samnmax.cpp +++ b/engines/scumm/imuse/sysex_samnmax.cpp @@ -53,8 +53,7 @@ void sysexHandler_SamNMax(Player *player, const byte *msg, uint16 len) { // something magical is supposed to happen.... for (a = 0; a < ARRAYSIZE(se->_snm_triggers); ++a) { if (se->_snm_triggers[a].sound == player->_id && - se->_snm_triggers[a].id == *p) - { + se->_snm_triggers[a].id == *p) { se->_snm_triggers[a].sound = se->_snm_triggers[a].id = 0; se->doCommand(8, se->_snm_triggers[a].command); break; diff --git a/engines/scumm/imuse/sysex_scumm.cpp b/engines/scumm/imuse/sysex_scumm.cpp index 4eb3bee93c..85ffc86f47 100644 --- a/engines/scumm/imuse/sysex_scumm.cpp +++ b/engines/scumm/imuse/sysex_scumm.cpp @@ -49,50 +49,52 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { switch (code = *p++) { case 0: // Allocate new part. - // There are 17 bytes of useful information here. + // There are 8 bytes (after decoding!) of useful information here. // Here is what we know about them so far: - // BYTE 00: Channel # - // BYTE 02: BIT 01(0x01): Part on?(1 = yes) + // BYTE 0: Channel # + // BYTE 1: BIT 01(0x01): Part on?(1 = yes) // BIT 02(0x02): Reverb? (1 = yes) [bug #1088045] - // BYTE 04: Priority adjustment [guessing] - // BYTE 05: Volume(upper 4 bits) [guessing] - // BYTE 06: Volume(lower 4 bits) [guessing] - // BYTE 07: Pan(upper 4 bits) [bug #1088045] - // BYTE 08: Pan(lower 4 bits) [bug #1088045] - // BYTE 09: BIT 04(0x08): Percussion?(1 = yes) - // BYTE 13: Pitchbend range(upper 4 bits) [bug #1088045] - // BYTE 14: Pitchbend range(lower 4 bits) [bug #1088045] - // BYTE 15: Program(upper 4 bits) - // BYTE 16: Program(lower 4 bits) - - // athrxx (05-21-2011): - // BYTE 9, 10: Transpose (if set to 0x80, this means that part->_transpose_eff will be 0 (also ignoring player->_transpose) - // BYTE 11, 12: Detune + // BYTE 2: Priority adjustment + // BYTE 3: Volume [guessing] + // BYTE 4: Pan [bug #1088045] + // BYTE 5: BIT 8(0x80): Percussion?(1 = yes) [guessed?] + // BYTE 5: Transpose, if set to 0x80(=-1) it means no transpose + // BYTE 6: Detune + // BYTE 7: Pitchbend factor [bug #1088045] + // BYTE 8: Program part = player->getPart(p[0] & 0x0F); + player->decode_sysex_bytes(p + 1, buf + 1, len - 1); if (part) { - part->set_onoff(p[2] & 0x01); - part->effectLevel((p[2] & 0x02) ? 127 : 0); - part->set_pri(p[4]); - part->volume((p[5] & 0x0F) << 4 |(p[6] & 0x0F)); - part->set_pan((p[7] & 0x0F) << 4 | (p[8] & 0x0F)); - part->_percussion = player->_isMIDI ? ((p[9] & 0x08) > 0) : false; - part->set_transpose((p[9] & 0x0F) << 4 | (p[10] & 0x0F)); - part->set_detune((p[11] & 0x0F) << 4 | (p[12] & 0x0F)); - part->pitchBendFactor((p[13] & 0x0F) << 4 | (p[14] & 0x0F)); + part->set_onoff(buf[1] & 0x01); + part->effectLevel((buf[1] & 0x02) ? 127 : 0); + part->set_pri(buf[2]); + part->volume(buf[3]); + part->set_pan(buf[4]); + part->_percussion = player->_isMIDI ? ((buf[5] & 0x80) > 0) : false; + part->set_transpose(buf[5]); + part->set_detune(buf[6]); + part->pitchBendFactor(buf[7]); if (part->_percussion) { if (part->_mc) { part->off(); se->reallocateMidiChannels(player->_midi); } } else { - // Even in cases where a program does not seem to be specified, - // i.e. bytes 15 and 16 are 0, we send a program change because - // 0 is a valid program number. MI2 tests show that in such - // cases, a regular program change message always seems to follow - // anyway. - if (player->_isMIDI) - part->_instrument.program((p[15] & 0x0F) << 4 |(p[16] & 0x0F), player->_isMT32); + if (player->_isMIDI) { + // Even in cases where a program does not seem to be specified, + // i.e. bytes 15 and 16 are 0, we send a program change because + // 0 is a valid program number. MI2 tests show that in such + // cases, a regular program change message always seems to follow + // anyway. + part->_instrument.program(buf[8], player->_isMT32); + } else { + // Like the original we set up the instrument data of the + // specified program here too. In case the global + // instrument data is not loaded already, this will take + // care of setting a default instrument too. + se->copyGlobalInstrument(buf[8], &part->_instrument); + } part->sendAll(); } } @@ -113,11 +115,10 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { ++p; // Skip hardware type part = player->getPart(a); if (part) { - if (len == 62) { + if (len == 62 || len == 48) { player->decode_sysex_bytes(p, buf, len - 2); part->set_instrument((byte *)buf); } else { - // SPK tracks have len == 48 here, and are not supported part->programChange(254); // Must be invalid, but not 255 (which is reserved) } } @@ -127,7 +128,8 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { p += 2; // Skip hardware type and... whatever came right before it a = *p++; player->decode_sysex_bytes(p, buf, len - 3); - se->setGlobalAdLibInstrument(a, buf); + if (len == 63 || len == 49) + se->setGlobalInstrument(a, buf); break; case 33: // Parameter adjust @@ -185,10 +187,9 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { case 80: // Loop player->decode_sysex_bytes(p + 1, buf, len - 1); - player->setLoop - (READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2), - READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6), - READ_BE_UINT16(buf + 8)); + player->setLoop(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2), + READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6), + READ_BE_UINT16(buf + 8)); break; case 81: // End loop diff --git a/engines/scumm/imuse_digi/dimuse_codecs.cpp b/engines/scumm/imuse_digi/dimuse_codecs.cpp index 69cd89320c..6edfe0bd33 100644 --- a/engines/scumm/imuse_digi/dimuse_codecs.cpp +++ b/engines/scumm/imuse_digi/dimuse_codecs.cpp @@ -105,7 +105,9 @@ static const byte imxOtherTable[6][64] = { void releaseImcTables() { free(_destImcTable); + _destImcTable = NULL; free(_destImcTable2); + _destImcTable2 = NULL; } void initializeImcTables() { diff --git a/engines/scumm/input.cpp b/engines/scumm/input.cpp index 07c52578c3..5eea7acc6b 100644 --- a/engines/scumm/input.cpp +++ b/engines/scumm/input.cpp @@ -181,7 +181,7 @@ void ScummEngine::parseEvent(Common::Event event) { _mouse.y = event.mouse.y; if (_renderMode == Common::kRenderHercA || _renderMode == Common::kRenderHercG) { - _mouse.x -= (Common::kHercW - _screenWidth * 2) / 2; + _mouse.x -= (kHercWidth - _screenWidth * 2) / 2; _mouse.x >>= 1; _mouse.y = _mouse.y * 4 / 7; } else if (_useCJKMode && _textSurfaceMultiplier == 2) { diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 1a60564a9e..99ffdf7f21 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -27,6 +27,7 @@ MODULE_OBJS := \ imuse/imuse_part.o \ imuse/imuse_player.o \ imuse/instrument.o \ + imuse/pcspk.o \ imuse/sysex_samnmax.o \ imuse/sysex_scumm.o \ input.o \ diff --git a/engines/scumm/palette.cpp b/engines/scumm/palette.cpp index ba13ff46d9..51ba2195d7 100644 --- a/engines/scumm/palette.cpp +++ b/engines/scumm/palette.cpp @@ -48,11 +48,7 @@ uint8 *ScummEngine::getHEPaletteSlot(uint16 palSlot) { } uint16 ScummEngine::get16BitColor(uint8 r, uint8 g, uint8 b) { - uint16 ar = (r >> 3) << 10; - uint16 ag = (g >> 3) << 5; - uint16 ab = (b >> 3) << 0; - uint16 col = ar | ag | ab; - return col; + return _outputPixelFormat.RGBToColor(r, g, b); } void ScummEngine::resetPalette() { diff --git a/engines/scumm/player_towns.cpp b/engines/scumm/player_towns.cpp index e71a8d0587..dd7630d370 100644 --- a/engines/scumm/player_towns.cpp +++ b/engines/scumm/player_towns.cpp @@ -32,7 +32,7 @@ Player_Towns::Player_Towns(ScummEngine *vm, bool isVersion2) : _vm(vm), _v2(isVe void Player_Towns::setSfxVolume(int vol) { if (!_intf) - return; + return; _intf->setSoundEffectVolume(vol); } @@ -98,17 +98,17 @@ void Player_Towns::playPcmTrack(int sound, const uint8 *data, int velo, int pan, return; const uint8 *sfxData = data + 16; - + int numChan = _v2 ? 1 : data[14]; for (int i = 0; i < numChan; i++) { int chan = allocatePcmChannel(sound, i, priority); if (!chan) return; - + _intf->callback(70, _unkFlags); _intf->callback(3, chan + 0x3f, pan); _intf->callback(37, chan + 0x3f, note, velo, sfxData); - + _pcmCurrentSound[chan].note = note; _pcmCurrentSound[chan].velo = velo; _pcmCurrentSound[chan].pan = pan; @@ -191,7 +191,7 @@ Player_Towns_v1::Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer) : Player_ if (_vm->_game.version == 3) { _soundOverride = new SoundOvrParameters[_numSoundMax]; memset(_soundOverride, 0, _numSoundMax * sizeof(SoundOvrParameters)); - } + } _driver = new TownsEuphonyDriver(mixer); } @@ -204,7 +204,7 @@ Player_Towns_v1::~Player_Towns_v1() { bool Player_Towns_v1::init() { if (!_driver) return false; - + if (!_driver->init()) return false; @@ -235,13 +235,13 @@ void Player_Towns_v1::startSound(int sound) { if (type == 0) { uint8 velocity = 0; uint8 note = 0; - + if (_vm->_game.version == 3) { velocity = (_soundOverride[sound].vLeft + _soundOverride[sound].vRight); note = _soundOverride[sound].note; } - velocity = velocity ? velocity >> 2 : ptr[14] >> 1; + velocity = velocity ? velocity >> 2 : ptr[14] >> 1; playPcmTrack(sound, ptr + 6, velocity, 64, note ? note : ptr[50], READ_LE_UINT16(ptr + 10)); } else if (type == 1) { @@ -267,7 +267,7 @@ void Player_Towns_v1::stopSound(int sound) { _eupLooping = false; _driver->stopParser(); } - + stopPcmTrack(sound); } @@ -293,7 +293,7 @@ int Player_Towns_v1::getSoundStatus(int sound) const { int32 Player_Towns_v1::doCommand(int numargs, int args[]) { int32 res = 0; - + switch (args[0]) { case 2: _driver->intf()->callback(73, 0); @@ -381,12 +381,12 @@ void Player_Towns_v1::saveLoadWithSerializer(Serializer *ser) { void Player_Towns_v1::restoreAfterLoad() { setVolumeCD(_cdaVolLeft, _cdaVolRight); - + if (_cdaCurrentSoundTemp) { uint8 *ptr = _vm->getResourceAddress(rtSound, _cdaCurrentSoundTemp) + 6; if (_vm->_game.version != 3) ptr += 2; - + if (ptr[7] == 2) { playCdaTrack(_cdaCurrentSoundTemp, ptr, true); _cdaCurrentSound = _cdaCurrentSoundTemp; @@ -398,7 +398,7 @@ void Player_Towns_v1::restoreAfterLoad() { uint8 *ptr = _vm->getResourceAddress(rtSound, _eupCurrentSound) + 6; if (_vm->_game.version != 3) ptr += 2; - + if (ptr[7] == 1) { setSoundVolume(_eupCurrentSound, _eupVolLeft, _eupVolRight); playEuphonyTrack(_eupCurrentSound, ptr); @@ -458,7 +458,7 @@ void Player_Towns_v1::startSoundEx(int sound, int velo, int pan, int note) { } else if (ptr[13] == 2) { int volLeft = velo; int volRight = velo; - + if (pan < 50) volRight = ((pan * 2 + 1) * velo + 50) / 100; else if (pan > 50) @@ -478,7 +478,7 @@ void Player_Towns_v1::stopSoundSuspendLooping(int sound) { return; } else if (sound == _cdaCurrentSound) { if (_cdaNumLoops && _cdaForceRestart) - _cdaForceRestart = 1; + _cdaForceRestart = 1; } else { for (int i = 1; i < 9; i++) { if (sound == _pcmCurrentSound[i].index) { @@ -487,7 +487,7 @@ void Player_Towns_v1::stopSoundSuspendLooping(int sound) { _driver->stopSoundEffect(i + 0x3f); if (_pcmCurrentSound[i].looping) _pcmCurrentSound[i].paused = 1; - else + else _pcmCurrentSound[i].index = 0; } } @@ -532,7 +532,7 @@ void Player_Towns_v1::playEuphonyTrack(int sound, const uint8 *data) { uint32 trackSize = READ_LE_UINT32(src); src += 4; uint8 startTick = *src++; - + _driver->setMusicTempo(*src++); _driver->startMusicTrack(trackData, trackSize, startTick); @@ -559,7 +559,7 @@ void Player_Towns_v1::playCdaTrack(int sound, const uint8 *data, bool skipTrackV } } - if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1) + if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1) return; ptr += 16; @@ -585,7 +585,7 @@ Player_Towns_v2::~Player_Towns_v2() { _intf = 0; if (_imuseDispose) - delete _imuse; + delete _imuse; delete[] _sblData; delete[] _soundOverride; @@ -594,7 +594,7 @@ Player_Towns_v2::~Player_Towns_v2() { bool Player_Towns_v2::init() { if (!_intf) return false; - + if (!_intf->init()) return false; @@ -637,7 +637,7 @@ void Player_Towns_v2::startSound(int sound) { void Player_Towns_v2::stopSound(int sound) { if (_soundOverride[sound].type == 7) { - stopPcmTrack(sound); + stopPcmTrack(sound); } else { _imuse->stopSound(sound); } @@ -651,7 +651,7 @@ void Player_Towns_v2::stopAllSounds() { int32 Player_Towns_v2::doCommand(int numargs, int args[]) { int32 res = -1; uint8 *ptr = 0; - + switch (args[0]) { case 8: startSound(args[1]); @@ -675,7 +675,7 @@ int32 Player_Towns_v2::doCommand(int numargs, int args[]) { case 258: if (_soundOverride[args[1]].type == 0) { ptr = _vm->getResourceAddress(rtSound, args[1]); - if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) + if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) _soundOverride[args[1]].type = 7; } if (_soundOverride[args[1]].type == 7) { @@ -683,11 +683,11 @@ int32 Player_Towns_v2::doCommand(int numargs, int args[]) { res = 0; } break; - + case 259: if (_soundOverride[args[1]].type == 0) { ptr = _vm->getResourceAddress(rtSound, args[1]); - if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) + if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) _soundOverride[args[1]].type = 7; } if (_soundOverride[args[1]].type == 7) { @@ -702,7 +702,7 @@ int32 Player_Towns_v2::doCommand(int numargs, int args[]) { if (res == -1) return _imuse->doCommand(numargs, args); - + return res; } @@ -718,9 +718,9 @@ void Player_Towns_v2::playVocTrack(const uint8 *data) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x04, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00 }; - + uint32 len = (READ_LE_UINT32(data) >> 8) - 2; - + int chan = allocatePcmChannel(0xffff, 0, 0x1000); if (!chan) return; diff --git a/engines/scumm/player_towns.h b/engines/scumm/player_towns.h index 470020d621..5c76d7c6c7 100644 --- a/engines/scumm/player_towns.h +++ b/engines/scumm/player_towns.h @@ -47,8 +47,8 @@ public: virtual void restoreAfterLoad(); // version 1 specific - virtual int getCurrentCdaSound() { return 0; } - virtual int getCurrentCdaVolume() { return 0; } + virtual int getCurrentCdaSound() { return 0; } + virtual int getCurrentCdaVolume() { return 0; } virtual void setVolumeCD(int left, int right) {} virtual void setSoundVolume(int sound, int left, int right) {} virtual void setSoundNote(int sound, int note) {} @@ -92,8 +92,8 @@ public: void stopAllSounds(); int getSoundStatus(int sound) const; - int getCurrentCdaSound() { return _cdaCurrentSound; } - int getCurrentCdaVolume() { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; } + int getCurrentCdaSound() { return _cdaCurrentSound; } + int getCurrentCdaVolume() { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; } int32 doCommand(int numargs, int args[]); @@ -124,7 +124,7 @@ private: uint8 _cdaVolLeft; uint8 _cdaVolRight; - + uint8 _eupCurrentSound; uint8 _eupLooping; uint8 _eupVolLeft; @@ -170,7 +170,7 @@ private: SoundOvrParameters *_soundOverride; uint8 *_sblData; - + IMuse *_imuse; const bool _imuseDispose; }; diff --git a/engines/scumm/resource.cpp b/engines/scumm/resource.cpp index 0448f60593..10301da3e3 100644 --- a/engines/scumm/resource.cpp +++ b/engines/scumm/resource.cpp @@ -822,11 +822,12 @@ byte *ResourceManager::createResource(ResType type, ResId idx, uint32 size) { expireResources(size); - byte *ptr = (byte *)calloc(size + SAFETY_AREA, 1); + byte *ptr = new byte[size + SAFETY_AREA]; if (ptr == NULL) { error("createResource(%s,%d): Out of memory while allocating %d", nameOfResType(type), idx, size); } + memset(ptr, 0, size + SAFETY_AREA); _allocatedSize += size; _types[type][idx]._address = ptr; @@ -845,12 +846,12 @@ ResourceManager::Resource::Resource() { } ResourceManager::Resource::~Resource() { - delete _address; + delete[] _address; _address = 0; } void ResourceManager::Resource::nuke() { - delete _address; + delete[] _address; _address = 0; _size = 0; _flags = 0; diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 19834cb35d..3cc710c207 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -1316,10 +1316,10 @@ void ScummEngine::saveOrLoad(Serializer *s) { MKEND() }; - s->saveLoadArrayOf(_textPalette, 48, sizeof(_textPalette[0]), sleUint8); + s->saveLoadArrayOf(_textPalette, 48, sizeof(_textPalette[0]), sleUint8); s->saveLoadArrayOf(_cyclRects, 10, sizeof(_cyclRects[0]), townsFields); s->saveLoadArrayOf(&_curStringRect, 1, sizeof(_curStringRect), townsFields); - s->saveLoadArrayOf(_townsCharsetColorMap, 16, sizeof(_townsCharsetColorMap[0]), sleUint8); + s->saveLoadArrayOf(_townsCharsetColorMap, 16, sizeof(_townsCharsetColorMap[0]), sleUint8); s->saveLoadEntries(this, townsExtraEntries); } #endif @@ -1489,7 +1489,7 @@ void ScummEngine_v5::saveOrLoad(Serializer *s) { // Reset cursors for old FM-Towns savegames saved with 256 color setting. // Otherwise the cursor will be messed up when displayed in the new hi color setting. - if (_game.platform == Common::kPlatformFMTowns && _bytesPerPixelOutput == 2 && s->isLoading() && s->getVersion() < VER(82)) { + if (_game.platform == Common::kPlatformFMTowns && _outputPixelFormat.bytesPerPixel == 2 && s->isLoading() && s->getVersion() < VER(82)) { if (_game.id == GID_LOOM) { redefineBuiltinCursorFromChar(1, 1); redefineBuiltinCursorHotspot(1, 0, 0); @@ -1497,6 +1497,16 @@ void ScummEngine_v5::saveOrLoad(Serializer *s) { resetCursors(); } } + + // Regenerate 16bit palette after loading. + // This avoids color issues when loading savegames that have been saved with a different ScummVM port + // that uses a different 16bit color mode than the ScummVM port which is currently used. +#ifdef USE_RGB_COLOR + if (_game.platform == Common::kPlatformPCEngine && s->isLoading()) { + for (int i = 0; i < 256; ++i) + _16BitPalette[i] = get16BitColor(_currentPalette[i * 3 + 0], _currentPalette[i * 3 + 1], _currentPalette[i * 3 + 2]); + } +#endif } #ifdef ENABLE_SCUMM_7_8 diff --git a/engines/scumm/script_v4.cpp b/engines/scumm/script_v4.cpp index 1302c8c28d..8340f62dbc 100644 --- a/engines/scumm/script_v4.cpp +++ b/engines/scumm/script_v4.cpp @@ -68,6 +68,18 @@ void ScummEngine_v4::o4_ifState() { int a = getVarOrDirectWord(PARAM_1); int b = getVarOrDirectByte(PARAM_2); + // WORKAROUND bug #3306145 (also occurs in original): Some old versions of + // Indy3 sometimes fail to allocate IQ points correctly. To quote: + // "About the points error leaving Castle Brunwald: It seems to "reversed"! + // When you get caught, free yourself and escape, you get 25 IQ points even + // though you're not supposed to. However if you escape WITHOUT getting + // caught, you get 0 IQ points (supposed to get 25 IQ points)." + // This workaround is meant to address that. + if (_game.id == GID_INDY3 && a == 367 && + vm.slot[_currentScript].number == 363 && _currentRoom == 25) { + b = 0; + } + jumpRelative(getState(a) == b); } diff --git a/engines/scumm/script_v5.cpp b/engines/scumm/script_v5.cpp index 2c8f65496f..02c8d977a5 100644 --- a/engines/scumm/script_v5.cpp +++ b/engines/scumm/script_v5.cpp @@ -1611,7 +1611,7 @@ void ScummEngine_v5::o5_resourceRoutines() { foo = getVarOrDirectByte(PARAM_2); bar = fetchScriptByte(); if (_townsPlayer) - _townsPlayer->setSoundVolume(resid, foo, bar); + _townsPlayer->setSoundVolume(resid, foo, bar); break; case 37: if (_townsPlayer) @@ -2095,6 +2095,20 @@ void ScummEngine_v5::o5_startScript() { if (_game.id == GID_ZAK && _game.platform == Common::kPlatformFMTowns && script == 171) return; + // WORKAROUND bug #3306145 (also occurs in original): Some old versions of + // Indy3 sometimes fail to allocate IQ points correctly. To quote: + // "In the Amiga version you get the 15 points for puzzle 30 if you give the + // book or KO the guy. The PC version correctly gives 10 points for puzzle + // 29 for KO and 15 for puzzle 30 when giving the book." + // This workaround is meant to address that. + if (_game.id == GID_INDY3 && vm.slot[_currentScript].number == 106 && script == 125 && VAR(115) != 2) { + // If Var[115] != 2, then: + // Correct: startScript(125,[29,10]); + // Wrong : startScript(125,[30,15]); + data[0] = 29; + data[1] = 10; + } + // Method used by original games to skip copy protection scheme if (!_copyProtection) { // Copy protection was disabled in LucasArts Classic Adventures (PC Disk) @@ -2319,7 +2333,7 @@ void ScummEngine_v5::o5_verbOps() { if (_game.platform == Common::kPlatformFMTowns && _game.version == 3 && slot) continue; - + if (slot == 0) { for (slot = 1; slot < _numVerbs; slot++) { if (_verbs[slot].verbid == 0) diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 0a5338374e..0f01e39459 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -71,6 +71,7 @@ #include "scumm/he/cup_player_he.h" #include "scumm/util.h" #include "scumm/verbs.h" +#include "scumm/imuse/pcspk.h" #include "backends/audiocd/audiocd.h" @@ -114,17 +115,18 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _rnd("scumm") { - if (_game.heversion > 0) { - _gdi = new GdiHE(this); - } else if (_game.platform == Common::kPlatformNES) { - _gdi = new GdiNES(this); #ifdef USE_RGB_COLOR - } else if (_game.features & GF_16BIT_COLOR) { + if (_game.features & GF_16BIT_COLOR) { if (_game.platform == Common::kPlatformPCEngine) _gdi = new GdiPCEngine(this); - else - _gdi = new Gdi16Bit(this); + else if (_game.heversion > 0) + _gdi = new GdiHE16bit(this); + } else #endif + if (_game.heversion > 0) { + _gdi = new GdiHE(this); + } else if (_game.platform == Common::kPlatformNES) { + _gdi = new GdiNES(this); } else if (_game.version <= 1) { _gdi = new GdiV1(this); } else if (_game.version == 2) { @@ -260,7 +262,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _switchRoomEffect2 = 0; _switchRoomEffect = 0; - _bytesPerPixelOutput = _bytesPerPixel = 1; + _bytesPerPixel = 1; _doEffect = false; _snapScroll = false; _currentLights = 0; @@ -282,8 +284,9 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _16BitPalette = NULL; #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE _townsScreen = 0; +#ifdef USE_RGB_COLOR _cjkFont = 0; - _cjkChar = 0; +#endif #endif _shadowPalette = NULL; _shadowPaletteSize = 0; @@ -328,7 +331,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) memset(&_cyclRects, 0, 16 * sizeof(Common::Rect)); _numCyclRects = 0; #endif - + // // Init all VARS to 0xFF // @@ -545,24 +548,25 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _screenHeight = 200; } - _bytesPerPixelOutput = _bytesPerPixel = (_game.features & GF_16BIT_COLOR) ? 2 : 1; + _bytesPerPixel = (_game.features & GF_16BIT_COLOR) ? 2 : 1; + uint8 sizeMult = _bytesPerPixel; #ifdef USE_RGB_COLOR #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE if (_game.platform == Common::kPlatformFMTowns) - _bytesPerPixelOutput = 2; + sizeMult = 2; #endif #endif // Allocate gfx compositing buffer (not needed for V7/V8 games). if (_game.version < 7) - _compositeBuf = (byte *)malloc(_screenWidth * _screenHeight * _bytesPerPixelOutput); + _compositeBuf = (byte *)malloc(_screenWidth * _screenHeight * sizeMult); else _compositeBuf = 0; _herculesBuf = 0; if (_renderMode == Common::kRenderHercA || _renderMode == Common::kRenderHercG) { - _herculesBuf = (byte *)malloc(Common::kHercW * Common::kHercH); + _herculesBuf = (byte *)malloc(kHercWidth * kHercHeight); } // Add debug levels @@ -589,7 +593,7 @@ ScummEngine::~ScummEngine() { delete _actors[i]; delete[] _actors; } - + delete[] _sortedActors; delete[] _2byteFontPtr; @@ -631,8 +635,10 @@ ScummEngine::~ScummEngine() { #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE delete _townsScreen; +#ifdef USE_RGB_COLOR delete _cjkFont; #endif +#endif delete _debugger; @@ -1138,7 +1144,7 @@ Common::Error ScummEngine::init() { // Initialize backend if (_renderMode == Common::kRenderHercA || _renderMode == Common::kRenderHercG) { - initGraphics(Common::kHercW, Common::kHercH, true); + initGraphics(kHercWidth, kHercHeight, true); } else { int screenWidth = _screenWidth; int screenHeight = _screenHeight; @@ -1148,16 +1154,37 @@ Common::Error ScummEngine::init() { screenWidth *= _textSurfaceMultiplier; screenHeight *= _textSurfaceMultiplier; } - if (_game.features & GF_16BIT_COLOR + if (_game.features & GF_16BIT_COLOR #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE || _game.platform == Common::kPlatformFMTowns #endif ) { #ifdef USE_RGB_COLOR - Graphics::PixelFormat format = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); - initGraphics(screenWidth, screenHeight, screenWidth > 320, &format); - if (format != _system->getScreenFormat()) - return Common::kUnsupportedColorMode; + _outputPixelFormat = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); + + if (_game.platform != Common::kPlatformFMTowns && _game.platform != Common::kPlatformPCEngine) { + initGraphics(screenWidth, screenHeight, screenWidth > 320, &_outputPixelFormat); + if (_outputPixelFormat != _system->getScreenFormat()) + return Common::kUnsupportedColorMode; + } else { + Common::List<Graphics::PixelFormat> tryModes = _system->getSupportedFormats(); + for (Common::List<Graphics::PixelFormat>::iterator g = tryModes.begin(); g != tryModes.end(); ++g) { + if (g->bytesPerPixel != 2 || g->aBits()) { + g = tryModes.erase(g); + g--; + } + + if (*g == _outputPixelFormat) { + tryModes.clear(); + tryModes.push_back(_outputPixelFormat); + break; + } + } + + initGraphics(screenWidth, screenHeight, screenWidth > 320, tryModes); + if (_system->getScreenFormat().bytesPerPixel != 2) + return Common::kUnsupportedColorMode; + } #else if (_game.platform == Common::kPlatformFMTowns && _game.version == 3) { warning("Starting game without the required 16bit color support.\nYou may experience color glitches"); @@ -1169,12 +1196,14 @@ Common::Error ScummEngine::init() { } else { #ifdef DISABLE_TOWNS_DUAL_LAYER_MODE if (_game.platform == Common::kPlatformFMTowns && _game.version == 5) - error("This game requires dual graphics layer support which is disabled in this build"); + return Common::Error(Common::kUnsupportedColorMode, "This game requires dual graphics layer support which is disabled in this build"); #endif initGraphics(screenWidth, screenHeight, (screenWidth > 320)); } } + _outputPixelFormat = _system->getScreenFormat(); + setupScumm(); readIndexFile(); @@ -1283,7 +1312,7 @@ void ScummEngine::setupScumm() { _res->setHeapThreshold(400000, maxHeapThreshold); free(_compositeBuf); - _compositeBuf = (byte *)malloc(_screenWidth * _textSurfaceMultiplier * _screenHeight * _textSurfaceMultiplier * _bytesPerPixelOutput); + _compositeBuf = (byte *)malloc(_screenWidth * _textSurfaceMultiplier * _screenHeight * _textSurfaceMultiplier * _outputPixelFormat.bytesPerPixel); } #ifdef ENABLE_SCUMM_7_8 @@ -1325,13 +1354,23 @@ void ScummEngine::setupCharsetRenderer() { _charset = new CharsetRendererPCE(this); else #endif + if (_game.platform == Common::kPlatformFMTowns) + _charset = new CharsetRendererTownsV3(this); + else _charset = new CharsetRendererV3(this); #ifdef ENABLE_SCUMM_7_8 } else if (_game.version == 8) { _charset = new CharsetRendererNut(this); #endif } else { - _charset = new CharsetRendererClassic(this); +#ifdef USE_RGB_COLOR +#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE + if (_game.platform == Common::kPlatformFMTowns) + _charset = new CharsetRendererTownsClassic(this); + else +#endif +#endif + _charset = new CharsetRendererClassic(this); } } @@ -1364,8 +1403,7 @@ void ScummEngine::resetScumm() { #ifdef USE_RGB_COLOR if (_game.features & GF_16BIT_COLOR #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - - || _game.platform == Common::kPlatformFMTowns + || (_game.platform == Common::kPlatformFMTowns) #endif ) _16BitPalette = (uint16 *)calloc(512, sizeof(uint16)); @@ -1374,8 +1412,8 @@ void ScummEngine::resetScumm() { #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE if (_game.platform == Common::kPlatformFMTowns) { delete _townsScreen; - _townsScreen = new TownsScreen(_system, _screenWidth * _textSurfaceMultiplier, _screenHeight * _textSurfaceMultiplier, _bytesPerPixelOutput); - _townsScreen->setupLayer(0, _screenWidth, _screenHeight, (_bytesPerPixelOutput == 2) ? 32767 : 256); + _townsScreen = new TownsScreen(_system, _screenWidth * _textSurfaceMultiplier, _screenHeight * _textSurfaceMultiplier, _outputPixelFormat); + _townsScreen->setupLayer(0, _screenWidth, _screenHeight, (_outputPixelFormat.bytesPerPixel == 2) ? 32767 : 256); _townsScreen->setupLayer(1, _screenWidth * _textSurfaceMultiplier, _screenHeight * _textSurfaceMultiplier, 16, _textPalette); } #endif @@ -1833,18 +1871,20 @@ void ScummEngine::setupMusic(int midi) { MidiDriver *nativeMidiDriver = 0; MidiDriver *adlibMidiDriver = 0; - if (_musicType != MDT_ADLIB && _musicType != MDT_TOWNS) + if (_musicType != MDT_ADLIB && _musicType != MDT_TOWNS && _musicType != MDT_PCSPK) nativeMidiDriver = MidiDriver::createMidi(dev); if (nativeMidiDriver != NULL && _native_mt32) nativeMidiDriver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); - bool multi_midi = ConfMan.getBool("multi_midi") && _musicType != MDT_NONE && (midi & MDT_ADLIB); + bool multi_midi = ConfMan.getBool("multi_midi") && _musicType != MDT_NONE && _musicType != MDT_PCSPK && (midi & MDT_ADLIB); if (_musicType == MDT_ADLIB || _musicType == MDT_TOWNS || multi_midi) { adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB)); adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0); + } else if (_musicType == MDT_PCSPK) { + adlibMidiDriver = new PcSpkDriver(_mixer); } _imuse = IMuse::create(_system, nativeMidiDriver, adlibMidiDriver); - + if (_game.platform == Common::kPlatformFMTowns) { _musicEngine = _townsPlayer = new Player_Towns_v2(this, _mixer, _imuse, true); if (!_townsPlayer->init()) @@ -1869,6 +1909,8 @@ void ScummEngine::setupMusic(int midi) { _imuse->property(IMuse::PROP_LIMIT_PLAYERS, 1); _imuse->property(IMuse::PROP_RECYCLE_PLAYERS, 1); } + if (_musicType == MDT_PCSPK) + _imuse->property(IMuse::PROP_PC_SPEAKER, 1); } } } diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 6e75f47d77..e503af750d 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -46,9 +46,10 @@ /* This disables the dual layer mode which is used in FM-Towns versions * of SCUMM games and which emulates the behavior of the original code. * The only purpose is code size reduction for certain backends. - * SCUMM 3 (FM-Towns) games will run in normal (DOS VGA) mode, which should - * work just fine in most situations. Some glitches might occur. SCUMM 5 games - * will not work without dual layer (and 16 bit color) support. + * SCUMM 3 (FM-Towns) games will run in English in normal (DOS VGA) mode, + * which should work just fine in most situations. Some glitches might + * occur. Japanese mode and SCUMM 5 FM-Towns games will not work without + * dual layer (and 16 bit color) support. */ #define DISABLE_TOWNS_DUAL_LAYER_MODE #endif @@ -345,6 +346,7 @@ class ResourceManager; class ScummEngine : public Engine { friend class ScummDebugger; friend class CharsetRenderer; + friend class CharsetRendererTownsClassic; friend class ResourceManager; public: @@ -897,7 +899,7 @@ public: Common::RenderMode _renderMode; uint8 _bytesPerPixel; - uint8 _bytesPerPixelOutput; + Graphics::PixelFormat _outputPixelFormat; protected: ColorCycle _colorCycle[16]; // Palette cycles @@ -1004,7 +1006,7 @@ protected: // Screen rendering byte *_compositeBuf; byte *_herculesBuf; - + virtual void drawDirtyScreenParts(); void updateDirtyScreen(VirtScreenNumber slot); void drawStripToScreen(VirtScreen *vs, int x, int w, int t, int b); @@ -1148,7 +1150,7 @@ protected: void restoreCharsetBg(); void clearCharsetMask(); void clearTextSurface(); - + virtual void initCharset(int charset); virtual void printString(int m, const byte *msg); @@ -1326,14 +1328,17 @@ public: // Exists both in V7 and in V72HE: byte VAR_NUM_GLOBAL_OBJS; +#ifdef USE_RGB_COLOR + // FM-Towns / PC-Engine specific + Graphics::FontSJIS *_cjkFont; +#endif + // FM-Towns specific #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE public: bool towns_isRectInStringBox(int x1, int y1, int x2, int y2); byte _townsPaletteFlags; - byte _townsCharsetColorMap[16]; - Graphics::FontSJIS *_cjkFont; - uint16 _cjkChar; + byte _townsCharsetColorMap[16]; protected: void towns_drawStripToScreen(VirtScreen *vs, int dstX, int dstY, int srcX, int srcY, int w, int h); @@ -1348,10 +1353,10 @@ protected: Common::Rect _cyclRects[16]; int _numCyclRects; - + Common::Rect _curStringRect; - byte _townsOverrideShadowColor; + byte _townsOverrideShadowColor; byte _textPalette[48]; byte _townsClearLayerFlag; byte _townsActiveLayerFlags; diff --git a/engines/scumm/smush/smush_player.cpp b/engines/scumm/smush/smush_player.cpp index 66502572de..2f4e86bf61 100644 --- a/engines/scumm/smush/smush_player.cpp +++ b/engines/scumm/smush/smush_player.cpp @@ -90,13 +90,13 @@ public: assert(def_end != NULL); char *id_end = def_end; - while (id_end >= def_start && !isdigit(*(id_end-1))) { + while (id_end >= def_start && !isdigit(static_cast<unsigned char>(*(id_end-1)))) { id_end--; } assert(id_end > def_start); char *id_start = id_end; - while (isdigit(*(id_start - 1))) { + while (isdigit(static_cast<unsigned char>(*(id_start - 1)))) { id_start--; } diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index c22da8e7c7..c3cad19fdc 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -312,7 +312,7 @@ void Sound::playSound(int soundID) { sound = (byte *)malloc(size); memcpy(sound, ptr + 6, size); stream = Audio::makeRawStream(sound, size, rate, Audio::FLAG_UNSIGNED); - _mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID); + _mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID); } else if (_vm->_game.platform != Common::kPlatformFMTowns && READ_BE_UINT32(ptr) == MKTAG('S','O','U','N')) { if (_vm->_game.version != 3) @@ -1137,14 +1137,27 @@ int ScummEngine::readSoundResource(ResId idx) { break; case MKTAG('S','P','K',' '): pri = -1; -// if (_musicType == MDT_PCSPK || _musicType == MDT_PCJR) -// pri = 11; + if (_musicType == MDT_PCSPK || _musicType == MDT_PCJR) + pri = 11; break; } + // We only allow SPK resources for PC Speaker, PCJr and CMS here + // since other resource would sound horribly with their output + // drivers. if ((_musicType == MDT_PCSPK || _musicType == MDT_PCJR || _musicType == MDT_CMS) && pri != 11) pri = -1; + // We only allow ADL resources when AdLib or FM-Towns is used as + // primary audio output. This fixes some odd sounds when Indy and + // Sophia leave Atlantis with the submarine in Indy4. (Easy to + // check with bootparam 4061 in the CD version). It seems the game + // only contains a ROL resource for sound id 60. Formerly we tried + // to play that via the AdLib or FM-Towns audio driver resulting + // in strange noises. Now we behave like the original did. + if ((_musicType == MDT_ADLIB || _musicType == MDT_TOWNS) && pri != 10) + pri = -1; + debugC(DEBUG_RESOURCE, " tag: %s, total_size=%d, pri=%d", tag2str(tag), size, pri); diff --git a/engines/scumm/string.cpp b/engines/scumm/string.cpp index 4b3207c6bf..2d2209c155 100644 --- a/engines/scumm/string.cpp +++ b/engines/scumm/string.cpp @@ -1383,10 +1383,10 @@ void ScummEngine_v7::loadLanguageBundle() { } else if (*ptr == '#') { // Number of subtags following a given basetag. We don't need that // information so we just skip it - } else if (isdigit(*ptr)) { + } else if (isdigit(static_cast<unsigned char>(*ptr))) { int idx = 0; // A number (up to three digits)... - while (isdigit(*ptr)) { + while (isdigit(static_cast<unsigned char>(*ptr))) { idx = idx * 10 + (*ptr - '0'); ptr++; } @@ -1424,12 +1424,12 @@ void ScummEngine_v7::loadLanguageBundle() { for (i = 0; i < _languageIndexSize; i++) { // First 8 chars in the line give the string ID / 'tag' int j; - for (j = 0; j < 8 && !isspace(*ptr); j++, ptr++) + for (j = 0; j < 8 && !isspace(static_cast<unsigned char>(*ptr)); j++, ptr++) _languageIndex[i].tag[j] = toupper(*ptr); _languageIndex[i].tag[j] = 0; // After that follows a single space which we skip - assert(isspace(*ptr)); + assert(isspace(static_cast<unsigned char>(*ptr))); ptr++; // Then comes the translated string: we record an offset to that. diff --git a/engines/scumm/vars.cpp b/engines/scumm/vars.cpp index 56f8de2ad1..4527d7a121 100644 --- a/engines/scumm/vars.cpp +++ b/engines/scumm/vars.cpp @@ -704,8 +704,12 @@ void ScummEngine_v99he::resetScummVars() { VAR(VAR_NUM_UNK) = _numUnk; if (_game.heversion >= 100 && (_game.features & GF_16BIT_COLOR)) { - // Disable Bink and Smacker video in 16bit color games + // Enable Bink video in 16bit color games +#ifdef USE_BINK + VAR(140) = 1; +#else VAR(140) = 0; +#endif } } #endif diff --git a/engines/scumm/verbs.cpp b/engines/scumm/verbs.cpp index 69caceb53a..0c1554840e 100644 --- a/engines/scumm/verbs.cpp +++ b/engines/scumm/verbs.cpp @@ -152,7 +152,7 @@ void ScummEngine_v0::setNewKidVerbs() { void ScummEngine_v0::switchActor(int slot) { resetSentence(false); - + if (_currentRoom == 45) return; @@ -728,7 +728,7 @@ void ScummEngine_v0::runObject(int obj, int entry) { entry = 0x0F; } } - + _v0ObjectInInventory = prev; if (getVerbEntrypoint(obj, entry) != 0) { @@ -830,7 +830,7 @@ bool ScummEngine_v0::verbObtain(int obj, int objIndex) { } else { _verbPickup = false; } - + // Ignore verbs? Actor *a = derefActor(VAR(VAR_EGO), "verbObtain"); if (((ActorC64 *)a)->_miscflags & 0x40) { @@ -989,7 +989,7 @@ bool ScummEngine_v0::verbExec() { runObject(_activeObjectIndex, entry); _v0ObjectIndex = false; } else if (_activeInventory) { - // Not sure this is the correct way to do this, + // Not sure this is the correct way to do this, // however its working for most situations - segra if (verbExecutes(_activeInventory, true) == false) { if (_activeObject2 && _activeObject2Inv && verbExecutes(_activeObject2, true)) { @@ -1001,7 +1001,7 @@ bool ScummEngine_v0::verbExec() { runObject(_activeObject, _activeVerb); } else { _v0ObjectInInventory = true; - + if (_activeObject2) { _activeObject = _activeObject2; @@ -1085,7 +1085,7 @@ void ScummEngine_v0::checkExecVerbs() { // Click into V2 inventory checkV2Inventory(_mouse.x, _mouse.y); - + // Did the Inventory position changed (arrows pressed, do nothing) if (invOff != _inventoryOffset) return; @@ -1143,7 +1143,7 @@ void ScummEngine_v0::checkExecVerbs() { obj = 0; objIdx = 0; } - + if (a->_miscflags & 0x80) { if (_activeVerb != 7 && over != 7) { _activeVerb = 0; @@ -1444,9 +1444,9 @@ void ScummEngine::restoreVerbBG(int verb) { VerbSlot *vs; vs = &_verbs[verb]; - uint8 col = + uint8 col = #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE - ((_game.platform == Common::kPlatformFMTowns) && (_game.id == GID_MONKEY2 || _game.id == GID_INDY4) && (vs->bkcolor == _townsOverrideShadowColor)) ? 0 : + ((_game.platform == Common::kPlatformFMTowns) && (_game.id == GID_MONKEY2 || _game.id == GID_INDY4) && (vs->bkcolor == _townsOverrideShadowColor)) ? 0 : #endif vs->bkcolor; |