2017-01-18 01:15:46 +00:00
|
|
|
"use strict";
|
2017-02-14 23:15:06 +00:00
|
|
|
|
2017-01-18 01:15:46 +00:00
|
|
|
var VICDUAL_PRESETS = [
|
2017-02-20 23:50:29 +00:00
|
|
|
{id:'minimal.c', name:'Minimal Example'},
|
|
|
|
{id:'hello.c', name:'Hello World'},
|
|
|
|
{id:'gfxtest.c', name:'Graphics Test'},
|
|
|
|
{id:'soundtest.c', name:'Sound Test'},
|
2017-03-15 14:11:28 +00:00
|
|
|
{id:'snake1.c', name:'Snake Game (Prototype)'},
|
|
|
|
{id:'snake2.c', name:'Snake Game (Full)'},
|
2017-03-31 17:25:18 +00:00
|
|
|
{id:'music.c', name:'Music Example'},
|
2017-01-18 01:15:46 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
var VicDualPlatform = function(mainElement) {
|
|
|
|
var self = this;
|
|
|
|
this.__proto__ = new BaseZ80Platform();
|
|
|
|
|
|
|
|
var cpu, ram, membus, iobus, rom;
|
2017-01-27 02:59:34 +00:00
|
|
|
var video, audio, psg, timer, pixels;
|
2017-02-20 23:50:29 +00:00
|
|
|
var inputs = [0xff, 0xff, 0xff, 0xff^0x8]; // most things active low
|
2017-01-26 05:09:57 +00:00
|
|
|
var palbank = 0;
|
2017-01-18 01:15:46 +00:00
|
|
|
|
|
|
|
var XTAL = 15468000.0;
|
|
|
|
var scanlinesPerFrame = 0x106;
|
|
|
|
var vsyncStart = 0xec;
|
|
|
|
var vsyncEnd = 0xf0;
|
|
|
|
var cpuFrequency = XTAL/8;
|
|
|
|
var hsyncFrequency = XTAL/3/scanlinesPerFrame;
|
|
|
|
var vsyncFrequency = hsyncFrequency/0x148;
|
|
|
|
var cpuCyclesPerLine = cpuFrequency/hsyncFrequency;
|
|
|
|
var timerFrequency = 500; // TODO
|
2017-02-20 23:50:29 +00:00
|
|
|
var reset_disable = false;
|
|
|
|
var reset_disable_timer;
|
2017-03-12 22:47:44 +00:00
|
|
|
var framestats;
|
2017-01-18 01:15:46 +00:00
|
|
|
|
|
|
|
var palette = [
|
2017-01-26 05:09:57 +00:00
|
|
|
0xff000000, // black
|
2017-01-18 01:15:46 +00:00
|
|
|
0xff0000ff, // red
|
2017-01-26 05:09:57 +00:00
|
|
|
0xff00ff00, // green
|
2017-01-18 01:15:46 +00:00
|
|
|
0xff00ffff, // yellow
|
2017-01-26 05:09:57 +00:00
|
|
|
0xffff0000, // blue
|
|
|
|
0xffff00ff, // magenta
|
2017-01-18 01:15:46 +00:00
|
|
|
0xffffff00, // cyan
|
|
|
|
0xffffffff // white
|
|
|
|
];
|
|
|
|
|
2017-01-26 05:09:57 +00:00
|
|
|
var colorprom = [
|
|
|
|
0,0,0,0,0,0,0,0,
|
|
|
|
7,3,1,3,6,3,2,6,
|
|
|
|
7,0,0,0,0,0,0,0,
|
|
|
|
0,1,2,3,4,5,6,7,
|
2017-02-18 22:50:51 +00:00
|
|
|
0,0,0,0,0,0,0,0,
|
|
|
|
7,7,7,7,3,3,3,3,
|
|
|
|
0,0,0,0,0,0,0,0,
|
|
|
|
7,7,7,7,7,7,7,7,
|
2017-01-26 05:09:57 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
// videoram 0xc000-0xc3ff
|
|
|
|
// RAM 0xc400-0xc7ff
|
|
|
|
// charram 0xc800-0xcfff
|
2017-01-18 01:15:46 +00:00
|
|
|
function drawScanline(pixels, sl) {
|
|
|
|
if (sl >= 224) return;
|
|
|
|
var pixofs = sl*256;
|
|
|
|
var outi = pixofs; // starting output pixel in frame buffer
|
|
|
|
var vramofs = (sl>>3)<<5; // offset in VRAM
|
|
|
|
var yy = sl & 7; // y offset within tile
|
|
|
|
for (var xx=0; xx<32; xx++) {
|
2017-01-27 02:59:34 +00:00
|
|
|
var code = ram.mem[vramofs+xx];
|
|
|
|
var data = ram.mem[0x800 + (code<<3) + yy];
|
|
|
|
var col = (code>>5) + (palbank<<4);
|
2017-01-26 05:09:57 +00:00
|
|
|
var color1 = palette[colorprom[col]];
|
|
|
|
var color2 = palette[colorprom[col+8]];
|
2017-01-18 01:15:46 +00:00
|
|
|
for (var i=0; i<8; i++) {
|
|
|
|
var bm = 128>>i;
|
|
|
|
pixels[outi] = (data&bm) ? color2 : color1;
|
2017-03-12 22:47:44 +00:00
|
|
|
if (framestats) {
|
|
|
|
framestats.layers.tiles[outi] = (data&bm) ? colorprom[col+8] : colorprom[col];
|
|
|
|
}
|
2017-01-18 01:15:46 +00:00
|
|
|
outi++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-21 13:13:36 +00:00
|
|
|
var CARNIVAL_KEYCODE_MAP = makeKeycodeMap([
|
2017-02-18 22:50:51 +00:00
|
|
|
[Keys.VK_SPACE, 2, -0x20],
|
|
|
|
[Keys.VK_SHIFT, 2, -0x40],
|
2017-01-21 13:13:36 +00:00
|
|
|
[Keys.VK_LEFT, 1, -0x10],
|
|
|
|
[Keys.VK_RIGHT, 1, -0x20],
|
2017-02-18 22:50:51 +00:00
|
|
|
[Keys.VK_UP, 1, -0x40],
|
|
|
|
[Keys.VK_DOWN, 1, -0x80],
|
2017-01-21 13:13:36 +00:00
|
|
|
[Keys.VK_1, 2, -0x10],
|
|
|
|
[Keys.VK_2, 3, -0x20],
|
|
|
|
[Keys.VK_5, 3, 0x8],
|
|
|
|
]);
|
2017-01-18 01:15:46 +00:00
|
|
|
|
|
|
|
this.getPresets = function() {
|
|
|
|
return VICDUAL_PRESETS;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.start = function() {
|
|
|
|
ram = new RAM(0x1000);
|
|
|
|
membus = {
|
|
|
|
read: new AddressDecoder([
|
|
|
|
[0x0000, 0x7fff, 0x3fff, function(a) { return rom ? rom[a] : null; }],
|
|
|
|
[0x8000, 0xffff, 0x0fff, function(a) { return ram.mem[a]; }],
|
|
|
|
]),
|
|
|
|
write: new AddressDecoder([
|
|
|
|
[0x8000, 0xffff, 0x0fff, function(a,v) { ram.mem[a] = v; }],
|
|
|
|
]),
|
|
|
|
isContended: function() { return false; },
|
|
|
|
};
|
|
|
|
iobus = {
|
2017-02-15 21:03:52 +00:00
|
|
|
read: function(addr) {
|
|
|
|
return inputs[addr&3];
|
|
|
|
},
|
2017-01-18 01:15:46 +00:00
|
|
|
write: function(addr, val) {
|
2017-02-18 22:50:51 +00:00
|
|
|
if (addr & 0x1) { psg.selectRegister(val & 0xf); }; // audio 1
|
2017-01-27 02:59:34 +00:00
|
|
|
if (addr & 0x2) { psg.setData(val); }; // audio 2
|
2017-01-18 01:15:46 +00:00
|
|
|
if (addr & 0x8) { }; // coin status
|
2017-01-26 05:09:57 +00:00
|
|
|
if (addr & 0x40) { palbank = val & 3; }; // palette
|
2017-01-18 01:15:46 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
cpu = window.Z80({
|
|
|
|
display: {},
|
|
|
|
memory: membus,
|
|
|
|
ioBus: iobus
|
|
|
|
});
|
|
|
|
video = new RasterVideo(mainElement,256,224,{rotate:-90});
|
2017-01-27 02:59:34 +00:00
|
|
|
audio = new MasterAudio();
|
|
|
|
psg = new AY38910_Audio(audio);
|
2017-04-02 04:38:08 +00:00
|
|
|
//var speech = new VotraxSpeech();
|
|
|
|
//audio.master.addChannel(speech);
|
2017-01-18 01:15:46 +00:00
|
|
|
video.create();
|
|
|
|
var idata = video.getFrameData();
|
2017-01-21 13:13:36 +00:00
|
|
|
setKeyboardFromMap(video, inputs, CARNIVAL_KEYCODE_MAP, function(o) {
|
|
|
|
// reset when coin inserted
|
2017-02-20 23:50:29 +00:00
|
|
|
if (o.index==3 && o.mask==0x8 && !reset_disable) cpu.reset();
|
|
|
|
// don't allow repeated resets in short period of time
|
|
|
|
reset_disable = true;
|
|
|
|
clearTimeout(reset_disable_timer);
|
|
|
|
reset_disable_timer = setTimeout(function() { reset_disable = false; }, 1100);
|
2017-01-21 13:13:36 +00:00
|
|
|
});
|
2017-01-18 01:15:46 +00:00
|
|
|
pixels = video.getFrameData();
|
|
|
|
timer = new AnimationTimer(60, function() {
|
|
|
|
if (!self.isRunning())
|
|
|
|
return;
|
|
|
|
var debugCond = self.getDebugCallback();
|
|
|
|
var targetTstates = cpu.getTstates();
|
|
|
|
for (var sl=0; sl<scanlinesPerFrame; sl++) {
|
|
|
|
drawScanline(pixels, sl);
|
|
|
|
targetTstates += cpuCyclesPerLine;
|
|
|
|
if (sl == vsyncStart) inputs[1] |= 0x8;
|
|
|
|
if (sl == vsyncEnd) inputs[1] &= ~0x8;
|
|
|
|
if (debugCond) {
|
|
|
|
while (cpu.getTstates() < targetTstates) {
|
|
|
|
if (debugCond && debugCond()) { debugCond = null; }
|
|
|
|
cpu.runFrame(cpu.getTstates() + 1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cpu.runFrame(targetTstates);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
video.updateFrame();
|
|
|
|
self.restartDebugState();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadROM = function(title, data) {
|
|
|
|
rom = padBytes(data, 0x4000);
|
|
|
|
self.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadState = function(state) {
|
|
|
|
cpu.loadState(state.c);
|
|
|
|
ram.mem.set(state.b);
|
|
|
|
inputs[0] = state.in0;
|
|
|
|
inputs[1] = state.in1;
|
|
|
|
inputs[2] = state.in2;
|
|
|
|
inputs[3] = state.in3;
|
2017-01-26 05:09:57 +00:00
|
|
|
palbank = state.pb;
|
2017-01-18 01:15:46 +00:00
|
|
|
}
|
|
|
|
this.saveState = function() {
|
|
|
|
return {
|
|
|
|
c:self.getCPUState(),
|
|
|
|
b:ram.mem.slice(0),
|
|
|
|
in0:inputs[0],
|
|
|
|
in1:inputs[1],
|
|
|
|
in2:inputs[2],
|
|
|
|
in3:inputs[3],
|
2017-01-26 05:09:57 +00:00
|
|
|
pb:palbank,
|
2017-01-18 01:15:46 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
this.getCPUState = function() {
|
|
|
|
return cpu.saveState();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isRunning = function() {
|
|
|
|
return timer.isRunning();
|
|
|
|
}
|
|
|
|
this.pause = function() {
|
|
|
|
timer.stop();
|
|
|
|
audio.stop();
|
|
|
|
}
|
|
|
|
this.resume = function() {
|
|
|
|
timer.start();
|
|
|
|
audio.start();
|
|
|
|
}
|
|
|
|
this.reset = function() {
|
|
|
|
cpu.reset();
|
2017-01-27 02:59:34 +00:00
|
|
|
psg.reset();
|
2017-01-18 01:15:46 +00:00
|
|
|
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
|
|
|
|
}
|
|
|
|
this.readAddress = function(addr) {
|
2017-03-12 22:47:44 +00:00
|
|
|
return membus.read(addr & 0xffff); // TODO?
|
|
|
|
}
|
|
|
|
this.setFrameStats = function(on) {
|
|
|
|
framestats = on ? {
|
|
|
|
palette: palette,
|
|
|
|
layers: {width:256, height:224, tiles:[]}
|
|
|
|
} : null;
|
2017-01-18 01:15:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PLATFORMS['vicdual'] = VicDualPlatform;
|