Movie Cart PAL format (#990)

* MVC format expanded to include vsync, vblank, overscan, visible, and framerate.
Allows for playback of various formats including, PAL.

* Match MovieCart title screen format to detected timing.
Does not affect encoded video content, just title screen.

* simple moviecart PAL examples

---------

Co-authored-by: LoDef Mode <lodef.mode@gmail.ca>
This commit is contained in:
lodefmode 2023-09-16 09:43:25 -04:00 committed by GitHub
parent afe84954a8
commit b2004d0c49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 56 deletions

2
src/emucore/CartDetector.cxx Normal file → Executable file
View File

@ -752,7 +752,7 @@ bool CartDetector::isProbablyMVC(const ByteBuffer& image, size_t size)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t CartDetector::isProbablyMVC(const FSNode& rom) size_t CartDetector::isProbablyMVC(const FSNode& rom)
{ {
constexpr size_t frameSize = 2 * CartridgeMVC::MVC_FIELD_PAD_SIZE; constexpr size_t frameSize = 2 * CartridgeMVC::MVC_FIELD_SIZE;
if(Bankswitch::typeFromExtension(rom) == Bankswitch::Type::_MVC) if(Bankswitch::typeFromExtension(rom) == Bankswitch::Type::_MVC)
return frameSize; return frameSize;

View File

@ -65,7 +65,7 @@ class StreamReader : public Serializable
} }
void blankPartialLines(bool index) { void blankPartialLines(bool index) {
constexpr int colorSize = 192 * 5; int colorSize = myVisibleLines * 5;
if (index) if (index)
{ {
// top line // top line
@ -91,13 +91,62 @@ class StreamReader : public Serializable
void swapField(bool index, bool odd) { void swapField(bool index, bool odd) {
uInt8* offset = index ? myBuffer1.data() : myBuffer2.data(); uInt8* offset = index ? myBuffer1.data() : myBuffer2.data();
myVersion = offset + VERSION_DATA_OFFSET; class FrameFormat
myFrame = offset + FRAME_DATA_OFFSET; {
myAudio = offset + AUDIO_DATA_OFFSET; public:
myGraph = offset + GRAPH_DATA_OFFSET;
myTimecode = offset + TIMECODE_DATA_OFFSET; uInt8 version[4]; // ('M', 'V', 'C', 0)
myColor = offset + COLOR_DATA_OFFSET; uInt8 format; // ( 1-------)
myColorBK = offset + COLORBK_DATA_OFFSET; uInt8 timecode[4]; // (hour, minute, second, fame)
uInt8 vsync; // eg 3
uInt8 vblank; // eg 37
uInt8 overscan; // eg 30
uInt8 visible; // eg 192
uInt8 rate; // eg 60
uInt8 dataStart;
// sound[vsync+blank+overscan+visible]
// graph[5 * visible]
// color[5 * visible]
// bkcolor[1 * visible]
// timecode[60]
// padding
};
FrameFormat* ff = (FrameFormat* )offset;
if (ff->format & 0x80)
{
myVSyncLines = ff->vsync;
myBlankLines = ff->vblank;
myOverscanLines = ff->overscan;
myVisibleLines = ff->visible;
myEmbeddedFrame = ff->timecode[3] + 1;
int totalLines = myVSyncLines + myBlankLines + myOverscanLines + myVisibleLines;
myAudio = (uInt8*)(&ff->dataStart);
myGraph = myAudio + totalLines;
myColor = ((uInt8*)myGraph) + 5 * myVisibleLines;
myColorBK = myColor + 5 * myVisibleLines;
myTimecode = myColorBK + 1 * myVisibleLines;
}
else // previous format, ntsc assumed
{
myVSyncLines = 3;
myBlankLines = 37;
myOverscanLines = 30;
myVisibleLines = 192;
myEmbeddedFrame = offset[4 + 3 -1];
int totalLines = myVSyncLines + myBlankLines + myOverscanLines + myVisibleLines;
myAudio = offset + 4 + 3;
myGraph = myAudio + totalLines;
myTimecode = ((uInt8*)myGraph) + 5*myVisibleLines;
myColor = ((uInt8*)myTimecode) + 60;
myColorBK = myColor + 5*myVisibleLines;
}
if (!odd) if (!odd)
myColorBK++; myColorBK++;
@ -106,9 +155,9 @@ class StreamReader : public Serializable
bool readField(uInt32 fnum, bool index) { bool readField(uInt32 fnum, bool index) {
if(myFile) if(myFile)
{ {
const size_t offset = ((fnum + 0) * CartridgeMVC::MVC_FIELD_PAD_SIZE); const size_t offset = ((fnum + 0) * CartridgeMVC::MVC_FIELD_SIZE);
if(offset + CartridgeMVC::MVC_FIELD_PAD_SIZE < myFileSize) if(offset + CartridgeMVC::MVC_FIELD_SIZE < myFileSize)
{ {
myFile.setPosition(offset); myFile.setPosition(offset);
if(index) if(index)
@ -122,8 +171,6 @@ class StreamReader : public Serializable
return false; return false;
} }
uInt8 readVersion() { return *myVersion++; }
uInt8 readFrame() { return *myFrame++; }
uInt8 readColor() { return *myColor++; } uInt8 readColor() { return *myColor++; }
uInt8 readColorBK() { return *myColorBK++; } uInt8 readColorBK() { return *myColorBK++; }
@ -135,6 +182,12 @@ class StreamReader : public Serializable
uInt8 readAudio() { return *myAudio++; } uInt8 readAudio() { return *myAudio++; }
uInt8 getVisibleLines() { return myVisibleLines; }
uInt8 getVSyncLines() { return myVSyncLines; }
uInt8 getBlankLines() { return myBlankLines; }
uInt8 getOverscanLines() { return myOverscanLines; }
uInt8 getEmbeddedFrame() { return myEmbeddedFrame; }
[[nodiscard]] uInt8 peekAudio() const { return *myAudio; } [[nodiscard]] uInt8 peekAudio() const { return *myAudio; }
void startTimeCode() { myGraph = myTimecode; } void startTimeCode() { myGraph = myTimecode; }
@ -152,8 +205,6 @@ class StreamReader : public Serializable
const uInt8* myTimecode const uInt8* myTimecode
const uInt8* myColor const uInt8* myColor
const uInt8* myColorBK const uInt8* myColorBK
const uInt8* myVersion
const uInt8* myFrame
#endif #endif
} }
catch(...) catch(...)
@ -176,8 +227,6 @@ class StreamReader : public Serializable
const uInt8* myTimecode const uInt8* myTimecode
const uInt8* myColor const uInt8* myColor
const uInt8* myColorBK const uInt8* myColorBK
const uInt8* myVersion
const uInt8* myFrame
#endif #endif
} }
catch(...) catch(...)
@ -188,16 +237,6 @@ class StreamReader : public Serializable
} }
private: private:
static constexpr int
VERSION_DATA_OFFSET = 0,
FRAME_DATA_OFFSET = 4,
AUDIO_DATA_OFFSET = 7,
GRAPH_DATA_OFFSET = 269,
TIMECODE_DATA_OFFSET = 1229,
COLOR_DATA_OFFSET = 1289,
COLORBK_DATA_OFFSET = 2249,
END_DATA_OFFSET = 2441;
const uInt8* myAudio{nullptr}; const uInt8* myAudio{nullptr};
const uInt8* myGraph{nullptr}; const uInt8* myGraph{nullptr};
@ -206,12 +245,16 @@ class StreamReader : public Serializable
const uInt8* myTimecode{nullptr}; const uInt8* myTimecode{nullptr};
uInt8* myColor{nullptr}; uInt8* myColor{nullptr};
uInt8* myColorBK{nullptr}; uInt8* myColorBK{nullptr};
const uInt8* myVersion{nullptr};
const uInt8* myFrame{nullptr};
std::array<uInt8, CartridgeMVC::MVC_FIELD_SIZE> myBuffer1; std::array<uInt8, CartridgeMVC::MVC_FIELD_SIZE> myBuffer1;
std::array<uInt8, CartridgeMVC::MVC_FIELD_SIZE> myBuffer2; std::array<uInt8, CartridgeMVC::MVC_FIELD_SIZE> myBuffer2;
uInt8 myVisibleLines{192};
uInt8 myVSyncLines{3};
uInt8 myBlankLines{37};
uInt8 myOverscanLines{30};
uInt8 myEmbeddedFrame{0};
Serializer myFile; Serializer myFile;
size_t myFileSize{0}; size_t myFileSize{0};
}; };
@ -301,8 +344,7 @@ class MovieInputs : public Serializable
static constexpr uInt8 static constexpr uInt8
TIMECODE_HEIGHT = 12, TIMECODE_HEIGHT = 12,
MAX_LEVEL = 11, MAX_LEVEL = 11,
DEFAULT_LEVEL = 6, DEFAULT_LEVEL = 6;
BLANK_LINE_SIZE = (30+3+37-1); // 70-1
// Automatically generated // Automatically generated
// Several not used // Several not used
@ -338,9 +380,12 @@ static constexpr uInt16
addr_end_lines = 0xa80, addr_end_lines = 0xa80,
addr_set_aud_endlines = 0xa80, addr_set_aud_endlines = 0xa80,
addr_set_overscan_size = 0xa9a, addr_set_overscan_size = 0xa9a,
addr_set_vsync_size = 0xaa3,
addr_set_vblank_size = 0xab0, addr_set_vblank_size = 0xab0,
addr_pick_extra_lines = 0xab9, addr_pick_extra_lines = 0xab9,
addr_pick_transport = 0xac6, addr_pick_transport = 0xac6,
addr_title_gap1 = 0xb2c,
addr_title_gap2 = 0xb40,
addr_title_loop = 0xb50, addr_title_loop = 0xb50,
addr_audio_bank = 0xb80; addr_audio_bank = 0xb80;
@ -752,6 +797,8 @@ class MovieCart : public Serializable
myROM[address & 1023] = data; myROM[address & 1023] = data;
} }
void setConsoleTiming(ConsoleTiming timing);
private: private:
enum Mode : uInt8 enum Mode : uInt8
{ {
@ -880,6 +927,33 @@ bool MovieCart::init(string_view path)
return true; return true;
} }
#define RAINBOW_HEIGHT 30
#define TITLE_HEIGHT 12
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MovieCart::setConsoleTiming(ConsoleTiming timing)
{
uInt8 lines;
switch(timing)
{
case ConsoleTiming::ntsc:
default:
lines = 192;
break;
case ConsoleTiming::pal:
case ConsoleTiming::secam:
lines = 242;
break;
}
uInt8 val = (lines - RAINBOW_HEIGHT - RAINBOW_HEIGHT - TITLE_HEIGHT * 2) / 2;
writeROM(addr_title_gap1 + 1, val);
writeROM(addr_title_gap2 + 1, val);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MovieCart::writeColor(uInt16 address, uInt8 v) void MovieCart::writeColor(uInt16 address, uInt8 v)
{ {
@ -1192,17 +1266,19 @@ void MovieCart::fill_addr_end_lines()
// keep at overscan=29, vblank=36 // keep at overscan=29, vblank=36
// or overscan=30, vblank=36 + 1 blank line // or overscan=30, vblank=36 + 1 blank line
writeROM(addr_set_vsync_size + 1, myStream.getVSyncLines());
if(myOdd) if(myOdd)
{ {
writeROM(addr_set_overscan_size + 1, 29); writeROM(addr_set_overscan_size + 1, myStream.getOverscanLines()-1);
writeROM(addr_set_vblank_size + 1, 36); writeROM(addr_set_vblank_size + 1, myStream.getBlankLines()-1);
writeROM(addr_pick_extra_lines + 1, 0); writeROM(addr_pick_extra_lines + 1, 0);
} }
else else
{ {
writeROM(addr_set_overscan_size + 1, 30); writeROM(addr_set_overscan_size + 1, myStream.getOverscanLines());
writeROM(addr_set_vblank_size + 1, 36); writeROM(addr_set_vblank_size + 1, myStream.getBlankLines()-1);
// extra line after vblank // extra line after vblank
writeROM(addr_pick_extra_lines + 1, 1); writeROM(addr_pick_extra_lines + 1, 1);
@ -1223,33 +1299,19 @@ void MovieCart::fill_addr_end_lines()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void MovieCart::fill_addr_blank_lines() void MovieCart::fill_addr_blank_lines()
{ {
// version number myOdd = (myStream.getEmbeddedFrame() & 1);
myStream.readVersion();
myStream.readVersion();
myStream.readVersion();
myStream.readVersion();
// frame number uInt8 blankTotal = (myStream.getOverscanLines() + myStream.getVSyncLines() + myStream.getBlankLines()-1); // 70-1
myStream.readFrame();
myStream.readFrame();
const uInt8 v = myStream.readFrame();
// make sure we're in sync with frame data
myOdd = (v & 1);
// 30 overscan
// 3 vsync
// 37 vblank
if(myOdd) if(myOdd)
{ {
writeAudioData(addr_audio_bank + 0, myFirstAudioVal); writeAudioData(addr_audio_bank + 0, myFirstAudioVal);
for(uInt8 i = 1; i < (BLANK_LINE_SIZE + 1); i++) for(uInt8 i = 1; i < (blankTotal + 1); i++)
writeAudio(addr_audio_bank + i); writeAudio(addr_audio_bank + i);
} }
else else
{ {
for(uInt8 i = 0; i < (BLANK_LINE_SIZE -1); i++) for(uInt8 i = 0; i < (blankTotal -1); i++)
writeAudio(addr_audio_bank + i); writeAudio(addr_audio_bank + i);
} }
} }
@ -1397,7 +1459,7 @@ void MovieCart::runStateMachine()
} }
myForceColor = 0; myForceColor = 0;
myLines = 191; myLines = myStream.getVisibleLines() - 1;
myState = 1; myState = 1;
} }
break; break;
@ -1590,6 +1652,12 @@ void CartridgeMVC::reset()
myMovie->init(myPath); myMovie->init(myPath);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeMVC::consoleChanged(ConsoleTiming timing)
{
myMovie->setConsoleTiming(timing);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const ByteBuffer& CartridgeMVC::getImage(size_t& size) const const ByteBuffer& CartridgeMVC::getImage(size_t& size) const
{ {

13
src/emucore/CartMVC.hxx Normal file → Executable file
View File

@ -37,8 +37,7 @@ class CartridgeMVC : public Cartridge
{ {
public: public:
static constexpr size_t static constexpr size_t
MVC_FIELD_SIZE = 2560, // round field to nearest 512 byte boundary MVC_FIELD_SIZE = 4096;
MVC_FIELD_PAD_SIZE = 4096; // round to nearest 4K
public: public:
/** /**
@ -123,6 +122,16 @@ class CartridgeMVC : public Cartridge
*/ */
bool load(Serializer& in) override; bool load(Serializer& in) override;
protected:
/**
Notification method invoked by the system when the console type
has changed. Simply used to change titlescreen format, content
still plays as encoded.
@param timing Enum representing the new console type
*/
void consoleChanged(ConsoleTiming timing) override;
private: private:
// Currently not used: // Currently not used:
// Pointer to a dynamically allocated ROM image of the cartridge // Pointer to a dynamically allocated ROM image of the cartridge

Binary file not shown.

Binary file not shown.

Binary file not shown.