//============================================================================ // // SSSS tt lll lll // SS SS tt ll ll // SS tttttt eeee ll ll aaaa // SSSS tt ee ee ll ll aa // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" // SS SS tt ee ll ll aa aa // SSSS ttt eeeee llll llll aaaaa // // Copyright (c) 1995-2023 by Bradford W. Mott, Stephen Anthony // and the Stella Team // // See the file "License.txt" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ #include "Console.hxx" #include "Event.hxx" #include "FSNode.hxx" #include "OSystem.hxx" #include "Sound.hxx" #include "Switches.hxx" #include "KidVid.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - KidVid::KidVid(Jack jack, const Event& event, const OSystem& osystem, const System& system, string_view romMd5, const onMessageCallbackForced& callback) : Controller(jack, event, system, Controller::Type::KidVid), myEnabled{myJack == Jack::Right}, myOSystem{osystem}, myCallback{callback} { // Right now, there are only two games that use the KidVid if(romMd5 == "ee6665683ebdb539e89ba620981cb0f6") myGame = Game::BBears; // Berenstain Bears else if(romMd5 == "a204cd4fb1944c86e800120706512a64") myGame = Game::Smurfs; // Smurfs Save the Day else myEnabled = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void KidVid::write(DigitalPin pin, bool value) { // Change the pin state based on value switch(pin) { // Pin 1: Signal tape running or stopped case DigitalPin::One: setPin(DigitalPin::One, value); break; default: break; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void KidVid::update() { if(!myEnabled) return; if(myContinueSong) { // Continue playing song after state load const uInt8 temp = ourSongPositions[mySongPointer - 1] & 0x7f; const uInt32 songLength = ourSongStart[temp + 1] - ourSongStart[temp] - (262 * ClickFrames); // Play the remaining WAV file const string& fileName = myOSystem.baseDir().getPath() + ((temp < 10) ? "KVSHARED.WAV": getFileName()); myOSystem.sound().playWav(fileName, ourSongStart[temp] + (songLength - mySongLength), mySongLength); myContinueSong = false; } if(myGame == Game::Smurfs && myEvent.get(Event::ConsoleReset)) // Reset does not work with BBears! { myTape = 0; // rewind Kid Vid tape myFilesFound = mySongPlaying = false; myOSystem.sound().stopWav(); } else if(myEvent.get(Event::RightKeyboard6) || myEvent.get(Event::ConsoleSelect) || (myOSystem.hasConsole() && !myOSystem.console().switches().tvColor())) { // Some first songs trigger a sequence of timed actions, they cannot be skipped if(mySongPointer && ourSongPositions[mySongPointer - 1] != 0 && // First song of all BBears games ourSongPositions[mySongPointer - 1] != 11) // First song of Harmony Smurf myOSystem.sound().stopWav(); } if(!myTape) { if(myEvent.get(Event::RightKeyboard1)) myTape = 2; else if(myEvent.get(Event::RightKeyboard2)) myTape = 3; else if(myEvent.get(Event::RightKeyboard3)) myTape = myGame == Game::BBears ? 4 : 1; // Berenstain Bears or Smurfs Save The Day? // If no Keyboard controller is available, use SELECT and the same // switches settings as for playing Smurfs without a Kid Vid. else if(myEvent.get(Event::ConsoleSelect) && myOSystem.hasConsole()) { // Harmony (2) : A B // Handy (3) : B A // Greedy (1/4): B B myTape = 1 + (myOSystem.console().switches().leftDifficultyA() ? 1 : 0) + (myOSystem.console().switches().rightDifficultyA() ? 2 : 0); if(myTape == 4) // ignore A A myTape = 0; else if(myTape == 1 && myGame == Game::BBears) myTape = 4; } if(myTape) { static constexpr uInt32 gameNumber[4] = { 3, 1, 2, 3 }; static constexpr string_view gameName[6] = { "Harmony Smurf", "Handy Smurf", "Greedy Smurf", "Big Number Hunt", "Great Letter Roundup", "Spooky Spelling Bee" }; myIdx = myGame == Game::BBears ? NumBlockBits : 0; // KVData48/KVData44 myBlockIdx = NumBlockBits; myBlock = 0; openSampleFiles(); ostringstream msg; msg << "Game #" << gameNumber[myTape - 1] << " - \"" << gameName[gameNumber[myTape - 1] + (myGame == Game::Smurfs ? -1 : 2)] << "\""; myCallback(msg.str(), true); } } // Is the tape running? if(myTape && getPin(DigitalPin::One) && !myTapeBusy) { setPin(DigitalPin::Four, (ourData[myIdx >> 3] << (myIdx & 0x07)) & 0x80); #ifdef DEBUG_BUILD cerr << (DigitalPin::Four, (ourData[myIdx >> 3] << (myIdx & 0x07)) & 0x80 ? "X" : "."); #endif // increase to next bit ++myIdx; --myBlockIdx; // increase to next block (byte) if(!myBlockIdx) { if(!myBlock) myIdx = ((myTape * 6) + 12 - NumBlocks) * 8; // KVData00 - ourData = 12 (2 * 6) else { const uInt32 lastBlock = myGame == Game::Smurfs ? ourBlocks[myTape - 1] : ourBlocks[myTape + 2 - 1]; if(myBlock >= lastBlock) myIdx = 42 * 8; // KVData80 - ourData = 42 (7 * 6) else { myIdx = 36 * 8; // KVPause - ourData = 36 (6 * 6) setNextSong(); } } ++myBlock; myBlockIdx = NumBlockBits; } } if(myFilesFound) { if(mySongPlaying) { mySongLength = myOSystem.sound().wavSize(); myTapeBusy = (mySongLength > 262 * TapeFrames) || !myBeep; // Check for end of played sample if(mySongLength == 0) { mySongPlaying = false; myTapeBusy = !myBeep; if(!myBeep) setNextSong(); } } } else { if(mySongLength) { --mySongLength; myTapeBusy = (mySongLength > TapeFrames); } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool KidVid::save(Serializer& out) const { // Save WAV player state out.putInt(myTape); out.putBool(myFilesFound); out.putBool(myTapeBusy); out.putBool(myBeep); out.putBool(mySongPlaying); out.putInt(mySongPointer); out.putInt(mySongLength); // Save tape input simulation state out.putInt(myIdx); out.putInt(myBlockIdx); out.putInt(myBlock); return Controller::save(out); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool KidVid::load(Serializer& in) { // Load WAV player state myTape = in.getInt(); myFilesFound = in.getBool(); myTapeBusy = in.getBool(); myBeep = in.getBool(); mySongPlaying = in.getBool(); mySongPointer = in.getInt(); mySongLength = in.getInt(); // Load tape input simulation state myIdx = in.getInt(); myBlockIdx = in.getInt(); myBlock = in.getInt(); myContinueSong = myFilesFound && mySongPlaying; return Controller::load(in); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const char* KidVid::getFileName() const { static constexpr const char* const fileNames[6] = { "KVS3.WAV", "KVS1.WAV", "KVS2.WAV", "KVB3.WAV", "KVB1.WAV", "KVB2.WAV" }; int i = myGame == Game::Smurfs ? myTape - 1 : myTape + 2; if(myTape == 4) i = 3; return fileNames[i]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void KidVid::openSampleFiles() { #ifdef SOUND_SUPPORT static constexpr uInt32 firstSongPointer[6] = { 44 + 38, 0, 44, 44 + 38 + 42 + 62 + 80, 44 + 38 + 42, 44 + 38 + 42 + 62 }; if(!myFilesFound) { int i = myGame == Game::Smurfs ? myTape - 1 : myTape + 2; if(myTape == 4) i = 3; myFilesFound = FSNode(myOSystem.baseDir().getPath() + getFileName()).exists() && FSNode(myOSystem.baseDir().getPath() + "KVSHARED.WAV").exists(); #ifdef DEBUG_BUILD if(myFilesFound) cerr << endl << "found file: " << getFileName() << endl << "found file: " << "KVSHARED.WAV" << endl; #endif mySongLength = 0; mySongPointer = firstSongPointer[i]; } myTapeBusy = false; #endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void KidVid::setNextSong() { if(myFilesFound) { myBeep = (ourSongPositions[mySongPointer] & 0x80) == 0; const uInt8 temp = ourSongPositions[mySongPointer] & 0x7f; mySongLength = ourSongStart[temp + 1] - ourSongStart[temp] - (262 * ClickFrames); // Play the WAV file const string& fileName = (temp < 10) ? "KVSHARED.WAV" : getFileName(); myOSystem.sound().playWav(myOSystem.baseDir().getPath() + fileName, ourSongStart[temp], mySongLength); ostringstream msg; msg << "Read song #" << mySongPointer << " (" << fileName << ")"; myCallback(msg.str(), false); #ifdef DEBUG_BUILD cerr << fileName << ": " << (ourSongPositions[mySongPointer] & 0x7f) << endl; #endif mySongPlaying = myTapeBusy = true; ++mySongPointer; } else { myBeep = true; myTapeBusy = true; mySongLength = TapeFrames + 32; /* delay needed for Harmony without tape */ } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const std::array KidVid::ourBlocks = { 2+40, 2+21, 2+35, /* Smurfs tapes 3, 1, 2 */ 42+60, 42+78, 42+60 /* BBears tapes 1, 2, 3 (40 extra blocks for intro) */ }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const std::array KidVid::ourData = { /* KVData44, Smurfs */ 0x7b, // 0111 1011b ; (1)0 0x1e, // 0001 1110b ; 1 0xc6, // 1100 0110b ; 00 0x31, // 0011 0001b ; 01 0xec, // 1110 1100b ; 0 0x60, // 0110 0000b ; 0+ /* KVData48, BBears */ 0x7b, // 0111 1011b ; (1)0 0x1e, // 0001 1110b ; 1 0xc6, // 1100 0110b ; 00 0x3d, // 0011 1101b ; 10 0x8c, // 1000 1100b ; 0 0x60, // 0110 0000b ; 0+ /* KVData00 */ 0xf6, // 1111 0110b 0x31, // 0011 0001b 0x8c, // 1000 1100b 0x63, // 0110 0011b 0x18, // 0001 1000b 0xc0, // 1100 0000b /* KVData01 */ 0xf6, // 1111 0110b 0x31, // 0011 0001b 0x8c, // 1000 1100b 0x63, // 0110 0011b 0x18, // 0001 1000b 0xf0, // 1111 0000b /* KVData02 */ 0xf6, // 1111 0110b 0x31, // 0011 0001b 0x8c, // 1000 1100b 0x63, // 0110 0011b 0x1e, // 0001 1110b 0xc0, // 1100 0000b /* KVData03 */ 0xf6, // 1111 0110b 0x31, // 0011 0001b 0x8c, // 1000 1100b 0x63, // 0110 0011b 0x1e, // 0001 1110b 0xf0, // 1111 0000b /* KVPause */ 0x3f, // 0011 1111b 0xf0, // 1111 0000b 0x00, // 0000 0000b 0x00, // 0000 0000b 0x00, // 0000 0000b 0x00, // 0000 0000b /* KVData80 */ 0xf7, // 1111 0111b ; marks end of tape (green/yellow screen) 0xb1, // 1011 0001b 0x8c, // 1000 1100b 0x63, // 0110 0011b 0x18, // 0001 1000b 0xc0 // 1100 0000b }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const std::array KidVid::ourSongPositions = { /* kvs1 44 */ 11, 12+0x80, 13+0x80, 14, 15+0x80, 16, 8+0x80, 17, 18+0x80, 19, 20+0x80, 21, 8+0x80, 22, 15+0x80, 23, 18+0x80, 14, 20+0x80, 16, 18+0x80, 17, 15+0x80, 19, 8+0x80, 21, 20+0x80, 22, 18+0x80, 23, 15+0x80, 14, 20+0x80, 16, 8+0x80, 22, 15+0x80, 23, 18+0x80, 14, 20+0x80, 16, 8+0x80, 9, /* kvs2 38 */ 25+0x80, 26, 27, 28, 8, 29, 30, 26, 27, 28, 8, 29, 30, 26, 27, 28, 8, 29, 30, 26, 27, 28, 8, 29, 30, 26, 27, 28, 8, 29, 30, 26, 27, 28, 8, 29, 30+0x80, 9, /* kvs3 42 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 34, 42, 36, 43, 40, 39, 38, 37, 34, 43, 36, 39, 40, 37, 38, 43, 34, 37, 36, 43, 40, 39, 38, 37, 34, 43, 36, 39, 40, 37, 38+0x80, 9, /* kvb1 62 */ 0, 1, 45, 2, 3, 46, 4, 5, 47, 6, 7, 48, 4, 3, 49, 2, 1, 50, 6, 7, 51, 4, 5, 52, 6, 1, 53, 2, 7, 54, 6, 5, 45, 2, 1, 46, 4, 3, 47, 2, 5, 48, 4, 7, 49, 6, 1, 50, 2, 5, 51, 6, 3, 52, 4, 7, 53, 2, 1, 54, 6+0x80, 9, /* kvb2 80 */ 0, 1, 56, 4, 3, 57, 2, 5, 58, 6, 7, 59, 2, 3, 60, 4, 1, 61, 6, 7, 62, 2, 5, 63, 6, 1, 64, 4, 7, 65, 6, 5, 66, 4, 1, 67, 2, 3, 68, 6, 5, 69, 2, 7, 70, 4, 1, 71, 2, 5, 72, 4, 3, 73, 6, 7, 74, 2, 1, 75, 6, 3, 76, 4, 5, 77, 6, 7, 78, 2, 3, 79, 4, 1, 80, 2, 7, 81, 4+0x80, 9, /* kvb3 62 */ 0, 1, 83, 2, 3, 84, 4, 5, 85, 6, 7, 86, 4, 3, 87, 2, 1, 88, 6, 7, 89, 2, 5, 90, 6, 1, 91, 4, 7, 92, 6, 5, 93, 4, 1, 94, 2, 3, 95, 6, 5, 96, 2, 7, 97, 4, 1, 98, 6, 5, 99, 4, 3, 100, 2, 7, 101, 4, 1, 102, 2+0x80, 9 }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const std::array KidVid::ourSongStart = { /* kvshared */ 44, /* Welcome + intro Berenstain Bears */ 980829, /* boulders in the road */ 1178398, /* standing ovations */ 1430063, /* brother bear */ 1691136, /* good work */ 1841665, /* crossing a bridge */ 2100386, /* not bad (applause) */ 2283843, /* ourgame */ 2629588, /* start the parade */ 2824805, /* rewind */ 3059116, /* kvs1 */ 44, /* Harmony intro 1 */ 164685, /* falling notes (intro 2) */ 395182, /* instructions */ 750335, /* high notes are high */ 962016, /* my hat's off to you */ 1204273, /* 1 2 3 do re mi */ 1538258, /* Harmony */ 1801683, /* congratulations (all of the Smurfs voted) */ 2086276, /* line or space */ 2399093, /* hooray */ 2589606, /* hear yeeh */ 2801287, /* over the river */ 3111752, /* musical deduction */ 3436329, /* kvs2 */ 44, /* Handy intro + instructions */ 778557, /* place in shape */ 1100782, /* sailor mate + whistle */ // 1281887, 1293648, /* attention */ 1493569, /* colours */ 1801682, /* congratulations (Handy and friends voted) */ 2086275, /* kvs3 */ 44, /* Greedy and Clumsy intro + instructions */ 686829, /* red */ 893806, /* don't count your chicken */ 1143119, /* yellow */ 1385376, /* thank you */ 1578241, /* mixin' and matchin' */ 1942802, /* fun / colour shake */ 2168595, /* colours can be usefull */ 2493172, /* hip hip horay */ 2662517, /* green */ 3022374, /* purple */ 3229351, /* white */ 3720920, /* kvb1 */ 44, /* 3 */ // can be one too late! 592749, /* 5 */ 936142, /* 2 */ 1465343, /* 4 */ 1787568, /* 1 */ 2145073, /* 7 */ 2568434, /* 9 */ 2822451, /* 8 */ 3045892, /* 6 */ 3709157, /* 0 */ 4219542, /* kvb2 */ 44, /* A */ 303453, /* B */ 703294, /* C */ 1150175, /* D */ 1514736, /* E */ 2208577, /* F */ 2511986, /* G */ 2864787, /* H */ 3306964, /* I */ 3864389, /* J */ 4148982, /* K */ 4499431, /* L */ 4824008, /* M */ 5162697, /* N */ 5581354, /* O */ 5844779, /* P */ 6162300, /* Q */ 6590365, /* R */ 6839678, /* S */ 7225407, /* T */ 7552336, /* U */ 7867505, /* V */ 8316738, /* W */ 8608387, /* X */ 8940020, /* Y */ 9274005, /* Z */ 9593878, /* kvb3 */ 44, /* cat */ 341085, /* one */ 653902, /* red */ 1018463, /* two */ 1265424, /* dog */ 1669969, /* six */ 1919282, /* hat */ 2227395, /* ten */ 2535508, /* mom */ 3057653, /* dad */ 3375174, /* ball */ 3704455, /* fish */ 4092536, /* nine */ 4487673, /* bear */ 5026282, /* four */ 5416715, /* bird */ 5670732, /* tree */ 6225805, /* rock */ 6736190, /* book */ 7110159, /* road */ 7676992 };