aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS10
-rw-r--r--backends/midi/coreaudio.cpp31
-rw-r--r--base/main.cpp3
-rwxr-xr-xconfigure2
-rw-r--r--engines/dreamweb/dreamweb.h2
-rw-r--r--engines/dreamweb/monitor.cpp4
-rw-r--r--engines/dreamweb/vgagrafx.cpp68
-rw-r--r--engines/hugo/file.cpp82
-rw-r--r--engines/hugo/file.h5
-rw-r--r--engines/queen/display.cpp38
-rw-r--r--engines/scumm/detection.cpp9
-rw-r--r--engines/scumm/detection_tables.h2
-rw-r--r--engines/scumm/imuse/imuse.cpp41
-rw-r--r--engines/scumm/imuse/imuse_internal.h2
-rw-r--r--engines/scumm/imuse/imuse_part.cpp13
-rw-r--r--engines/scumm/imuse/imuse_player.cpp5
-rw-r--r--engines/scumm/imuse/instrument.cpp60
-rw-r--r--engines/scumm/imuse/instrument.h4
-rw-r--r--engines/scumm/imuse/mac_m68k.cpp510
-rw-r--r--engines/scumm/imuse/mac_m68k.h177
-rw-r--r--engines/scumm/imuse/sysex_scumm.cpp2
-rw-r--r--engines/scumm/module.mk1
-rw-r--r--engines/scumm/saveload.h2
-rw-r--r--engines/scumm/scumm.cpp31
-rw-r--r--engines/scumm/scumm.h11
-rw-r--r--engines/scumm/sound.cpp5
-rw-r--r--engines/toltecs/movie.cpp8
-rw-r--r--engines/tsage/blue_force/blueforce_scenes3.cpp8
-rw-r--r--engines/tucker/resource.cpp33
-rw-r--r--engines/wintermute/base/gfx/osystem/base_render_osystem.cpp5
-rw-r--r--graphics/decoders/bmp.cpp6
-rw-r--r--graphics/decoders/jpeg.cpp2
-rw-r--r--graphics/decoders/pcx.cpp213
-rw-r--r--graphics/decoders/pcx.h68
-rw-r--r--graphics/decoders/tga.cpp5
-rw-r--r--graphics/module.mk1
-rw-r--r--graphics/scaler/aspect.cpp60
-rw-r--r--graphics/yuv_to_rgb.cpp188
-rw-r--r--graphics/yuv_to_rgb.h116
-rw-r--r--video/bink_decoder.cpp2
-rw-r--r--video/codecs/svq1.cpp43
-rw-r--r--video/psx_decoder.cpp2
-rw-r--r--video/smk_decoder.cpp2
-rw-r--r--video/theora_decoder.cpp2
-rw-r--r--video/video_decoder.cpp14
45 files changed, 1546 insertions, 352 deletions
diff --git a/NEWS b/NEWS
index 1f736aa74f..923844e32c 100644
--- a/NEWS
+++ b/NEWS
@@ -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;
}
diff --git a/configure b/configure
index 008593927f..27b6645efe 100755
--- a/configure
+++ b/configure
@@ -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, &currentP[x], width)) {
+ if (!svq1DecodeBlockIntra(&frameData, &currentP[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, &currentP[x], previous, width, pmv, x, y)) {
+ if (!svq1DecodeDeltaBlock(&frameData, &currentP[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 {