diff options
45 files changed, 1546 insertions, 352 deletions
@@ -10,6 +10,16 @@ For a more comprehensive changelog of the latest experimental code, see: - Rewrote VideoDecoder subsystem. - Added Galician translation. + Cine: + - Improved audio support for Amiga and AtariST versions of Future Wars. + Now music fades out slowly instead of stopping immediately. Sound + effects are now properly panned, when requested by the game. + + SCUMM: + - Implemented Monkey Island 2 Macintosh's audio driver. Now we properly + support its sample based audio output. The same output is also used for + the m68k Macintosh version of Indiana Jones and the Fate of Atlantis. + 1.5.0 (2012-07-27) New Games: - Added support for Backyard Baseball 2003. diff --git a/backends/midi/coreaudio.cpp b/backends/midi/coreaudio.cpp index 43c801287d..94262d0d92 100644 --- a/backends/midi/coreaudio.cpp +++ b/backends/midi/coreaudio.cpp @@ -172,10 +172,15 @@ int MidiDriver_CORE::open() { // Load custom soundfont, if specified if (ConfMan.hasKey("soundfont")) { - FSRef fsref; - FSSpec fsSpec; const char *soundfont = ConfMan.get("soundfont").c_str(); + // TODO: We should really check whether the file contains an + // actual soundfont... + +#if USE_DEPRECATED_COREAUDIO_API + // Before 10.5, we need to use kMusicDeviceProperty_SoundBankFSSpec + FSRef fsref; + FSSpec fsSpec; err = FSPathMakeRef ((const byte *)soundfont, &fsref, NULL); if (err == noErr) { @@ -183,8 +188,6 @@ int MidiDriver_CORE::open() { } if (err == noErr) { - // TODO: We should really check here whether the file contains an - // actual soundfont... err = AudioUnitSetProperty ( _synth, kMusicDeviceProperty_SoundBankFSSpec, kAudioUnitScope_Global, @@ -192,9 +195,27 @@ int MidiDriver_CORE::open() { &fsSpec, sizeof(fsSpec) ); } +#else + // kMusicDeviceProperty_SoundBankFSSpec is present on 10.6+, but broken + // kMusicDeviceProperty_SoundBankURL was added in 10.5 as a replacement + CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)soundfont, strlen(soundfont), false); + + if (url) { + err = AudioUnitSetProperty ( + _synth, + kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global, + 0, + &url, sizeof(url) + ); + + CFRelease(url); + } else { + warning("Failed to allocate CFURLRef from '%s'", soundfont); + } +#endif if (err != noErr) - warning("Failed loading custom sound font '%s' (error %ld)\n", soundfont, (long)err); + error("Failed loading custom sound font '%s' (error %ld)", soundfont, (long)err); } #ifdef COREAUDIO_DISABLE_REVERB diff --git a/base/main.cpp b/base/main.cpp index 25e1b881cc..355a65f883 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -57,6 +57,7 @@ #include "graphics/cursorman.h" #include "graphics/fontman.h" +#include "graphics/yuv_to_rgb.h" #ifdef USE_FREETYPE2 #include "graphics/fonts/ttf.h" #endif @@ -511,6 +512,8 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { #ifdef USE_FREETYPE2 Graphics::shutdownTTF(); #endif + EngineManager::destroy(); + Graphics::YUVToRGBManager::destroy(); return 0; } @@ -62,7 +62,7 @@ get_var() { eval echo \$${1} } -# Add an engine: id name build subengines dependencies +# Add an engine: id name build subengines base-games dependencies add_engine() { _engines="${_engines} ${1}" if test "${3}" = "no" ; then diff --git a/engines/dreamweb/dreamweb.h b/engines/dreamweb/dreamweb.h index 1f6deb8566..a4597b1867 100644 --- a/engines/dreamweb/dreamweb.h +++ b/engines/dreamweb/dreamweb.h @@ -196,7 +196,7 @@ protected: // from monitor.cpp char _inputLine[64]; - char _operand1[14]; + char _operand1[64]; char _currentFile[14]; // from newplace.cpp diff --git a/engines/dreamweb/monitor.cpp b/engines/dreamweb/monitor.cpp index 4e9d8eecc1..1886a80b6a 100644 --- a/engines/dreamweb/monitor.cpp +++ b/engines/dreamweb/monitor.cpp @@ -194,7 +194,7 @@ void DreamWebEngine::printLogo() { } void DreamWebEngine::input() { - memset(_inputLine, 0, 64); + memset(_inputLine, 0, sizeof(_inputLine)); _curPos = 0; printChar(_monitorCharset, _monAdX, _monAdY, '>', 0, NULL, NULL); multiDump(_monAdX, _monAdY, 6, 8); @@ -665,7 +665,7 @@ void DreamWebEngine::searchForFiles(const char *filesString) { const char *DreamWebEngine::parser() { char *output = _operand1; - memset(output, 0, 14); + memset(output, 0, sizeof(_operand1)); *output++ = '='; diff --git a/engines/dreamweb/vgagrafx.cpp b/engines/dreamweb/vgagrafx.cpp index ec306c4924..50c0ef3161 100644 --- a/engines/dreamweb/vgagrafx.cpp +++ b/engines/dreamweb/vgagrafx.cpp @@ -23,6 +23,7 @@ #include "dreamweb/dreamweb.h" #include "engines/util.h" #include "graphics/surface.h" +#include "graphics/decoders/pcx.h" namespace DreamWeb { @@ -152,70 +153,33 @@ void DreamWebEngine::setMode() { void DreamWebEngine::showPCX(const Common::String &suffix) { Common::String name = getDatafilePrefix() + suffix; Common::File pcxFile; - if (!pcxFile.open(name)) { warning("showpcx: Could not open '%s'", name.c_str()); return; } - uint8 *mainGamePal; - int i, j; + Graphics::PCXDecoder pcx; + if (!pcx.loadStream(pcxFile)) { + warning("showpcx: Could not process '%s'", name.c_str()); + return; + } // Read the 16-color palette into the 'maingamepal' buffer. Note that // the color components have to be adjusted from 8 to 6 bits. - - pcxFile.seek(16, SEEK_SET); - mainGamePal = _mainPal; - pcxFile.read(mainGamePal, 48); - - memset(mainGamePal + 48, 0xff, 720); - for (i = 0; i < 48; i++) { - mainGamePal[i] >>= 2; + memset(_mainPal, 0xff, 256 * 3); + memcpy(_mainPal, pcx.getPalette(), 48); + for (int i = 0; i < 48; i++) { + _mainPal[i] >>= 2; } - // Decode the image data. - Graphics::Surface *s = g_system->lockScreen(); - Common::Rect rect(640, 480); - - s->fillRect(rect, 0); - pcxFile.seek(128, SEEK_SET); - - for (int y = 0; y < 480; y++) { - byte *dst = (byte *)s->getBasePtr(0, y); - int decoded = 0; - - while (decoded < 320) { - byte col = pcxFile.readByte(); - byte len; - - if ((col & 0xc0) == 0xc0) { - len = col & 0x3f; - col = pcxFile.readByte(); - } else { - len = 1; - } - - // The image uses 16 colors and is stored as four bit - // planes, one for each bit of the color, least - // significant bit plane first. - - for (i = 0; i < len; i++) { - int plane = decoded / 80; - int pos = decoded % 80; - - for (j = 0; j < 8; j++) { - byte bit = (col >> (7 - j)) & 1; - dst[8 * pos + j] |= (bit << plane); - } - - decoded++; - } - } - } - + s->fillRect(Common::Rect(640, 480), 0); + const Graphics::Surface *pcxSurface = pcx.getSurface(); + if (pcxSurface->format.bytesPerPixel != 1) + error("Invalid bytes per pixel in PCX surface (%d)", pcxSurface->format.bytesPerPixel); + for (uint16 y = 0; y < pcxSurface->h; y++) + memcpy((byte *)s->getBasePtr(0, y), pcxSurface->getBasePtr(0, y), pcxSurface->w); g_system->unlockScreen(); - pcxFile.close(); } void DreamWebEngine::frameOutV(uint8 *dst, const uint8 *src, uint16 pitch, uint16 width, uint16 height, int16 x, int16 y) { diff --git a/engines/hugo/file.cpp b/engines/hugo/file.cpp index 15ee06c82a..1758f3f6a5 100644 --- a/engines/hugo/file.cpp +++ b/engines/hugo/file.cpp @@ -32,7 +32,11 @@ #include "common/savefile.h" #include "common/textconsole.h" #include "common/config-manager.h" + +#include "graphics/surface.h" +#include "graphics/decoders/pcx.h" #include "graphics/thumbnail.h" + #include "gui/saveload.h" #include "hugo/hugo.h" @@ -88,66 +92,33 @@ const char *FileManager::getUifFilename() const { } /** - * Convert 4 planes (RGBI) data to 8-bit DIB format - * Return original plane data ptr - */ -byte *FileManager::convertPCC(byte *p, const uint16 y, const uint16 bpl, ImagePtr dataPtr) const { - debugC(2, kDebugFile, "convertPCC(byte *p, %d, %d, ImagePtr dataPtr)", y, bpl); - - dataPtr += y * bpl * 8; // Point to correct DIB line - for (int16 r = 0, g = bpl, b = g + bpl, i = b + bpl; r < bpl; r++, g++, b++, i++) { // Each byte in all planes - for (int8 bit = 7; bit >= 0; bit--) { // Each bit in byte - *dataPtr++ = (((p[r] >> bit & 1) << 0) | - ((p[g] >> bit & 1) << 1) | - ((p[b] >> bit & 1) << 2) | - ((p[i] >> bit & 1) << 3)); - } - } - return p; -} - -/** * Read a pcx file of length len. Use supplied seqPtr and image_p or * allocate space if NULL. Name used for errors. Returns address of seqPtr * Set first TRUE to initialize b_index (i.e. not reading a sequential image in file). */ -Seq *FileManager::readPCX(Common::ReadStream &f, Seq *seqPtr, byte *imagePtr, const bool firstFl, const char *name) { +Seq *FileManager::readPCX(Common::SeekableReadStream &f, Seq *seqPtr, byte *imagePtr, const bool firstFl, const char *name) { debugC(1, kDebugFile, "readPCX(..., %s)", name); - // Read in the PCC header and check consistency - _PCCHeader._mfctr = f.readByte(); - _PCCHeader._vers = f.readByte(); - _PCCHeader._enc = f.readByte(); - _PCCHeader._bpx = f.readByte(); - _PCCHeader._x1 = f.readUint16LE(); - _PCCHeader._y1 = f.readUint16LE(); - _PCCHeader._x2 = f.readUint16LE(); - _PCCHeader._y2 = f.readUint16LE(); - _PCCHeader._xres = f.readUint16LE(); - _PCCHeader._yres = f.readUint16LE(); - f.read(_PCCHeader._palette, sizeof(_PCCHeader._palette)); - _PCCHeader._vmode = f.readByte(); - _PCCHeader._planes = f.readByte(); - _PCCHeader._bytesPerLine = f.readUint16LE(); - f.read(_PCCHeader._fill2, sizeof(_PCCHeader._fill2)); - - if (_PCCHeader._mfctr != 10) - error("Bad data file format: %s", name); - // Allocate memory for Seq if 0 if (seqPtr == 0) { if ((seqPtr = (Seq *)malloc(sizeof(Seq))) == 0) error("Insufficient memory to run game."); } + Graphics::PCXDecoder pcx; + if (!pcx.loadStream(f)) + error("Error while reading PCX image"); + + const Graphics::Surface *pcxSurface = pcx.getSurface(); + if (pcxSurface->format.bytesPerPixel != 1) + error("Invalid bytes per pixel in PCX surface (%d)", pcxSurface->format.bytesPerPixel); + // Find size of image data in 8-bit DIB format // Note save of x2 - marks end of valid data before garbage - uint16 bytesPerLine4 = _PCCHeader._bytesPerLine * 4; // 4-bit bpl - seqPtr->_bytesPerLine8 = bytesPerLine4 * 2; // 8-bit bpl - seqPtr->_lines = _PCCHeader._y2 - _PCCHeader._y1 + 1; - seqPtr->_x2 = _PCCHeader._x2 - _PCCHeader._x1 + 1; + seqPtr->_lines = pcxSurface->h; + seqPtr->_x2 = seqPtr->_bytesPerLine8 = pcxSurface->w; // Size of the image - uint16 size = seqPtr->_lines * seqPtr->_bytesPerLine8; + uint16 size = pcxSurface->w * pcxSurface->h; // Allocate memory for image data if NULL if (imagePtr == 0) @@ -156,26 +127,9 @@ Seq *FileManager::readPCX(Common::ReadStream &f, Seq *seqPtr, byte *imagePtr, co assert(imagePtr); seqPtr->_imagePtr = imagePtr; + for (uint16 y = 0; y < pcxSurface->h; y++) + memcpy(imagePtr + y * pcxSurface->w, pcxSurface->getBasePtr(0, y), pcxSurface->w); - // Process the image data, converting to 8-bit DIB format - uint16 y = 0; // Current line index - byte pline[kXPix]; // Hold 4 planes of data - byte *p = pline; // Ptr to above - while (y < seqPtr->_lines) { - byte c = f.readByte(); - if ((c & kRepeatMask) == kRepeatMask) { - byte d = f.readByte(); // Read data byte - for (int i = 0; i < (c & kLengthMask); i++) { - *p++ = d; - if ((uint16)(p - pline) == bytesPerLine4) - p = convertPCC(pline, y++, _PCCHeader._bytesPerLine, imagePtr); - } - } else { - *p++ = c; - if ((uint16)(p - pline) == bytesPerLine4) - p = convertPCC(pline, y++, _PCCHeader._bytesPerLine, imagePtr); - } - } return seqPtr; } diff --git a/engines/hugo/file.h b/engines/hugo/file.h index 1438bd2054..44f257a2af 100644 --- a/engines/hugo/file.h +++ b/engines/hugo/file.h @@ -112,16 +112,13 @@ protected: Common::File _sceneryArchive1; // Handle for scenery file Common::File _objectsArchive; // Handle for objects file - PCCHeader _PCCHeader; - - Seq *readPCX(Common::ReadStream &f, Seq *seqPtr, byte *imagePtr, const bool firstFl, const char *name); + Seq *readPCX(Common::SeekableReadStream &f, Seq *seqPtr, byte *imagePtr, const bool firstFl, const char *name); // If this is the first call, read the lookup table bool _hasReadHeader; SoundHdr _soundHdr[kMaxSounds]; // Sound lookup table private: - byte *convertPCC(byte *p, const uint16 y, const uint16 bpl, ImagePtr dataPtr) const; UifHdr *getUIFHeader(const Uif id); }; diff --git a/engines/queen/display.cpp b/engines/queen/display.cpp index 83dc1a9f60..cd9a1075fa 100644 --- a/engines/queen/display.cpp +++ b/engines/queen/display.cpp @@ -23,9 +23,13 @@ #include "common/system.h" #include "common/events.h" +#include "common/stream.h" +#include "common/memstream.h" #include "graphics/cursorman.h" #include "graphics/palette.h" +#include "graphics/surface.h" +#include "graphics/decoders/pcx.h" #include "queen/display.h" #include "queen/input.h" @@ -806,28 +810,22 @@ void Display::fill(uint8 *dstBuf, uint16 dstPitch, uint16 x, uint16 y, uint16 w, } void Display::decodePCX(const uint8 *src, uint32 srcSize, uint8 *dst, uint16 dstPitch, uint16 *w, uint16 *h, uint8 *pal, uint16 palStart, uint16 palEnd) { - *w = READ_LE_UINT16(src + 12); - *h = READ_LE_UINT16(src + 14); + Common::MemoryReadStream str(src, srcSize); + + ::Graphics::PCXDecoder pcx; + if (!pcx.loadStream(str)) + error("Error while reading PCX image"); + + const ::Graphics::Surface *pcxSurface = pcx.getSurface(); + if (pcxSurface->format.bytesPerPixel != 1) + error("Invalid bytes per pixel in PCX surface (%d)", pcxSurface->format.bytesPerPixel); + *w = pcxSurface->w; + *h = pcxSurface->h; assert(palStart <= palEnd && palEnd <= 256); - const uint8 *palData = src + srcSize - 768; - memcpy(pal, palData + palStart * 3, (palEnd - palStart) * 3); - - src += 128; - for (int y = 0; y < *h; ++y) { - uint8 *p = dst; - while (p < dst + *w) { - uint8 col = *src++; - if ((col & 0xC0) == 0xC0) { - uint8 len = col & 0x3F; - memset(p, *src++, len); - p += len; - } else { - *p++ = col; - } - } - dst += dstPitch; - } + memcpy(pal, pcx.getPalette() + palStart * 3, (palEnd - palStart) * 3); + for (uint16 y = 0; y < pcxSurface->h; y++) + memcpy(dst + y * dstPitch, pcxSurface->getBasePtr(0, y), pcxSurface->w); } void Display::decodeLBM(const uint8 *src, uint32 srcSize, uint8 *dst, uint16 dstPitch, uint16 *w, uint16 *h, uint8 *pal, uint16 palStart, uint16 palEnd, uint8 colorBase) { diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index 5404c7f8b1..e5c3023380 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -242,6 +242,10 @@ static Common::String generateFilenameForDetection(const char *pattern, Filename return result; } +bool ScummEngine::isMacM68kIMuse() const { + return _game.platform == Common::kPlatformMacintosh && (_game.id == GID_MONKEY2 || _game.id == GID_INDY4) && !(_game.features & GF_MAC_CONTAINER); +} + struct DetectorDesc { Common::FSNode node; Common::String md5; @@ -473,6 +477,11 @@ static void computeGameSettingsFromMD5(const Common::FSList &fslist, const GameF if (dr.language == UNK_LANG) { dr.language = detectLanguage(fslist, dr.game.id); } + + // HACK: Detect between 68k and PPC versions + if (dr.game.platform == Common::kPlatformMacintosh && dr.game.version >= 5 && dr.game.heversion == 0 && strstr(gfp->pattern, "Data")) + dr.game.features |= GF_MAC_CONTAINER; + break; } } diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h index be1b90e356..3120017db6 100644 --- a/engines/scumm/detection_tables.h +++ b/engines/scumm/detection_tables.h @@ -236,7 +236,7 @@ static const GameSettings gameVariantsTable[] = { {"monkey", "VGA", "vga", GID_MONKEY_VGA, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO1(GUIO_NOSPEECH)}, {"monkey", "EGA", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, GF_16COLOR, Common::kPlatformPC, GUIO1(GUIO_NOSPEECH)}, {"monkey", "No AdLib", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR, GF_16COLOR, Common::kPlatformAtariST, GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)}, - {"monkey", "Demo", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_ADLIB, GF_16COLOR, Common::kPlatformPC, GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)}, + {"monkey", "Demo", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB, GF_16COLOR, Common::kPlatformPC, GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)}, {"monkey", "CD", 0, GID_MONKEY, 5, 0, MDT_ADLIB, GF_AUDIOTRACKS, UNK, GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)}, {"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_TOWNS, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO4(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_MIDITOWNS, GUIO_NOASPECT)}, {"monkey", "SEGA", 0, GID_MONKEY, 5, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI)}, diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index 27a72c2afe..016ba89e7b 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -164,7 +164,7 @@ bool IMuseInternal::isMT32(int sound) { return true; case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2 - return true; + return false; case MKTAG('G', 'M', 'D', ' '): return false; @@ -226,6 +226,45 @@ bool IMuseInternal::isMIDI(int sound) { return false; } +bool IMuseInternal::supportsPercussion(int sound) { + byte *ptr = g_scumm->_res->_types[rtSound][sound]._address; + if (ptr == NULL) + return false; + + 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', ' '): + return false; + + 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 + // This is MIDI, i.e. uses MIDI style program changes, but without a + // special percussion channel. + return false; + + case MKTAG('G', 'M', 'D', ' '): + case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max + return true; + } + + // Old style 'RO' has equivalent properties to 'ROL' + if (ptr[0] == 'R' && ptr[1] == 'O') + return true; + // Euphony tracks show as 'SO' and have equivalent properties to 'ADL' + // FIXME: Right now we're pretending it's GM. + if (ptr[4] == 'S' && ptr[5] == 'O') + return true; + + error("Unknown music type: '%c%c%c%c'", (char)tag >> 24, (char)tag >> 16, (char)tag >> 8, (char)tag); + + return false; +} + MidiDriver *IMuseInternal::getBestMidiDriver(int sound) { MidiDriver *driver = NULL; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 3b0d36e119..846e2d7545 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -204,6 +204,7 @@ protected: bool _isMT32; bool _isMIDI; + bool _supportsPercussion; protected: // Player part @@ -458,6 +459,7 @@ protected: byte *findStartOfSound(int sound, int ct = (kMThd | kFORM)); bool isMT32(int sound); bool isMIDI(int sound); + bool supportsPercussion(int sound); int get_queue_sound_status(int sound) const; void handle_marker(uint id, byte data); int get_channel_volume(uint a); diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp index 73e7704469..89c16a8bb5 100644 --- a/engines/scumm/imuse/imuse_part.cpp +++ b/engines/scumm/imuse/imuse_part.cpp @@ -27,6 +27,7 @@ #include "common/util.h" #include "scumm/imuse/imuse_internal.h" #include "scumm/saveload.h" +#include "scumm/scumm.h" namespace Scumm { @@ -365,7 +366,17 @@ void Part::set_instrument(uint b) { _bank = (byte)(b >> 8); if (_bank) error("Non-zero instrument bank selection. Please report this"); - _instrument.program((byte)b, _player->isMT32()); + // HACK: Horrible hack to allow tracing of program change source. + // The Mac m68k versions of MI2 and Indy4 use a different program "bank" + // when it gets program change events through the iMuse SysEx handler. + // We emulate this by introducing a special instrument, which sets + // the instrument via sysEx_customInstrument. This seems to be + // exclusively used for special sound effects like the "spit" sound. + if (g_scumm->isMacM68kIMuse()) { + _instrument.macSfx(b); + } else { + _instrument.program((byte)b, _player->isMT32()); + } if (clearToTransmit()) _instrument.send(_mc); } diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp index 53ccfb3734..3a9c42a920 100644 --- a/engines/scumm/imuse/imuse_player.cpp +++ b/engines/scumm/imuse/imuse_player.cpp @@ -78,6 +78,7 @@ Player::Player() : _speed(128), _isMT32(false), _isMIDI(false), + _supportsPercussion(false), _se(0), _vol_chan(0) { } @@ -103,6 +104,7 @@ bool Player::startSound(int sound, MidiDriver *midi) { _isMT32 = _se->isMT32(sound); _isMIDI = _se->isMIDI(sound); + _supportsPercussion = _se->supportsPercussion(sound); _parts = NULL; _active = true; @@ -386,6 +388,8 @@ void Player::sysEx(const byte *p, uint16 len) { // SysEx manufacturer 0x97 has been spotted in the // Monkey Island 2 AdLib music, so don't make this a // fatal error. See bug #1481383. + // The Macintosh version of Monkey Island 2 simply + // ignores these SysEx events too. if (a == 0) warning("Unknown SysEx manufacturer 0x00 0x%02X 0x%02X", p[0], p[1]); else @@ -1009,6 +1013,7 @@ void Player::fixAfterLoad() { _parser->jumpToTick(_music_tick); // start_seq_sound already switched tracks _isMT32 = _se->isMT32(_id); _isMIDI = _se->isMIDI(_id); + _supportsPercussion = _se->supportsPercussion(_id); } } diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp index 11bb4e7605..61c73b1e2d 100644 --- a/engines/scumm/imuse/instrument.cpp +++ b/engines/scumm/imuse/instrument.cpp @@ -278,6 +278,21 @@ private: byte _instrument[23]; }; +class Instrument_MacSfx : public InstrumentInternal { +private: + byte _program; + +public: + Instrument_MacSfx(byte program); + Instrument_MacSfx(Serializer *s); + void saveOrLoad(Serializer *s); + void send(MidiChannel *mc); + void copy_to(Instrument *dest) { dest->macSfx(_program); } + bool is_valid() { + return (_program < 128); + } +}; + //////////////////////////////////////// // // Instrument class members @@ -326,6 +341,14 @@ void Instrument::pcspk(const byte *instrument) { _instrument = new Instrument_PcSpk(instrument); } +void Instrument::macSfx(byte prog) { + clear(); + if (prog > 127) + return; + _type = itMacSfx; + _instrument = new Instrument_MacSfx(prog); +} + void Instrument::saveOrLoad(Serializer *s) { if (s->isSaving()) { s->saveByte(_type); @@ -349,6 +372,9 @@ void Instrument::saveOrLoad(Serializer *s) { case itPcSpk: _instrument = new Instrument_PcSpk(s); break; + case itMacSfx: + _instrument = new Instrument_MacSfx(s); + break; default: warning("No known instrument classification #%d", (int)_type); _type = itNone; @@ -528,4 +554,38 @@ void Instrument_PcSpk::send(MidiChannel *mc) { mc->sysEx_customInstrument('SPK ', (byte *)&_instrument); } +//////////////////////////////////////// +// +// Instrument_MacSfx class members +// +//////////////////////////////////////// + +Instrument_MacSfx::Instrument_MacSfx(byte program) : + _program(program) { + if (program > 127) { + _program = 255; + } +} + +Instrument_MacSfx::Instrument_MacSfx(Serializer *s) { + _program = 255; + if (!s->isSaving()) { + saveOrLoad(s); + } +} + +void Instrument_MacSfx::saveOrLoad(Serializer *s) { + if (s->isSaving()) { + s->saveByte(_program); + } else { + _program = s->loadByte(); + } +} + +void Instrument_MacSfx::send(MidiChannel *mc) { + if (_program > 127) { + return; + } + mc->sysEx_customInstrument('MAC ', &_program); +} } // End of namespace Scumm diff --git a/engines/scumm/imuse/instrument.h b/engines/scumm/imuse/instrument.h index a855c64155..7e09e86fa5 100644 --- a/engines/scumm/imuse/instrument.h +++ b/engines/scumm/imuse/instrument.h @@ -52,7 +52,8 @@ public: itProgram = 1, itAdLib = 2, itRoland = 3, - itPcSpk = 4 + itPcSpk = 4, + itMacSfx = 5 }; Instrument() : _type(0), _instrument(0) { } @@ -72,6 +73,7 @@ public: void adlib(const byte *instrument); void roland(const byte *instrument); void pcspk(const byte *instrument); + void macSfx(byte program); byte getType() { return _type; } bool isValid() { return (_instrument ? _instrument->is_valid() : false); } diff --git a/engines/scumm/imuse/mac_m68k.cpp b/engines/scumm/imuse/mac_m68k.cpp new file mode 100644 index 0000000000..4d7a6a64c0 --- /dev/null +++ b/engines/scumm/imuse/mac_m68k.cpp @@ -0,0 +1,510 @@ +/* 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/mac_m68k.h" + +#include "common/util.h" +#include "common/macresman.h" +#include "common/stream.h" + +namespace Scumm { + +MacM68kDriver::MacM68kDriver(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer) { +} + +MacM68kDriver::~MacM68kDriver() { +} + +int MacM68kDriver::open() { + if (_isOpen) { + return MERR_ALREADY_OPEN; + } + + const int error = MidiDriver_Emulated::open(); + if (error) { + return error; + } + + for (uint i = 0; i < ARRAYSIZE(_channels); ++i) { + _channels[i].init(this, i); + } + + memset(_voiceChannels, 0, sizeof(_voiceChannels)); + _lastUsedVoiceChannel = 0; + + loadAllInstruments(); + + _pitchTable[116] = 1664510; + _pitchTable[117] = 1763487; + _pitchTable[118] = 1868350; + _pitchTable[119] = 1979447; + _pitchTable[120] = 2097152; + _pitchTable[121] = 2221855; + _pitchTable[122] = 2353973; + _pitchTable[123] = 2493948; + _pitchTable[124] = 2642246; + _pitchTable[125] = 2799362; + _pitchTable[126] = 2965820; + _pitchTable[127] = 3142177; + for (int i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + _volumeTable = new byte[8192]; + for (int i = 0; i < 32; ++i) { + for (int j = 0; j < 256; ++j) { + _volumeTable[i * 256 + j] = ((-128 + j) * _volumeBaseTable[i]) / 127 - 128; + } + } + + _mixBuffer = 0; + _mixBufferLength = 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 MacM68kDriver::close() { + if (!_isOpen) { + return; + } + + _mixer->stopHandle(_mixerSoundHandle); + _isOpen = false; + for (InstrumentMap::iterator i = _instruments.begin(); i != _instruments.end(); ++i) { + delete[] i->_value.data; + } + _instruments.clear(); + delete[] _volumeTable; + _volumeTable = 0; + delete[] _mixBuffer; + _mixBuffer = 0; + _mixBufferLength = 0; +} + +void MacM68kDriver::send(uint32 d) { + assert(false); +} + +void MacM68kDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { + assert(false); +} + +MidiChannel *MacM68kDriver::allocateChannel() { + for (uint i = 0; i < ARRAYSIZE(_channels); ++i) { + if (_channels[i].allocate()) { + return &_channels[i]; + } + } + + return 0; +} + +MacM68kDriver::Instrument MacM68kDriver::getInstrument(int idx) const { + InstrumentMap::const_iterator i = _instruments.find(idx); + if (i != _instruments.end()) { + return i->_value; + } else { + return _defaultInstrument; + } +} + +void MacM68kDriver::generateSamples(int16 *buf, int len) { + int silentChannels = 0; + + if (_mixBufferLength < len) { + delete[] _mixBuffer; + + _mixBufferLength = len; + _mixBuffer = new int[_mixBufferLength]; + assert(_mixBuffer); + } + memset(_mixBuffer, 0, sizeof(int) * _mixBufferLength); + + for (int i = 0; i < kChannelCount; ++i) { + OutputChannel &out = _voiceChannels[i].out; + if (out.isFinished) { + ++silentChannels; + continue; + } + + byte *volumeTable = &_volumeTable[(out.volume / 4) * 256]; + int *buffer = _mixBuffer; + + int samplesLeft = len; + while (samplesLeft) { + out.subPos += out.pitchModifier; + while (out.subPos >= 0x10000) { + out.subPos -= 0x10000; + out.instrument++; + } + + if (out.instrument >= out.end) { + if (!out.start) { + break; + } + + out.instrument = out.start; + out.subPos = 0; + } + + *buffer++ += volumeTable[*out.instrument]; + --samplesLeft; + } + + if (samplesLeft) { + out.isFinished = true; + while (samplesLeft--) { + *buffer++ += 0x80; + } + } + } + + const int *buffer = _mixBuffer; + const int silenceAdd = silentChannels << 7; + while (len--) { + *buf++ = (((*buffer++ + silenceAdd) >> 3) << 8) ^ 0x8000; + } +} + +void MacM68kDriver::loadAllInstruments() { + Common::MacResManager resource; + if (resource.open("iMUSE Setups")) { + for (int i = 0x3E7; i < 0x468; ++i) { + Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i); + if (stream) { + addInstrument(i, stream); + delete stream; + } + } + + for (int i = 0x7D0; i < 0x8D0; ++i) { + Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i); + if (stream) { + addInstrument(i, stream); + delete stream; + } + } + + InstrumentMap::iterator inst = _instruments.find(kDefaultInstrument); + if (inst != _instruments.end()) { + _defaultInstrument = inst->_value; + } else { + error("MacM68kDriver::loadAllInstruments: Could not load default instrument"); + } + } else { + error("MacM68kDriver::loadAllInstruments: Could not load \"iMUSE Setups\""); + } +} + +void MacM68kDriver::addInstrument(int idx, Common::SeekableReadStream *data) { + // We parse the "SND" files manually here, since we need special data + // from their header and need to work on them raw while mixing. + data->skip(2); + int count = data->readUint16BE(); + data->skip(2 * (3 * count)); + count = data->readUint16BE(); + data->skip(2 * (4 * count)); + + Instrument inst; + // Skip (optional) pointer to data + data->skip(4); + inst.length = data->readUint32BE(); + inst.sampleRate = data->readUint32BE(); + inst.loopStart = data->readUint32BE(); + inst.loopEnd = data->readUint32BE(); + // Skip encoding + data->skip(1); + inst.baseFrequency = data->readByte(); + + inst.data = new byte[inst.length]; + assert(inst.data); + data->read(inst.data, inst.length); + _instruments[idx] = inst; +} + +void MacM68kDriver::setPitch(OutputChannel *out, int frequency) { + out->frequency = frequency; + out->isFinished = false; + + const int pitchIdx = (frequency >> 7) + 60 - out->baseFrequency; + assert(pitchIdx >= 0); + + const int low7Bits = frequency & 0x7F; + if (low7Bits) { + out->pitchModifier = _pitchTable[pitchIdx] + (((_pitchTable[pitchIdx + 1] - _pitchTable[pitchIdx]) * low7Bits) >> 7); + } else { + out->pitchModifier = _pitchTable[pitchIdx]; + } +} + +void MacM68kDriver::VoiceChannel::off() { + if (out.start) { + out.isFinished = true; + } + + part->removeVoice(this); + part = 0; +} + +void MacM68kDriver::MidiChannel_MacM68k::release() { + _allocated = false; + while (_voice) { + _voice->off(); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::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 MacM68kDriver::MidiChannel_MacM68k::noteOff(byte note) { + for (VoiceChannel *i = _voice; i; i = i->next) { + if (i->note == note) { + if (_sustain) { + i->sustainNoteOff = true; + } else { + i->off(); + } + } + } +} + +void MacM68kDriver::MidiChannel_MacM68k::noteOn(byte note, byte velocity) { + // Do not start a not unless there is an instrument set up + if (!_instrument.data) { + return; + } + + // Allocate a voice channel + VoiceChannel *voice = _owner->allocateVoice(_priority); + if (!voice) { + return; + } + addVoice(voice); + + voice->note = note; + // This completly ignores the note's volume, but is in accordance + // to the original. + voice->out.volume = _volume; + + // Set up the instrument data + voice->out.baseFrequency = _instrument.baseFrequency; + voice->out.soundStart = _instrument.data; + voice->out.soundEnd = _instrument.data + _instrument.length; + if (_instrument.loopEnd && _instrument.loopEnd - 12 > _instrument.loopStart) { + voice->out.loopStart = _instrument.data + _instrument.loopStart; + voice->out.loopEnd = _instrument.data + _instrument.loopEnd; + } else { + voice->out.loopStart = 0; + voice->out.loopEnd = voice->out.soundEnd; + } + + voice->out.start = voice->out.loopStart; + voice->out.end = voice->out.loopEnd; + + // Set up the pitch + _owner->setPitch(&voice->out, (note << 7) + _pitchBend); + + // Set up the sample position + voice->out.instrument = voice->out.soundStart; + voice->out.subPos = 0; +} + +void MacM68kDriver::MidiChannel_MacM68k::programChange(byte program) { + _instrument = _owner->getInstrument(program + kProgramChangeBase); +} + +void MacM68kDriver::MidiChannel_MacM68k::pitchBend(int16 bend) { + _pitchBend = (bend * _pitchBendFactor) >> 6; + for (VoiceChannel *i = _voice; i; i = i->next) { + _owner->setPitch(&i->out, (i->note << 7) + _pitchBend); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::controlChange(byte control, byte value) { + switch (control) { + // volume change + case 7: + _volume = value; + for (VoiceChannel *i = _voice; i; i = i->next) { + i->out.volume = value; + i->out.isFinished = false; + } + break; + + // sustain + case 64: + _sustain = value; + if (!_sustain) { + for (VoiceChannel *i = _voice; i; i = i->next) { + if (i->sustainNoteOff) { + i->off(); + } + } + } + break; + + // all notes off + case 123: + for (VoiceChannel *i = _voice; i; i = i->next) { + i->off(); + } + break; + + default: + break; + } +} + +void MacM68kDriver::MidiChannel_MacM68k::pitchBendFactor(byte value) { + _pitchBendFactor = value; +} + +void MacM68kDriver::MidiChannel_MacM68k::priority(byte value) { + _priority = value; +} + +void MacM68kDriver::MidiChannel_MacM68k::sysEx_customInstrument(uint32 type, const byte *instr) { + assert(instr); + if (type == 'MAC ') { + _instrument = _owner->getInstrument(*instr + kSysExBase); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::init(MacM68kDriver *owner, byte channel) { + _owner = owner; + _number = channel; + _allocated = false; +} + +bool MacM68kDriver::MidiChannel_MacM68k::allocate() { + if (_allocated) { + return false; + } + + _allocated = true; + _voice = 0; + _priority = 0; + memset(&_instrument, 0, sizeof(_instrument)); + _pitchBend = 0; + _pitchBendFactor = 0; + _volume = 0; + return true; +} + +void MacM68kDriver::MidiChannel_MacM68k::addVoice(VoiceChannel *voice) { + voice->next = _voice; + voice->prev = 0; + voice->part = this; + if (_voice) { + _voice->prev = voice; + } + _voice = voice; +} + +void MacM68kDriver::MidiChannel_MacM68k::removeVoice(VoiceChannel *voice) { + VoiceChannel *i = _voice; + while (i && i != voice) { + i = i->next; + } + + if (i) { + if (i->next) { + i->next->prev = i->prev; + } + + if (i->prev) { + i->prev->next = i->next; + } else { + _voice = i->next; + } + } +} + +MacM68kDriver::VoiceChannel *MacM68kDriver::allocateVoice(int priority) { + VoiceChannel *channel = 0; + for (int i = 0; i < kChannelCount; ++i) { + if (++_lastUsedVoiceChannel == kChannelCount) { + _lastUsedVoiceChannel = 0; + } + + VoiceChannel *cur = &_voiceChannels[_lastUsedVoiceChannel]; + if (!cur->part) { + memset(cur, 0, sizeof(*cur)); + return cur; + } else if (!cur->next) { + if (cur->part->_priority <= priority) { + priority = cur->part->_priority; + channel = cur; + } + } + } + + if (channel) { + channel->off(); + memset(channel, 0, sizeof(*channel)); + } + + return channel; +} + +const int MacM68kDriver::_volumeBaseTable[32] = { + 0, 0, 1, 1, 2, 3, 5, 6, + 8, 11, 13, 16, 19, 22, 26, 30, + 34, 38, 43, 48, 53, 58, 64, 70, + 76, 83, 89, 96, 104, 111, 119, 127 +}; + +} // End of namespace Scumm diff --git a/engines/scumm/imuse/mac_m68k.h b/engines/scumm/imuse/mac_m68k.h new file mode 100644 index 0000000000..59e2f68b9b --- /dev/null +++ b/engines/scumm/imuse/mac_m68k.h @@ -0,0 +1,177 @@ +/* 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_MAC_M68K_H +#define SCUMM_IMUSE_MAC_M68K_H + +#include "audio/softsynth/emumidi.h" + +#include "common/hashmap.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Scumm { + +class MacM68kDriver : public MidiDriver_Emulated { + friend class MidiChannel_MacM68k; +public: + MacM68kDriver(Audio::Mixer *mixer); + ~MacM68kDriver(); + + 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; } + + virtual bool isStereo() const { return false; } + virtual int getRate() const { + // The original is using a frequency of approx. 22254.54546 here. + // To be precise it uses the 16.16 fixed point value 0x56EE8BA3. + return 22254; + } + +protected: + virtual void generateSamples(int16 *buf, int len); + virtual void onTimer() {} + +private: + int *_mixBuffer; + int _mixBufferLength; + + struct Instrument { + uint length; + uint sampleRate; + uint loopStart; + uint loopEnd; + int baseFrequency; + + byte *data; + }; + + enum { + kDefaultInstrument = 0x3E7, + kProgramChangeBase = 0x3E8, + kSysExBase = 0x7D0 + }; + + Instrument getInstrument(int idx) const; + typedef Common::HashMap<int, Instrument> InstrumentMap; + InstrumentMap _instruments; + Instrument _defaultInstrument; + void loadAllInstruments(); + void addInstrument(int idx, Common::SeekableReadStream *data); + + struct OutputChannel { + int pitchModifier; + + const byte *instrument; + uint subPos; + + const byte *start; + const byte *end; + + const byte *soundStart; + const byte *soundEnd; + const byte *loopStart; + const byte *loopEnd; + + int frequency; + int volume; + + bool isFinished; + + int baseFrequency; + }; + + void setPitch(OutputChannel *out, int frequency); + int _pitchTable[128]; + + byte *_volumeTable; + static const int _volumeBaseTable[32]; + + class MidiChannel_MacM68k; + + struct VoiceChannel { + MidiChannel_MacM68k *part; + VoiceChannel *prev, *next; + int channel; + int note; + bool sustainNoteOff; + OutputChannel out; + + void off(); + }; + + class MidiChannel_MacM68k : public MidiChannel { + friend class MacM68kDriver; + public: + virtual MidiDriver *device() { return _owner; } + virtual byte getNumber() { return _number; } + 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(MacM68kDriver *owner, byte channel); + bool allocate(); + + void addVoice(VoiceChannel *voice); + void removeVoice(VoiceChannel *voice); + private: + MacM68kDriver *_owner; + bool _allocated; + int _number; + + VoiceChannel *_voice; + int _priority; + int _sustain; + Instrument _instrument; + int _pitchBend; + int _pitchBendFactor; + int _volume; + }; + + MidiChannel_MacM68k _channels[32]; + + enum { + kChannelCount = 8 + }; + VoiceChannel _voiceChannels[kChannelCount]; + int _lastUsedVoiceChannel; + VoiceChannel *allocateVoice(int priority); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/imuse/sysex_scumm.cpp b/engines/scumm/imuse/sysex_scumm.cpp index 85ffc86f47..8f230ebac3 100644 --- a/engines/scumm/imuse/sysex_scumm.cpp +++ b/engines/scumm/imuse/sysex_scumm.cpp @@ -71,7 +71,7 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) { part->set_pri(buf[2]); part->volume(buf[3]); part->set_pan(buf[4]); - part->_percussion = player->_isMIDI ? ((buf[5] & 0x80) > 0) : false; + part->_percussion = player->_supportsPercussion ? ((buf[5] & 0x80) > 0) : false; part->set_transpose(buf[5]); part->set_detune(buf[6]); part->pitchBendFactor(buf[7]); diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 1f219f5187..8499c9bad3 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/mac_m68k.o \ imuse/pcspk.o \ imuse/sysex_samnmax.o \ imuse/sysex_scumm.o \ diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h index d5f7ea526e..a640bc1e17 100644 --- a/engines/scumm/saveload.h +++ b/engines/scumm/saveload.h @@ -47,7 +47,7 @@ namespace Scumm { * only saves/loads those which are valid for the version of the savegame * which is being loaded/saved currently. */ -#define CURRENT_VER 92 +#define CURRENT_VER 93 /** * An auxillary macro, used to specify savegame versions. We use this instead diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index d0f46f3e56..c9c9e991e4 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -73,6 +73,7 @@ #include "scumm/util.h" #include "scumm/verbs.h" #include "scumm/imuse/pcspk.h" +#include "scumm/imuse/mac_m68k.h" #include "backends/audiocd/audiocd.h" @@ -1835,17 +1836,31 @@ void ScummEngine::setupMusic(int midi) { } else if (_game.version >= 3 && _game.heversion <= 62) { MidiDriver *nativeMidiDriver = 0; MidiDriver *adlibMidiDriver = 0; - - if (_sound->_musicType != MDT_ADLIB && _sound->_musicType != MDT_TOWNS && _sound->_musicType != MDT_PCSPK) + bool multi_midi = ConfMan.getBool("multi_midi") && _sound->_musicType != MDT_NONE && _sound->_musicType != MDT_PCSPK && (midi & MDT_ADLIB); + bool useOnlyNative = false; + + if (isMacM68kIMuse()) { + // We setup this driver as native MIDI driver to avoid playback + // of the Mac music via a selected MIDI device. + nativeMidiDriver = new MacM68kDriver(_mixer); + // The Mac driver is never MT-32. + _native_mt32 = false; + // Ignore non-native drivers. This also ignores the multi MIDI setting. + useOnlyNative = true; + } else if (_sound->_musicType != MDT_ADLIB && _sound->_musicType != MDT_TOWNS && _sound->_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") && _sound->_musicType != MDT_NONE && _sound->_musicType != MDT_PCSPK && (midi & MDT_ADLIB); - if (_sound->_musicType == MDT_ADLIB || _sound->_musicType == MDT_TOWNS || multi_midi) { - adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_sound->_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB)); - adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0); - } else if (_sound->_musicType == MDT_PCSPK) { - adlibMidiDriver = new PcSpkDriver(_mixer); + + if (!useOnlyNative) { + if (_sound->_musicType == MDT_ADLIB || _sound->_musicType == MDT_TOWNS || multi_midi) { + adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_sound->_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB)); + adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0); + } else if (_sound->_musicType == MDT_PCSPK) { + adlibMidiDriver = new PcSpkDriver(_mixer); + } } _imuse = IMuse::create(_system, nativeMidiDriver, adlibMidiDriver); diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index c8cf096a19..700389414c 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -150,7 +150,13 @@ enum GameFeatures { GF_HE_985 = 1 << 14, /** HE games with 16 bit color */ - GF_16BIT_COLOR = 1 << 15 + GF_16BIT_COLOR = 1 << 15, + + /** + * SCUMM v5-v7 Mac games stored in a container file + * Used to differentiate between m68k and PPC versions of Indy4 + */ + GF_MAC_CONTAINER = 1 << 16 }; /* SCUMM Debug Channels */ @@ -713,6 +719,9 @@ public: bool openFile(BaseScummFile &file, const Common::String &filename, bool resourceFile = false); + /** Is this game a Mac m68k v5 game with iMuse? */ + bool isMacM68kIMuse() const; + protected: int _resourceHeaderSize; byte _resourceMapper[128]; diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 1dc026ad52..43c86db85f 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -248,7 +248,10 @@ void Sound::playSound(int soundID) { _mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID); } // Support for sampled sound effects in Monkey Island 1 and 2 - else if (_vm->_game.platform != Common::kPlatformFMTowns && READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) { + else if (_vm->_game.platform != Common::kPlatformFMTowns + // The Macintosh m68k versions of MI2/Indy4 just ignore SBL effects. + && !_vm->isMacM68kIMuse() + && READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) { debugC(DEBUG_SOUND, "Using SBL sound effect"); // SBL resources essentially contain VOC sound data. diff --git a/engines/toltecs/movie.cpp b/engines/toltecs/movie.cpp index 33fe249514..74364630f5 100644 --- a/engines/toltecs/movie.cpp +++ b/engines/toltecs/movie.cpp @@ -98,7 +98,7 @@ void MoviePlayer::playMovie(uint resIndex) { uint32 lastTime = _vm->_mixer->getSoundElapsedTime(_audioStreamHandle); byte *chunkBuffer = NULL; - uint32 prevChunkSize = 0; + uint32 chunkBufferSize = 0; while (_chunkCount--) { byte chunkType = _vm->_arc->readByte(); @@ -111,13 +111,13 @@ void MoviePlayer::playMovie(uint resIndex) { if (chunkType == kChunkAudio) { _vm->_arc->skip(chunkSize); } else { - // Only reallocate the chunk buffer if it's smaller than the previous frame - if (chunkSize > prevChunkSize) { + // Only reallocate the chunk buffer if the new chunk is bigger + if (chunkSize > chunkBufferSize) { delete[] chunkBuffer; chunkBuffer = new byte[chunkSize]; + chunkBufferSize = chunkSize; } - prevChunkSize = chunkSize; _vm->_arc->read(chunkBuffer, chunkSize); } diff --git a/engines/tsage/blue_force/blueforce_scenes3.cpp b/engines/tsage/blue_force/blueforce_scenes3.cpp index 22c831f531..81e4af6e97 100644 --- a/engines/tsage/blue_force/blueforce_scenes3.cpp +++ b/engines/tsage/blue_force/blueforce_scenes3.cpp @@ -346,6 +346,14 @@ void Scene300::postInit(SceneObjectList *OwnerList) { break; } + if (BF_GLOBALS.getFlag(onBike) && !BF_GLOBALS.getFlag(onDuty)) { + BF_GLOBALS._sound1.play(30); + } else if ((BF_GLOBALS._dayNumber == 2) && (BF_GLOBALS._bookmark < bEndDayOne)) { + BF_GLOBALS._sound1.changeSound(49); + } else if (BF_GLOBALS._sceneManager._previousScene != 190) { + BF_GLOBALS._sound1.changeSound(33); + } + _item10.setDetails(4, 300, 7, 13, 16, 1); _item11.setDetails(2, 300, 9, 13, 18, 1); _item12.setDetails(5, 300, 10, 13, 19, 1); diff --git a/engines/tucker/resource.cpp b/engines/tucker/resource.cpp index bee09f7391..1b04f3fae9 100644 --- a/engines/tucker/resource.cpp +++ b/engines/tucker/resource.cpp @@ -29,6 +29,9 @@ #include "audio/decoders/vorbis.h" #include "audio/decoders/wave.h" +#include "graphics/surface.h" +#include "graphics/decoders/pcx.h" + #include "tucker/tucker.h" #include "tucker/graphics.h" @@ -298,23 +301,21 @@ void TuckerEngine::loadImage(const char *fname, uint8 *dst, int type) { return; } } - f.seek(128, SEEK_SET); - int size = 0; - while (size < 64000) { - int code = f.readByte(); - if (code >= 0xC0) { - const int sz = code - 0xC0; - code = f.readByte(); - memset(dst + size, code, sz); - size += sz; - } else { - dst[size++] = code; - } - } + + ::Graphics::PCXDecoder pcx; + if (!pcx.loadStream(f)) + error("Error while reading PCX image"); + + const ::Graphics::Surface *pcxSurface = pcx.getSurface(); + if (pcxSurface->format.bytesPerPixel != 1) + error("Invalid bytes per pixel in PCX surface (%d)", pcxSurface->format.bytesPerPixel); + if (pcxSurface->w != 320 || pcxSurface->h != 200) + error("Invalid PCX surface size (%d x %d)", pcxSurface->w, pcxSurface->h); + for (uint16 y = 0; y < pcxSurface->h; y++) + memcpy(dst + y * 320, pcxSurface->getBasePtr(0, y), pcxSurface->w); + if (type != 0) { - if (f.readByte() != 12) - return; - f.read(_currentPalette, 768); + memcpy(_currentPalette, pcx.getPalette(), 3 * 256); setBlackPalette(); } } diff --git a/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp index 6d67253038..03ec827668 100644 --- a/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp +++ b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp @@ -573,6 +573,11 @@ Rect32 BaseRenderOSystem::getViewPort() { ////////////////////////////////////////////////////////////////////////// void BaseRenderOSystem::modTargetRect(Common::Rect *rect) { + // FIXME: This is wrong in quite a few ways right now, and ends up + // breaking the notebook in Dirty Split, so we disable the correction + // for now, this will need fixing when a game with odd aspect-ratios + // show up. + return; rect->left = (int16)MathUtil::round(rect->left * _ratioX + _borderLeft - _renderRect.left); rect->top = (int16)MathUtil::round(rect->top * _ratioY + _borderTop - _renderRect.top); rect->setWidth((int16)MathUtil::roundUp(rect->width() * _ratioX)); diff --git a/graphics/decoders/bmp.cpp b/graphics/decoders/bmp.cpp index f15d4e2519..0d2165643d 100644 --- a/graphics/decoders/bmp.cpp +++ b/graphics/decoders/bmp.cpp @@ -100,10 +100,10 @@ bool BitmapDecoder::loadStream(Common::SeekableReadStream &stream) { _paletteColorCount = stream.readUint32LE(); /* uint32 colorsImportant = */ stream.readUint32LE(); - if (_paletteColorCount == 0) - _paletteColorCount = 256; - if (bitsPerPixel == 8) { + if (_paletteColorCount == 0) + _paletteColorCount = 256; + // Read the palette _palette = new byte[_paletteColorCount * 3]; for (uint16 i = 0; i < _paletteColorCount; i++) { diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp index a871377ca1..748275b84b 100644 --- a/graphics/decoders/jpeg.cpp +++ b/graphics/decoders/jpeg.cpp @@ -81,7 +81,7 @@ const Surface *JPEGDecoder::getSurface() const { const Graphics::Surface *uComponent = getComponent(2); const Graphics::Surface *vComponent = getComponent(3); - convertYUV444ToRGB(_rgbSurface, (byte *)yComponent->pixels, (byte *)uComponent->pixels, (byte *)vComponent->pixels, yComponent->w, yComponent->h, yComponent->pitch, uComponent->pitch); + YUVToRGBMan.convert444(_rgbSurface, Graphics::YUVToRGBManager::kScaleFull, (byte *)yComponent->pixels, (byte *)uComponent->pixels, (byte *)vComponent->pixels, yComponent->w, yComponent->h, yComponent->pitch, uComponent->pitch); return _rgbSurface; } diff --git a/graphics/decoders/pcx.cpp b/graphics/decoders/pcx.cpp new file mode 100644 index 0000000000..f5c1c24bb0 --- /dev/null +++ b/graphics/decoders/pcx.cpp @@ -0,0 +1,213 @@ +/* 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 "common/stream.h" +#include "common/textconsole.h" + +#include "graphics/pixelformat.h" +#include "graphics/surface.h" +#include "graphics/decoders/pcx.h" + +/** + * Based on the PCX specs: + * http://www.fileformat.info/format/pcx/spec/a10e75307b3a4cc49c3bbe6db4c41fa2/view.htm + * and the PCX decoder of FFmpeg (libavcodec/pcx.c): + * http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavcodec/pcx.c + */ + +namespace Graphics { + +PCXDecoder::PCXDecoder() { + _surface = 0; + _palette = 0; + _paletteColorCount = 0; +} + +PCXDecoder::~PCXDecoder() { + destroy(); +} + +void PCXDecoder::destroy() { + if (_surface) { + _surface->free(); + delete _surface; + _surface = 0; + } + + delete[] _palette; + _palette = 0; + _paletteColorCount = 0; +} + +bool PCXDecoder::loadStream(Common::SeekableReadStream &stream) { + destroy(); + + if (stream.readByte() != 0x0a) // ZSoft PCX + return false; + + byte version = stream.readByte(); // 0 - 5 + if (version > 5) + return false; + + bool compressed = stream.readByte(); // encoding, 1 = run length encoding + byte bitsPerPixel = stream.readByte(); // 1, 2, 4 or 8 + + // Window + uint16 xMin = stream.readUint16LE(); + uint16 yMin = stream.readUint16LE(); + uint16 xMax = stream.readUint16LE(); + uint16 yMax = stream.readUint16LE(); + + uint16 width = xMax - xMin + 1; + uint16 height = yMax - yMin + 1; + + if (xMax < xMin || yMax < yMin) { + warning("Invalid PCX image dimensions"); + return false; + } + + stream.skip(4); // HDpi, VDpi + + // Read the EGA palette (colormap) + _palette = new byte[16 * 3]; + for (uint16 i = 0; i < 16; i++) { + _palette[i * 3 + 0] = stream.readByte(); + _palette[i * 3 + 1] = stream.readByte(); + _palette[i * 3 + 2] = stream.readByte(); + } + + if (stream.readByte() != 0) // reserved, should be set to 0 + return false; + + byte nPlanes = stream.readByte(); + uint16 bytesPerLine = stream.readUint16LE(); + uint16 bytesPerscanLine = nPlanes * bytesPerLine; + + if (bytesPerscanLine < width * bitsPerPixel * nPlanes / 8) { + warning("PCX data is corrupted"); + return false; + } + + stream.skip(60); // PaletteInfo, HscreenSize, VscreenSize, Filler + + _surface = new Graphics::Surface(); + + byte *scanLine = new byte[bytesPerscanLine]; + byte *dst; + int x, y; + + if (nPlanes == 3 && bitsPerPixel == 8) { // 24bpp + Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); + _surface->create(width, height, format); + dst = (byte *)_surface->pixels; + _paletteColorCount = 0; + + for (y = 0; y < height; y++) { + decodeRLE(stream, scanLine, bytesPerscanLine, compressed); + + for (x = 0; x < width; x++) { + byte b = scanLine[x]; + byte g = scanLine[x + bytesPerLine]; + byte r = scanLine[x + (bytesPerLine << 1)]; + uint32 color = format.RGBToColor(r, g, b); + + *((uint32 *)dst) = color; + dst += format.bytesPerPixel; + } + } + } else if (nPlanes == 1 && bitsPerPixel == 8) { // 8bpp indexed + _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + dst = (byte *)_surface->pixels; + _paletteColorCount = 16; + + for (y = 0; y < height; y++, dst += _surface->pitch) { + decodeRLE(stream, scanLine, bytesPerscanLine, compressed); + memcpy(dst, scanLine, width); + } + + if (version == 5) { + if (stream.readByte() != 12) { + warning("Expected a palette after the PCX image data"); + delete[] scanLine; + return false; + } + + // Read the VGA palette + delete[] _palette; + _palette = new byte[256 * 3]; + for (uint16 i = 0; i < 256; i++) { + _palette[i * 3 + 0] = stream.readByte(); + _palette[i * 3 + 1] = stream.readByte(); + _palette[i * 3 + 2] = stream.readByte(); + } + + _paletteColorCount = 256; + } + } else if ((nPlanes == 2 || nPlanes == 3 || nPlanes == 4) && bitsPerPixel == 1) { // planar, 4, 8 or 16 colors + _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + dst = (byte *)_surface->pixels; + _paletteColorCount = 16; + + for (y = 0; y < height; y++, dst += _surface->pitch) { + decodeRLE(stream, scanLine, bytesPerscanLine, compressed); + + for (x = 0; x < width; x++) { + int m = 0x80 >> (x & 7), v = 0; + for (int i = nPlanes - 1; i >= 0; i--) { + v <<= 1; + v += (scanLine[i * bytesPerLine + (x >> 3)] & m) == 0 ? 0 : 1; + } + dst[x] = v; + } + } + } else { + // Known unsupported case: 1 plane and bpp < 8 (1, 2 or 4) + warning("Invalid PCX file (%d planes, %d bpp)", nPlanes, bitsPerPixel); + delete[] scanLine; + return false; + } + + delete[] scanLine; + + return true; +} + +void PCXDecoder::decodeRLE(Common::SeekableReadStream &stream, byte *dst, uint32 bytesPerscanLine, bool compressed) { + uint32 i = 0; + byte run, value; + + if (compressed) { + while (i < bytesPerscanLine) { + run = 1; + value = stream.readByte(); + if (value >= 0xc0) { + run = value & 0x3f; + value = stream.readByte(); + } + while (i < bytesPerscanLine && run--) + dst[i++] = value; + } + } else { + stream.read(dst, bytesPerscanLine); + } +} + +} // End of namespace Graphics diff --git a/graphics/decoders/pcx.h b/graphics/decoders/pcx.h new file mode 100644 index 0000000000..bcff754a2d --- /dev/null +++ b/graphics/decoders/pcx.h @@ -0,0 +1,68 @@ +/* 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. + */ + +/** + * PCX decoder used in engines: + * - dreamweb + * - hugo + * - queen + * - tucker + */ + +#ifndef GRAPHICS_DECODERS_PCX_H +#define GRAPHICS_DECODERS_PCX_H + +#include "common/scummsys.h" +#include "common/str.h" +#include "graphics/decoders/image_decoder.h" + +namespace Common{ +class SeekableReadStream; +} + +namespace Graphics { + +struct PixelFormat; +struct Surface; + +class PCXDecoder : public ImageDecoder { +public: + PCXDecoder(); + virtual ~PCXDecoder(); + + // ImageDecoder API + void destroy(); + virtual bool loadStream(Common::SeekableReadStream &stream); + virtual const Surface *getSurface() const { return _surface; } + const byte *getPalette() const { return _palette; } + uint16 getPaletteColorCount() const { return _paletteColorCount; } + +private: + void decodeRLE(Common::SeekableReadStream &stream, byte *dst, uint32 bytesPerScanline, bool compressed); + + Surface *_surface; + byte *_palette; + uint16 _paletteColorCount; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/decoders/tga.cpp b/graphics/decoders/tga.cpp index 0b2318e127..c3b9d84055 100644 --- a/graphics/decoders/tga.cpp +++ b/graphics/decoders/tga.cpp @@ -145,7 +145,10 @@ bool TGADecoder::readHeader(Common::SeekableReadStream &tga, byte &imageType, by if (pixelDepth == 24) { _format = PixelFormat(3, 8, 8, 8, 0, 16, 8, 0, 0); } else if (pixelDepth == 32) { - _format = PixelFormat(4, 8, 8, 8, attributeBits, 16, 8, 0, 24); + // HACK: According to the spec, attributeBits should determine the amount + // of alpha-bits, however, as the game files that use this decoder seems + // to ignore that fact, we force the amount to 8 for 32bpp files for now. + _format = PixelFormat(4, 8, 8, 8, /* attributeBits */ 8, 16, 8, 0, 24); } else if (pixelDepth == 16 && imageType == TYPE_TRUECOLOR) { // 16bpp TGA is ARGB1555 _format = PixelFormat(2, 5, 5, 5, attributeBits, 10, 5, 0, 15); diff --git a/graphics/module.mk b/graphics/module.mk index e67efd2cf5..f560d9dc97 100644 --- a/graphics/module.mk +++ b/graphics/module.mk @@ -25,6 +25,7 @@ MODULE_OBJS := \ yuv_to_rgb.o \ decoders/bmp.o \ decoders/jpeg.o \ + decoders/pcx.o \ decoders/pict.o \ decoders/png.o \ decoders/tga.o diff --git a/graphics/scaler/aspect.cpp b/graphics/scaler/aspect.cpp index 2f06b2e4f6..429640fdbd 100644 --- a/graphics/scaler/aspect.cpp +++ b/graphics/scaler/aspect.cpp @@ -23,6 +23,13 @@ #include "graphics/scaler/intern.h" #include "graphics/scaler/aspect.h" +#ifdef OPENPANDORA +#define NEON_ASPECT_CORRECTOR +#endif + +#ifdef NEON_ASPECT_CORRECTOR +#include <arm_neon.h> +#endif #define kSuperFastAndUglyAspectMode 0 // No interpolation at all, but super-fast #define kVeryFastAndGoodAspectMode 1 // Good quality with very good speed @@ -55,13 +62,66 @@ static inline void interpolate5Line(uint16 *dst, const uint16 *srcA, const uint1 #if ASPECT_MODE == kVeryFastAndGoodAspectMode +#ifdef NEON_ASPECT_CORRECTOR + +template<typename ColorMask> +static void interpolate5LineNeon(uint16 *dst, const uint16 *srcA, const uint16 *srcB, int width, int k1, int k2) { + uint16x4_t kRedBlueMask_4 = vdup_n_u16(ColorMask::kRedBlueMask); + uint16x4_t kGreenMask_4 = vdup_n_u16(ColorMask::kGreenMask); + uint16x4_t k1_4 = vdup_n_u16(k1); + uint16x4_t k2_4 = vdup_n_u16(k2); + while (width >= 4) { + uint16x4_t srcA_4 = vld1_u16(srcA); + uint16x4_t srcB_4 = vld1_u16(srcB); + uint16x4_t p1_4 = srcB_4; + uint16x4_t p2_4 = srcA_4; + + uint16x4_t p1_rb_4 = vand_u16(p1_4, kRedBlueMask_4); + uint16x4_t p1_g_4 = vand_u16(p1_4, kGreenMask_4); + uint16x4_t p2_rb_4 = vand_u16(p2_4, kRedBlueMask_4); + uint16x4_t p2_g_4 = vand_u16(p2_4, kGreenMask_4); + + uint32x4_t tmp_rb_4 = vshrq_n_u32(vmlal_u16(vmull_u16(p2_rb_4, k2_4), p1_rb_4, k1_4), 3); + uint32x4_t tmp_g_4 = vshrq_n_u32(vmlal_u16(vmull_u16(p2_g_4, k2_4), p1_g_4, k1_4), 3); + uint16x4_t p_rb_4 = vmovn_u32(tmp_rb_4); + p_rb_4 = vand_u16(p_rb_4, kRedBlueMask_4); + uint16x4_t p_g_4 = vmovn_u32(tmp_g_4); + p_g_4 = vand_u16(p_g_4, kGreenMask_4); + + uint16x4_t result_4 = p_rb_4 | p_g_4; + vst1_u16(dst, result_4); + + dst += 4; + srcA += 4; + srcB += 4; + width -= 4; + } +} +#endif + template<typename ColorMask, int scale> static void interpolate5Line(uint16 *dst, const uint16 *srcA, const uint16 *srcB, int width) { if (scale == 1) { +#ifdef NEON_ASPECT_CORRECTOR + int width4 = width & ~3; + interpolate5LineNeon<ColorMask>(dst, srcA, srcB, width4, 7, 1); + srcA += width4; + srcB += width4; + dst += width4; + width -= width4; +#endif while (width--) { *dst++ = interpolate16_7_1<ColorMask>(*srcB++, *srcA++); } } else { + #ifdef NEON_ASPECT_CORRECTOR + int width4 = width & ~3; + interpolate5LineNeon<ColorMask>(dst, srcA, srcB, width4, 5, 3); + srcA += width4; + srcB += width4; + dst += width4; + width -= width4; +#endif while (width--) { *dst++ = interpolate16_5_3<ColorMask>(*srcB++, *srcA++); } diff --git a/graphics/yuv_to_rgb.cpp b/graphics/yuv_to_rgb.cpp index 78903d0cd8..6043315a13 100644 --- a/graphics/yuv_to_rgb.cpp +++ b/graphics/yuv_to_rgb.cpp @@ -83,129 +83,127 @@ // BASIS, AND BROWN UNIVERSITY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, // SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -#include "common/scummsys.h" -#include "common/singleton.h" - #include "graphics/surface.h" +#include "graphics/yuv_to_rgb.h" + +namespace Common { +DECLARE_SINGLETON(Graphics::YUVToRGBManager); +} namespace Graphics { class YUVToRGBLookup { public: - YUVToRGBLookup(Graphics::PixelFormat format); - ~YUVToRGBLookup(); - - int16 *_colorTab; - uint32 *_rgbToPix; -}; + YUVToRGBLookup(Graphics::PixelFormat format, YUVToRGBManager::LuminanceScale scale); -YUVToRGBLookup::YUVToRGBLookup(Graphics::PixelFormat format) { - _colorTab = new int16[4 * 256]; // 2048 bytes + Graphics::PixelFormat getFormat() const { return _format; } + YUVToRGBManager::LuminanceScale getScale() const { return _scale; } + const uint32 *getRGBToPix() const { return _rgbToPix; } - int16 *Cr_r_tab = &_colorTab[0 * 256]; - int16 *Cr_g_tab = &_colorTab[1 * 256]; - int16 *Cb_g_tab = &_colorTab[2 * 256]; - int16 *Cb_b_tab = &_colorTab[3 * 256]; +private: + Graphics::PixelFormat _format; + YUVToRGBManager::LuminanceScale _scale; + uint32 _rgbToPix[3 * 768]; // 9216 bytes +}; - _rgbToPix = new uint32[3 * 768]; // 9216 bytes +YUVToRGBLookup::YUVToRGBLookup(Graphics::PixelFormat format, YUVToRGBManager::LuminanceScale scale) { + _format = format; + _scale = scale; uint32 *r_2_pix_alloc = &_rgbToPix[0 * 768]; uint32 *g_2_pix_alloc = &_rgbToPix[1 * 768]; uint32 *b_2_pix_alloc = &_rgbToPix[2 * 768]; - int16 CR, CB; - int i; + if (scale == YUVToRGBManager::kScaleFull) { + // Set up entries 0-255 in rgb-to-pixel value tables. + for (int i = 0; i < 256; i++) { + r_2_pix_alloc[i + 256] = format.RGBToColor(i, 0, 0); + g_2_pix_alloc[i + 256] = format.RGBToColor(0, i, 0); + b_2_pix_alloc[i + 256] = format.RGBToColor(0, 0, i); + } + + // Spread out the values we have to the rest of the array so that we do + // not need to check for overflow. + for (int i = 0; i < 256; i++) { + r_2_pix_alloc[i] = r_2_pix_alloc[256]; + r_2_pix_alloc[i + 512] = r_2_pix_alloc[511]; + g_2_pix_alloc[i] = g_2_pix_alloc[256]; + g_2_pix_alloc[i + 512] = g_2_pix_alloc[511]; + b_2_pix_alloc[i] = b_2_pix_alloc[256]; + b_2_pix_alloc[i + 512] = b_2_pix_alloc[511]; + } + } else { + // Set up entries 16-235 in rgb-to-pixel value tables + for (int i = 16; i < 236; i++) { + int scaledValue = (i - 16) * 255 / 219; + r_2_pix_alloc[i + 256] = format.RGBToColor(scaledValue, 0, 0); + g_2_pix_alloc[i + 256] = format.RGBToColor(0, scaledValue, 0); + b_2_pix_alloc[i + 256] = format.RGBToColor(0, 0, scaledValue); + } + + // Spread out the values we have to the rest of the array so that we do + // not need to check for overflow. We have to do it here in two steps. + for (int i = 0; i < 256 + 16; i++) { + r_2_pix_alloc[i] = r_2_pix_alloc[256 + 16]; + g_2_pix_alloc[i] = g_2_pix_alloc[256 + 16]; + b_2_pix_alloc[i] = b_2_pix_alloc[256 + 16]; + } + + for (int i = 256 + 236; i < 768; i++) { + r_2_pix_alloc[i] = r_2_pix_alloc[256 + 236 - 1]; + g_2_pix_alloc[i] = g_2_pix_alloc[256 + 236 - 1]; + b_2_pix_alloc[i] = b_2_pix_alloc[256 + 236 - 1]; + } + } +} + +YUVToRGBManager::YUVToRGBManager() { + _lookup = 0; + + int16 *Cr_r_tab = &_colorTab[0 * 256]; + int16 *Cr_g_tab = &_colorTab[1 * 256]; + int16 *Cb_g_tab = &_colorTab[2 * 256]; + int16 *Cb_b_tab = &_colorTab[3 * 256]; // Generate the tables for the display surface - for (i = 0; i < 256; i++) { + for (int i = 0; i < 256; i++) { // Gamma correction (luminescence table) and chroma correction // would be done here. See the Berkeley mpeg_play sources. - CR = CB = (i - 128); + int16 CR = (i - 128), CB = CR; Cr_r_tab[i] = (int16) ( (0.419 / 0.299) * CR) + 0 * 768 + 256; Cr_g_tab[i] = (int16) (-(0.299 / 0.419) * CR) + 1 * 768 + 256; Cb_g_tab[i] = (int16) (-(0.114 / 0.331) * CB); Cb_b_tab[i] = (int16) ( (0.587 / 0.331) * CB) + 2 * 768 + 256; } - - // Set up entries 0-255 in rgb-to-pixel value tables. - for (i = 0; i < 256; i++) { - r_2_pix_alloc[i + 256] = format.RGBToColor(i, 0, 0); - g_2_pix_alloc[i + 256] = format.RGBToColor(0, i, 0); - b_2_pix_alloc[i + 256] = format.RGBToColor(0, 0, i); - } - - // Spread out the values we have to the rest of the array so that we do - // not need to check for overflow. - for (i = 0; i < 256; i++) { - r_2_pix_alloc[i] = r_2_pix_alloc[256]; - r_2_pix_alloc[i + 512] = r_2_pix_alloc[511]; - g_2_pix_alloc[i] = g_2_pix_alloc[256]; - g_2_pix_alloc[i + 512] = g_2_pix_alloc[511]; - b_2_pix_alloc[i] = b_2_pix_alloc[256]; - b_2_pix_alloc[i + 512] = b_2_pix_alloc[511]; - } -} - -YUVToRGBLookup::~YUVToRGBLookup() { - delete[] _rgbToPix; - delete[] _colorTab; -} - -class YUVToRGBManager : public Common::Singleton<YUVToRGBManager> { -public: - const YUVToRGBLookup *getLookup(Graphics::PixelFormat format); - -private: - friend class Common::Singleton<SingletonBaseType>; - YUVToRGBManager(); - ~YUVToRGBManager(); - - Graphics::PixelFormat _lastFormat; - YUVToRGBLookup *_lookup; -}; - -YUVToRGBManager::YUVToRGBManager() { - _lookup = 0; } YUVToRGBManager::~YUVToRGBManager() { delete _lookup; } -const YUVToRGBLookup *YUVToRGBManager::getLookup(Graphics::PixelFormat format) { - if (_lastFormat == format) +const YUVToRGBLookup *YUVToRGBManager::getLookup(Graphics::PixelFormat format, YUVToRGBManager::LuminanceScale scale) { + if (_lookup && _lookup->getFormat() == format && _lookup->getScale() == scale) return _lookup; delete _lookup; - _lookup = new YUVToRGBLookup(format); - _lastFormat = format; + _lookup = new YUVToRGBLookup(format, scale); return _lookup; } -} // End of namespace Graphics - -namespace Common { -DECLARE_SINGLETON(Graphics::YUVToRGBManager); -} - -#define YUVToRGBMan (Graphics::YUVToRGBManager::instance()) - -namespace Graphics { - #define PUT_PIXEL(s, d) \ L = &rgbToPix[(s)]; \ *((PixelInt *)(d)) = (L[cr_r] | L[crb_g] | L[cb_b]) template<typename PixelInt> -void convertYUV444ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { +void convertYUV444ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup, int16 *colorTab, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Keep the tables in pointers here to avoid a dereference on each pixel - const int16 *Cr_r_tab = lookup->_colorTab; + const int16 *Cr_r_tab = colorTab; const int16 *Cr_g_tab = Cr_r_tab + 256; const int16 *Cb_g_tab = Cr_g_tab + 256; const int16 *Cb_b_tab = Cb_g_tab + 256; - const uint32 *rgbToPix = lookup->_rgbToPix; + const uint32 *rgbToPix = lookup->getRGBToPix(); for (int h = 0; h < yHeight; h++) { for (int w = 0; w < yWidth; w++) { @@ -229,32 +227,32 @@ void convertYUV444ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup } } -void convertYUV444ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { +void YUVToRGBManager::convert444(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks assert(dst && dst->pixels); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); - const YUVToRGBLookup *lookup = YUVToRGBMan.getLookup(dst->format); + const YUVToRGBLookup *lookup = getLookup(dst->format, scale); // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV444ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV444ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV444ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV444ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } template<typename PixelInt> -void convertYUV420ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { +void convertYUV420ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup, int16 *colorTab, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { int halfHeight = yHeight >> 1; int halfWidth = yWidth >> 1; // Keep the tables in pointers here to avoid a dereference on each pixel - const int16 *Cr_r_tab = lookup->_colorTab; + const int16 *Cr_r_tab = colorTab; const int16 *Cr_g_tab = Cr_r_tab + 256; const int16 *Cb_g_tab = Cr_g_tab + 256; const int16 *Cb_b_tab = Cb_g_tab + 256; - const uint32 *rgbToPix = lookup->_rgbToPix; + const uint32 *rgbToPix = lookup->getRGBToPix(); for (int h = 0; h < halfHeight; h++) { for (int w = 0; w < halfWidth; w++) { @@ -283,7 +281,7 @@ void convertYUV420ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup } } -void convertYUV420ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { +void YUVToRGBManager::convert420(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks assert(dst && dst->pixels); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); @@ -291,13 +289,13 @@ void convertYUV420ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uS assert((yWidth & 1) == 0); assert((yHeight & 1) == 0); - const YUVToRGBLookup *lookup = YUVToRGBMan.getLookup(dst->format); + const YUVToRGBLookup *lookup = getLookup(dst->format, scale); // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV420ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV420ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV420ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV420ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } #define READ_QUAD(ptr, prefix) \ @@ -325,13 +323,13 @@ void convertYUV420ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uS xDiff++ template<typename PixelInt> -void convertYUV410ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { +void convertYUV410ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup, int16 *colorTab, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Keep the tables in pointers here to avoid a dereference on each pixel - const int16 *Cr_r_tab = lookup->_colorTab; + const int16 *Cr_r_tab = colorTab; const int16 *Cr_g_tab = Cr_r_tab + 256; const int16 *Cb_g_tab = Cr_g_tab + 256; const int16 *Cb_b_tab = Cb_g_tab + 256; - const uint32 *rgbToPix = lookup->_rgbToPix; + const uint32 *rgbToPix = lookup->getRGBToPix(); int quarterWidth = yWidth >> 2; @@ -368,7 +366,7 @@ void convertYUV410ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup #undef DO_INTERPOLATION #undef DO_YUV410_PIXEL -void convertYUV410ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { +void YUVToRGBManager::convert410(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks assert(dst && dst->pixels); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); @@ -376,13 +374,13 @@ void convertYUV410ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uS assert((yWidth & 3) == 0); assert((yHeight & 3) == 0); - const YUVToRGBLookup *lookup = YUVToRGBMan.getLookup(dst->format); + const YUVToRGBLookup *lookup = getLookup(dst->format, scale); // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV410ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV410ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV410ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV410ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } } // End of namespace Graphics diff --git a/graphics/yuv_to_rgb.h b/graphics/yuv_to_rgb.h index 73a2c69d7d..0e832be0ec 100644 --- a/graphics/yuv_to_rgb.h +++ b/graphics/yuv_to_rgb.h @@ -32,57 +32,85 @@ #define GRAPHICS_YUV_TO_RGB_H #include "common/scummsys.h" +#include "common/singleton.h" #include "graphics/surface.h" namespace Graphics { -/** - * Convert a YUV444 image to an RGB surface - * - * @param dst the destination surface - * @param ySrc the source of the y component - * @param uSrc the source of the u component - * @param vSrc the source of the v component - * @param yWidth the width of the y surface - * @param yHeight the height of the y surface - * @param yPitch the pitch of the y surface - * @param uvPitch the pitch of the u and v surfaces - */ -void convertYUV444ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch); +class YUVToRGBLookup; -/** - * Convert a YUV420 image to an RGB surface - * - * @param dst the destination surface - * @param ySrc the source of the y component - * @param uSrc the source of the u component - * @param vSrc the source of the v component - * @param yWidth the width of the y surface (must be divisible by 2) - * @param yHeight the height of the y surface (must be divisible by 2) - * @param yPitch the pitch of the y surface - * @param uvPitch the pitch of the u and v surfaces - */ -void convertYUV420ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch); +class YUVToRGBManager : public Common::Singleton<YUVToRGBManager> { +public: + /** The scale of the luminance values */ + enum LuminanceScale { + kScaleFull, /** Luminance values range from [0, 255] */ + kScaleITU /** Luminance values range from [16, 235], the range from ITU-R BT.601 */ + }; -/** - * Convert a YUV410 image to an RGB surface - * - * Since the chroma has a very low resolution in 410, we perform bilinear scaling - * on the two chroma planes to produce the image. The chroma planes must have - * at least one extra row that can be read from in order to produce a proper - * image (filled with 0x80). This is required in order to speed up this function. - * - * @param dst the destination surface - * @param ySrc the source of the y component - * @param uSrc the source of the u component - * @param vSrc the source of the v component - * @param yWidth the width of the y surface (must be divisible by 4) - * @param yHeight the height of the y surface (must be divisible by 4) - * @param yPitch the pitch of the y surface - * @param uvPitch the pitch of the u and v surfaces - */ -void convertYUV410ToRGB(Graphics::Surface *dst, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch); + /** + * Convert a YUV444 image to an RGB surface + * + * @param dst the destination surface + * @param scale the scale of the luminance values + * @param ySrc the source of the y component + * @param uSrc the source of the u component + * @param vSrc the source of the v component + * @param yWidth the width of the y surface + * @param yHeight the height of the y surface + * @param yPitch the pitch of the y surface + * @param uvPitch the pitch of the u and v surfaces + */ + void convert444(Graphics::Surface *dst, LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch); + + /** + * Convert a YUV420 image to an RGB surface + * + * @param dst the destination surface + * @param scale the scale of the luminance values + * @param ySrc the source of the y component + * @param uSrc the source of the u component + * @param vSrc the source of the v component + * @param yWidth the width of the y surface (must be divisible by 2) + * @param yHeight the height of the y surface (must be divisible by 2) + * @param yPitch the pitch of the y surface + * @param uvPitch the pitch of the u and v surfaces + */ + void convert420(Graphics::Surface *dst, LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch); + + /** + * Convert a YUV410 image to an RGB surface + * + * Since the chroma has a very low resolution in 410, we perform bilinear scaling + * on the two chroma planes to produce the image. The chroma planes must have + * at least one extra row and one extra column that can be read from in order to + * produce a proper image. It is suggested that you fill these in with the previous + * row and column's data. This is required in order to speed up this function. + * + * @param dst the destination surface + * @param scale the scale of the luminance values + * @param ySrc the source of the y component + * @param uSrc the source of the u component + * @param vSrc the source of the v component + * @param yWidth the width of the y surface (must be divisible by 4) + * @param yHeight the height of the y surface (must be divisible by 4) + * @param yPitch the pitch of the y surface + * @param uvPitch the pitch of the u and v surfaces + */ + void convert410(Graphics::Surface *dst, LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch); + +private: + friend class Common::Singleton<SingletonBaseType>; + YUVToRGBManager(); + ~YUVToRGBManager(); + + const YUVToRGBLookup *getLookup(Graphics::PixelFormat format, LuminanceScale scale); + + YUVToRGBLookup *_lookup; + int16 _colorTab[4 * 256]; // 2048 bytes +}; } // End of namespace Graphics +#define YUVToRGBMan (::Graphics::YUVToRGBManager::instance()) + #endif diff --git a/video/bink_decoder.cpp b/video/bink_decoder.cpp index 30632cdd6c..37e12624b8 100644 --- a/video/bink_decoder.cpp +++ b/video/bink_decoder.cpp @@ -348,7 +348,7 @@ void BinkDecoder::BinkVideoTrack::decodePacket(VideoFrame &frame) { // The width used here is the surface-width, and not the video-width // to allow for odd-sized videos. assert(_curPlanes[0] && _curPlanes[1] && _curPlanes[2]); - Graphics::convertYUV420ToRGB(&_surface, _curPlanes[0], _curPlanes[1], _curPlanes[2], + YUVToRGBMan.convert420(&_surface, Graphics::YUVToRGBManager::kScaleITU, _curPlanes[0], _curPlanes[1], _curPlanes[2], _surfaceWidth, _surfaceHeight, _surfaceWidth, _surfaceWidth >> 1); // And swap the planes with the reference planes diff --git a/video/codecs/svq1.cpp b/video/codecs/svq1.cpp index 14452ab15b..56b376f590 100644 --- a/video/codecs/svq1.cpp +++ b/video/codecs/svq1.cpp @@ -180,28 +180,29 @@ const Graphics::Surface *SVQ1Decoder::decodeImage(Common::SeekableReadStream *st frameData.skip(8); } - int yWidth = ALIGN(_frameWidth, 16); - int yHeight = ALIGN(_frameHeight, 16); - int uvWidth = ALIGN(yWidth / 4, 16); - int uvHeight = ALIGN(yHeight / 4, 16); + uint yWidth = ALIGN(_frameWidth, 16); + uint yHeight = ALIGN(_frameHeight, 16); + uint uvWidth = ALIGN(yWidth / 4, 16); + uint uvHeight = ALIGN(yHeight / 4, 16); + uint uvPitch = uvWidth + 4; // we need at least one extra column and pitch must be divisible by 4 byte *current[3]; // Decode Y, U and V component planes for (int i = 0; i < 3; i++) { - int width, height; + uint width, height, pitch; if (i == 0) { width = yWidth; height = yHeight; + pitch = width; current[i] = new byte[width * height]; } else { width = uvWidth; height = uvHeight; + pitch = uvPitch; - // Add an extra row's worth of data to not go out-of-bounds in the - // color conversion. Then fill that with "empty" data. - current[i] = new byte[width * (height + 1)]; - memset(current[i] + width * height, 0x80, width); + // Add an extra row here. See below for more information. + current[i] = new byte[pitch * (height + 1)]; } if (frameType == 0) { // I Frame @@ -209,12 +210,12 @@ const Graphics::Surface *SVQ1Decoder::decodeImage(Common::SeekableReadStream *st byte *currentP = current[i]; for (uint16 y = 0; y < height; y += 16) { for (uint16 x = 0; x < width; x += 16) { - if (!svq1DecodeBlockIntra(&frameData, ¤tP[x], width)) { + if (!svq1DecodeBlockIntra(&frameData, ¤tP[x], pitch)) { warning("svq1DecodeBlockIntra decode failure"); return _surface; } } - currentP += 16 * width; + currentP += 16 * pitch; } } else { // Delta frame (P or B) @@ -233,7 +234,7 @@ const Graphics::Surface *SVQ1Decoder::decodeImage(Common::SeekableReadStream *st byte *currentP = current[i]; for (uint16 y = 0; y < height; y += 16) { for (uint16 x = 0; x < width; x += 16) { - if (!svq1DecodeDeltaBlock(&frameData, ¤tP[x], previous, width, pmv, x, y)) { + if (!svq1DecodeDeltaBlock(&frameData, ¤tP[x], previous, pitch, pmv, x, y)) { warning("svq1DecodeDeltaBlock decode failure"); return _surface; } @@ -241,7 +242,7 @@ const Graphics::Surface *SVQ1Decoder::decodeImage(Common::SeekableReadStream *st pmv[0].x = pmv[0].y = 0; - currentP += 16 * width; + currentP += 16 * pitch; } delete[] pmv; @@ -256,7 +257,21 @@ const Graphics::Surface *SVQ1Decoder::decodeImage(Common::SeekableReadStream *st _surface->h = _height; } - convertYUV410ToRGB(_surface, current[0], current[1], current[2], yWidth, yHeight, yWidth, uvWidth); + // We need to massage the chrominance data a bit to be able to be used by the converter + // Since the thing peeks at values one column and one row beyond the data, we need to fill it in + + // First, fill in the column-after-last with the last column's value + for (uint i = 0; i < uvHeight; i++) { + current[1][i * uvPitch + uvWidth] = current[1][i * uvPitch + uvWidth - 1]; + current[2][i * uvPitch + uvWidth] = current[2][i * uvPitch + uvWidth - 1]; + } + + // Then, copy the last row to the one after the last row + memcpy(current[1] + uvHeight * uvPitch, current[1] + (uvHeight - 1) * uvPitch, uvWidth + 1); + memcpy(current[2] + uvHeight * uvPitch, current[2] + (uvHeight - 1) * uvPitch, uvWidth + 1); + + // Finally, actually do the conversion ;) + YUVToRGBMan.convert410(_surface, Graphics::YUVToRGBManager::kScaleFull, current[0], current[1], current[2], yWidth, yHeight, yWidth, uvPitch); // Store the current surfaces for later and free the old ones for (int i = 0; i < 3; i++) { diff --git a/video/psx_decoder.cpp b/video/psx_decoder.cpp index fa7f1e8cfe..f19802f349 100644 --- a/video/psx_decoder.cpp +++ b/video/psx_decoder.cpp @@ -483,7 +483,7 @@ void PSXStreamDecoder::PSXVideoTrack::decodeFrame(Common::SeekableReadStream *fr decodeMacroBlock(&bits, mbX, mbY, scale, version); // Output data onto the frame - Graphics::convertYUV420ToRGB(_surface, _yBuffer, _cbBuffer, _crBuffer, _surface->w, _surface->h, _macroBlocksW * 16, _macroBlocksW * 8); + YUVToRGBMan.convert420(_surface, Graphics::YUVToRGBManager::kScaleFull, _yBuffer, _cbBuffer, _crBuffer, _surface->w, _surface->h, _macroBlocksW * 16, _macroBlocksW * 8); _curFrame++; diff --git a/video/smk_decoder.cpp b/video/smk_decoder.cpp index bea65142a1..c49791100d 100644 --- a/video/smk_decoder.cpp +++ b/video/smk_decoder.cpp @@ -318,7 +318,7 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) { // 1 - set to 1 if file is Y-interlaced // 2 - set to 1 if file is Y-doubled // If bits 1 or 2 are set, the frame should be scaled to twice its height - // before it is displayed. + // before it is displayed. _header.flags = _fileStream->readUint32LE(); SmackerVideoTrack *videoTrack = createVideoTrack(width, height, frameCount, frameRate, _header.flags, _header.signature); diff --git a/video/theora_decoder.cpp b/video/theora_decoder.cpp index d7260469e6..bf953b0de9 100644 --- a/video/theora_decoder.cpp +++ b/video/theora_decoder.cpp @@ -328,7 +328,7 @@ void TheoraDecoder::TheoraVideoTrack::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuf assert(YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height >> 1); assert(YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height >> 1); - Graphics::convertYUV420ToRGB(&_surface, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride); + YUVToRGBMan.convert420(&_surface, Graphics::YUVToRGBManager::kScaleITU, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride); } static vorbis_info *info = 0; diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp index 24287c4d33..b4f44d91b5 100644 --- a/video/video_decoder.cpp +++ b/video/video_decoder.cpp @@ -342,19 +342,23 @@ void VideoDecoder::stop() { if (!isPlaying()) return; + // Stop audio here so we don't have it affect getTime() + stopAudio(); + + // Keep the time marked down in case we start up again + // We do this before _isPlaying is set so we don't get + // _lastTimeChange returned, but before _pauseLevel is + // reset. + _lastTimeChange = getTime(); + _isPlaying = false; _startTime = 0; _palette = 0; _dirtyPalette = false; _needsUpdate = false; - stopAudio(); - // Also reset the pause state. _pauseLevel = 0; - - // Keep the time marked down in case we start up again - _lastTimeChange = getTime(); } Audio::Timestamp VideoDecoder::getDuration() const { |