[Scummvm-git-logs] scummvm master -> 0e0db53cc20b2d319377268747d02bfdbb7ba62d
neuromancer
noreply at scummvm.org
Mon Mar 30 09:32:53 UTC 2026
This automated email contains information about 8 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
17a050bcf2 FREESCAPE: restored thunder for castle cpc
f1a8a11bc9 FREESCAPE: enable multi language support in castle cpc
91c3070977 FREESCAPE: only run fillColorPairArray for CPC
83eb589944 FREESCAPE: improved driller music for C64
d9f552b451 FREESCAPE: improved dark music for C64
cceaa7b31d FREESCAPE: implement standing/flying indicator in dark for C64
49abb97a8a FREESCAPE: more indicators in dark for C64
0e0db53cc2 FREESCAPE: added music for eclipse c64
Commit: 17a050bcf264b9d1f0ab9a64021b84a5640f4377
https://github.com/scummvm/scummvm/commit/17a050bcf264b9d1f0ab9a64021b84a5640f4377
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: restored thunder for castle cpc
Changed paths:
engines/freescape/games/castle/cpc.cpp
diff --git a/engines/freescape/games/castle/cpc.cpp b/engines/freescape/games/castle/cpc.cpp
index cc8dc6d0ead..47b6fe8fac8 100644
--- a/engines/freescape/games/castle/cpc.cpp
+++ b/engines/freescape/games/castle/cpc.cpp
@@ -115,6 +115,28 @@ byte mountainsData[288] {
0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
+// Data for the thunder frames. This is not included in the original game for some reason
+// but all the other releases have it. This is coming from the ZX Spectrum version.
+// Each row stores two 2-byte variants side by side, so we decode it as a
+// 4-byte-wide bitmap and split it into the two thunder frames.
+byte thunderData[172] {
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
+ 0x04, 0x00, 0x08, 0x00, 0x18, 0x00, 0x10, 0x00, 0x30, 0x00, 0x20, 0x00,
+ 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x50, 0x00, 0x50, 0x00, 0x88, 0x00,
+ 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10,
+ 0x00, 0x08, 0x00, 0x0c, 0x00, 0x1c, 0x00, 0x32, 0x00, 0x22, 0x00, 0xc2,
+ 0x01, 0x81, 0x02, 0x01, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
+ 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0c, 0x00,
+ 0x18, 0x00, 0x30, 0x00, 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x4c, 0x00,
+ 0x86, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x60,
+ 0x00, 0x30, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02,
+ 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00
+};
+
// Expand a 5-byte CPC riddle frame row definition into a 240-pixel CLUT8 row.
@@ -224,6 +246,21 @@ void CastleEngine::loadAssetsCPCFullGame() {
_background = loadFrame(&mountainsStream, background, backgroundWidth, backgroundHeight, front);
+ Common::MemoryReadStream thunderStream(thunderData, sizeof(thunderData));
+ Graphics::ManagedSurface *thunderFrame = new Graphics::ManagedSurface();
+ thunderFrame->create(4 * 8, 43, _gfx->_texturePixelFormat);
+ thunderFrame->fillRect(Common::Rect(0, 0, 4 * 8, 43), 0);
+ thunderFrame = loadFrame(&thunderStream, thunderFrame, 4, 43, front);
+
+ _thunderFrames.push_back(new Graphics::ManagedSurface);
+ _thunderFrames.push_back(new Graphics::ManagedSurface);
+ _thunderFrames[0]->create(2 * 8, 43, _gfx->_texturePixelFormat);
+ _thunderFrames[1]->create(2 * 8, 43, _gfx->_texturePixelFormat);
+ _thunderFrames[0]->copyRectToSurface(*thunderFrame, 0, 0, Common::Rect(0, 0, 2 * 8, 43));
+ _thunderFrames[1]->copyRectToSurface(*thunderFrame, 0, 0, Common::Rect(2 * 8, 0, 4 * 8, 43));
+ thunderFrame->free();
+ delete thunderFrame;
+
// CPC UI Sprites stored as CLUT8 (indexed by ink 0-3).
// On real CPC hardware, the 4-color palette changes per area, automatically
// recoloring everything. We store CLUT8 sprites and setPalette + convert
Commit: f1a8a11bc9cee36fc87fc2c3b7a0701f8757b412
https://github.com/scummvm/scummvm/commit/f1a8a11bc9cee36fc87fc2c3b7a0701f8757b412
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: enable multi language support in castle cpc
Changed paths:
engines/freescape/detection.cpp
engines/freescape/games/castle/castle.cpp
engines/freescape/games/castle/cpc.cpp
engines/freescape/ui.cpp
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index 7a27cfd9174..63778cce8fc 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -874,7 +874,7 @@ static const ADGameDescription gameDescriptions[] = {
{"CMSCR.BIN", 0, "75fe4a8af0ca797c51922f0ceeb8d383", 16512},
AD_LISTEND
},
- Common::EN_ANY,
+ Common::UNK_LANG, // Multi-language
Common::kPlatformAmstradCPC,
ADGF_NO_FLAGS,
GUIO4(GUIO_NOMIDI, GUIO_RENDERCPC, GAMEOPTION_TRAVEL_ROCK, GAMEOPTION_WASD_CONTROLS)
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index 6cc0ba831c4..7fdfc7f1ad5 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -1011,7 +1011,36 @@ void CastleEngine::drawInfoMenu() {
Common::Array<Common::String> lines;
lines.push_back(centerAndPadString("********************", 21));
- if (_language == Common::EN_ANY) {
+ if (isCPC() && _messagesList.size() > 74) {
+ Common::String commandLine = _messagesList[68];
+ Common::String keysLabel = _messagesList[69];
+ Common::String spiritsLabel = _messagesList[70];
+ Common::String strengthLabel = _messagesList[71];
+ Common::String keysText = _messagesList[72];
+ Common::String spiritsText = _messagesList[73];
+ Common::String scoreText = _messagesList[74];
+ Common::String strengthText = _messagesList[62 + shield / 6];
+
+ commandLine.trim();
+ keysLabel.trim();
+ spiritsLabel.trim();
+ strengthLabel.trim();
+ keysText.trim();
+ spiritsText.trim();
+ scoreText.trim();
+ strengthText.trim();
+
+ Common::replace(keysText, "XX", Common::String::format("%2d", _keysCollected.size()));
+ Common::replace(spiritsText, "XX", Common::String::format("%2d", spiritsDestroyed));
+ Common::replace(scoreText, "XXXXXXX", Common::String::format("%07d", score));
+
+ lines.push_back(centerAndPadString(commandLine, 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString(Common::String::format("%s %s", keysLabel.c_str(), keysText.c_str()), 21));
+ lines.push_back(centerAndPadString(Common::String::format("%s %s", spiritsLabel.c_str(), spiritsText.c_str()), 21));
+ lines.push_back(centerAndPadString(Common::String::format("%s %s", strengthLabel.c_str(), strengthText.c_str()), 21));
+ lines.push_back(centerAndPadString(scoreText, 21));
+ } else if (_language == Common::EN_ANY) {
lines.push_back(centerAndPadString("s-save l-load q-quit", 21));
lines.push_back("");
lines.push_back(centerAndPadString(Common::String::format("keys %d collected", _keysCollected.size()), 21));
@@ -1026,7 +1055,12 @@ void CastleEngine::drawInfoMenu() {
lines.push_back(centerAndPadString(Common::String::format("fuerza %s", _messagesList[62 + shield / 6].c_str()), 21));
lines.push_back(centerAndPadString(Common::String::format("puntos %07d", score), 21));
} else {
- error("Language not supported");
+ lines.push_back(centerAndPadString("s-save l-load q-quit", 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString(Common::String::format("keys %d collected", _keysCollected.size()), 21));
+ lines.push_back(centerAndPadString(Common::String::format("spirits %d destroyed", spiritsDestroyed), 21));
+ lines.push_back(centerAndPadString(Common::String::format("strength %s", _messagesList[62 + shield / 6].c_str()), 21));
+ lines.push_back(centerAndPadString(Common::String::format("score %07d", score), 21));
}
lines.push_back("");
@@ -1258,7 +1292,7 @@ void CastleEngine::drawFullscreenGameOverAndWait() {
else if (_language == Common::ES_ESP)
scoreString = "PUNTOS XXXXXXX";
else
- error("Language not supported");
+ scoreString = "SCORE XXXXXXX";
}
Common::replace(scoreString, "XXXXXXX", Common::String::format("%07d", score));
@@ -1273,7 +1307,7 @@ void CastleEngine::drawFullscreenGameOverAndWait() {
else if (_language == Common::ES_ESP)
spiritsDestroyedString = "X DESTRUIDOS";
else
- error("Language not supported");
+ spiritsDestroyedString = "X DESTROYED";
}
Common::replace(spiritsDestroyedString, "X", Common::String::format("%d", spiritsDestroyed));
@@ -1479,8 +1513,14 @@ void CastleEngine::loadRiddles(Common::SeekableReadStream *file, int offset, int
x = file->readByte();
y = file->readByte();
int size = file->readByte();
+ const uint32 recordOffset = file->pos() - 3;
debugC(1, kFreescapeDebugParser, "size: %d (max %d?)", size, maxLineSize);
+ // Castle CPC French has one malformed riddle record in CM.BIN where the
+ // stored size for "NOUS PARVIENT" is 11 instead of 13.
+ if (isCPC() && _language == Common::FR_FRA && recordOffset == 0x86d && size == 11)
+ size = 13;
+
Common::String message = "";
if (size == 255) {
size = 19;
@@ -1968,6 +2008,39 @@ void CastleEngine::selectCharacterScreen() {
lines.push_back(centerAndPadString("2. Princesa", 21));
lines.push_back("");
lines.push_back(centerAndPadString("*******************", 21));
+ } else if (isCPC() && _language == Common::FR_FRA) {
+ // Original Castle Master CPC multilingual strings from CM.BIN @ 0x143.
+ lines.push_back(centerAndPadString("*******************", 21));
+ lines.push_back(centerAndPadString("SELECTIONNEZ LE", 21));
+ lines.push_back(centerAndPadString("PERSONNAGE DESIRE ET", 21));
+ lines.push_back(centerAndPadString("APPUYEZ SUR RETURN", 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("1. PRINCE", 21));
+ lines.push_back(centerAndPadString("2. PRINCESSE", 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("*******************", 21));
+ } else if (isCPC() && _language == Common::DE_DEU) {
+ // Original Castle Master CPC multilingual strings from CM.BIN @ 0x1ac.
+ lines.push_back(centerAndPadString("*******************", 21));
+ lines.push_back(centerAndPadString("GEWUNSCHTE FIGUR", 21));
+ lines.push_back(centerAndPadString("WAHLEN UND", 21));
+ lines.push_back(centerAndPadString("RETURN DRUCKEN", 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("1. PRINZ", 21));
+ lines.push_back(centerAndPadString("2. PRINZESSIN", 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("*******************", 21));
+ } else if (isCPC()) {
+ // Original Castle Master CPC English strings from CM.BIN @ 0xda.
+ lines.push_back(centerAndPadString("*******************", 21));
+ lines.push_back(centerAndPadString("SELECT THE CHARACTER", 21));
+ lines.push_back(centerAndPadString("YOU WISH TO PLAY", 21));
+ lines.push_back(centerAndPadString("AND PRESS RETURN", 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("1. PRINCE", 21));
+ lines.push_back(centerAndPadString("2. PRINCESS", 21));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("*******************", 21));
} else {
lines.push_back(centerAndPadString("*******************", 21));
lines.push_back(centerAndPadString("Select the character", 21));
diff --git a/engines/freescape/games/castle/cpc.cpp b/engines/freescape/games/castle/cpc.cpp
index 47b6fe8fac8..f19a2a775dc 100644
--- a/engines/freescape/games/castle/cpc.cpp
+++ b/engines/freescape/games/castle/cpc.cpp
@@ -192,7 +192,10 @@ void CastleEngine::loadAssetsCPCFullGame() {
if (!file.isOpen())
error("Failed to open TECODE.BIN/TE2.BI2");
- loadMessagesVariableSize(&file, 0x16c6, 71);
+ int messagesOffset = -1;
+ int riddlesOffset = -1;
+ // Multi-language CM.BIN keeps per-language message/riddle blocks in-place:
+ // FR at 0x027B/0x0716, DE at 0x0A47/0x0EE2, EN at 0x16C6/0x1B61.
switch (_language) {
/*case Common::ES_ESP:
loadRiddles(&file, 0x1470 - 4 - 2 - 9 * 2, 9);
@@ -211,27 +214,40 @@ void CastleEngine::loadAssetsCPCFullGame() {
_fontLoaded = true;
break;*/
+ case Common::FR_FRA:
+ messagesOffset = 0x027b;
+ riddlesOffset = 0x0716;
+ break;
+ case Common::DE_DEU:
+ messagesOffset = 0x0a47;
+ riddlesOffset = 0x0ee2;
+ break;
case Common::EN_ANY:
- loadRiddles(&file, 0x1b75 - 2 - 9 * 2, 9);
- load8bitBinary(&file, 0x791a, 16);
- loadSoundsCPC(&file, 0x21E2, 48, 0x2212, 204, 0x2179, 105);
-
- file.seek(0x2724);
- for (int i = 0; i < 90; i++) {
- Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
- surface->create(8, 8, Graphics::PixelFormat::createFormatCLUT8());
- chars.push_back(loadFrame(&file, surface, 1, 8, 1));
- }
- _font = Font(chars);
- _font.setCharWidth(9);
- _fontLoaded = true;
-
+ messagesOffset = 0x16c6;
+ riddlesOffset = 0x1b75 - 2 - 9 * 2;
break;
default:
error("Language not supported");
break;
}
+ // Castle Master CPC keeps the info-menu strings in entries 68..74, just before
+ // the tape/disk prompt block and before the riddle table for each language.
+ loadMessagesVariableSize(&file, messagesOffset, 75);
+ loadRiddles(&file, riddlesOffset, 9);
+ load8bitBinary(&file, 0x791a, 16);
+ loadSoundsCPC(&file, 0x21E2, 48, 0x2212, 204, 0x2179, 105);
+
+ file.seek(0x2724);
+ for (int i = 0; i < 90; i++) {
+ Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
+ surface->create(8, 8, Graphics::PixelFormat::createFormatCLUT8());
+ chars.push_back(loadFrame(&file, surface, 1, 8, 1));
+ }
+ _font = Font(chars);
+ _font.setCharWidth(9);
+ _fontLoaded = true;
+
loadColorPalette();
int backgroundWidth = 16;
diff --git a/engines/freescape/ui.cpp b/engines/freescape/ui.cpp
index dca45969c04..30b948573d4 100644
--- a/engines/freescape/ui.cpp
+++ b/engines/freescape/ui.cpp
@@ -246,6 +246,26 @@ void FreescapeEngine::borderScreen() {
lines.push_back("");
lines.push_back(centerAndPadString("ENTER: EMPEZAR MISION", pad));
lines.push_back(centerAndPadString("(c) 1990 INCENTIVE", pad));
+ } else if (isCastle() && _language == Common::FR_FRA) {
+ lines.push_back(centerAndPadString("MENU CONFIGURATION", pad));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("1 CLAVIER ", pad));
+ lines.push_back(centerAndPadString("2 JOYSTICK SINCLAIR", pad));
+ lines.push_back(centerAndPadString("3 JOYSTICK KEMSTON ", pad));
+ lines.push_back(centerAndPadString("4 JOYSTICK CURSEUR ", pad));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("RETURN: DEBUT MISSION", pad));
+ lines.push_back(centerAndPadString("(c) 1990 INCENTIVE", pad));
+ } else if (isCastle() && _language == Common::DE_DEU) {
+ lines.push_back(centerAndPadString("AUSWAHL-MENUE", pad));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("1 TASTATUR ", pad));
+ lines.push_back(centerAndPadString("2 SINCLAIR JOYSTICK", pad));
+ lines.push_back(centerAndPadString("3 KEMSTON JOYSTICK ", pad));
+ lines.push_back(centerAndPadString("4 CURSOR JOYSTICK ", pad));
+ lines.push_back("");
+ lines.push_back(centerAndPadString("RETURN: MISSION START", pad));
+ lines.push_back(centerAndPadString("(c) 1990 INCENTIVE", pad));
} else {
lines.push_back(centerAndPadString("CONTROL OPTIONS", pad));
lines.push_back("");
Commit: 91c3070977dca9257d93e876c857e869c96eb714
https://github.com/scummvm/scummvm/commit/91c3070977dca9257d93e876c857e869c96eb714
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: only run fillColorPairArray for CPC
Changed paths:
engines/freescape/games/castle/castle.cpp
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index 7fdfc7f1ad5..2b88fef5c20 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -598,7 +598,8 @@ void CastleEngine::gotoArea(uint16 areaID, int entranceID) {
// Ignore sky/ground fields
_gfx->_keyColor = 0;
_gfx->clearColorPairArray();
- _gfx->fillColorPairArray();
+ if (isCPC())
+ _gfx->fillColorPairArray();
swapPalette(areaID);
Commit: 83eb589944b5bda7ef2afd2e75c2fc13f4db6660
https://github.com/scummvm/scummvm/commit/83eb589944b5bda7ef2afd2e75c2fc13f4db6660
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: improved driller music for C64
Changed paths:
engines/freescape/games/driller/c64.music.cpp
engines/freescape/games/driller/c64.music.h
diff --git a/engines/freescape/games/driller/c64.music.cpp b/engines/freescape/games/driller/c64.music.cpp
index 90eac0d1968..e595f713eb8 100644
--- a/engines/freescape/games/driller/c64.music.cpp
+++ b/engines/freescape/games/driller/c64.music.cpp
@@ -108,9 +108,9 @@ const uint8_t arpeggio_data[] = {0x00, 0x0C, 0x18};
// In a real implementation, these would point into _musicData
// Tracks (0x1057 - 0x118A)
-const uint8_t voice1_track_data[] = {0x01, 0x01, 0x07, 0x09, 0x09, 0x09, 0x01, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x03, 0x03, 0x0F, 0x0F, 0x13, 0x13, 0x0F, 0x13, 0x0F, 0x13, 0x0F, 0x13, 0x0F, 0x13, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1B, 0x1D, 0x1E, 0x0F, 0x1B, 0x1D, 0x1E, 0x0F, 0x1B, 0x1D, 0x1E, 0x12, 0x12, 0x12, 0x12, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x08, 0x08, 0x28, 0x00, 0x00, 0x00, 0x00, 0xFF};
+const uint8_t voice1_track_data[] = {0x01, 0x01, 0x07, 0x09, 0x09, 0x09, 0x01, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x03, 0x03, 0x0F, 0x0F, 0x13, 0x13, 0x0F, 0x13, 0x0F, 0x13, 0x0F, 0x13, 0x0F, 0x13, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1B, 0x1D, 0x1E, 0x0F, 0x1B, 0x1D, 0x1E, 0x0F, 0x1B, 0x1D, 0x1E, 0x12, 0x12, 0x12, 0x12, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x24, 0x24, 0x21, 0x21, 0x08, 0x08, 0x28, 0x00, 0x00, 0x00, 0x00, 0xFF};
const uint8_t voice2_track_data[] = {0x03, 0x03, 0x08, 0x0A, 0x0D, 0x0D, 0x0D, 0x0D, 0x08, 0x07, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x05, 0x12, 0x12, 0x12, 0x12, 0x14, 0x15, 0x14, 0x15, 0x14, 0x15, 0x14, 0x15, 0x08, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x07, 0x07, 0x1F, 0x1F, 0x1F, 0x1F, 0x07, 0x07, 0x00, 0x00, 0x25, 0x25, 0x26, 0x25, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x28, 0x00, 0x00, 0x00, 0x00, 0xFF};
-const uint8_t voice3_track_data[] = {0x00, 0x00, 0x00, 0x00, 0x04, 0x06, 0x06, 0x0C, 0x0B, 0x0C, 0x0B, 0x0C, 0x0B, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x0F, 0x0F, 0x10, 0x11, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x16, 0x07, 0x07, 0x07, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x08, 0x08, 0x1C, 0x08, 0x08, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x29, 0x00, 0x00, 0x00, 0x00, 0xFF};
+const uint8_t voice3_track_data[] = {0x00, 0x00, 0x00, 0x00, 0x04, 0x06, 0x06, 0x0C, 0x0B, 0x0C, 0x0B, 0x0C, 0x0B, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x0F, 0x0F, 0x10, 0x11, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x16, 0x07, 0x07, 0x07, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x08, 0x08, 0x1C, 0x08, 0x08, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x23, 0x23, 0x22, 0x22, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x29, 0x00, 0x00, 0x00, 0x00, 0xFF};
// Patterns (0x118B - 0x1579) - Need to define these based on disassembly
const uint8_t pattern_00[] = {0xFD, 0x3F, 0xFA, 0x04, 0x00, 0xFF};
@@ -145,7 +145,7 @@ const uint8_t pattern_28[] = {0xFA, 0x01, 0xFD, 0x1F, 0x3B, 0xFD, 0x0F, 0x3A, 0x
const uint8_t pattern_29[] = {0xFA, 0x0B, 0xFD, 0x01, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0xFF};
const uint8_t pattern_30[] = {0xFA, 0x0B, 0xFD, 0x01, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0xFF};
const uint8_t pattern_31[] = {0xFA, 0x09, 0xFD, 0x3F, 0x23, 0x1B, 0x1C, 0x1E, 0xFF};
-const uint8_t pattern_32[] = {0xFA, 0x01, 0xFD, 0x7F, 0x17, 0x17, 0xFF}; // Note: Simplified, removed data after FF
+const uint8_t pattern_32[] = {0xFA, 0x01, 0xFD, 0x7F, 0x17, 0x17, 0xFF, 0x21, 0x26, 0xFD, 0x11, 0x28, 0xFF};
const uint8_t pattern_33[] = {0xFA, 0x15, 0xFD, 0x01, 0x1F, 0x1F, 0xFD, 0x03, 0x1F, 0xFA, 0x0F, 0xFD, 0x01, 0x2E, 0x27, 0xFA, 0x15, 0x1F, 0xFD, 0x03, 0x1F, 0xFD, 0x01, 0x1F, 0xFD, 0x03, 0x1F, 0xFD, 0x01, 0xFA, 0x0F, 0x2F, 0xFA, 0x15, 0x1A, 0x1D, 0x1F, 0xFF};
const uint8_t pattern_34[] = {0xFA, 0x09, 0xFD, 0x01, 0x13, 0x13, 0xFD, 0x03, 0x13, 0xFD, 0x01, 0xFA, 0x00, 0x2E, 0x27, 0xFA, 0x09, 0x13, 0xFD, 0x03, 0x13, 0xFD, 0x01, 0x13, 0xFD, 0x03, 0x13, 0xFD, 0x01, 0x13, 0x10, 0x11, 0x13, 0xFF};
const uint8_t pattern_35[] = {0xFA, 0x09, 0xFD, 0x01, 0x17, 0x17, 0xFD, 0x03, 0x17, 0xFD, 0x01, 0xFA, 0x00, 0x2E, 0x27, 0xFA, 0x09, 0x17, 0xFD, 0x03, 0x17, 0xFD, 0x01, 0x17, 0xFD, 0x03, 0x17, 0xFD, 0x01, 0x17, 0x12, 0x15, 0x17, 0xFF};
@@ -172,11 +172,41 @@ const uint8_t *const tune_track_data[][3] = {
{nullptr, nullptr, nullptr}, // Tune 0 (null pointers = stop)
{voice1_track_data, voice2_track_data, voice3_track_data}, // Tune 1
};
-const int NUM_TUNES = ARRAYSIZE(tune_tempo_data);
+const int NUM_TUNES = ARRAYSIZE(tune_track_data);
// SID Base Addresses for Voices
const int voice_sid_offset[] = {0, 7, 14};
+const uint8_t initialSomethingData[][3] = {
+ {0x00, 0x00, 0x3F},
+ {0x00, 0x00, 0x3F},
+ {0x00, 0x00, 0x3F}
+};
+
+const uint8_t initialInstrumentIndex[] = {0x08, 0x08, 0x20};
+
+const uint8_t initialSomethingElseData[][3] = {
+ {0xBB, 0x90, 0x02},
+ {0xBB, 0x90, 0x02},
+ {0xF0, 0x90, 0x0C}
+};
+
+const uint8_t initialCtrl0[] = {0x00, 0x00, 0x06};
+const uint8_t initialPwDirection[] = {0x00, 0x00, 0x01};
+const uint8_t initialStuffData[][7] = {
+ {0x47, 0x47, 0x06, 0x1F, 0x06, 0x00, 0x00},
+ {0x23, 0x23, 0x03, 0x13, 0x03, 0x00, 0x00},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+const uint8_t initialThingsData[][7] = {
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+const uint8_t initialTwoCtr[] = {0x02, 0x02, 0x02};
+
// Debug log levels
#define DEBUG_LEVEL 4 // 0: Minimal, 1: Basic Flow, 2: Detailed State
@@ -312,43 +342,10 @@ void DrillerSIDPlayer::handleChangeTune(int tuneIndex) {
debug(DEBUG_LEVEL >= 0, "Driller: Invalid tune index %d in handleChangeTune, using 1", tuneIndex);
tuneIndex = 1; // Default to tune 1 if invalid
}
-
- // *** ADD THIS LOG - BEFORE ASSIGNMENT ***
- debug(DEBUG_LEVEL >= 1, "Driller: Tune %d - Accessing tune_track_data[%d]...", tuneIndex, tuneIndex);
-
const uint8_t *const *currentTuneTracks = tune_track_data[tuneIndex];
- // *** ADD THIS LOG - AFTER ACCESSING THE TUNE'S TRACK ARRAY ***
- // Check if the pointer to the array itself is valid
- if (!currentTuneTracks) {
- debug(DEBUG_LEVEL >= 0, "Driller: FATAL - tune_track_data[%d] is NULL!", tuneIndex);
- // Optional: Handle this error more gracefully, maybe stop music?
- } else {
- debug(DEBUG_LEVEL >= 2, "Driller: tune_track_data[%d] pointer is valid.", tuneIndex);
- }
-
for (int i = 0; i < 3; ++i) {
- // *** ADD THIS LOG - BEFORE ASSIGNING TO voiceState ***
- const uint8_t *trackPtr = nullptr; // Temp variable
- if (currentTuneTracks) { // Check if the tune array pointer is valid
- trackPtr = currentTuneTracks[i];
- debug(DEBUG_LEVEL >= 1, "Driller: V%d - Got track pointer %p from currentTuneTracks[%d]", i, (const void *)trackPtr, i);
- } else {
- debug(DEBUG_LEVEL >= 0, "Driller: V%d - Cannot get track pointer because currentTuneTracks is NULL", i);
- }
-
- // Assign the pointer
- _voiceState[i].trackDataPtr = trackPtr;
-
- // *** ADD THIS LOG - AFTER ASSIGNING TO voiceState ***
- debug(DEBUG_LEVEL >= 1, "Driller: V%d - Assigned _voiceState[%d].trackDataPtr = %p", i, i, (const void *)_voiceState[i].trackDataPtr);
-
- if (!_voiceState[i].trackDataPtr) {
- // This block now just confirms the assignment result
- debug(DEBUG_LEVEL >= 1, "Driller: Voice %d has null track data assigned for tune %d.", i, tuneIndex);
- // Don't reset here, handleResetVoices will do it.
- }
- // Pointers setup in resetVoices below
+ _voiceState[i].trackDataPtr = currentTuneTracks ? currentTuneTracks[i] : nullptr;
}
_globalTempo = tune_tempo_data[tuneIndex];
@@ -368,67 +365,59 @@ void DrillerSIDPlayer::handleResetVoices() {
SID_Write(0x18, 0x0F); // Volume Max
for (int i = 0; i < 3; ++i) {
- // *** DO NOT CALL _voiceState[i].reset() HERE ***
- // The trackDataPtr was just assigned in handleChangeTune.
- // Reset only the playback state relevant for starting a tune/track.
-
- debug(DEBUG_LEVEL >= 1, "Driller: Reset Voice %d - Checking _voiceState[%d].trackDataPtr (%p)...", i, i, (const void *)_voiceState[i].trackDataPtr);
-
- if (_voiceState[i].trackDataPtr != nullptr) {
- debug(DEBUG_LEVEL >= 1, "Driller: Reset Voice %d - Track pointer OK. Initializing playback state.", i);
-
- // Reset playback state, keep trackDataPtr
- _voiceState[i].trackIndex = 0;
- _voiceState[i].patternDataPtr = nullptr; // Will be set by pattern lookup
- _voiceState[i].patternIndex = 0;
- _voiceState[i].instrumentIndex = 0; // Default instrument? Or should tune load set this? Let's keep 0.
- _voiceState[i].delayCounter = -1; // Ready for first note
- _voiceState[i].noteDuration = 0;
- _voiceState[i].gateMask = 0xFF;
- _voiceState[i].currentNote = 0;
- _voiceState[i].portaTargetNote = 0;
- _voiceState[i].currentFreq = 0;
- _voiceState[i].baseFreq = 0;
- _voiceState[i].targetFreq = 0;
- _voiceState[i].pulseWidth = 0; // Default PW?
- _voiceState[i].attackDecay = 0x00; // Default ADSR?
- _voiceState[i].sustainRelease = 0x00;
- _voiceState[i].effect = 0;
- _voiceState[i].hardRestartActive = false;
- _voiceState[i].waveform = 0x10; // Default waveform (Triangle)
- _voiceState[i].keyOn = false;
- _voiceState[i].currentNoteSlideTarget = 0;
- _voiceState[i].glideDownTimer = 0; // Reset glide timer
-
- // Reset other potentially problematic state variables from the struct
- _voiceState[i].whatever0 = 0;
- _voiceState[i].whatever1 = 0;
- _voiceState[i].whatever2 = 0;
- _voiceState[i].whatever3 = 0;
- _voiceState[i].whatever4 = 0;
- _voiceState[i].whatever2_vibDirToggle = 0;
- _voiceState[i].portaStepRaw = 0;
- memset(_voiceState[i].something_else, 0, sizeof(_voiceState[i].something_else));
- _voiceState[i].ctrl0 = 0;
- _voiceState[i].arpTableIndex = 0;
- _voiceState[i].arpSpeedHiNibble = 0;
- _voiceState[i].stuff_freq_porta_vib = 0;
- _voiceState[i].stuff_freq_base = 0;
- _voiceState[i].stuff_freq_hard_restart = 0;
- _voiceState[i].stuff_arp_counter = 0;
- _voiceState[i].stuff_arp_note_index = 0;
- _voiceState[i].things_vib_state = 0;
- _voiceState[i].things_vib_depth = 0;
- _voiceState[i].things_vib_delay_reload = 0;
- _voiceState[i].things_vib_delay_ctr = 0;
- _voiceState[i].portaSpeed = 0;
-
- } else {
- debug(DEBUG_LEVEL >= 0, "Driller: Reset Voice %d - Check FAILED. trackDataPtr is NULL here!", i);
- // Ensure voice is silent if no track data
- int sidOffset = voice_sid_offset[i];
- SID_Write(sidOffset + 4, 0); // Gate off
+ VoiceState &v = _voiceState[i];
+ if (!v.trackDataPtr) {
+ SID_Write(voice_sid_offset[i] + 4, 0);
+ continue;
}
+
+ v.trackIndex = 0;
+ v.patternDataPtr = nullptr;
+ v.patternIndex = 0;
+ v.instrumentIndex = initialInstrumentIndex[i];
+ v.delayCounter = 0;
+ v.noteDuration = initialSomethingData[i][2];
+ v.gateMask = 0xFF;
+ v.currentNote = initialStuffData[i][3];
+ v.portaTargetNote = 0;
+ v.currentFreq = 0;
+ v.baseFreq = 0;
+ v.targetFreq = 0;
+ v.pulseWidth = initialSomethingElseData[i][0] | (initialSomethingElseData[i][2] << 8);
+ v.attackDecay = 0;
+ v.sustainRelease = 0;
+ v.effect = 0;
+ v.hardRestartValue = 0;
+ v.hardRestartDelay = 0;
+ v.hardRestartCounter = 0;
+ v.hardRestartActive = false;
+ v.waveform = 0x10;
+ v.keyOn = false;
+ v.sync = false;
+ v.ringMod = false;
+
+ v.whatever0 = 0;
+ v.whatever1 = 0;
+ v.whatever2 = 0;
+ v.whatever3 = 0;
+ v.whatever4 = 0;
+ v.whatever2_vibDirToggle = initialPwDirection[i];
+ v.portaStepRaw = initialSomethingData[i][0];
+ memcpy(v.something_else, initialSomethingElseData[i], sizeof(v.something_else));
+ v.ctrl0 = initialCtrl0[i];
+ v.arpTableIndex = 0;
+ v.arpSpeedHiNibble = initialStuffData[i][5];
+ v.stuff_freq_porta_vib = initialStuffData[i][0] | (initialStuffData[i][4] << 8);
+ v.stuff_freq_base = initialStuffData[i][1] | (initialStuffData[i][2] << 8);
+ v.stuff_freq_hard_restart = 0;
+ v.stuff_arp_counter = initialStuffData[i][6];
+ v.stuff_arp_note_index = 0;
+ v.things_vib_state = initialThingsData[i][0];
+ v.things_vib_depth = initialThingsData[i][1];
+ v.things_vib_delay_reload = initialThingsData[i][2];
+ v.things_vib_delay_ctr = initialThingsData[i][3];
+ v.currentNoteSlideTarget = initialThingsData[i][6];
+ v.glideDownTimer = initialTwoCtr[i];
}
// Reset global tempo counter (0x093D)
@@ -437,606 +426,353 @@ void DrillerSIDPlayer::handleResetVoices() {
// --- Voice Processing ---
void DrillerSIDPlayer::playVoice(int voiceIndex) {
- // debug(DEBUG_LEVEL >= 2, "Driller: Processing Voice %d", voiceIndex);
VoiceState &v = _voiceState[voiceIndex];
int sidOffset = voice_sid_offset[voiceIndex];
- // If track data is null, this voice is inactive for the current tune
- if (!v.trackDataPtr) {
+ if (!v.trackDataPtr)
return;
- }
-
- // --- Effect application before note processing (Tempo independent) ---
- // Corresponds roughly to L0944 - L0964 (instrument specific effects)
- // And L0B33 onwards (general effects like vibrato, portamento, arpeggio)
int instBase = v.instrumentIndex; // Already scaled by 8
- // Safety check for instrument index
if (instBase < 0 || (size_t)instBase >= sizeof(instrumentDataA0)) {
- instBase = 0; // Default to instrument 0 if invalid
+ instBase = 0;
v.instrumentIndex = 0;
}
const uint8_t *instA0 = &instrumentDataA0[instBase];
const uint8_t *instA1 = &instrumentDataA1[instBase];
- // Waveform transition effect (L0944-L095E) - Inst A0[7] & 0x04
- // This logic updates ctrl register $D404, likely wave or gate
if (instA0[7] & 0x04) {
- if (v.glideDownTimer > 0) { // voice1_two_ctr,x (0xD3E)
+ if (v.glideDownTimer != 0) {
v.glideDownTimer--;
- uint8_t ctrlVal = instA1[2]; // possibly_instrument_a1+2,y
- SID_Write(sidOffset + 4, ctrlVal);
- // bne L0964 - skip waveform reset if timer > 0
+ if (instA1[2] != 0)
+ SID_Write(sidOffset + 4, instA1[2]);
+ else
+ SID_Write(sidOffset + 4, instA0[1]);
} else {
- // L095E: timer is 0
- uint8_t ctrlVal = instA0[1]; // possibly_instrument_a0+1,y
- SID_Write(sidOffset + 4, ctrlVal);
- // Resets waveform/gate based on inst A0[1]
+ SID_Write(sidOffset + 4, instA0[1]);
}
}
- // Corresponds to lda tempo_ctr; bne L096E (0x0964)
- // The per-voice processing is gated by the global tempo counter being zero.
- if (_globalTempoCounter == 0) {
- // dec voice1_ctrl2,x (0x0969)
- if (v.delayCounter >= 0) {
- v.delayCounter--;
+ if (_globalTempoCounter != 0) {
+ applyContinuousEffects(v, sidOffset, instA0, instA1, false);
+ return;
+ }
+
+ v.delayCounter--;
+ if (v.delayCounter >= 0) {
+ applyContinuousEffects(v, sidOffset, instA0, instA1, false);
+ return;
+ }
+
+ uint8_t patternNum = v.trackDataPtr[v.trackIndex];
+ if (patternNum == 0xFE) {
+ stopMusic();
+ return;
+ }
+ if (patternNum == 0xFF) {
+ v.trackIndex = 0;
+ patternNum = v.trackDataPtr[v.trackIndex];
+ if (patternNum == 0xFF || patternNum == 0xFE) {
+ stopMusic();
+ return;
}
+ }
+ if (patternNum >= NUM_PATTERNS) {
+ debug(DEBUG_LEVEL >= 0, "Driller V%d: Invalid pattern number %d", voiceIndex, patternNum);
+ stopMusic();
+ return;
+ }
- // If delay counter has expired, read new data from the pattern.
- if (v.delayCounter < 0) {
- debug(DEBUG_LEVEL >= 1, "Driller V%d: Delay Counter Expired - Reading new pattern data", voiceIndex);
-
- // --- Start of inlined pattern reading logic ---
- // Get current pattern index from track (09C0-09CE)
- uint8_t patternNum = v.trackDataPtr[v.trackIndex];
-
- // Handle track end/loop markers (0AE7, 0AF2)
- if (patternNum == 0xFF) { // End of track list
- debug(DEBUG_LEVEL >= 1, "Driller V%d: Track %d end marker (FF), looping.", voiceIndex, v.trackIndex);
- v.trackIndex = 0; // Loop to start
- patternNum = v.trackDataPtr[v.trackIndex];
- if (patternNum == 0xFF || patternNum == 0xFE || !tune_track_data[_targetTuneIndex][voiceIndex]) { // Check again after loop or if track is null initially
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Stopping music after track loop (FF/FE/Null).", voiceIndex);
- stopMusic(); // Stop if loop points to end marker or track is invalid
- return;
- }
- } else if (patternNum == 0xFE) { // Stop playback command
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Stopping music due to track marker FE.", voiceIndex);
- stopMusic();
- return;
- }
+ v.patternDataPtr = pattern_addresses[patternNum];
+ v.gateMask = 0xFF;
+ v.whatever2 = 0;
+ v.whatever1 = 0;
+ v.whatever0 = 0;
- if (patternNum >= NUM_PATTERNS) {
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Invalid pattern number %d at track index %d", voiceIndex, patternNum, v.trackIndex);
- v.trackIndex++; // Skip invalid entry
- // Fetch next pattern number immediately to avoid getting stuck in invalid state for a frame
- size_t trackSize = (voiceIndex == 0) ? sizeof(voice1_track_data) : ((voiceIndex == 1) ? sizeof(voice2_track_data) : sizeof(voice3_track_data));
- if (v.trackIndex >= trackSize) { // Check for track end
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Stopping music, track index out of bounds after skipping invalid pattern.", voiceIndex);
- stopMusic();
- return;
- }
- patternNum = v.trackDataPtr[v.trackIndex];
- if (patternNum == 0xFF || patternNum == 0xFE) {
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Stopping music, encountered FF/FE after skipping invalid pattern.", voiceIndex);
- stopMusic();
- return;
- }
- if (patternNum >= NUM_PATTERNS) { // Still invalid? Stop.
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Stopping music, encountered second invalid pattern.", voiceIndex);
- stopMusic();
- return;
- }
- // Continue with the new valid patternNum
- }
+ while (true) {
+ uint8_t cmd = v.patternDataPtr[v.patternIndex];
- // Only update pattern pointer if it changed or wasn't set
- if (v.patternDataPtr != pattern_addresses[patternNum]) {
- v.patternDataPtr = pattern_addresses[patternNum];
- v.patternIndex = 0; // Reset index when pattern changes
- debug(DEBUG_LEVEL >= 2, "Driller V%d: Switched to Pattern %d", voiceIndex, patternNum);
- }
+ if (cmd >= 0xFD) {
+ v.patternIndex++;
+ v.noteDuration = v.patternDataPtr[v.patternIndex];
+ v.patternIndex++;
+ continue;
+ }
- // Reset state related to previous note/effects for gate control
- v.gateMask = 0xFF; // Reset gate mask (0x09D0: lda #$FF; sta control3)
- v.whatever0 = 0; // Reset effect states (0x09D5 onwards)
+ if (cmd >= 0xFB) {
+ v.patternIndex++;
+ v.whatever2 = (cmd == 0xFB) ? 1 : 2;
+ v.portaStepRaw = v.patternDataPtr[v.patternIndex];
v.whatever1 = 0;
- v.whatever2 = 0;
-
- // --- Read Pattern Data Loop (0x09E0 read_note_or_ctrl) ---
- bool noteProcessed = false;
- while (!noteProcessed) {
- if (!v.patternDataPtr) { // Safety check
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Pattern pointer is null!", voiceIndex);
- v.trackIndex++; // Advance track to avoid getting stuck
- noteProcessed = true; // Exit loop, try next track index next frame
- break;
- }
-
- // Check pattern bounds - Use FF as terminator
- if (v.patternIndex >= 255) { // Sanity check pattern length
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Pattern index overflow (>255), resetting.", voiceIndex);
- v.patternIndex = 0; // Reset pattern index
- v.trackIndex++; // Advance track index
- noteProcessed = true; // Exit loop
- break; // Go to next track entry
- }
-
- uint8_t cmd = v.patternDataPtr[v.patternIndex];
- debug(DEBUG_LEVEL >= 3, "Driller V%d: Reading Pat %d Idx %d: Cmd $%02X", voiceIndex, patternNum, v.patternIndex, cmd);
-
- if (cmd == 0xFF) { // End of pattern marker (0x0AD6)
- debug(DEBUG_LEVEL >= 2, "Driller V%d: End of Pattern %d detected.", voiceIndex, patternNum);
- v.patternIndex = 0; // Reset pattern index
- v.trackIndex++; // Advance track index (0x0ADF)
- noteProcessed = true; // Exit inner loop, done processing for this tick
- break; // Exit pattern loop, next tick will get next pattern index from track
- }
-
- if (cmd >= 0xFD) { // --- Control Commands ---
- v.patternIndex++; // Consume command byte
- if (!v.patternDataPtr || v.patternDataPtr[v.patternIndex] == 0xFF) { // Check bounds before reading data
- debug(DEBUG_LEVEL >= 1, "Driller V%d: Pattern ended unexpectedly after Fx command.", voiceIndex);
- noteProcessed = true;
- break;
- }
- uint8_t dataByte = v.patternDataPtr[v.patternIndex]; // Read data byte
-
- // Effect FD/FE: Set Note Duration (0x09E5 + 0x09ED)
- // Any command >= FD that is not FF (end of pattern) sets the duration.
- v.noteDuration = dataByte; // Store duration (0x09EF)
- debug(DEBUG_LEVEL >= 2, "Driller V%d: Cmd $%02X, Set Duration = %d", voiceIndex, cmd, v.noteDuration);
-
- // Continue reading pattern (next_note_or_ctrl 09F2/0A15)
- v.patternIndex++;
-
- } else if (cmd >= 0xFB) { // Effect FB/FC
- v.patternIndex++; // Consume command byte
- if (!v.patternDataPtr || v.patternDataPtr[v.patternIndex] == 0xFF) { // Check bounds before reading data
- debug(DEBUG_LEVEL >= 1, "Driller V%d: Pattern ended unexpectedly after FB/FC command.", voiceIndex);
- noteProcessed = true;
- break;
- }
- uint8_t portaParam = v.patternDataPtr[v.patternIndex]; // Consume data byte
-
- // FB = porta type 1 (down lo), FC = porta type 2 (up lo)
- // Assembly: FB -> lda #$01 (0x09FF), FC -> lda #$02 (0x0A17)
- if (cmd == 0xFB) {
- v.whatever2 = 1; // (0x09FF: lda #$01; sta whatever+2)
- } else { // FC
- v.whatever2 = 2; // (0x0A17: lda #$02)
- }
- v.portaStepRaw = portaParam; // sta voice1_something (0x0A0A)
- v.whatever1 = 0; // sta whatever+1 (0x0A0F)
- v.whatever0 = 0; // sta whatever (0x0A12)
- v.portaSpeed = 0; // Force recalc
- v.patternIndex++; // Continue reading pattern (0A15)
-
- } else if (cmd == 0xFA) { // --- Effect FA: Set Instrument --- (0x0A1B)
- v.patternIndex++;
- if (!v.patternDataPtr || v.patternDataPtr[v.patternIndex] == 0xFF) { // Check bounds before reading data
- debug(DEBUG_LEVEL >= 1, "Driller V%d: Pattern ended unexpectedly after FA command.", voiceIndex);
- noteProcessed = true;
- break;
- }
- uint8_t instNum = v.patternDataPtr[v.patternIndex];
- if (instNum >= NUM_INSTRUMENTS) {
- debug(DEBUG_LEVEL >= 0, "Driller V%d: Invalid instrument number %d, using 0.", voiceIndex, instNum);
- instNum = 0;
- }
- v.instrumentIndex = instNum * 8; // Store base offset (0A28)
- debug(DEBUG_LEVEL >= 2, "Driller V%d: Cmd FA, Set Instrument = %d", voiceIndex, instNum);
-
- // Update local pointers for instrument data
- instBase = v.instrumentIndex;
- if (instBase < 0 || (size_t)instBase >= sizeof(instrumentDataA0))
- instBase = 0; // Bounds check
- instA0 = &instrumentDataA0[instBase];
- instA1 = &instrumentDataA1[instBase];
-
- // Set ADSR based on instrument (0A2C - 0A3E)
- uint8_t adsrByte = instA0[0]; // 0A2C
- v.sustainRelease = adsrByte & 0x0F; // Low nibble to SR (0A32) -> ctrl0
- v.attackDecay = adsrByte & 0xF0; // High nibble to AD (0A3B/0A3E) -> something_else[0/1]
- // Store in voice state for SID write later
- v.ctrl0 = v.sustainRelease;
- v.something_else[0] = v.attackDecay;
- v.something_else[1] = v.attackDecay; // Seems duplicated in disassembly?
- // Also set PW from instA0[0]? Disassembly sets something_else[0] and [1] to AD (hi nibble)
- // Pulse width seems set later from something_else[0] and [2] ? Let's use [0] for AD.
- // Let's assume instA0[2] (often xx) and instA0[3] (often 00) are PW lo/hi nibble?
- // Or maybe something_else[0]/[2] ARE PW and ADSR needs separate vars?
- // Revisit PW setting in applyNote based on L0AC2. It uses something_else[0] and [2].
- // Let's store ADSR in dedicated vars, and use something_else for PW based on instrument.
- // What part of instrument sets PW? L0AC2 uses something_else[0/2]. FA command sets something_else[0/1/2].
- // FA: pla -> and #F0 -> sta something_else[0] / [1]
- // FA: pha -> and #0F -> sta something_else[2] / ctrl0
- // This means: AD Hi Nibble -> PW Lo Byte? AD Hi Nibble -> something_else[1]? SR Lo Nibble -> PW Hi Nibble? SR Lo Nibble -> ctrl0?
- // Let's follow the variable names:
- v.attackDecay = instA0[0] & 0xF0; // Stored in something_else[0] & [1]
- v.sustainRelease = instA0[0] & 0x0F; // Stored in something_else[2] & ctrl0
- v.something_else[0] = v.attackDecay;
- v.something_else[1] = v.attackDecay; // ???
- v.something_else[2] = v.sustainRelease; // PW Hi?
- v.ctrl0 = v.sustainRelease; // SR?
-
- debug(DEBUG_LEVEL >= 3, "Driller V%d: Inst %d - ADSR Byte: $%02X -> AD: $%02X, SR: $%02X", voiceIndex, instNum, adsrByte, v.attackDecay, v.sustainRelease);
-
- // Continue reading pattern (0A41 -> 09F2)
- v.patternIndex++;
-
- } else { // --- Plain Note --- (0x0A1D -> 0A44)
- v.currentNote = cmd; // Store note value (0A44: sta stuff+3)
- // Set delay counter based on previously read duration (0A47-0A4A)
- v.delayCounter = v.noteDuration;
-
- // Reset hard restart counters (0A4D-0A52)
- v.whatever3 = 0;
- v.whatever4 = 0;
-
- // Reset glide down timer (0A55-0A57)
- v.glideDownTimer = 2; // voice1_two_ctr = 2
-
- // Apply Note Data (0A5D-0AB3)
- applyNote(v, sidOffset, instA0, instA1, voiceIndex);
-
- // Continue reading pattern
- v.patternIndex++;
- noteProcessed = true;
- }
-
- } // End while(!noteProcessed)
- // --- End of inlined pattern reading logic ---
-
- // L0AFC: Post-note effect setup - determine which continuous effect is active
- postNoteEffectSetup(v, sidOffset, instA0, instA1);
+ v.whatever0 = 0;
+ v.patternIndex++;
+ continue;
+ }
+
+ if (cmd >= 0xFA) {
+ v.patternIndex++;
+ uint8_t instNum = v.patternDataPtr[v.patternIndex];
+ if (instNum >= NUM_INSTRUMENTS)
+ instNum = 0;
+ v.instrumentIndex = instNum * 8;
+ instBase = v.instrumentIndex;
+ instA0 = &instrumentDataA0[instBase];
+ instA1 = &instrumentDataA1[instBase];
+
+ uint8_t instrumentByte = instA0[0];
+ v.attackDecay = instrumentByte & 0xF0;
+ v.sustainRelease = instrumentByte & 0x0F;
+ v.something_else[0] = v.attackDecay;
+ v.something_else[1] = v.attackDecay;
+ v.something_else[2] = v.sustainRelease;
+ v.ctrl0 = v.sustainRelease;
+ v.patternIndex++;
+ continue;
}
- }
- // ALWAYS apply continuous effects (L0B33+) for the current state of the voice.
- // This runs every frame: on tempo ticks after note processing, and on non-tempo ticks directly.
- applyContinuousEffects(v, sidOffset, instA0, instA1);
+ v.currentNote = cmd;
+ v.delayCounter = v.noteDuration;
+ v.whatever3 = 0;
+ v.whatever4 = 0;
+ v.glideDownTimer = 2;
+
+ applyNote(v, sidOffset, instA0);
+
+ v.patternIndex++;
+ if (v.patternDataPtr[v.patternIndex] == 0xFF) {
+ v.patternIndex = 0;
+ v.trackIndex++;
+ uint8_t trackCmd = v.trackDataPtr[v.trackIndex];
+ if (trackCmd == 0xFF) {
+ v.trackIndex = 0;
+ } else if (trackCmd == 0xFE) {
+ stopMusic();
+ return;
+ }
+ }
+
+ ContinuousEffectEntry entry = postNoteEffectSetup(v, instA0, instA1);
+ if (entry == kFullEffectPath)
+ applyContinuousEffects(v, sidOffset, instA0, instA1, false);
+ else if (entry == kPortamentoOnlyPath)
+ applyContinuousEffects(v, sidOffset, instA0, instA1, true);
+ return;
+ }
}
// --- Note Application ---
// Corresponds to @plain_note (0x0A44) through L0AAD (0x0AB3)
-void DrillerSIDPlayer::applyNote(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1, int voiceIndex) {
- uint8_t note = v.currentNote; // Already stored at @plain_note (0x0A44)
- // v.delayCounter already set from v.noteDuration (0x0A47-0x0A4A)
- // v.whatever3/4 already reset to 0 (0x0A4D-0x0A52)
- // v.glideDownTimer already set to 2 (0x0A55-0x0A57)
+void DrillerSIDPlayer::applyNote(VoiceState &v, int sidOffset, const uint8_t *instA0) {
+ uint8_t note = v.currentNote;
- // Check legato bit instA0[7] & 0x02 (0x0A5D-0x0A6D)
if (instA0[7] & 0x02) {
- // Legato: restore PW values from FA command backup
- v.something_else[0] = v.something_else[1]; // something_else+1 -> something_else+0
- v.something_else[2] = v.ctrl0; // ctrl0 -> something_else+2
+ v.something_else[0] = v.something_else[1];
+ v.something_else[2] = v.ctrl0;
}
- // Handle note=0 (rest) at L0A70
if (note == 0) {
- // Load previous note from things+6 (0x0A75)
note = v.currentNoteSlideTarget;
- v.currentNote = note; // Update stuff+3 equivalent
- v.currentNoteSlideTarget = 0; // Clear things+6 (0x0A7B-0x0A7D)
-
- // dec control3 (0x0A83)
+ v.currentNote = note;
+ v.currentNoteSlideTarget = 0;
v.gateMask--;
-
- // bne L0AAD - since control3 was 0xFF, now 0xFE, always branches
- // Skip frequency write entirely for rests, jump to L0AAD
} else {
- // Non-zero note: store as previous note (L0A88)
- v.currentNoteSlideTarget = note; // things+6 = note
-
- // Set frequency from note (L0A8C-L0AA1)
- if (note >= 96) note = 95;
- SID_Write(sidOffset + 1, frq_hi[note]); // $D401
- SID_Write(sidOffset + 0, frq_lo[note]); // $D400
- // Store in stuff variables: stuff[0]=lo, stuff[1]=lo, stuff[2]=hi, stuff[4]=hi
+ v.currentNoteSlideTarget = note;
+ if (note >= 96)
+ note = 95;
+
uint8_t fLo = frq_lo[note];
uint8_t fHi = frq_hi[note];
- v.stuff_freq_porta_vib = fLo | (fHi << 8); // stuff[0]/[4]
- v.stuff_freq_base = fLo | (fHi << 8); // stuff[1]/[2]
- v.stuff_freq_hard_restart = fLo | (fHi << 8);
- v.currentFreq = v.stuff_freq_porta_vib;
-
- // Write initial waveform (gate-on transient): instA0[6] -> $D404 (L0AA7-L0AAA)
+ SID_Write(sidOffset + 1, fHi);
+ SID_Write(sidOffset + 0, fLo);
+ v.stuff_freq_porta_vib = fLo | (fHi << 8);
+ v.stuff_freq_base = fLo | (fHi << 8);
SID_Write(sidOffset + 4, instA0[6]);
}
- // L0AAD: Write control register with gate mask
- // lda instA0[1]; AND control3; sta $D404 (0x0AAD-0x0AB3)
SID_Write(sidOffset + 4, instA0[1] & v.gateMask);
-
- // Write ADSR from instrument (0x0AB6-0x0ABF)
- SID_Write(sidOffset + 5, instA0[2]); // Attack / Decay
- SID_Write(sidOffset + 6, instA0[3]); // Sustain / Release
-
- // Write Pulse Width from something_else (0x0AC2-0x0ACB)
- SID_Write(sidOffset + 2, v.something_else[0]); // PW Lo
- SID_Write(sidOffset + 3, v.something_else[2]); // PW Hi
-
+ SID_Write(sidOffset + 5, instA0[2]);
+ SID_Write(sidOffset + 6, instA0[3]);
+ SID_Write(sidOffset + 2, v.something_else[0]);
+ SID_Write(sidOffset + 3, v.something_else[2]);
v.pulseWidth = v.something_else[0] | (v.something_else[2] << 8);
}
-// --- Post-Note Effect Setup ---
-// Corresponds to L0AFC in the assembly. Runs after each note is applied.
-// Determines which continuous effect should be active based on instrument data.
-void DrillerSIDPlayer::postNoteEffectSetup(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1) {
- // L0AFC: lda voice1_things+6,x; beq L0B33
+DrillerSIDPlayer::ContinuousEffectEntry DrillerSIDPlayer::postNoteEffectSetup(VoiceState &v, const uint8_t *instA0, const uint8_t *instA1) {
if (v.currentNoteSlideTarget == 0)
- return; // No note stored, skip to continuous effects (PW LFO etc.)
+ return kFullEffectPath;
- // Check if portamento already active from FB/FC pattern command (L0B04)
- // lda voice1_whatever+2,x; bne L0B17
- if (v.whatever2 != 0) {
- // Already have porta from pattern command, go to porta processing
- return; // Porta will run in applyContinuousEffects
+ if (v.whatever2 != 0)
+ return kPortamentoOnlyPath;
+
+ if (instA1[4] != 0) {
+ v.whatever2 = instA1[4];
+ v.portaStepRaw = instA1[3];
+ return kPortamentoOnlyPath;
}
- int instBase = v.instrumentIndex;
- if (instBase < 0 || (size_t)instBase >= sizeof(instrumentDataA0))
- instBase = 0;
+ if (instA1[0] != 0) {
+ if (instA0[5] != 0) {
+ v.arpTableIndex = instA0[5] & 0x0F;
+ v.arpSpeedHiNibble = (instA0[5] & 0xF0) >> 4;
+ v.stuff_arp_counter = 0;
+ v.whatever1 = 1;
+ v.whatever0 = 0;
+ return kVoiceDone;
+ }
- // Check instA1[4] for instrument-level portamento (L0B09)
- // lda possibly_instrument_a1+4,y; beq L0B1A
- if (instA1[4] != 0) {
- v.whatever2 = instA1[4]; // Store as porta type (L0B0E)
- v.portaStepRaw = instA1[3]; // Store porta speed (L0B11-L0B14)
- v.portaSpeed = 0; // Force recalc
- return; // jmp L0C5A - porta will run in applyContinuousEffects
+ v.whatever1 = 0;
+ v.things_vib_depth = instA1[0];
+ v.things_vib_delay_reload = instA1[1];
+ v.things_vib_delay_ctr = instA1[1];
+ v.things_vib_state = 0;
+ v.whatever0 = 1;
+ return kVoiceDone;
}
- // Check instA0[5] for arpeggio (L0B1A -> L0E67)
- // lda possibly_instrument_a0+5,y; beq L0B22
if (instA0[5] != 0) {
- // L0E67: arpeggio setup
- uint8_t arpData = instA0[5];
- v.arpTableIndex = arpData & 0x0F; // and #$0F -> ctrl1
- v.arpSpeedHiNibble = (arpData & 0xF0) >> 4; // and #$F0; lsr*4 -> stuff+5
- v.stuff_arp_counter = 0; // sta stuff+6
- v.whatever1 = 1; // sta whatever+1
- v.whatever0 = 0; // sta whatever
- return; // jmp voice_done
+ v.arpTableIndex = instA0[5] & 0x0F;
+ v.arpSpeedHiNibble = (instA0[5] & 0xF0) >> 4;
+ v.stuff_arp_counter = 0;
+ v.whatever1 = 1;
+ v.whatever0 = 0;
+ return kVoiceDone;
}
- // L0B22: Clear arpeggio flag (A=0 from beq)
v.whatever1 = 0;
-
- // Check instA1[0] for vibrato (L0B25 -> L0E89)
- // lda possibly_instrument_a1,y; beq L0B2D
- if (instA1[0] != 0) {
- // L0E89: vibrato setup
- v.things_vib_depth = instA1[0]; // sta things+1
- v.things_vib_delay_reload = instA1[1]; // sta things+2
- v.things_vib_delay_ctr = v.things_vib_delay_reload; // sta things+3
- v.things_vib_state = 0; // sta things
- v.whatever1 = 0; // sta whatever+1
- v.whatever0 = 1; // sta whatever
- return; // jmp voice_done
- }
-
- // L0B2D: Clear vibrato flag (A=0 from beq)
v.whatever0 = 0;
- // jmp voice_done
+ return kVoiceDone;
}
-// --- Continuous Effect Application (Vibrato, Porta, Arp) ---
-void DrillerSIDPlayer::applyContinuousEffects(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1) {
- // Corresponds to logic starting around L0B33 / L0B82 / L0BC0 / L0C5A
-
- uint16_t freq = v.stuff_freq_porta_vib; // Start with base freq + porta/vib from previous step
- bool freqDirty = false; // Track if frequency needs writing
-
- // PW LFO (L0B33-L0B82) - instA0[4] = modulation speed (stored in control1)
- uint8_t lfoSpeed = instA0[4];
- if (lfoSpeed != 0) {
- // Operates on something_else[0] (lo byte) and something_else[2] (hi nibble)
- if (v.whatever2_vibDirToggle == 0) {
- // Add phase (L0B40-L0B5D): clc; adc control1 on lo; adc #0 on hi
- uint16_t sum = (uint16_t)v.something_else[0] + lfoSpeed;
- v.something_else[0] = sum & 0xFF;
- v.something_else[2] = v.something_else[2] + (sum >> 8);
- SID_Write(sidOffset + 2, v.something_else[0]); // $D402
- SID_Write(sidOffset + 3, v.something_else[2]); // $D403
- // clc; cmp #$0E - check hi byte >= 0x0E (L0B59)
- if (v.something_else[2] >= 0x0E) {
- v.whatever2_vibDirToggle = 1; // inc whatever2 (L0B5D)
+void DrillerSIDPlayer::applyContinuousEffects(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1, bool startAtPortamento) {
+ if (!startAtPortamento) {
+ uint8_t lfoSpeed = instA0[4];
+ if (lfoSpeed != 0) {
+ if (v.whatever2_vibDirToggle == 0) {
+ uint16_t sum = (uint16_t)v.something_else[0] + lfoSpeed;
+ v.something_else[0] = sum & 0xFF;
+ v.something_else[2] = (v.something_else[2] + (sum >> 8)) & 0xFF;
+ SID_Write(sidOffset + 2, v.something_else[0]);
+ SID_Write(sidOffset + 3, v.something_else[2]);
+ if (v.something_else[2] >= 0x0E)
+ v.whatever2_vibDirToggle++;
+ } else {
+ uint16_t diff = (uint16_t)v.something_else[0] - lfoSpeed;
+ uint8_t borrow = (diff > 0xFF) ? 1 : 0;
+ v.something_else[0] = diff & 0xFF;
+ v.something_else[2] = (v.something_else[2] - borrow) & 0xFF;
+ SID_Write(sidOffset + 2, v.something_else[0]);
+ SID_Write(sidOffset + 3, v.something_else[2]);
+ if (v.something_else[2] < 0x08)
+ v.whatever2_vibDirToggle--;
}
- } else {
- // Subtract phase (L0B62-L0B7F): sec; sbc control1 on lo; sbc #0 on hi
- uint16_t diff = (uint16_t)v.something_else[0] + 0x100 - lfoSpeed;
- v.something_else[0] = diff & 0xFF;
- if (diff < 0x100) // borrow
- v.something_else[2]--;
- SID_Write(sidOffset + 2, v.something_else[0]); // $D402
- SID_Write(sidOffset + 3, v.something_else[2]); // $D403
- // clc; cmp #$08 - check hi byte < 0x08 (L0B7B)
- if (v.something_else[2] < 0x08) {
- v.whatever2_vibDirToggle = 0; // dec whatever2 (L0B7F)
- }
- }
- v.pulseWidth = v.something_else[0] | (v.something_else[2] << 8);
- }
-
- // Arpeggio (L0B82) - Check 'whatever1' flag
- if (v.whatever1) {
- // Assembly: single counter (stuff+6) cycles 0..speed-1, used directly as arp table index
- // lda stuff+6; cmp stuff+5; bne L0BA5; lda #0; sta stuff+6
- uint8_t speed = v.arpSpeedHiNibble; // stuff+5: set from instA0[5] hi nibble
- if (v.stuff_arp_counter == speed) {
- v.stuff_arp_counter = 0; // Reset when counter == speed (L0BA0)
+ v.pulseWidth = v.something_else[0] | (v.something_else[2] << 8);
}
- // tay; lda stuff+3; clc; adc arpeggio_0,y (L0BA5-L0BAD)
- uint8_t baseNote = v.currentNote;
- if (baseNote > 0 && baseNote < 96 && v.stuff_arp_counter < 3) {
- uint8_t arpOffset = arpeggio_data[v.stuff_arp_counter]; // Counter IS the table index
- uint8_t arpNote = baseNote + arpOffset;
- if (arpNote >= 96)
- arpNote = 95;
-
- freq = frq_lo[arpNote] | (frq_hi[arpNote] << 8);
- SID_Write(sidOffset + 0, frq_lo[arpNote]); // sta $D400 (L0BB1)
- SID_Write(sidOffset + 1, frq_hi[arpNote]); // sta $D401 (L0BB7)
- v.currentFreq = freq;
- }
+ if (v.whatever1) {
+ if (v.stuff_arp_counter == v.arpSpeedHiNibble)
+ v.stuff_arp_counter = 0;
- v.stuff_arp_counter++; // inc stuff+6 (L0BBA)
- // jmp voice_done - arpeggio skips vibrato/porta
- return;
- }
-
- // Vibrato (L0BC0 / L0BC8) - Check 'whatever0' flag
- // Assembly applies frequency modification EVERY frame.
- // The timer (things+3) only controls when the direction state advances.
- if (v.whatever0) {
- int state = v.things_vib_state;
- uint8_t freqLo = v.stuff_freq_porta_vib & 0xFF;
- uint8_t freqHi = (v.stuff_freq_porta_vib >> 8) & 0xFF;
+ uint8_t arpNote = v.currentNote;
+ if (v.stuff_arp_counter < ARRAYSIZE(arpeggio_data)) {
+ uint16_t noteWithOffset = v.currentNote + arpeggio_data[v.stuff_arp_counter];
+ arpNote = noteWithOffset >= 96 ? 95 : noteWithOffset;
+ }
- // Apply depth based on state (L0C06, L0C2F, L0BD1)
- // States 0, 3, 4: subtract (down). States 1, 2: add (up).
- // State 0: L0C06 (beq). States 1,2: L0C2F (cmp #3; bcc). States 3,4: fall through.
- if (state == 1 || state == 2) {
- // Add (L0C2F): clc; lda stuff,x; adc things+1,x
- uint16_t sum = (uint16_t)freqLo + (v.things_vib_depth & 0xFF);
- freqLo = sum & 0xFF;
- freqHi = freqHi + (sum >> 8);
- } else {
- // Subtract (L0C06/L0BD1): sec; lda stuff,x; sbc things+1,x
- uint16_t diff = (uint16_t)freqLo + 0x100 - (v.things_vib_depth & 0xFF);
- freqLo = diff & 0xFF;
- if (diff < 0x100) // borrow occurred
- freqHi--;
+ SID_Write(sidOffset + 0, frq_lo[arpNote]);
+ SID_Write(sidOffset + 1, frq_hi[arpNote]);
+ v.stuff_arp_counter++;
+ return;
}
- v.stuff_freq_porta_vib = (uint16_t)freqLo | ((uint16_t)freqHi << 8);
- freq = v.stuff_freq_porta_vib;
- freqDirty = true;
-
- // Decrement timer, advance state only when expired (dec things+3; bne done)
- v.things_vib_delay_ctr--;
- if (v.things_vib_delay_ctr == 0) {
- v.things_vib_delay_ctr = v.things_vib_delay_reload;
- v.things_vib_state++;
- if (v.things_vib_state >= 5) { // cmp #$05; bcc
- v.things_vib_state = 1; // Reset to state 1 (0BFE)
+ if (v.whatever0) {
+ uint8_t freqLo = v.stuff_freq_porta_vib & 0xFF;
+ uint8_t freqHi = (v.stuff_freq_porta_vib >> 8) & 0xFF;
+
+ if (v.things_vib_state == 0 || v.things_vib_state >= 3) {
+ uint16_t diff = (uint16_t)freqLo - v.things_vib_depth;
+ uint8_t borrow = (diff > 0xFF) ? 1 : 0;
+ freqLo = diff & 0xFF;
+ freqHi = (freqHi - borrow) & 0xFF;
+ } else {
+ uint16_t sum = (uint16_t)freqLo + v.things_vib_depth;
+ freqLo = sum & 0xFF;
+ freqHi = (freqHi + (sum >> 8)) & 0xFF;
}
- }
- } // end if(v.whatever0)
-
- // Portamento (L0C5A) - Check 'whatever2' flag
- // 4 distinct types matching assembly:
- // Type 1 (L0C7B): CLC+SBC on lo byte (slide down, borrow to hi)
- // Type 2 (L0CA6): CLC+ADC on lo byte (slide up, carry to hi)
- // Type 3 (L0C96): SEC+SBC on hi byte only (fast slide down)
- // Type >= 4 (L0C6B): CLC+ADC on hi byte only (fast slide up)
- if (v.whatever2) {
- uint8_t freqLo = v.stuff_freq_porta_vib & 0xFF; // stuff[0]
- uint8_t freqHi = (v.stuff_freq_porta_vib >> 8) & 0xFF; // stuff[4]
- uint8_t speed = v.portaStepRaw & 0xFF; // voice1_something
-
- if (v.whatever2 == 1) {
- // Type 1 (L0C7B): clc; sbc = subtract (speed+1) from lo, borrow to hi
- uint16_t diff = (uint16_t)freqLo - speed; // CLC means borrow, so effectively -(speed+1)
- freqLo = (diff - 1) & 0xFF; // CLC+SBC = subtract with extra borrow
- if ((diff - 1) > 0xFF) freqHi--; // Propagate borrow
- SID_Write(sidOffset + 0, freqLo);
- SID_Write(sidOffset + 1, freqHi);
- } else if (v.whatever2 == 2) {
- // Type 2 (L0CA6): clc; adc on lo, carry to hi
- uint16_t sum = (uint16_t)freqLo + speed;
- freqLo = sum & 0xFF;
- freqHi = freqHi + (sum >> 8);
+
+ v.stuff_freq_porta_vib = freqLo | (freqHi << 8);
SID_Write(sidOffset + 0, freqLo);
SID_Write(sidOffset + 1, freqHi);
- } else if (v.whatever2 == 3) {
- // Type 3 (L0C96): sec; sbc on hi byte only (fast slide down)
- freqHi = freqHi - speed;
- SID_Write(sidOffset + 1, freqHi);
- } else {
- // Type >= 4 (L0C6B): clc; adc on hi byte only (fast slide up)
- freqHi = freqHi + speed;
- SID_Write(sidOffset + 1, freqHi);
+ v.things_vib_delay_ctr--;
+ if (v.things_vib_delay_ctr == 0) {
+ v.things_vib_delay_ctr = v.things_vib_delay_reload;
+ v.things_vib_state++;
+ if (v.things_vib_state >= 5)
+ v.things_vib_state = 1;
+ }
+ return;
}
+ }
- v.stuff_freq_porta_vib = (uint16_t)freqLo | ((uint16_t)freqHi << 8);
- v.currentFreq = v.stuff_freq_porta_vib;
+ if (v.whatever2 == 1) {
+ uint8_t freqLo = v.stuff_freq_porta_vib & 0xFF;
+ uint8_t freqHi = (v.stuff_freq_porta_vib >> 8) & 0xFF;
+ uint16_t diff = (uint16_t)freqLo - v.portaStepRaw - 1;
+ uint8_t borrow = (diff > 0xFF) ? 1 : 0;
+ freqLo = diff & 0xFF;
+ freqHi = (freqHi - borrow) & 0xFF;
+ v.stuff_freq_porta_vib = freqLo | (freqHi << 8);
+ SID_Write(sidOffset + 0, freqLo);
+ SID_Write(sidOffset + 1, freqHi);
+ } else if (v.whatever2 == 2) {
+ uint8_t freqLo = v.stuff_freq_porta_vib & 0xFF;
+ uint8_t freqHi = (v.stuff_freq_porta_vib >> 8) & 0xFF;
+ uint16_t sum = (uint16_t)freqLo + v.portaStepRaw;
+ freqLo = sum & 0xFF;
+ freqHi = (freqHi + (sum >> 8)) & 0xFF;
+ v.stuff_freq_porta_vib = freqLo | (freqHi << 8);
+ SID_Write(sidOffset + 0, freqLo);
+ SID_Write(sidOffset + 1, freqHi);
+ } else if (v.whatever2 == 3) {
+ uint8_t freqLo = v.stuff_freq_porta_vib & 0xFF;
+ uint8_t freqHi = ((v.stuff_freq_porta_vib >> 8) - v.portaStepRaw) & 0xFF;
+ v.stuff_freq_porta_vib = freqLo | (freqHi << 8);
+ SID_Write(sidOffset + 1, freqHi);
+ } else if (v.whatever2 != 0) {
+ uint8_t freqLo = v.stuff_freq_porta_vib & 0xFF;
+ uint8_t freqHi = ((v.stuff_freq_porta_vib >> 8) + v.portaStepRaw) & 0xFF;
+ v.stuff_freq_porta_vib = freqLo | (freqHi << 8);
+ SID_Write(sidOffset + 1, freqHi);
}
- // After porta, check for hard restart (L0CBE)
- // lda instA0[7]; and #$01; beq voice_done; jmp L1005
- if (instA0[7] & 0x01) {
- applyHardRestart(v, sidOffset, instA0, instA1);
+ if (instA0[7] & 0x01)
+ applyHardRestart(v, sidOffset, instA1);
+}
+
+void DrillerSIDPlayer::applyHardRestart(VoiceState &v, int sidOffset, const uint8_t *instA1) {
+ uint8_t storedHi = (v.stuff_freq_base >> 8) & 0xFF;
+ if (storedHi != 0) {
+ storedHi--;
+ v.stuff_freq_base = (v.stuff_freq_base & 0x00FF) | (storedHi << 8);
}
- // Write final frequency if modified by vibrato (arp and porta write directly)
- if (freqDirty && v.currentFreq != freq) {
- v.currentFreq = freq;
- SID_Write(sidOffset + 0, freq & 0xFF);
- SID_Write(sidOffset + 1, (freq >> 8) & 0xFF);
+ if (v.whatever3 != 0) {
+ v.whatever3--;
+ SID_Write(sidOffset + 4, 0x81);
+ SID_Write(sidOffset + 1, storedHi ^ 0x23);
+ return;
}
-}
-// --- Hard Restart / Buzz Effect ---
-void DrillerSIDPlayer::applyHardRestart(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1) {
- // Corresponds to L1005 onwards
- debug(DEBUG_LEVEL >= 2, "Driller 1: Applying Hard Restart (Delay=%d, Ctr=%d, Val=%d)", v.hardRestartDelay, v.hardRestartCounter, v.hardRestartValue);
-
- // Check delay phase (L100D)
- if (v.hardRestartDelay > 0) {
- v.hardRestartDelay--;
- // Set high bit of waveform? (L1015)
- SID_Write(sidOffset + 4, 0x81); // Force waveform to noise? Or just toggle sync/ring? Or maybe $80 = Noise, $01 = Gate On
- // Modify frequency slightly (L101A)
- uint16_t freq = v.stuff_freq_hard_restart; // Use stored base freq
- // freq ^= 0x2300; // EOR with #$23 on high byte? (L101D) - Check calculation
- uint8_t hiByte = (freq >> 8) ^ 0x23; // EOR high byte only
- SID_Write(sidOffset + 1, hiByte); // Write modified high byte
- // Keep low byte as is? Yes, original only writes high byte $D401.
- // Keep current frequency updated? No, use stored base.
- // v.currentFreq = (hiByte << 8) | (freq & 0xFF); // Update internal state if needed
+ uint8_t currentHi = (v.stuff_freq_porta_vib >> 8) & 0xFF;
+ if (v.whatever4 != instA1[5]) {
+ v.whatever3++;
+ v.whatever4++;
} else {
- // Delay phase over, check frequency change phase (L103A)
- if (v.hardRestartCounter < v.hardRestartValue) { // Compare with value from inst A1[5] (L103D)
- v.hardRestartCounter++; // Increment counter (L1045)
- v.hardRestartDelay++; // Also increment delay? Seems odd (L1042) - Maybe reloads delay? Yes, seems to reload.
- // Reset frequency and waveform (L1048 -> L1028)
- uint16_t freq = v.stuff_freq_hard_restart;
- SID_Write(sidOffset + 1, (freq >> 8) & 0xFF); // Restore high byte (L1028)
- SID_Write(sidOffset + 0, freq & 0xFF); // Restore low byte (L102B implies sta $D401,x AND sta $D400,x ?) No, only $D401. Assume low byte restored too.
- v.currentFreq = freq; // Update internal state
-
- // Restore waveform from instrument? (L1031) - Uses instA1[2]? Needs Gate bit.
- uint8_t ctrl = instA1[2];
- if (v.keyOn)
- ctrl |= 0x01;
- else
- ctrl &= 0xFE; // Add gate state
- SID_Write(sidOffset + 4, ctrl);
- } else {
- // Effect finished (L104A)
- debug(DEBUG_LEVEL >= 2, "Driller 1: Hard Restart Finished");
- v.hardRestartActive = false;
- v.hardRestartCounter = 0; // Reset counters
- v.hardRestartDelay = 0;
- // Restore frequency and waveform (L104A -> L1052 -> L1028)
- uint16_t freq = v.stuff_freq_hard_restart;
- SID_Write(sidOffset + 1, (freq >> 8) & 0xFF);
- SID_Write(sidOffset + 0, freq & 0xFF);
- v.currentFreq = freq; // Update internal state
-
- uint8_t ctrl = instA1[2]; // Restore waveform from instA1[2]? Needs Gate bit.
- if (v.keyOn)
- ctrl |= 0x01;
- else
- ctrl &= 0xFE; // Add gate state
- SID_Write(sidOffset + 4, ctrl);
- }
+ v.whatever4 = 0;
+ v.whatever3 = 0;
}
+
+ SID_Write(sidOffset + 1, currentHi);
+ v.stuff_freq_base = (v.stuff_freq_base & 0x00FF) | (currentHi << 8);
+ SID_Write(sidOffset + 4, instA1[2]);
}
} // namespace Freescape
diff --git a/engines/freescape/games/driller/c64.music.h b/engines/freescape/games/driller/c64.music.h
index 4cdcc587f5d..f36fc86491f 100644
--- a/engines/freescape/games/driller/c64.music.h
+++ b/engines/freescape/games/driller/c64.music.h
@@ -203,6 +203,11 @@ class DrillerSIDPlayer {
enum PlayState { STOPPED,
PLAYING,
CHANGING_TUNE };
+ enum ContinuousEffectEntry {
+ kVoiceDone,
+ kFullEffectPath,
+ kPortamentoOnlyPath
+ };
PlayState _playState;
uint8_t _targetTuneIndex; // Tune index requested via startMusic
@@ -223,16 +228,16 @@ public:
void initSID();
void destroySID();
-private:
+ private:
void SID_Write(int reg, uint8_t data);
void onTimer();
void handleChangeTune(int tuneIndex);
void handleResetVoices();
void playVoice(int voiceIndex);
- void applyNote(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1, int voiceIndex);
- void postNoteEffectSetup(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1);
- void applyContinuousEffects(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1);
- void applyHardRestart(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1);
-};
+ void applyNote(VoiceState &v, int sidOffset, const uint8_t *instA0);
+ ContinuousEffectEntry postNoteEffectSetup(VoiceState &v, const uint8_t *instA0, const uint8_t *instA1);
+ void applyContinuousEffects(VoiceState &v, int sidOffset, const uint8_t *instA0, const uint8_t *instA1, bool startAtPortamento);
+ void applyHardRestart(VoiceState &v, int sidOffset, const uint8_t *instA1);
+ };
} // namespace Freescape
Commit: d9f552b45129a3afb87e8be9edd589945b842c1e
https://github.com/scummvm/scummvm/commit/d9f552b45129a3afb87e8be9edd589945b842c1e
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: improved dark music for C64
Changed paths:
engines/freescape/games/dark/c64.music.cpp
engines/freescape/games/dark/c64.music.h
diff --git a/engines/freescape/games/dark/c64.music.cpp b/engines/freescape/games/dark/c64.music.cpp
index bc0a39a2e52..1943217b897 100644
--- a/engines/freescape/games/dark/c64.music.cpp
+++ b/engines/freescape/games/dark/c64.music.cpp
@@ -53,7 +53,7 @@ static const uint8 kFreqLo[96] = {
};
// Instrument table at $1010 (18 instruments x 8 bytes)
-// Bytes: ctrl, AD, SR, envCtl, vibrato, pwMod, autoFx, flags
+// Bytes: ctrl, AD, SR, initPW, vib/env mode, pwMod, autoFx, flags
// Instruments 16-17 are stored between $1090-$109F (past the nominal 16-entry table)
static const uint8 kInstruments[18 * 8] = {
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0: Pulse (silence)
@@ -76,8 +76,8 @@ static const uint8 kInstruments[18 * 8] = {
0x81, 0x0B, 0x20, 0x66, 0x00, 0x00, 0x00, 0x01, // 17: Noise+G (percussion, envelope seq)
};
-// Envelope volume tables ($154B and $156B, 16 entries each)
-static const uint8 kEnvVolume[2][16] = {
+// Auxiliary envelope data tables ($154B and $156B, 16 entries each)
+static const uint8 kEnvData[2][16] = {
{ 0xFA, 0x01, 0xFF, 0x20, 0x0A, 0x12, 0x04, 0x16, 0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x00 },
{ 0x10, 0x0A, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 },
};
@@ -183,7 +183,6 @@ void DarkSideC64MusicPlayer::ChannelState::reset() {
patData = nullptr;
patOffset = 0;
instIdx = 0;
- noteActive = 0;
curNote = 0;
transpose = 0;
freqLo = 0;
@@ -199,24 +198,27 @@ void DarkSideC64MusicPlayer::ChannelState::reset() {
arpSeqPos = 0;
arpSeqLen = 0;
memset(arpSeqData, 0, sizeof(arpSeqData));
- portaDelta = 0;
- portaTarget = 0;
+ noteStepCommand = 0;
+ stepDownCounter = 0;
vibPhase = 0;
vibCounter = 0;
pwDirection = 0;
+ delayValue = 0;
delayCounter = 0;
envCounter = 0;
- envTable = 0;
- envSeqActive = false;
- sustainMode = false;
+ gateOffDisabled = false;
+ gateModeControl = false;
+ specialAttack = false;
+ attackDone = false;
waveform = 0;
+ instFlags = 0;
}
DarkSideC64MusicPlayer::DarkSideC64MusicPlayer() {
_sid = nullptr;
_musicActive = false;
_speedDiv = 1;
- _speedCounter = 1;
+ _speedCounter = 0;
for (int i = 0; i < 3; i++)
_ch[i].reset();
initSID();
@@ -274,12 +276,12 @@ void DarkSideC64MusicPlayer::stopMusic() {
void DarkSideC64MusicPlayer::setupSong() {
silenceAll();
- // Set filter: LP+HP on, full volume ($5F)
+ // Init routine at $0FF6.
sidWrite(0x18, 0x5F);
sidWrite(0x17, 0x00);
_speedDiv = 1;
- _speedCounter = 1;
+ _speedCounter = 0;
for (int ch = 0; ch < 3; ch++) {
_ch[ch].reset();
@@ -324,98 +326,145 @@ uint8 DarkSideC64MusicPlayer::readPatByte(int ch) {
return b;
}
+void DarkSideC64MusicPlayer::buildEffectArpeggio(int ch) {
+ uint8 bits = _ch[ch].effectParam;
+ uint8 len = 1;
+
+ _ch[ch].arpSeqData[0] = 0;
+ for (int i = 0; i < 8 && len < sizeof(_ch[ch].arpSeqData); i++) {
+ if (bits & (1 << i))
+ _ch[ch].arpSeqData[len++] = kArpIntervals[i];
+ }
+
+ _ch[ch].arpSeqLen = len;
+ _ch[ch].arpSeqPos = 0;
+ _ch[ch].arpPattern = 0;
+}
+
+void DarkSideC64MusicPlayer::loadCurrentFrequency(int ch) {
+ int off = kSIDOffset[ch];
+ uint8 note = (_ch[ch].curNote > 94) ? 94 : _ch[ch].curNote;
+
+ _ch[ch].freqLo = kFreqLo[note];
+ _ch[ch].freqHi = kFreqHi[note];
+ sidWrite(off + 0, _ch[ch].freqLo);
+ sidWrite(off + 1, _ch[ch].freqHi);
+}
+
+void DarkSideC64MusicPlayer::finalizeChannel(int ch) {
+ int off = kSIDOffset[ch];
+
+ if (_ch[ch].durReload != 0 && !_ch[ch].gateOffDisabled) {
+ if ((_ch[ch].durReload >> 1) == _ch[ch].durCounter)
+ sidWrite(off + 4, _ch[ch].waveform & 0xFE);
+ }
+
+ applyPWModulation(ch);
+ sidWrite(off + 2, _ch[ch].pwLo);
+ sidWrite(off + 3, _ch[ch].pwHi & 0x0F);
+}
+
// ---- Main timer callback (50 Hz) ----
void DarkSideC64MusicPlayer::onTimer() {
if (!_musicActive)
return;
- // Increment envelope counters for all channels
- for (int ch = 0; ch < 3; ch++) {
- if (_ch[ch].envCounter < 15)
- _ch[ch].envCounter++;
- }
+ for (int ch = 0; ch < 3; ch++)
+ _ch[ch].envCounter++;
- // Speed counter
- _speedCounter--;
bool newBeat = (_speedCounter == 0);
- if (newBeat)
- _speedCounter = _speedDiv;
- // Process channels (2 down to 0, matching original)
for (int ch = 2; ch >= 0; ch--)
processChannel(ch, newBeat);
-}
-void DarkSideC64MusicPlayer::processChannel(int ch, bool newBeat) {
- int off = kSIDOffset[ch];
+ if (!_musicActive)
+ return;
- // Handle delay countdown
- if (_ch[ch].delayCounter > 0) {
- _ch[ch].delayCounter--;
- if (_ch[ch].delayCounter == 0 && _ch[ch].noteActive) {
- // Delay expired: gate on now
- sidWrite(off + 4, _ch[ch].waveform | 0x01);
- }
- }
+ if (newBeat)
+ _speedCounter = _speedDiv;
+ else
+ _speedCounter--;
+}
+void DarkSideC64MusicPlayer::processChannel(int ch, bool newBeat) {
if (newBeat) {
- if (_ch[ch].durCounter > 0)
- _ch[ch].durCounter--;
-
- if (_ch[ch].durCounter == 0) {
+ _ch[ch].durCounter--;
+ if (_ch[ch].durCounter == 0xFF) {
parseCommands(ch);
+ if (!_musicActive)
+ return;
+ finalizeChannel(ch);
+ return;
}
- }
- // Apply continuous effects
- applyContinuousEffects(ch);
-
- // Gate-off at half duration (if not in sustain mode and not in envelope seq mode)
- if (!_ch[ch].sustainMode && !_ch[ch].envSeqActive && _ch[ch].noteActive) {
- if (_ch[ch].durReload > 1 && _ch[ch].durCounter == _ch[ch].durReload / 2) {
- sidWrite(off + 4, _ch[ch].waveform & 0xFE);
+ if (_ch[ch].noteStepCommand != 0) {
+ if (_ch[ch].noteStepCommand == 0xDE) {
+ if (_ch[ch].curNote > 0)
+ _ch[ch].curNote--;
+ } else if (_ch[ch].curNote < 94) {
+ _ch[ch].curNote++;
+ }
+ loadCurrentFrequency(ch);
+ finalizeChannel(ch);
+ return;
}
+ } else if (_ch[ch].stepDownCounter != 0) {
+ _ch[ch].stepDownCounter--;
+ if (_ch[ch].curNote > 0)
+ _ch[ch].curNote--;
+ loadCurrentFrequency(ch);
+ finalizeChannel(ch);
+ return;
}
+
+ applyFrameEffects(ch);
+ finalizeChannel(ch);
}
// ---- Command parser ----
void DarkSideC64MusicPlayer::parseCommands(int ch) {
+ if (_ch[ch].effectMode != 2) {
+ _ch[ch].effectParam = 0;
+ _ch[ch].effectMode = 0;
+ _ch[ch].arpSeqLen = 0;
+ _ch[ch].arpSeqPos = 0;
+ }
+ _ch[ch].arpPattern = 0;
+ _ch[ch].noteStepCommand = 0;
+
int safety = 200;
while (safety-- > 0) {
uint8 cmd = readPatByte(ch);
if (cmd == 0xFF) {
- // End of pattern: load next from order list
loadNextPattern(ch);
continue;
}
if (cmd == 0xFE) {
- // End of song: stop
stopMusic();
return;
}
if (cmd == 0xFD) {
- // Filter control: read 1 parameter byte
uint8 filterVal = readPatByte(ch);
sidWrite(0x18, filterVal);
sidWrite(0x17, (filterVal >> 4) | 0x07);
- continue;
+ cmd = readPatByte(ch);
+ if (cmd == 0xFF) {
+ loadNextPattern(ch);
+ continue;
+ }
}
if (cmd >= 0xF0) {
- // Speed: low nybble
_speedDiv = cmd & 0x0F;
- if (_speedDiv == 0)
- _speedDiv = 1;
continue;
}
if (cmd >= 0xC0) {
- // Instrument: (cmd & 0x1F) * 8
uint8 instNum = cmd & 0x1F;
if (instNum < 18)
_ch[ch].instIdx = instNum * 8;
@@ -423,66 +472,51 @@ void DarkSideC64MusicPlayer::parseCommands(int ch) {
}
if (cmd >= 0x80) {
- // Duration: cmd & 0x3F
_ch[ch].durReload = cmd & 0x3F;
- if (_ch[ch].durReload == 0)
- _ch[ch].durReload = 1;
continue;
}
if (cmd == 0x7F) {
- // Portamento up
+ _ch[ch].noteStepCommand = 0xDE;
_ch[ch].effectMode = 0xDE;
continue;
}
if (cmd == 0x7E) {
- // Portamento down
+ _ch[ch].noteStepCommand = 0xFE;
_ch[ch].effectMode = 0xFE;
continue;
}
if (cmd == 0x7D) {
- // Vibrato mode 1
_ch[ch].effectMode = 1;
_ch[ch].effectParam = readPatByte(ch);
- _ch[ch].arpPattern = 0;
- _ch[ch].vibPhase = 0;
- _ch[ch].vibCounter = 0;
+ buildEffectArpeggio(ch);
continue;
}
if (cmd == 0x7C) {
- // Vibrato mode 2
_ch[ch].effectMode = 2;
_ch[ch].effectParam = readPatByte(ch);
- _ch[ch].vibPhase = 0;
- _ch[ch].vibCounter = 0;
+ buildEffectArpeggio(ch);
continue;
}
if (cmd == 0x7B) {
- // Arpeggio: read 2 bytes
- uint8 arpX = readPatByte(ch);
- uint8 arpY = readPatByte(ch);
- _ch[ch].effectMode = 1;
_ch[ch].effectParam = 0;
- _ch[ch].arpPattern = (arpX + _ch[ch].transpose) & 0xFF;
- _ch[ch].arpParam2 = arpY;
- _ch[ch].arpSeqPos = 0;
- unpackArpeggio(ch);
+ _ch[ch].effectMode = 1;
+ _ch[ch].arpPattern = (readPatByte(ch) + _ch[ch].transpose) & 0xFF;
+ _ch[ch].arpParam2 = readPatByte(ch);
continue;
}
if (cmd == 0x7A) {
- // Delay: read 1 byte (delay value)
- _ch[ch].delayCounter = readPatByte(ch);
- continue;
+ _ch[ch].delayValue = readPatByte(ch);
+ cmd = readPatByte(ch);
}
- // Note (0x00-0x5F)
applyNote(ch, cmd);
- break;
+ return;
}
}
@@ -491,226 +525,220 @@ void DarkSideC64MusicPlayer::parseCommands(int ch) {
void DarkSideC64MusicPlayer::applyNote(int ch, uint8 note) {
int off = kSIDOffset[ch];
uint8 instBase = _ch[ch].instIdx;
-
- if (note == 0) {
- // Rest: gate off
- _ch[ch].noteActive = 0;
- sidWrite(off + 4, _ch[ch].waveform & 0xFE);
- _ch[ch].durCounter = _ch[ch].durReload;
- return;
- }
-
- // Compute actual note index with transpose
- uint8 actualNote = (note + _ch[ch].transpose) & 0xFF;
- if (actualNote > 94)
- actualNote = 94;
-
- // Previous frequency (for portamento)
- uint16 prevFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
-
- // Look up new frequency
- _ch[ch].freqLo = kFreqLo[actualNote];
- _ch[ch].freqHi = kFreqHi[actualNote];
- _ch[ch].curNote = actualNote;
- _ch[ch].noteActive = 1;
-
- // Load instrument parameters
uint8 ctrl = kInstruments[instBase + 0];
uint8 ad = kInstruments[instBase + 1];
uint8 sr = kInstruments[instBase + 2];
- uint8 vibrato = kInstruments[instBase + 4];
+ uint8 initialPW = kInstruments[instBase + 3];
uint8 autoFx = kInstruments[instBase + 6];
uint8 flags = kInstruments[instBase + 7];
+ uint8 actualNote = note;
- _ch[ch].waveform = ctrl;
- _ch[ch].sustainMode = (flags & 0x08) != 0;
- _ch[ch].envSeqActive = (flags & 0x01) != 0;
- _ch[ch].envTable = (vibrato >> 4) & 0x01;
-
- // Write ADSR
- sidWrite(off + 5, ad);
- sidWrite(off + 6, sr);
-
- // Write PW
- sidWrite(off + 2, _ch[ch].pwLo);
- sidWrite(off + 3, _ch[ch].pwHi & 0x0F);
-
- // Write frequency
- sidWrite(off + 0, _ch[ch].freqLo);
- sidWrite(off + 1, _ch[ch].freqHi);
-
- // Setup portamento if active
- if (_ch[ch].effectMode == 0xDE || _ch[ch].effectMode == 0xFE) {
- uint16 newFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
- _ch[ch].portaTarget = newFreq;
- // Slide from previous frequency
- _ch[ch].freqLo = prevFreq & 0xFF;
- _ch[ch].freqHi = (prevFreq >> 8) & 0xFF;
- if (_ch[ch].durReload > 0) {
- _ch[ch].portaDelta = (int16)(newFreq - prevFreq) / (int16)_ch[ch].durReload;
- }
- sidWrite(off + 0, _ch[ch].freqLo);
- sidWrite(off + 1, _ch[ch].freqHi);
- }
+ if (actualNote != 0)
+ actualNote = (actualNote + _ch[ch].transpose) & 0xFF;
+ if (actualNote > 94)
+ actualNote = 94;
- // Setup auto-effect from instrument
- if (autoFx != 0 && _ch[ch].effectMode == 0) {
- _ch[ch].effectMode = 1;
+ _ch[ch].curNote = actualNote;
+ _ch[ch].waveform = ctrl;
+ _ch[ch].instFlags = flags;
+ _ch[ch].attackDone = false;
+ _ch[ch].envCounter = 0xFF;
+ _ch[ch].gateModeControl = (flags & 0x80) != 0;
+ _ch[ch].specialAttack = (flags & 0x08) != 0;
+ _ch[ch].stepDownCounter = 0;
+
+ if (actualNote != 0 && _ch[ch].effectParam == 0 && autoFx != 0) {
_ch[ch].effectParam = autoFx;
+ buildEffectArpeggio(ch);
}
- // Gate on (unless delay is active)
- if (_ch[ch].delayCounter > 0) {
- // Delay: don't gate on yet, gate off first
- sidWrite(off + 4, ctrl & 0xFE);
- } else if (_ch[ch].envSeqActive) {
- // Envelope sequencer controls the gate
- _ch[ch].envCounter = 0;
- sidWrite(off + 4, kEnvControl[_ch[ch].envTable][0]);
- } else {
- sidWrite(off + 4, ctrl | 0x01);
+ if (actualNote != 0 && (flags & 0x02)) {
+ _ch[ch].stepDownCounter = 2;
+ _ch[ch].curNote = (_ch[ch].curNote > 92) ? 94 : _ch[ch].curNote + 2;
}
- // Reset envelope counter
- _ch[ch].envCounter = 0;
+ loadCurrentFrequency(ch);
- // Set duration
- _ch[ch].durCounter = _ch[ch].durReload;
-}
-
-// ---- Continuous effects ----
-
-void DarkSideC64MusicPlayer::applyContinuousEffects(int ch) {
- // PW modulation
- applyPWModulation(ch);
-
- // Frequency effects (mutually exclusive based on effectMode)
- switch (_ch[ch].effectMode) {
- case 1:
- if (_ch[ch].arpPattern != 0)
- applyArpeggio(ch);
- else
- applyVibrato(ch);
- break;
- case 2:
- applyVibrato(ch);
- break;
- case 0xDE:
- case 0xFE:
- applyPortamento(ch);
- break;
- default:
- break;
+ if (!_ch[ch].gateModeControl) {
+ _ch[ch].pwLo = initialPW & 0xF0;
+ _ch[ch].pwHi = initialPW & 0x0F;
+ sidWrite(off + 2, _ch[ch].pwLo);
+ sidWrite(off + 3, _ch[ch].pwHi & 0x0F);
}
- // Envelope sequencer
- if (_ch[ch].envSeqActive)
- applyEnvelope(ch);
-
- // Write frequency to SID
- int off = kSIDOffset[ch];
- sidWrite(off + 0, _ch[ch].freqLo);
- sidWrite(off + 1, _ch[ch].freqHi);
-
- // Write PW to SID
- sidWrite(off + 2, _ch[ch].pwLo);
- sidWrite(off + 3, _ch[ch].pwHi & 0x0F);
+ sidWrite(off + 5, ad);
+ sidWrite(off + 6, sr);
+ _ch[ch].gateOffDisabled = (sr & 0x0F) == 0x0F;
+ sidWrite(off + 4, 0);
+ sidWrite(off + 4, ctrl);
+ _ch[ch].durCounter = _ch[ch].durReload;
+ _ch[ch].delayCounter = _ch[ch].delayValue;
+ _ch[ch].arpSeqPos = 0;
}
-void DarkSideC64MusicPlayer::applyVibrato(int ch) {
- if (!_ch[ch].noteActive)
+void DarkSideC64MusicPlayer::applyFrameEffects(int ch) {
+ if (_ch[ch].curNote == 0)
return;
- uint8 vibParam = _ch[ch].effectParam;
- if (vibParam == 0)
+ if (applySpecialAttack(ch))
return;
- uint8 depth = vibParam >> 4;
- uint8 speed = vibParam & 0x0F;
- if (depth == 0 || speed == 0)
+ if (applyEnvelopeSequence(ch))
return;
- uint8 note = _ch[ch].curNote;
- if (note == 0 || note >= 94)
+ if (applyInstrumentVibrato(ch))
return;
- // Compute frequency delta between this note and the next semitone
- uint16 curFreq = ((uint16)kFreqHi[note] << 8) | kFreqLo[note];
- uint16 nextFreq = ((uint16)kFreqHi[note + 1] << 8) | kFreqLo[note + 1];
- int16 semitoneDelta = (int16)(nextFreq - curFreq);
+ applyEffectArpeggio(ch);
+ applyTimedSlide(ch);
+}
- // Scale by depth
- int16 vibDelta = semitoneDelta * depth / 16;
+bool DarkSideC64MusicPlayer::applySpecialAttack(int ch) {
+ if (!_ch[ch].specialAttack || _ch[ch].attackDone)
+ return false;
- // Oscillate: counter counts up to speed, then phase flips
- _ch[ch].vibCounter++;
- if (_ch[ch].vibCounter >= speed) {
- _ch[ch].vibCounter = 0;
- _ch[ch].vibPhase ^= 1;
+ int off = kSIDOffset[ch];
+ if (_ch[ch].envCounter < 1) {
+ sidWrite(off + 0, 0x00);
+ sidWrite(off + 1, 0x48);
+ sidWrite(off + 4, 0x81);
+ } else {
+ loadCurrentFrequency(ch);
+ _ch[ch].attackDone = true;
+ sidWrite(off + 4, _ch[ch].waveform);
}
+ return true;
+}
- // Apply offset
- int16 offset = _ch[ch].vibPhase ? vibDelta : -vibDelta;
- int16 baseFreq = (int16)curFreq;
- uint16 newFreq = (uint16)(baseFreq + offset);
+bool DarkSideC64MusicPlayer::applyEnvelopeSequence(int ch) {
+ if ((_ch[ch].instFlags & 0x01) == 0 || _ch[ch].envCounter >= 15)
+ return false;
- _ch[ch].freqLo = newFreq & 0xFF;
- _ch[ch].freqHi = (newFreq >> 8) & 0xFF;
+ int off = kSIDOffset[ch];
+ uint8 instBase = _ch[ch].instIdx;
+ uint8 envMode = kInstruments[instBase + 4];
+ uint8 table = (envMode & 0x0F) ? 1 : 0;
+ uint8 data = kEnvData[table][_ch[ch].envCounter];
+
+ sidWrite(off + 4, kEnvControl[table][_ch[ch].envCounter]);
+ if (envMode & 0x10) {
+ uint8 note = (_ch[ch].curNote + data > 94) ? 94 : _ch[ch].curNote + data;
+ sidWrite(off + 0, kFreqLo[note]);
+ sidWrite(off + 1, kFreqHi[note]);
+ } else {
+ sidWrite(off + 0, 0x00);
+ sidWrite(off + 1, data + 0x0D);
+ }
+ return true;
}
-void DarkSideC64MusicPlayer::applyArpeggio(int ch) {
- if (!_ch[ch].noteActive || _ch[ch].arpSeqLen == 0)
+bool DarkSideC64MusicPlayer::applyInstrumentVibrato(int ch) {
+ uint8 instBase = _ch[ch].instIdx;
+ uint8 vib = kInstruments[instBase + 4];
+ if (vib == 0 || _ch[ch].curNote >= 94)
+ return false;
+
+ uint8 shift = vib & 0x0F;
+ uint8 span = vib >> 4;
+ if (span == 0)
+ return false;
+
+ uint16 noteFreq = ((uint16)kFreqHi[_ch[ch].curNote] << 8) | kFreqLo[_ch[ch].curNote];
+ uint16 nextFreq = ((uint16)kFreqHi[_ch[ch].curNote + 1] << 8) | kFreqLo[_ch[ch].curNote + 1];
+ uint16 delta = nextFreq - noteFreq;
+
+ while (shift-- != 0)
+ delta >>= 1;
+
+ if (_ch[ch].vibPhase & 0x80) {
+ if (_ch[ch].vibCounter != 0)
+ _ch[ch].vibCounter--;
+ if (_ch[ch].vibCounter == 0)
+ _ch[ch].vibPhase = 0;
+ } else {
+ _ch[ch].vibCounter++;
+ if (_ch[ch].vibCounter >= span)
+ _ch[ch].vibPhase = 0xFF;
+ }
+
+ if (_ch[ch].delayCounter != 0) {
+ _ch[ch].delayCounter--;
+ return false;
+ }
+
+ int32 freq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
+ for (uint8 i = 0; i < (span >> 1); i++)
+ freq -= delta;
+ for (uint8 i = 0; i < _ch[ch].vibCounter; i++)
+ freq += delta;
+
+ if (freq < 0)
+ freq = 0;
+ if (freq > 0xFFFF)
+ freq = 0xFFFF;
+
+ int off = kSIDOffset[ch];
+ sidWrite(off + 0, freq & 0xFF);
+ sidWrite(off + 1, (freq >> 8) & 0xFF);
+ return true;
+}
+
+void DarkSideC64MusicPlayer::applyEffectArpeggio(int ch) {
+ if (_ch[ch].effectParam == 0 || _ch[ch].arpSeqLen == 0)
return;
- // Cycle through unpacked arpeggio intervals
- uint8 interval = _ch[ch].arpSeqData[_ch[ch].arpSeqPos];
- _ch[ch].arpSeqPos++;
if (_ch[ch].arpSeqPos >= _ch[ch].arpSeqLen)
_ch[ch].arpSeqPos = 0;
- // Compute arpeggiated note
- uint8 arpNote = _ch[ch].curNote + interval;
- if (arpNote > 94)
- arpNote = 94;
+ uint8 note = (_ch[ch].curNote + _ch[ch].arpSeqData[_ch[ch].arpSeqPos] > 94) ? 94 : _ch[ch].curNote + _ch[ch].arpSeqData[_ch[ch].arpSeqPos];
+ _ch[ch].arpSeqPos++;
- _ch[ch].freqLo = kFreqLo[arpNote];
- _ch[ch].freqHi = kFreqHi[arpNote];
+ int off = kSIDOffset[ch];
+ sidWrite(off + 0, kFreqLo[note]);
+ sidWrite(off + 1, kFreqHi[note]);
}
-void DarkSideC64MusicPlayer::unpackArpeggio(int ch) {
- uint8 bits = _ch[ch].arpPattern;
+void DarkSideC64MusicPlayer::applyTimedSlide(int ch) {
+ if (_ch[ch].arpPattern == 0)
+ return;
- // First entry: base note (interval 0)
- _ch[ch].arpSeqData[0] = 0;
- uint8 len = 1;
+ uint8 total = _ch[ch].durReload;
+ uint8 remaining = _ch[ch].durCounter;
+ uint8 start = _ch[ch].arpParam2 >> 4;
+ uint8 span = _ch[ch].arpParam2 & 0x0F;
+ uint8 elapsed = total - remaining;
- for (int i = 0; i < 8 && len < 19; i++) {
- if (bits & (1 << i)) {
- _ch[ch].arpSeqData[len] = kArpIntervals[i];
- len++;
- }
- }
+ if (elapsed <= start || elapsed > start + span || span == 0)
+ return;
- _ch[ch].arpSeqLen = len;
- _ch[ch].arpSeqPos = 0;
-}
+ uint8 currentNote = (_ch[ch].curNote > 94) ? 94 : _ch[ch].curNote;
+ uint8 targetNote = (_ch[ch].arpPattern > 94) ? 94 : _ch[ch].arpPattern;
+ if (currentNote == targetNote)
+ return;
-void DarkSideC64MusicPlayer::applyPortamento(int ch) {
- if (!_ch[ch].noteActive)
+ uint16 currentFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
+ uint16 sourceFreq = ((uint16)kFreqHi[currentNote] << 8) | kFreqLo[currentNote];
+ uint16 targetFreq = ((uint16)kFreqHi[targetNote] << 8) | kFreqLo[targetNote];
+ uint16 diff = (sourceFreq > targetFreq) ? (sourceFreq - targetFreq) : (targetFreq - sourceFreq);
+ uint16 divisor = span * (_speedDiv + 1);
+ if (divisor == 0)
return;
- uint16 curFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
- int32 newFreq = (int32)curFreq + _ch[ch].portaDelta;
+ uint16 delta = diff / divisor;
+ if (delta == 0)
+ return;
- // Clamp and check if target reached
- if (_ch[ch].portaDelta > 0 && (uint16)newFreq >= _ch[ch].portaTarget) {
- newFreq = _ch[ch].portaTarget;
- } else if (_ch[ch].portaDelta < 0 && (uint16)newFreq <= _ch[ch].portaTarget) {
- newFreq = _ch[ch].portaTarget;
- }
+ if (targetFreq > sourceFreq)
+ currentFreq += delta;
+ else
+ currentFreq -= delta;
- _ch[ch].freqLo = (uint16)newFreq & 0xFF;
- _ch[ch].freqHi = ((uint16)newFreq >> 8) & 0xFF;
+ _ch[ch].freqLo = currentFreq & 0xFF;
+ _ch[ch].freqHi = (currentFreq >> 8) & 0xFF;
+
+ int off = kSIDOffset[ch];
+ sidWrite(off + 0, _ch[ch].freqLo);
+ sidWrite(off + 1, _ch[ch].freqHi);
}
void DarkSideC64MusicPlayer::applyPWModulation(int ch) {
@@ -721,44 +749,23 @@ void DarkSideC64MusicPlayer::applyPWModulation(int ch) {
uint8 flags = kInstruments[instBase + 7];
- uint16 pw = ((uint16)_ch[ch].pwHi << 8) | _ch[ch].pwLo;
-
if (flags & 0x04) {
- // Direct add (one-directional sweep)
- pw += pwMod;
+ _ch[ch].pwLo += pwMod;
} else {
- // Triangle sweep between pwHi=$08 and pwHi=$0F
+ uint16 pw = ((uint16)_ch[ch].pwHi << 8) | _ch[ch].pwLo;
if (_ch[ch].pwDirection == 0) {
pw += pwMod;
- if ((_ch[ch].pwHi + (pw >> 8)) >= 0x0F || (pw >> 8) >= 0x0F) {
- pw = ((uint16)_ch[ch].pwHi << 8) | _ch[ch].pwLo;
- pw += pwMod;
- if ((pw >> 8) >= 0x0F)
- _ch[ch].pwDirection = 1;
- }
+ if ((pw >> 8) >= 0x0F)
+ _ch[ch].pwDirection = 1;
} else {
pw -= pwMod;
- if ((pw >> 8) <= 0x08)
+ if ((pw >> 8) < 0x08)
_ch[ch].pwDirection = 0;
}
- }
-
- _ch[ch].pwLo = pw & 0xFF;
- _ch[ch].pwHi = (pw >> 8) & 0xFF;
-}
-
-void DarkSideC64MusicPlayer::applyEnvelope(int ch) {
- if (_ch[ch].envCounter >= 15)
- return;
-
- int off = kSIDOffset[ch];
- uint8 tbl = _ch[ch].envTable;
- // Write waveform control from envelope table
- sidWrite(off + 4, kEnvControl[tbl][_ch[ch].envCounter]);
-
- // Write AD register from envelope volume table
- sidWrite(off + 5, kEnvVolume[tbl][_ch[ch].envCounter]);
+ _ch[ch].pwLo = pw & 0xFF;
+ _ch[ch].pwHi = (pw >> 8) & 0xFF;
+ }
}
} // namespace Freescape
diff --git a/engines/freescape/games/dark/c64.music.h b/engines/freescape/games/dark/c64.music.h
index ffbd7a8427b..19d61343e6b 100644
--- a/engines/freescape/games/dark/c64.music.h
+++ b/engines/freescape/games/dark/c64.music.h
@@ -49,18 +49,21 @@ private:
void setupSong();
void silenceAll();
void loadNextPattern(int ch);
- void unpackArpeggio(int ch);
+ void buildEffectArpeggio(int ch);
+ void loadCurrentFrequency(int ch);
+ void finalizeChannel(int ch);
// Per-tick processing
void processChannel(int ch, bool newBeat);
void parseCommands(int ch);
void applyNote(int ch, uint8 note);
- void applyContinuousEffects(int ch);
- void applyVibrato(int ch);
- void applyArpeggio(int ch);
- void applyPortamento(int ch);
+ void applyFrameEffects(int ch);
+ bool applySpecialAttack(int ch);
+ bool applyEnvelopeSequence(int ch);
+ bool applyInstrumentVibrato(int ch);
+ void applyEffectArpeggio(int ch);
+ void applyTimedSlide(int ch);
void applyPWModulation(int ch);
- void applyEnvelope(int ch);
uint8 readPatByte(int ch);
@@ -78,7 +81,6 @@ private:
int patOffset;
uint8 instIdx; // $15F6: instrument# * 8
- uint8 noteActive; // $1599
uint8 curNote; // $159C: note index (with transpose)
uint8 transpose; // $15C4
@@ -90,7 +92,7 @@ private:
uint8 durReload; // $15B8
uint8 durCounter; // $15BB
- uint8 effectMode; // $15BE: 0=none, 1=arp/vib1, 2=vib2, 0xDE=portaUp, 0xFE=portaDn
+ uint8 effectMode; // $15BE: mode 2 keeps effectParam alive across note loads
uint8 effectParam; // $15C1
uint8 arpPattern; // $15C7
@@ -99,23 +101,24 @@ private:
uint8 arpSeqData[20]; // $160C
uint8 arpSeqLen;
- int16 portaDelta; // $15CD/$15CE: per-tick freq delta
- uint16 portaTarget; // target frequency for portamento
+ uint8 noteStepCommand; // $15D0: self-modified INC/DEC opcode for beat step
+ uint8 stepDownCounter; // $15D3: short downward slide counter
uint8 vibPhase; // $15DE
uint8 vibCounter; // $15E1
uint8 pwDirection; // $15D6: 0=up, 1=down
+ uint8 delayValue; // $15E4
uint8 delayCounter; // $15E7
uint8 envCounter; // $15FC
- uint8 envTable; // envelope table set (0 or 1)
- bool envSeqActive; // flags bit 0
-
- bool sustainMode; // flags bit 3
-
- uint8 waveform; // current waveform ctrl byte (from instrument)
+ bool gateOffDisabled; // $1606: set when SR release nibble is $F
+ bool gateModeControl; // $1600: flags bit 7
+ bool specialAttack; // flags bit 3
+ bool attackDone; // $1603
+ uint8 waveform; // current instrument ctrl byte
+ uint8 instFlags; // current instrument flags byte
void reset();
};
Commit: cceaa7b31d72d96ff1e9e41571b1f1a241247741
https://github.com/scummvm/scummvm/commit/cceaa7b31d72d96ff1e9e41571b1f1a241247741
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: implement standing/flying indicator in dark for C64
Changed paths:
engines/freescape/games/dark/c64.cpp
engines/freescape/games/dark/dark.cpp
diff --git a/engines/freescape/games/dark/c64.cpp b/engines/freescape/games/dark/c64.cpp
index 1d4b1d521d9..d3ce0bd2555 100644
--- a/engines/freescape/games/dark/c64.cpp
+++ b/engines/freescape/games/dark/c64.cpp
@@ -28,12 +28,60 @@
namespace Freescape {
+extern byte kC64Palette[16][3];
+
+static const byte *getDarkC64IndicatorSprite(const byte *spriteData, byte pointer) {
+ assert(pointer >= 3 && pointer <= 10);
+ return spriteData + (pointer - 3) * 64;
+}
+
+static void blitDarkC64IndicatorSprite(Graphics::Surface *surface, const byte *sprite, byte color, const Graphics::PixelFormat &pixelFormat) {
+ uint32 pixel = pixelFormat.ARGBToColor(0xFF, kC64Palette[color][0], kC64Palette[color][1], kC64Palette[color][2]);
+ for (int y = 0; y < 21; y++) {
+ for (int byteIndex = 0; byteIndex < 3; byteIndex++) {
+ byte value = sprite[y * 3 + byteIndex];
+ for (int bit = 0; bit < 8; bit++) {
+ if ((value & (0x80 >> bit)) == 0)
+ continue;
+ surface->setPixel(byteIndex * 8 + bit, y, pixel);
+ }
+ }
+ }
+}
+
+static Graphics::Surface *composeDarkC64Indicator(const byte *spriteData, byte basePointer, byte overlayPointer1, byte overlayColor1, byte overlayPointer2, byte overlayColor2, bool includeSprite6, const Graphics::PixelFormat &pixelFormat) {
+ Graphics::Surface *surface = new Graphics::Surface();
+ surface->create(24, 21, pixelFormat);
+ surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), pixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00));
+
+ blitDarkC64IndicatorSprite(surface, getDarkC64IndicatorSprite(spriteData, basePointer), 2, pixelFormat);
+ blitDarkC64IndicatorSprite(surface, getDarkC64IndicatorSprite(spriteData, 4), 12, pixelFormat);
+ if (overlayPointer1)
+ blitDarkC64IndicatorSprite(surface, getDarkC64IndicatorSprite(spriteData, overlayPointer1), overlayColor1, pixelFormat);
+ if (overlayPointer2)
+ blitDarkC64IndicatorSprite(surface, getDarkC64IndicatorSprite(spriteData, overlayPointer2), overlayColor2, pixelFormat);
+ if (includeSprite6)
+ blitDarkC64IndicatorSprite(surface, getDarkC64IndicatorSprite(spriteData, 6), 8, pixelFormat);
+ return surface;
+}
+
+static void loadDarkC64Indicators(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::Surface *> &indicators, const Graphics::PixelFormat &pixelFormat) {
+ byte spriteData[8 * 64];
+ file->seek(offset);
+ file->read(spriteData, sizeof(spriteData));
+
+ // $8161 has three distinct body states: crawl (pointer 3 with layers 5/7),
+ // walk (pointer 3 with layers disabled), and fly (pointer 8 with 5/7 then 9/10).
+ indicators.push_back(composeDarkC64Indicator(spriteData, 3, 0, 0, 0, 0, false, pixelFormat));
+ indicators.push_back(composeDarkC64Indicator(spriteData, 3, 0, 0, 0, 0, false, pixelFormat));
+ indicators.push_back(composeDarkC64Indicator(spriteData, 8, 5, 7, 7, 8, true, pixelFormat));
+ indicators.push_back(composeDarkC64Indicator(spriteData, 8, 9, 7, 10, 8, true, pixelFormat));
+}
+
void DarkEngine::initC64() {
_viewArea = Common::Rect(32, 24, 288, 127);
}
-extern byte kC64Palette[16][3];
-
void DarkEngine::loadAssetsC64FullGame() {
Common::File file;
file.open("darkside.c64.data");
@@ -52,6 +100,7 @@ void DarkEngine::loadAssetsC64FullGame() {
loadFonts(&dfile, 0xc3e);
loadGlobalObjects(&dfile, 0x20bd, 23);
load8bitBinary(&dfile, 0x9b3e, 16);
+ loadDarkC64Indicators(&dfile, 0xadba, _indicators, _gfx->_texturePixelFormat);
} else if (_variant & GF_C64_DISC) {
loadMessagesFixedSize(&file, 0x16a3, 16, 27);
loadFonts(&file, 0x402);
@@ -66,6 +115,7 @@ void DarkEngine::loadAssetsC64FullGame() {
Common::MemoryReadStream stream(_extraBuffer, 0x300, DisposeAfterUse::NO);
loadGlobalObjects(&stream, 0x0, 23);
load8bitBinary(&file, 0x8914, 16);
+ loadDarkC64Indicators(&file, 0xb2d4, _indicators, _gfx->_texturePixelFormat);
} else
error("Unknown C64 variant %x", _variant);
@@ -274,6 +324,8 @@ void DarkEngine::drawC64UI(Graphics::Surface *surface) {
}
drawBinaryClock(surface, 304, 124, front, back);
drawVerticalCompass(surface, 17, 77, _pitch, front);
+ surface->fillRect(Common::Rect(152, 148 - 5, 184, 176 - 15), _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00));
+ drawIndicator(surface, 160 - 1, 148 - 1);
}
} // End of namespace Freescape
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index c16c082cb89..068b14a9789 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -986,6 +986,17 @@ void DarkEngine::drawIndicator(Graphics::Surface *surface, int xPosition, int yP
return;
}
+ if (isC64() && _indicators.size() >= 4) {
+ if (_hasFallen)
+ return;
+
+ const Graphics::Surface *indicator = _indicators[1];
+ if (_flyMode)
+ indicator = _indicators[2 + ((g_system->getMillis() / 40) & 1)];
+ surface->copyRectToSurfaceWithKey(*indicator, xPosition, yPosition, Common::Rect(indicator->w, indicator->h), 0);
+ return;
+ }
+
if (_indicators.size() == 0)
return;
if (_hasFallen)
Commit: 49abb97a8ad394d669a6cff4be2e08d0b5fd243a
https://github.com/scummvm/scummvm/commit/49abb97a8ad394d669a6cff4be2e08d0b5fd243a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: more indicators in dark for C64
Changed paths:
engines/freescape/games/dark/c64.cpp
engines/freescape/games/dark/dark.cpp
engines/freescape/games/dark/dark.h
diff --git a/engines/freescape/games/dark/c64.cpp b/engines/freescape/games/dark/c64.cpp
index d3ce0bd2555..6b882b15fd4 100644
--- a/engines/freescape/games/dark/c64.cpp
+++ b/engines/freescape/games/dark/c64.cpp
@@ -30,6 +30,26 @@ namespace Freescape {
extern byte kC64Palette[16][3];
+static const byte kDarkC64CompassMask[] = {0xfe, 0xfa, 0x69, 0xf5, 0xea};
+static const byte kDarkC64CompassColor1[] = {0xfc, 0x1f, 0xf1, 0xcb, 0x3b};
+static const byte kDarkC64CompassColor2[] = {0x0b, 0x0c, 0x0c, 0x0f, 0x0c};
+static const byte kDarkC64ModeIndicatorPalette[2][3][4] = {
+ {{0, 11, 15, 1}, {0, 11, 15, 2}, {0, 15, 1, 11}},
+ {{0, 15, 12, 11}, {0, 15, 12, 11}, {0, 15, 2, 11}}
+};
+
+static bool isDarkC64BrokenCompassArea(uint16 areaID) {
+ return areaID == 1 || areaID == 18 || areaID == 27 || areaID == 28;
+}
+
+static int getDarkC64CompassTarget(float yaw) {
+ int target = int(yaw / 5.0f + 0.5f);
+ target = (target + 18) % 72;
+ if (target < 0)
+ target += 72;
+ return target;
+}
+
static const byte *getDarkC64IndicatorSprite(const byte *spriteData, byte pointer) {
assert(pointer >= 3 && pointer <= 10);
return spriteData + (pointer - 3) * 64;
@@ -65,6 +85,12 @@ static Graphics::Surface *composeDarkC64Indicator(const byte *spriteData, byte b
return surface;
}
+static void loadDarkC64CompassTable(Common::SeekableReadStream *file, uint32 offset, Common::Array<byte> &table) {
+ table.resize(80);
+ file->seek(offset);
+ file->read(table.data(), table.size());
+}
+
static void loadDarkC64Indicators(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::Surface *> &indicators, const Graphics::PixelFormat &pixelFormat) {
byte spriteData[8 * 64];
file->seek(offset);
@@ -78,10 +104,112 @@ static void loadDarkC64Indicators(Common::SeekableReadStream *file, uint32 offse
indicators.push_back(composeDarkC64Indicator(spriteData, 8, 9, 7, 10, 8, true, pixelFormat));
}
+static void loadDarkC64ModeFrames(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::ManagedSurface *> &frames, const Graphics::PixelFormat &pixelFormat) {
+ byte data[6 * 48];
+ file->seek(offset);
+ file->read(data, sizeof(data));
+
+ for (int frameIndex = 0; frameIndex < 6; frameIndex++) {
+ Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
+ surface->create(24, 16, pixelFormat);
+ surface->fillRect(Common::Rect(0, 0, surface->w, surface->h),
+ pixelFormat.ARGBToColor(0xFF, kC64Palette[0][0], kC64Palette[0][1], kC64Palette[0][2]));
+
+ for (int band = 0; band < 2; band++) {
+ for (int cell = 0; cell < 3; cell++) {
+ for (int row = 0; row < 8; row++) {
+ byte bitmapByte = data[frameIndex * 48 + band * 24 + cell * 8 + row];
+ for (int pair = 0; pair < 4; pair++) {
+ byte color = kDarkC64ModeIndicatorPalette[band][cell][(bitmapByte >> (6 - pair * 2)) & 0x03];
+ uint32 pixel = pixelFormat.ARGBToColor(0xFF, kC64Palette[color][0], kC64Palette[color][1], kC64Palette[color][2]);
+ int x = cell * 8 + pair * 2;
+ int y = band * 8 + row;
+ surface->setPixel(x, y, pixel);
+ surface->setPixel(x + 1, y, pixel);
+ }
+ }
+ }
+ }
+ frames.push_back(surface);
+ }
+}
+
void DarkEngine::initC64() {
_viewArea = Common::Rect(32, 24, 288, 127);
}
+void DarkEngine::drawC64Compass(Graphics::Surface *surface) {
+ if (!_currentArea || _c64CompassTable.size() < 80)
+ return;
+
+ int target = getDarkC64CompassTarget(_yaw);
+ if (!_c64CompassInitialized) {
+ _c64CompassPosition = target;
+ _c64CompassInitialized = true;
+ }
+
+ if (isDarkC64BrokenCompassArea(_currentArea->getAreaID())) {
+ if ((_ticks & 1) == 0)
+ _c64CompassPosition = (_c64CompassPosition + 71) % 72;
+ } else {
+ int delta = (_c64CompassPosition - target + 72) % 72;
+ if (delta != 0) {
+ if (delta < 37)
+ _c64CompassPosition = (_c64CompassPosition + 71) % 72;
+ else
+ _c64CompassPosition = (_c64CompassPosition + 1) % 72;
+ }
+ }
+
+ int start = (_c64CompassPosition + 72 - 20) % 72;
+ int tableOffset = start & 0xf8;
+ int shift = (start & 0x07) >> 1;
+
+ for (int cell = 0; cell < 5; cell++) {
+ byte color1 = kDarkC64CompassColor2[cell] & 0x0f;
+ byte color2 = kDarkC64CompassColor1[cell] >> 4;
+ byte color3 = kDarkC64CompassColor1[cell] & 0x0f;
+ byte mask = kDarkC64CompassMask[cell];
+
+ for (int row = 0; row < 8; row++) {
+ int index = tableOffset + cell * 8 + row;
+ if (index >= 72)
+ index -= 72;
+
+ byte bitmapByte = _c64CompassTable[index];
+ if (shift > 0) {
+ byte nextByte = _c64CompassTable[index + 8];
+ bitmapByte = ((bitmapByte << (2 * shift)) | (nextByte >> (8 - 2 * shift))) & 0xff;
+ }
+ bitmapByte &= mask;
+
+ for (int pair = 0; pair < 4; pair++) {
+ byte pixelPair = (bitmapByte >> (6 - pair * 2)) & 0x03;
+ byte color = 0;
+ if (pixelPair == 1)
+ color = color2;
+ else if (pixelPair == 2)
+ color = color3;
+ else if (pixelPair == 3)
+ color = color1;
+
+ uint32 pixel = _gfx->_texturePixelFormat.ARGBToColor(0xFF, kC64Palette[color][0], kC64Palette[color][1], kC64Palette[color][2]);
+ surface->setPixel(256 + cell * 8 + pair * 2, 144 + row, pixel);
+ surface->setPixel(256 + cell * 8 + pair * 2 + 1, 144 + row, pixel);
+ }
+ }
+ }
+}
+
+void DarkEngine::drawC64ModeIndicator(Graphics::Surface *surface) {
+ if (_c64ModeFrames.size() < 6)
+ return;
+
+ const Graphics::ManagedSurface *frame = _shootMode ? _c64ModeFrames[0] : _c64ModeFrames[1];
+
+ surface->copyRectToSurface(*frame, 264, 176, Common::Rect(frame->w, frame->h));
+}
+
void DarkEngine::loadAssetsC64FullGame() {
Common::File file;
file.open("darkside.c64.data");
@@ -100,7 +228,9 @@ void DarkEngine::loadAssetsC64FullGame() {
loadFonts(&dfile, 0xc3e);
loadGlobalObjects(&dfile, 0x20bd, 23);
load8bitBinary(&dfile, 0x9b3e, 16);
+ loadDarkC64CompassTable(&dfile, 0x7e37, _c64CompassTable);
loadDarkC64Indicators(&dfile, 0xadba, _indicators, _gfx->_texturePixelFormat);
+ loadDarkC64ModeFrames(&dfile, 0xd2f6, _c64ModeFrames, _gfx->_texturePixelFormat);
} else if (_variant & GF_C64_DISC) {
loadMessagesFixedSize(&file, 0x16a3, 16, 27);
loadFonts(&file, 0x402);
@@ -115,7 +245,9 @@ void DarkEngine::loadAssetsC64FullGame() {
Common::MemoryReadStream stream(_extraBuffer, 0x300, DisposeAfterUse::NO);
loadGlobalObjects(&stream, 0x0, 23);
load8bitBinary(&file, 0x8914, 16);
+ loadDarkC64CompassTable(&file, 0x7c5a, _c64CompassTable);
loadDarkC64Indicators(&file, 0xb2d4, _indicators, _gfx->_texturePixelFormat);
+ loadDarkC64ModeFrames(&file, 0xc0d1, _c64ModeFrames, _gfx->_texturePixelFormat);
} else
error("Unknown C64 variant %x", _variant);
@@ -323,9 +455,11 @@ void DarkEngine::drawC64UI(Graphics::Surface *surface) {
surface->drawLine(64, 147 + 8, 127 - (_maxEnergy - energy) - 1, 155, lineColor);
}
drawBinaryClock(surface, 304, 124, front, back);
+ drawC64Compass(surface);
drawVerticalCompass(surface, 17, 77, _pitch, front);
surface->fillRect(Common::Rect(152, 148 - 5, 184, 176 - 15), _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0x00, 0x00, 0x00));
drawIndicator(surface, 160 - 1, 148 - 1);
+ drawC64ModeIndicator(surface);
}
} // End of namespace Freescape
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index 068b14a9789..b8d3988e021 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -37,6 +37,8 @@ DarkEngine::DarkEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEn
_playerC64Sfx = nullptr;
_playerC64Music = nullptr;
_c64UseSFX = false;
+ _c64CompassInitialized = false;
+ _c64CompassPosition = 0;
// These sounds can be overriden by the class of each platform
_soundIndexShoot = 1;
@@ -118,6 +120,10 @@ DarkEngine::~DarkEngine() {
indicator->free();
delete indicator;
}
+ for (auto &frame : _c64ModeFrames) {
+ frame->free();
+ delete frame;
+ }
}
void DarkEngine::addECDs(Area *area) {
diff --git a/engines/freescape/games/dark/dark.h b/engines/freescape/games/dark/dark.h
index 984bed34cf5..e60926e1522 100644
--- a/engines/freescape/games/dark/dark.h
+++ b/engines/freescape/games/dark/dark.h
@@ -101,6 +101,7 @@ public:
void drawZXUI(Graphics::Surface *surface) override;
void drawCPCUI(Graphics::Surface *surface) override;
void drawAmigaAtariSTUI(Graphics::Surface *surface) override;
+ void drawC64Compass(Graphics::Surface *surface);
Font _fontBig;
Font _fontMedium;
@@ -109,6 +110,7 @@ public:
Common::Array<Graphics::ManagedSurface *> _cpcJetpackIndicators;
Common::Array<Graphics::ManagedSurface *> _cpcActionIndicators;
uint32 _cpcActionIndicatorUntilMillis;
+ Common::Array<Graphics::ManagedSurface *> _c64ModeFrames;
// Dark Side Amiga stores the grounded jetpack indicator states as raw
// 4-plane bitplane data. The executable drives those frames through a tiny
@@ -143,6 +145,9 @@ public:
DarkSideC64SFXPlayer *_playerC64Sfx;
DarkSideC64MusicPlayer *_playerC64Music;
bool _c64UseSFX;
+ bool _c64CompassInitialized;
+ int _c64CompassPosition;
+ Common::Array<byte> _c64CompassTable;
void playSoundC64(int index) override;
void toggleC64Sound();
@@ -165,6 +170,7 @@ private:
void loadCPCIndicator(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::ManagedSurface *> &target);
void loadCPCIndicatorData(const byte *data, int widthBytes, int height, Common::Array<Graphics::ManagedSurface *> &target);
void loadCPCIndicators(Common::SeekableReadStream *file);
+ void drawC64ModeIndicator(Graphics::Surface *surface);
void drawCPCSprite(Graphics::Surface *surface, const Graphics::ManagedSurface *indicator, int xPosition, int yPosition);
void drawCPCIndicator(Graphics::Surface *surface, int xPosition, int yPosition);
void drawVerticalCompass(Graphics::Surface *surface, int x, int y, float angle, uint32 color);
Commit: 0e0db53cc20b2d319377268747d02bfdbb7ba62d
https://github.com/scummvm/scummvm/commit/0e0db53cc20b2d319377268747d02bfdbb7ba62d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-30T11:32:36+02:00
Commit Message:
FREESCAPE: added music for eclipse c64
Changed paths:
A engines/freescape/games/eclipse/c64.music.cpp
A engines/freescape/games/eclipse/c64.music.h
engines/freescape/games/eclipse/c64.cpp
engines/freescape/games/eclipse/eclipse.cpp
engines/freescape/games/eclipse/eclipse.h
engines/freescape/module.mk
diff --git a/engines/freescape/games/eclipse/c64.cpp b/engines/freescape/games/eclipse/c64.cpp
index e9e670585f5..cded7fcc8a8 100644
--- a/engines/freescape/games/eclipse/c64.cpp
+++ b/engines/freescape/games/eclipse/c64.cpp
@@ -22,6 +22,7 @@
#include "common/file.h"
#include "freescape/freescape.h"
+#include "freescape/games/eclipse/c64.music.h"
#include "freescape/games/eclipse/eclipse.h"
#include "freescape/language/8bitDetokeniser.h"
@@ -83,6 +84,18 @@ void EclipseEngine::loadAssetsC64FullGame() {
for (auto &it : _indicators)
it->convertToInPlace(_gfx->_texturePixelFormat);
+
+ Common::File musicFile;
+ musicFile.open("totec1.prg");
+ if (musicFile.isOpen()) {
+ uint16 loadAddress = musicFile.readUint16LE();
+ if (loadAddress == 0x0410) {
+ _c64MusicData.resize(musicFile.size() - 2);
+ musicFile.read(_c64MusicData.data(), _c64MusicData.size());
+ delete _playerC64Music;
+ _playerC64Music = new EclipseC64MusicPlayer(_c64MusicData);
+ }
+ }
}
diff --git a/engines/freescape/games/eclipse/c64.music.cpp b/engines/freescape/games/eclipse/c64.music.cpp
new file mode 100644
index 00000000000..82fae5526ca
--- /dev/null
+++ b/engines/freescape/games/eclipse/c64.music.cpp
@@ -0,0 +1,569 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/freescape/games/eclipse/c64.music.h"
+
+#include "common/textconsole.h"
+#include "freescape/wb.h"
+
+namespace Freescape {
+
+static const int kSIDOffset[] = {0, 7, 14};
+
+void EclipseC64MusicPlayer::ChannelState::reset() {
+ orderAddr = 0;
+ orderPos = 0;
+ patternAddr = 0;
+ patternOffset = 0;
+ instrumentOffset = 0;
+ currentNote = 0;
+ transpose = 0;
+ frequencyLow = 0;
+ frequencyHigh = 0;
+ pulseWidthLow = 0;
+ pulseWidthHigh = 0;
+ durationReload = 0;
+ durationCounter = 0;
+ effectMode = 0;
+ effectParam = 0;
+ arpeggioTarget = 0;
+ arpeggioParam = 0;
+ arpeggioSequencePos = 0;
+ memset(arpeggioSequence, 0, sizeof(arpeggioSequence));
+ arpeggioSequenceLen = 0;
+ noteStepCommand = 0;
+ stepDownCounter = 0;
+ vibratoPhase = 0;
+ vibratoCounter = 0;
+ pulseWidthDirection = 0;
+ delayValue = 0;
+ delayCounter = 0;
+ waveform = 0;
+ instrumentFlags = 0;
+ gateOffDisabled = false;
+}
+
+EclipseC64MusicPlayer::EclipseC64MusicPlayer(const Common::Array<byte> &musicData)
+ : _sid(nullptr),
+ _musicData(musicData),
+ _musicActive(false),
+ _speedDivider(1),
+ _speedCounter(0) {
+ memset(_arpeggioIntervals, 0, sizeof(_arpeggioIntervals));
+ for (int i = 0; i < 8; i++)
+ _arpeggioIntervals[i] = readByte(kArpeggioIntervalTable + i);
+ initSID();
+}
+
+EclipseC64MusicPlayer::~EclipseC64MusicPlayer() {
+ destroySID();
+}
+
+void EclipseC64MusicPlayer::destroySID() {
+ if (_sid) {
+ _sid->stop();
+ delete _sid;
+ _sid = nullptr;
+ }
+}
+
+void EclipseC64MusicPlayer::initSID() {
+ if (_sid) {
+ _sid->stop();
+ delete _sid;
+ }
+
+ _sid = SID::Config::create(SID::Config::kSidPAL);
+ if (!_sid || !_sid->init()) {
+ warning("EclipseC64MusicPlayer: Failed to create SID emulator");
+ return;
+ }
+
+ _sid->start(new Common::Functor0Mem<void, EclipseC64MusicPlayer>(this, &EclipseC64MusicPlayer::onTimer), 50);
+}
+
+bool EclipseC64MusicPlayer::isPlaying() const {
+ return _musicActive;
+}
+
+void EclipseC64MusicPlayer::sidWrite(int reg, byte data) {
+ if (_sid)
+ _sid->writeReg(reg, data);
+}
+
+void EclipseC64MusicPlayer::silenceAll() {
+ for (int i = 0; i <= 0x18; i++)
+ sidWrite(i, 0);
+}
+
+byte EclipseC64MusicPlayer::readByte(uint16 address) const {
+ if (address < kLoadAddress)
+ return 0;
+ uint32 offset = address - kLoadAddress;
+ if (offset >= _musicData.size())
+ return 0;
+ return _musicData[offset];
+}
+
+uint16 EclipseC64MusicPlayer::readWordLE(uint16 address) const {
+ return readByte(address) | (readByte(address + 1) << 8);
+}
+
+uint16 EclipseC64MusicPlayer::readPatternPointer(byte index) const {
+ if (index >= kPatternCount)
+ return 0;
+ return readByte(kPatternPointerLowTable + index) | (readByte(kPatternPointerHighTable + index) << 8);
+}
+
+byte EclipseC64MusicPlayer::readInstrumentByte(byte instrumentOffset, byte field) const {
+ return readByte(kInstrumentTable + instrumentOffset + field);
+}
+
+byte EclipseC64MusicPlayer::readPatternByte(int channel) {
+ byte value = readByte(_channels[channel].patternAddr + _channels[channel].patternOffset);
+ _channels[channel].patternOffset++;
+ return value;
+}
+
+byte EclipseC64MusicPlayer::clampNote(byte note) const {
+ return note > kMaxNote ? kMaxNote : note;
+}
+
+void EclipseC64MusicPlayer::startMusic() {
+ if (_musicData.empty())
+ return;
+ setupSong();
+}
+
+void EclipseC64MusicPlayer::stopMusic() {
+ _musicActive = false;
+ silenceAll();
+}
+
+void EclipseC64MusicPlayer::setupSong() {
+ silenceAll();
+
+ sidWrite(0x15, 0x00);
+ sidWrite(0x16, 0x00);
+ sidWrite(0x17, 0x77);
+ sidWrite(0x18, 0x5F);
+
+ _speedDivider = 1;
+ _speedCounter = 0;
+
+ for (int i = 0; i < kChannelCount; i++) {
+ _channels[i].reset();
+ _channels[i].orderAddr = readWordLE(kOrderPointerTable + i * 2);
+ loadNextPattern(i);
+ }
+
+ _musicActive = true;
+}
+
+void EclipseC64MusicPlayer::loadNextPattern(int channel) {
+ int safety = 200;
+ while (safety-- > 0) {
+ byte value = readByte(_channels[channel].orderAddr + _channels[channel].orderPos);
+ _channels[channel].orderPos++;
+
+ if (value == 0xFF) {
+ _channels[channel].orderPos = 0;
+ continue;
+ }
+
+ if (value >= 0xC0) {
+ _channels[channel].transpose = (byte)WBCommon::decodeOrderTranspose(value);
+ continue;
+ }
+
+ _channels[channel].patternAddr = readPatternPointer(value);
+ _channels[channel].patternOffset = 0;
+ break;
+ }
+}
+
+void EclipseC64MusicPlayer::buildEffectArpeggio(int channel) {
+ _channels[channel].arpeggioSequenceLen = WBCommon::buildArpeggioTable(
+ _arpeggioIntervals,
+ _channels[channel].effectParam,
+ _channels[channel].arpeggioSequence,
+ sizeof(_channels[channel].arpeggioSequence),
+ true);
+ _channels[channel].arpeggioSequencePos = 0;
+}
+
+void EclipseC64MusicPlayer::loadCurrentFrequency(int channel) {
+ byte note = clampNote(_channels[channel].currentNote);
+
+ _channels[channel].frequencyLow = readByte(kFrequencyLowTable + note);
+ _channels[channel].frequencyHigh = readByte(kFrequencyHighTable + note);
+ sidWrite(kSIDOffset[channel] + 0, _channels[channel].frequencyLow);
+ sidWrite(kSIDOffset[channel] + 1, _channels[channel].frequencyHigh);
+}
+
+void EclipseC64MusicPlayer::finalizeChannel(int channel) {
+ if (_channels[channel].durationReload != 0 &&
+ !_channels[channel].gateOffDisabled &&
+ ((_channels[channel].durationReload >> 1) == _channels[channel].durationCounter)) {
+ sidWrite(kSIDOffset[channel] + 4, _channels[channel].waveform & 0xFE);
+ }
+
+ applyPulseWidthModulation(channel);
+ sidWrite(kSIDOffset[channel] + 2, _channels[channel].pulseWidthLow);
+ sidWrite(kSIDOffset[channel] + 3, _channels[channel].pulseWidthHigh & 0x0F);
+}
+
+void EclipseC64MusicPlayer::onTimer() {
+ if (!_musicActive)
+ return;
+
+ bool newBeat = (_speedCounter == 0);
+
+ for (int channel = kChannelCount - 1; channel >= 0; channel--)
+ processChannel(channel, newBeat);
+
+ if (!_musicActive)
+ return;
+
+ if (newBeat)
+ _speedCounter = _speedDivider;
+ else
+ _speedCounter--;
+}
+
+void EclipseC64MusicPlayer::processChannel(int channel, bool newBeat) {
+ if (newBeat) {
+ _channels[channel].durationCounter--;
+ if (_channels[channel].durationCounter == 0xFF) {
+ parseCommands(channel);
+ if (!_musicActive)
+ return;
+ finalizeChannel(channel);
+ return;
+ }
+
+ if (_channels[channel].noteStepCommand != 0) {
+ if (_channels[channel].noteStepCommand == 0xDE) {
+ if (_channels[channel].currentNote > 0)
+ _channels[channel].currentNote--;
+ } else if (_channels[channel].currentNote < kMaxNote) {
+ _channels[channel].currentNote++;
+ }
+ loadCurrentFrequency(channel);
+ finalizeChannel(channel);
+ return;
+ }
+ } else if (_channels[channel].stepDownCounter != 0) {
+ _channels[channel].stepDownCounter--;
+ if (_channels[channel].currentNote > 0)
+ _channels[channel].currentNote--;
+ loadCurrentFrequency(channel);
+ finalizeChannel(channel);
+ return;
+ }
+
+ applyFrameEffects(channel);
+ finalizeChannel(channel);
+}
+
+void EclipseC64MusicPlayer::parseCommands(int channel) {
+ if (_channels[channel].effectMode != 2) {
+ _channels[channel].effectParam = 0;
+ _channels[channel].effectMode = 0;
+ _channels[channel].arpeggioSequenceLen = 0;
+ _channels[channel].arpeggioSequencePos = 0;
+ }
+
+ _channels[channel].arpeggioTarget = 0;
+ _channels[channel].noteStepCommand = 0;
+
+ int safety = 200;
+ while (safety-- > 0) {
+ byte cmd = readPatternByte(channel);
+
+ if (cmd == 0xFF) {
+ loadNextPattern(channel);
+ continue;
+ }
+
+ if (cmd == 0xFE) {
+ stopMusic();
+ return;
+ }
+
+ if (cmd == 0xFD) {
+ sidWrite(0x18, readPatternByte(channel));
+ cmd = readPatternByte(channel);
+ if (cmd == 0xFF) {
+ loadNextPattern(channel);
+ continue;
+ }
+ }
+
+ if (cmd >= 0xF0) {
+ _speedDivider = cmd & 0x0F;
+ continue;
+ }
+
+ if (cmd >= 0xC0) {
+ byte instrument = cmd & 0x1F;
+ if (instrument < kInstrumentCount)
+ _channels[channel].instrumentOffset = instrument * 8;
+ continue;
+ }
+
+ if (cmd >= 0x80) {
+ _channels[channel].durationReload = cmd & 0x3F;
+ continue;
+ }
+
+ if (cmd == 0x7F) {
+ _channels[channel].noteStepCommand = 0xDE;
+ _channels[channel].effectMode = 0xDE;
+ continue;
+ }
+
+ if (cmd == 0x7E) {
+ _channels[channel].effectMode = 0xFE;
+ continue;
+ }
+
+ if (cmd == 0x7D) {
+ _channels[channel].effectMode = 1;
+ _channels[channel].effectParam = readPatternByte(channel);
+ buildEffectArpeggio(channel);
+ continue;
+ }
+
+ if (cmd == 0x7C) {
+ _channels[channel].effectMode = 2;
+ _channels[channel].effectParam = readPatternByte(channel);
+ buildEffectArpeggio(channel);
+ continue;
+ }
+
+ if (cmd == 0x7B) {
+ _channels[channel].effectParam = 0;
+ _channels[channel].effectMode = 1;
+ _channels[channel].arpeggioTarget = readPatternByte(channel) + _channels[channel].transpose;
+ _channels[channel].arpeggioParam = readPatternByte(channel);
+ continue;
+ }
+
+ if (cmd == 0x7A) {
+ _channels[channel].delayValue = readPatternByte(channel);
+ cmd = readPatternByte(channel);
+ }
+
+ applyNote(channel, cmd);
+ return;
+ }
+}
+
+void EclipseC64MusicPlayer::applyNote(int channel, byte note) {
+ byte instrumentOffset = _channels[channel].instrumentOffset;
+ byte ctrl = readInstrumentByte(instrumentOffset, 0);
+ byte attackDecay = readInstrumentByte(instrumentOffset, 1);
+ byte sustainRelease = readInstrumentByte(instrumentOffset, 2);
+ byte initialPulseWidth = readInstrumentByte(instrumentOffset, 3);
+ byte autoEffect = readInstrumentByte(instrumentOffset, 6);
+ byte flags = readInstrumentByte(instrumentOffset, 7);
+ byte actualNote = note;
+
+ if (actualNote != 0)
+ actualNote = clampNote(actualNote + _channels[channel].transpose);
+
+ _channels[channel].currentNote = actualNote;
+ _channels[channel].waveform = ctrl;
+ _channels[channel].instrumentFlags = flags;
+ _channels[channel].stepDownCounter = 0;
+
+ if (actualNote != 0 && _channels[channel].effectParam == 0 && autoEffect != 0) {
+ _channels[channel].effectParam = autoEffect;
+ buildEffectArpeggio(channel);
+ }
+
+ if (actualNote != 0 && (flags & 0x02) != 0) {
+ _channels[channel].stepDownCounter = 2;
+ _channels[channel].currentNote = clampNote(_channels[channel].currentNote + 2);
+ }
+
+ loadCurrentFrequency(channel);
+
+ _channels[channel].pulseWidthLow = initialPulseWidth & 0xF0;
+ _channels[channel].pulseWidthHigh = initialPulseWidth & 0x0F;
+ sidWrite(kSIDOffset[channel] + 2, _channels[channel].pulseWidthLow);
+ sidWrite(kSIDOffset[channel] + 3, _channels[channel].pulseWidthHigh & 0x0F);
+
+ sidWrite(kSIDOffset[channel] + 5, attackDecay);
+ sidWrite(kSIDOffset[channel] + 6, sustainRelease);
+ _channels[channel].gateOffDisabled = (sustainRelease & 0x0F) == 0x0F;
+ sidWrite(kSIDOffset[channel] + 4, 0x00);
+ sidWrite(kSIDOffset[channel] + 4, ctrl);
+
+ _channels[channel].durationCounter = _channels[channel].durationReload;
+ _channels[channel].delayCounter = _channels[channel].delayValue;
+ _channels[channel].arpeggioSequencePos = 0;
+}
+
+void EclipseC64MusicPlayer::applyFrameEffects(int channel) {
+ if (_channels[channel].currentNote == 0)
+ return;
+
+ if (applyInstrumentVibrato(channel))
+ return;
+
+ applyEffectArpeggio(channel);
+ applyTimedSlide(channel);
+}
+
+bool EclipseC64MusicPlayer::applyInstrumentVibrato(int channel) {
+ byte vibrato = readInstrumentByte(_channels[channel].instrumentOffset, 4);
+ if (vibrato == 0 || _channels[channel].currentNote >= kMaxNote)
+ return false;
+
+ byte shift = vibrato & 0x0F;
+ byte span = vibrato >> 4;
+ if (span == 0)
+ return false;
+
+ uint16 noteFrequency = (readByte(kFrequencyHighTable + _channels[channel].currentNote) << 8) |
+ readByte(kFrequencyLowTable + _channels[channel].currentNote);
+ uint16 nextFrequency = (readByte(kFrequencyHighTable + _channels[channel].currentNote + 1) << 8) |
+ readByte(kFrequencyLowTable + _channels[channel].currentNote + 1);
+ uint16 delta = nextFrequency - noteFrequency;
+
+ while (shift-- != 0)
+ delta >>= 1;
+
+ if ((_channels[channel].vibratoPhase & 0x80) != 0) {
+ if (_channels[channel].vibratoCounter != 0)
+ _channels[channel].vibratoCounter--;
+ if (_channels[channel].vibratoCounter == 0)
+ _channels[channel].vibratoPhase = 0;
+ } else {
+ _channels[channel].vibratoCounter++;
+ if (_channels[channel].vibratoCounter >= span)
+ _channels[channel].vibratoPhase = 0xFF;
+ }
+
+ if (_channels[channel].delayCounter != 0) {
+ _channels[channel].delayCounter--;
+ return false;
+ }
+
+ int32 frequency = (_channels[channel].frequencyHigh << 8) | _channels[channel].frequencyLow;
+ for (byte i = 0; i < (span >> 1); i++)
+ frequency -= delta;
+ for (byte i = 0; i < _channels[channel].vibratoCounter; i++)
+ frequency += delta;
+
+ if (frequency < 0)
+ frequency = 0;
+ if (frequency > 0xFFFF)
+ frequency = 0xFFFF;
+
+ sidWrite(kSIDOffset[channel] + 0, frequency & 0xFF);
+ sidWrite(kSIDOffset[channel] + 1, (frequency >> 8) & 0xFF);
+ return true;
+}
+
+void EclipseC64MusicPlayer::applyEffectArpeggio(int channel) {
+ if (_channels[channel].effectParam == 0 || _channels[channel].arpeggioSequenceLen == 0)
+ return;
+
+ if (_channels[channel].arpeggioSequencePos >= _channels[channel].arpeggioSequenceLen)
+ _channels[channel].arpeggioSequencePos = 0;
+
+ byte note = clampNote(_channels[channel].currentNote + _channels[channel].arpeggioSequence[_channels[channel].arpeggioSequencePos]);
+ _channels[channel].arpeggioSequencePos++;
+
+ sidWrite(kSIDOffset[channel] + 0, readByte(kFrequencyLowTable + note));
+ sidWrite(kSIDOffset[channel] + 1, readByte(kFrequencyHighTable + note));
+}
+
+void EclipseC64MusicPlayer::applyTimedSlide(int channel) {
+ if (_channels[channel].arpeggioTarget == 0)
+ return;
+
+ byte total = _channels[channel].durationReload;
+ byte remaining = _channels[channel].durationCounter;
+ byte start = _channels[channel].arpeggioParam >> 4;
+ byte span = _channels[channel].arpeggioParam & 0x0F;
+ byte elapsed = total - remaining;
+
+ if (elapsed <= start || elapsed > start + span || span == 0)
+ return;
+
+ byte currentNote = clampNote(_channels[channel].currentNote);
+ byte targetNote = clampNote(_channels[channel].arpeggioTarget);
+ if (currentNote == targetNote)
+ return;
+
+ uint16 currentFrequency = (_channels[channel].frequencyHigh << 8) | _channels[channel].frequencyLow;
+ uint16 sourceFrequency = (readByte(kFrequencyHighTable + currentNote) << 8) | readByte(kFrequencyLowTable + currentNote);
+ uint16 targetFrequency = (readByte(kFrequencyHighTable + targetNote) << 8) | readByte(kFrequencyLowTable + targetNote);
+ uint16 difference = sourceFrequency > targetFrequency ? sourceFrequency - targetFrequency : targetFrequency - sourceFrequency;
+ uint16 divisor = span * (_speedDivider + 1);
+ if (divisor == 0)
+ return;
+
+ uint16 delta = difference / divisor;
+ if (delta == 0)
+ return;
+
+ if (targetFrequency > sourceFrequency)
+ currentFrequency += delta;
+ else
+ currentFrequency -= delta;
+
+ _channels[channel].frequencyLow = currentFrequency & 0xFF;
+ _channels[channel].frequencyHigh = (currentFrequency >> 8) & 0xFF;
+ sidWrite(kSIDOffset[channel] + 0, _channels[channel].frequencyLow);
+ sidWrite(kSIDOffset[channel] + 1, _channels[channel].frequencyHigh);
+}
+
+void EclipseC64MusicPlayer::applyPulseWidthModulation(int channel) {
+ byte pulseWidthMod = readInstrumentByte(_channels[channel].instrumentOffset, 5);
+ if (pulseWidthMod == 0)
+ return;
+
+ if ((_channels[channel].instrumentFlags & 0x04) != 0) {
+ _channels[channel].pulseWidthLow += pulseWidthMod;
+ return;
+ }
+
+ uint16 pulseWidth = (_channels[channel].pulseWidthHigh << 8) | _channels[channel].pulseWidthLow;
+ if (_channels[channel].pulseWidthDirection == 0) {
+ pulseWidth += pulseWidthMod;
+ if ((pulseWidth >> 8) >= 0x0F)
+ _channels[channel].pulseWidthDirection = 1;
+ } else {
+ pulseWidth -= pulseWidthMod;
+ if ((pulseWidth >> 8) < 0x08)
+ _channels[channel].pulseWidthDirection = 0;
+ }
+
+ _channels[channel].pulseWidthLow = pulseWidth & 0xFF;
+ _channels[channel].pulseWidthHigh = (pulseWidth >> 8) & 0xFF;
+}
+
+} // namespace Freescape
diff --git a/engines/freescape/games/eclipse/c64.music.h b/engines/freescape/games/eclipse/c64.music.h
new file mode 100644
index 00000000000..8496dff7f35
--- /dev/null
+++ b/engines/freescape/games/eclipse/c64.music.h
@@ -0,0 +1,135 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef FREESCAPE_ECLIPSE_C64_MUSIC_H
+#define FREESCAPE_ECLIPSE_C64_MUSIC_H
+
+#include "audio/sid.h"
+#include "common/array.h"
+
+namespace Freescape {
+
+class EclipseC64MusicPlayer {
+public:
+ EclipseC64MusicPlayer(const Common::Array<byte> &musicData);
+ ~EclipseC64MusicPlayer();
+
+ void startMusic();
+ void stopMusic();
+ bool isPlaying() const;
+ void initSID();
+ void destroySID();
+
+private:
+ static const uint16 kLoadAddress = 0x0410;
+ static const uint16 kOrderPointerTable = 0x1041;
+ static const uint16 kInstrumentTable = 0x1047;
+ static const uint16 kPatternPointerLowTable = 0x10A7;
+ static const uint16 kPatternPointerHighTable = 0x10C6;
+ static const uint16 kFrequencyHighTable = 0x0F5F;
+ static const uint16 kFrequencyLowTable = 0x0FBE;
+ static const uint16 kArpeggioIntervalTable = 0x148B;
+
+ static const byte kChannelCount = 3;
+ static const byte kInstrumentCount = 12;
+ static const byte kPatternCount = 31;
+ static const byte kMaxNote = 94;
+
+ struct ChannelState {
+ uint16 orderAddr;
+ byte orderPos;
+
+ uint16 patternAddr;
+ byte patternOffset;
+
+ byte instrumentOffset;
+ byte currentNote;
+ byte transpose;
+
+ byte frequencyLow;
+ byte frequencyHigh;
+ byte pulseWidthLow;
+ byte pulseWidthHigh;
+
+ byte durationReload;
+ byte durationCounter;
+
+ byte effectMode;
+ byte effectParam;
+ byte arpeggioTarget;
+ byte arpeggioParam;
+ byte arpeggioSequencePos;
+ byte arpeggioSequence[9];
+ byte arpeggioSequenceLen;
+
+ byte noteStepCommand;
+ byte stepDownCounter;
+
+ byte vibratoPhase;
+ byte vibratoCounter;
+ byte pulseWidthDirection;
+
+ byte delayValue;
+ byte delayCounter;
+
+ byte waveform;
+ byte instrumentFlags;
+ bool gateOffDisabled;
+
+ void reset();
+ };
+
+ SID::SID *_sid;
+ Common::Array<byte> _musicData;
+ byte _arpeggioIntervals[8];
+ bool _musicActive;
+ byte _speedDivider;
+ byte _speedCounter;
+ ChannelState _channels[kChannelCount];
+
+ byte readByte(uint16 address) const;
+ uint16 readWordLE(uint16 address) const;
+ uint16 readPatternPointer(byte index) const;
+ byte readInstrumentByte(byte instrumentOffset, byte field) const;
+ byte readPatternByte(int channel);
+ byte clampNote(byte note) const;
+
+ void sidWrite(int reg, byte data);
+ void onTimer();
+ void silenceAll();
+ void setupSong();
+ void loadNextPattern(int channel);
+ void buildEffectArpeggio(int channel);
+ void loadCurrentFrequency(int channel);
+ void finalizeChannel(int channel);
+ void processChannel(int channel, bool newBeat);
+ void parseCommands(int channel);
+ void applyNote(int channel, byte note);
+ void applyFrameEffects(int channel);
+ bool applyInstrumentVibrato(int channel);
+ void applyEffectArpeggio(int channel);
+ void applyTimedSlide(int channel);
+ void applyPulseWidthModulation(int channel);
+};
+
+} // namespace Freescape
+
+#endif
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index b984bffca00..dc74dcebb30 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -30,6 +30,7 @@
#include "common/translation.h"
#include "freescape/freescape.h"
+#include "freescape/games/eclipse/c64.music.h"
#include "freescape/games/eclipse/eclipse.h"
#include "freescape/language/8bitDetokeniser.h"
@@ -40,6 +41,8 @@ Audio::AudioStream *makeEclipseAtariMusicStream(const byte *data, uint32 dataSiz
int songNum = 1, int rate = 44100);
EclipseEngine::EclipseEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEngine(syst, gd) {
+ _playerC64Music = nullptr;
+
// These sounds can be overriden by the class of each platform
_soundIndexStartFalling = -1;
_soundIndexEndFalling = -1;
@@ -99,6 +102,10 @@ EclipseEngine::EclipseEngine(OSystem *syst, const ADGameDescription *gd) : Frees
_flashlightOn = false;
}
+EclipseEngine::~EclipseEngine() {
+ delete _playerC64Music;
+}
+
void EclipseEngine::initGameState() {
FreescapeEngine::initGameState();
@@ -116,8 +123,10 @@ void EclipseEngine::initGameState() {
_resting = false;
_flashlightOn = false;
- // Start playing music, if any, in any supported format
- playMusic("Total Eclipse Theme");
+ if (isC64() && _playerC64Music)
+ _playerC64Music->startMusic();
+ else
+ playMusic("Total Eclipse Theme");
}
void EclipseEngine::loadAssets() {
diff --git a/engines/freescape/games/eclipse/eclipse.h b/engines/freescape/games/eclipse/eclipse.h
index 9d902e65795..88eadf1862d 100644
--- a/engines/freescape/games/eclipse/eclipse.h
+++ b/engines/freescape/games/eclipse/eclipse.h
@@ -25,6 +25,8 @@
namespace Freescape {
+class EclipseC64MusicPlayer;
+
enum EclipseReleaseFlags {
GF_ZX_DEMO_CRASH = (1 << 0),
GF_ZX_DEMO_MICROHOBBY = (1 << 1),
@@ -37,6 +39,7 @@ enum {
class EclipseEngine : public FreescapeEngine {
public:
EclipseEngine(OSystem *syst, const ADGameDescription *gd);
+ ~EclipseEngine() override;
void gotoArea(uint16 areaID, int entranceID) override;
@@ -105,6 +108,8 @@ public:
void drawHeartIndicator(Graphics::Surface *surface, int x, int y);
Common::Array<byte> _musicData; // TEMUSIC.ST TEXT segment (Atari ST)
+ Common::Array<byte> _c64MusicData;
+ EclipseC64MusicPlayer *_playerC64Music;
// Atari ST UI sprites (extracted from binary, pre-converted to target format)
Font _fontScore; // Font B (10 score digit glyphs, 4-plane at $249BE)
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index b4ba06e75ad..914af1a0015 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -36,6 +36,7 @@ MODULE_OBJS := \
games/eclipse/atari.o \
games/eclipse/atari.music.o \
games/eclipse/c64.o \
+ games/eclipse/c64.music.o \
games/eclipse/dos.o \
games/eclipse/eclipse.o \
games/eclipse/cpc.o \
More information about the Scummvm-git-logs
mailing list