mirror of https://github.com/stella-emu/stella.git
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:
parent
afe84954a8
commit
b2004d0c49
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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.
Loading…
Reference in New Issue