1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-28 03:29:35 +00:00

replay slider for some platforms; fixed williams audio

This commit is contained in:
Steven Hugg 2018-08-21 23:39:34 -04:00
parent 2b41164b66
commit b386d2e87a
11 changed files with 232 additions and 128 deletions

View File

@ -122,6 +122,9 @@ div.mem_info a.selected {
.btn_stopped { .btn_stopped {
color: #ff9933; color: #ff9933;
} }
.btn_recording {
color: #ff3333;
}
.seg_code { color: #ff9966; } .seg_code { color: #ff9966; }
.seg_data { color: #66ff66; } .seg_data { color: #66ff66; }
.seg_stack { color: #ffff66; } .seg_stack { color: #ffff66; }
@ -285,3 +288,12 @@ canvas.pixelated {
content: '\2713'; content: '\2713';
font-weight: 400; font-weight: 400;
} }
div.replaydiv {
position:absolute;
padding:20px;
left:50%;
bottom:0;
width:50%;
background-color: #666;
margin-top: 20px auto 0;
}

View File

@ -138,6 +138,9 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
<button id="dbg_profile" type="submit" title="Show Profile" style="display:none"><span class="glyphicon glyphicon-stats" aria-hidden="true"></span></button> <button id="dbg_profile" type="submit" title="Show Profile" style="display:none"><span class="glyphicon glyphicon-stats" aria-hidden="true"></span></button>
<button id="dbg_bitmap" type="submit" title="Edit Bitmap"><span class="glyphicon glyphicon-camera" aria-hidden="true"></span></button> <button id="dbg_bitmap" type="submit" title="Edit Bitmap"><span class="glyphicon glyphicon-camera" aria-hidden="true"></span></button>
</span> </span>
<span class="btn_group view_group" id="replay_bar" style="display:none">
<button id="dbg_record" type="submit" title="Toggle Replay Buffer"><span class="glyphicon glyphicon-record" aria-hidden="true"></span></button>
</span>
<span class="dropdown" style="float:right"> <span class="dropdown" style="float:right">
<a class="btn btn-secondary dropdown-toggle" id="booksMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="btn btn-secondary dropdown-toggle" id="booksMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
GET BOOKS <span class="caret"></span> GET BOOKS <span class="caret"></span>
@ -174,6 +177,9 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
</div> </div>
<div id="mem_info" class="mem_info" style="display:none"> <div id="mem_info" class="mem_info" style="display:none">
</div> </div>
<div id="replaydiv" class="replaydiv" style="display:none">
<input type="range" min="0" max="0" value="0" class="slider" id="replayslider">
</div>
</div> </div>
<!-- <!--
<div class="twitbtn"> <div class="twitbtn">
@ -273,6 +279,7 @@ function require(modname) {
<script src="gen/project.js"></script> <script src="gen/project.js"></script>
<script src="gen/windows.js"></script> <script src="gen/windows.js"></script>
<script src="gen/views.js"></script> <script src="gen/views.js"></script>
<script src="gen/recorder.js"></script>
<script src="gen/ui.js"></script> <script src="gen/ui.js"></script>
<!-- <script src="src/audio/votrax.js"></script> --> <!-- <script src="src/audio/votrax.js"></script> -->

View File

@ -25,6 +25,12 @@
****************************************************************************/ ****************************************************************************/
var window = {}; var window = {};
var exports = {};
function require(modname) {
if (modname == 'jquery') return $;
else if (modname.startsWith('.')) return exports;
else { console.log("Unknown require()", modname); return exports; }
}
importScripts("../../gen/emu.js"); importScripts("../../gen/emu.js");
importScripts("../cpu/z80.js"); importScripts("../cpu/z80.js");

View File

@ -12,15 +12,15 @@ export interface OpcodeMetadata {
opcode: number; opcode: number;
} }
interface CpuState { export interface CpuState {
PC:number, T?:number, o?:number,/*opcode*/ PC:number, T?:number, o?:number,/*opcode*/
SP?:number SP?:number
/* /*
A:number, X:number, Y:number, SP:number, R:boolean, A:number, X:number, Y:number, SP:number, R:boolean,
N,V,D,Z,C:boolean*/ N,V,D,Z,C:boolean*/
}; };
interface EmuState {c:CpuState, b?:number[]}; export interface EmuState {c:CpuState, b?:number[]};
type DisasmLine = {line:string, nbytes:number}; export type DisasmLine = {line:string, nbytes:number};
export interface Platform { export interface Platform {
start() : void; start() : void;
@ -57,6 +57,9 @@ export interface Platform {
getDebugCategories() : string[]; getDebugCategories() : string[];
getDebugInfo(category:string, state:EmuState) : string; getDebugInfo(category:string, state:EmuState) : string;
setRecorder?(recorder : EmuRecorder) : void;
advance?(novideo? : boolean) : void;
} }
export interface Preset { export interface Preset {
@ -75,6 +78,11 @@ type DebugCondition = () => boolean;
type DebugEvalCondition = (c:CpuState) => boolean; type DebugEvalCondition = (c:CpuState) => boolean;
type BreakpointCallback = (EmuState) => void; type BreakpointCallback = (EmuState) => void;
export interface EmuRecorder {
frameRequested() : boolean;
recordFrame(platform : Platform, state : EmuState);
}
///// /////
abstract class BaseDebugPlatform { abstract class BaseDebugPlatform {
@ -84,6 +92,7 @@ abstract class BaseDebugPlatform {
debugBreakState : EmuState = null; debugBreakState : EmuState = null;
debugTargetClock : number = 0; debugTargetClock : number = 0;
debugClock : number = 0; debugClock : number = 0;
recorder : EmuRecorder = null;
abstract getCPUState() : CpuState; abstract getCPUState() : CpuState;
abstract saveState() : EmuState; abstract saveState() : EmuState;
@ -117,6 +126,15 @@ abstract class BaseDebugPlatform {
this.debugBreakState = null; this.debugBreakState = null;
this.resume(); this.resume();
} }
setRecorder?(recorder : EmuRecorder) : void {
this.recorder = recorder;
}
updateRecorder() {
// are we recording and do we need to save a frame?
if (this.recorder && (<Platform><any>this).isRunning() && this.recorder.frameRequested()) {
this.recorder.recordFrame(<Platform><any>this, this.saveState());
}
}
} }
abstract class BaseFrameBasedPlatform extends BaseDebugPlatform { abstract class BaseFrameBasedPlatform extends BaseDebugPlatform {
@ -128,11 +146,14 @@ abstract class BaseFrameBasedPlatform extends BaseDebugPlatform {
} }
} }
restartDebugState() { restartDebugState() {
if (this.debugCondition && !this.debugBreakState) { // save state every frame and rewind debug clocks
var debugging = this.debugCondition && !this.debugBreakState;
if (debugging) {
this.debugSavedState = this.saveState(); this.debugSavedState = this.saveState();
this.debugTargetClock -= this.debugClock; this.debugTargetClock -= this.debugClock;
this.debugClock = 0; this.debugClock = 0;
} }
this.updateRecorder();
} }
breakpointHit(targetClock : number) { breakpointHit(targetClock : number) {
this.debugTargetClock = targetClock; this.debugTargetClock = targetClock;
@ -389,6 +410,7 @@ export abstract class BaseZ80Platform extends BaseDebugPlatform {
this.debugSavedState.c.T = 0; this.debugSavedState.c.T = 0;
this.loadState(this.debugSavedState); this.loadState(this.debugSavedState);
} }
this.updateRecorder();
} }
breakpointHit(targetClock : number) { breakpointHit(targetClock : number) {
this.debugTargetClock = targetClock; this.debugTargetClock = targetClock;

View File

@ -48,6 +48,7 @@ var Apple2Platform = function(mainElement) {
// value to add when reading & writing each of these banks // value to add when reading & writing each of these banks
// bank 1 is E000-FFFF, bank 2 is D000-DFFF // bank 1 is E000-FFFF, bank 2 is D000-DFFF
var bank2rdoffset=0, bank2wroffset=0; var bank2rdoffset=0, bank2wroffset=0;
var grparams;
this.getPresets = function() { this.getPresets = function() {
return APPLE2_PRESETS; return APPLE2_PRESETS;
@ -177,37 +178,40 @@ var Apple2Platform = function(mainElement) {
} }
}); });
var idata = video.getFrameData(); var idata = video.getFrameData();
var grparams = {dirty:grdirty, grswitch:grswitch, mem:ram.mem}; grparams = {dirty:grdirty, grswitch:grswitch, mem:ram.mem};
ap2disp = new Apple2Display(idata, grparams); ap2disp = new Apple2Display(idata, grparams);
var colors = [0xffff0000, 0xff00ff00]; timer = new AnimationTimer(60, this.advance.bind(this));
timer = new AnimationTimer(60, function() { }
// 262.5 scanlines per frame
var clock = 0; this.advance = function(novideo : boolean) {
var debugCond = self.getDebugCallback(); // 262.5 scanlines per frame
var rendered = false; var clock = 0;
for (var sl=0; sl<262; sl++) { var debugCond = this.getDebugCallback();
for (var i=0; i<cpuCyclesPerLine; i++) { var rendered = false;
if (debugCond && debugCond()) { for (var sl=0; sl<262; sl++) {
grparams.dirty = grdirty; for (var i=0; i<cpuCyclesPerLine; i++) {
grparams.grswitch = grswitch; if (debugCond && debugCond()) {
ap2disp.updateScreen(); grparams.dirty = grdirty;
debugCond = null; grparams.grswitch = grswitch;
rendered = true; ap2disp.updateScreen();
} debugCond = null;
clock++; rendered = true;
cpu.clockPulse();
audio.feedSample(soundstate, 1);
} }
clock++;
cpu.clockPulse();
audio.feedSample(soundstate, 1);
} }
}
if (!novideo) {
if (!rendered) { if (!rendered) {
grparams.dirty = grdirty; grparams.dirty = grdirty;
grparams.grswitch = grswitch; grparams.grswitch = grswitch;
ap2disp.updateScreen(); ap2disp.updateScreen();
} }
video.updateFrame(); video.updateFrame();
soundstate = 0; // to prevent clicking }
self.restartDebugState(); // reset debug start state //soundstate = 0; // to prevent clicking
}); this.restartDebugState(); // reset debug start state
} }
function doLanguageCardIO(address, value) function doLanguageCardIO(address, value)

View File

@ -294,35 +294,36 @@ var GalaxianPlatform = function(mainElement, options) {
var idata = video.getFrameData(); var idata = video.getFrameData();
setKeyboardFromMap(video, inputs, keyMap); setKeyboardFromMap(video, inputs, keyMap);
pixels = video.getFrameData(); pixels = video.getFrameData();
timer = new AnimationTimer(60, function() { timer = new AnimationTimer(60, this.advance.bind(this));
if (!self.isRunning()) }
return;
var debugCond = self.getDebugCallback(); this.advance = function(novideo : boolean) {
var targetTstates = cpu.getTstates(); var debugCond = this.getDebugCallback();
// TODO: get raster position var targetTstates = cpu.getTstates();
for (var sl=0; sl<scanlinesPerFrame; sl++) { for (var sl=0; sl<scanlinesPerFrame; sl++) {
drawScanline(pixels, sl); if (!novideo) {
targetTstates += cpuCyclesPerLine; drawScanline(pixels, sl);
if (debugCond) { }
while (cpu.getTstates() < targetTstates) { targetTstates += cpuCyclesPerLine;
if (debugCond && debugCond()) { debugCond = null; } if (debugCond) {
cpu.runFrame(cpu.getTstates() + 1); while (cpu.getTstates() < targetTstates) {
} if (debugCond && debugCond()) { debugCond = null; }
} else { cpu.runFrame(cpu.getTstates() + 1);
cpu.runFrame(targetTstates);
} }
} else {
cpu.runFrame(targetTstates);
} }
// visible area is 256x224 (before rotation) }
video.updateFrame(0, 0, 0, 0, showOffscreenObjects ? 264 : 256, 264); // visible area is 256x224 (before rotation)
frameCounter = (frameCounter + 1) & 0xff; video.updateFrame(0, 0, 0, 0, showOffscreenObjects ? 264 : 256, 264);
if (watchdog_counter-- <= 0) { frameCounter = (frameCounter + 1) & 0xff;
console.log("WATCHDOG FIRED, PC ", hex(cpu.getPC())); // TODO: alert on video if (watchdog_counter-- <= 0) {
self.reset(); console.log("WATCHDOG FIRED, PC ", hex(cpu.getPC())); // TODO: alert on video
} this.reset();
self.restartDebugState(); }
// NMI interrupt @ 0x66 // NMI interrupt @ 0x66
if (interruptEnabled) { cpu.nonMaskableInterrupt(); } if (interruptEnabled) { cpu.nonMaskableInterrupt(); }
}); this.restartDebugState(); // TODO: after interrupt?
} }
var bitcolors = [ var bitcolors = [
@ -397,7 +398,6 @@ var GalaxianPlatform = function(mainElement, options) {
} }
var GalaxianScramblePlatform = function(mainElement) { var GalaxianScramblePlatform = function(mainElement) {
var self = this;
this.__proto__ = new GalaxianPlatform(mainElement, { this.__proto__ = new GalaxianPlatform(mainElement, {
romSize: 0x5020, romSize: 0x5020,
gfxBase: 0x4000, gfxBase: 0x4000,

View File

@ -113,38 +113,40 @@ var Midway8080BWPlatform = function(mainElement) {
var idata = video.getFrameData(); var idata = video.getFrameData();
setKeyboardFromMap(video, inputs, SPACEINV_KEYCODE_MAP); setKeyboardFromMap(video, inputs, SPACEINV_KEYCODE_MAP);
pixels = video.getFrameData(); pixels = video.getFrameData();
timer = new AnimationTimer(60, function() { timer = new AnimationTimer(60, this.advance.bind(this));
if (!self.isRunning()) }
return;
var debugCond = self.getDebugCallback(); this.advance = function(novideo : boolean) {
var targetTstates = cpu.getTstates(); var debugCond = this.getDebugCallback();
for (var sl=0; sl<224; sl++) { var targetTstates = cpu.getTstates();
targetTstates += cpuCyclesPerLine; for (var sl=0; sl<224; sl++) {
if (debugCond) { targetTstates += cpuCyclesPerLine;
while (cpu.getTstates() < targetTstates) { if (debugCond) {
if (debugCond && debugCond()) { while (cpu.getTstates() < targetTstates) {
debugCond = null; if (debugCond && debugCond()) {
break; debugCond = null;
}
cpu.runFrame(cpu.getTstates() + 1);
}
if (!debugCond)
break; break;
} else { }
cpu.runFrame(targetTstates); cpu.runFrame(cpu.getTstates() + 1);
} }
if (sl == 95) if (!debugCond)
cpu.requestInterrupt(0x8); // RST $8 break;
else if (sl == 223) } else {
cpu.requestInterrupt(0x10); // RST $10 cpu.runFrame(targetTstates);
} }
if (sl == 95)
cpu.requestInterrupt(0x8); // RST $8
else if (sl == 223)
cpu.requestInterrupt(0x10); // RST $10
}
if (!novideo) {
video.updateFrame(); video.updateFrame();
if (watchdog_counter-- <= 0) { }
console.log("WATCHDOG FIRED"); // TODO: alert on video if (watchdog_counter-- <= 0) {
self.reset(); console.log("WATCHDOG FIRED"); // TODO: alert on video
} this.reset();
self.restartDebugState(); }
}); this.restartDebugState();
} }
this.loadROM = function(title, data) { this.loadROM = function(title, data) {

View File

@ -127,6 +127,10 @@ var JSNESPlatform = function(mainElement) {
nes.buttonUp(o.index+1, o.mask); // controller, button nes.buttonUp(o.index+1, o.mask); // controller, button
}); });
} }
this.advance = function(novideo : boolean) {
nes.frame();
}
this.loadROM = function(title, data) { this.loadROM = function(title, data) {
var romstr = String.fromCharCode.apply(null, data); var romstr = String.fromCharCode.apply(null, data);

View File

@ -142,23 +142,24 @@ var VicDualPlatform = function(mainElement) {
reset_disable_timer = setTimeout(function() { reset_disable = false; }, 1100); reset_disable_timer = setTimeout(function() { reset_disable = false; }, 1100);
}); });
pixels = video.getFrameData(); pixels = video.getFrameData();
timer = new AnimationTimer(60, function() { timer = new AnimationTimer(60, this.advance.bind(this));
if (!self.isRunning()) }
return;
var debugCond = self.getDebugCallback(); this.advance = function(novideo : boolean) {
var targetTstates = cpu.getTstates(); var targetTstates = cpu.getTstates();
for (var sl=0; sl<scanlinesPerFrame; sl++) { for (var sl=0; sl<scanlinesPerFrame; sl++) {
inputs[2] &= ~0x8; inputs[2] &= ~0x8;
inputs[2] |= ((cpu.getTstates() / cyclesPerTimerTick) & 1) << 3; inputs[2] |= ((cpu.getTstates() / cyclesPerTimerTick) & 1) << 3;
drawScanline(pixels, sl); if (!novideo) {
targetTstates += cpuCyclesPerLine; drawScanline(pixels, sl);
if (sl == vblankStart) inputs[1] |= 0x8;
if (sl == vsyncEnd) inputs[1] &= ~0x8;
self.runCPU(cpu, targetTstates - cpu.getTstates());
} }
video.updateFrame(); targetTstates += cpuCyclesPerLine;
self.restartDebugState(); if (sl == vblankStart) inputs[1] |= 0x8;
}); if (sl == vsyncEnd) inputs[1] &= ~0x8;
this.runCPU(cpu, targetTstates - cpu.getTstates());
}
video.updateFrame();
this.restartDebugState();
} }
this.loadROM = function(title, data) { this.loadROM = function(title, data) {

View File

@ -306,38 +306,38 @@ var WilliamsPlatform = function(mainElement, proto) {
var idata = video.getFrameData(); var idata = video.getFrameData();
setKeyboardFromMap(video, pia6821, ROBOTRON_KEYCODE_MAP); setKeyboardFromMap(video, pia6821, ROBOTRON_KEYCODE_MAP);
pixels = video.getFrameData(); pixels = video.getFrameData();
timer = new AnimationTimer(60, function() { timer = new AnimationTimer(60, this.advance.bind(this));
if (!self.isRunning()) }
return;
var cpuCyclesPerSection = Math.round(cpuCyclesPerFrame / 65); this.advance = function(novideo:boolean) {
for (var sl=0; sl<256; sl+=4) { var cpuCyclesPerSection = Math.round(cpuCyclesPerFrame / 65);
video_counter = sl; for (var sl=0; sl<256; sl+=4) {
// interrupts happen every 1/4 of the screen video_counter = sl;
if (sl == 0 || sl == 0x3c || sl == 0xbc || sl == 0xfc) { // interrupts happen every 1/4 of the screen
if (membus.read != memread_defender || pia6821[7] == 0x3c) { // TODO? if (sl == 0 || sl == 0x3c || sl == 0xbc || sl == 0xfc) {
if (cpu.interrupt) if (membus.read != memread_defender || pia6821[7] == 0x3c) { // TODO?
cpu.interrupt(); if (cpu.interrupt)
if (cpu.requestInterrupt) cpu.interrupt();
cpu.requestInterrupt(); if (cpu.requestInterrupt)
} cpu.requestInterrupt();
} }
self.runCPU(cpu, cpuCyclesPerSection);
if (sl < 256) video.updateFrame(0, 0, 256-4-sl, 0, 4, 304);
} }
// last 6 lines this.runCPU(cpu, cpuCyclesPerSection);
self.runCPU(cpu, cpuCyclesPerSection*2); if (sl < 256) video.updateFrame(0, 0, 256-4-sl, 0, 4, 304);
if (screenNeedsRefresh) { }
for (var i=0; i<0x9800; i++) // last 6 lines
drawDisplayByte(i, ram.mem[i]); this.runCPU(cpu, cpuCyclesPerSection*2);
screenNeedsRefresh = false; if (screenNeedsRefresh && !novideo) {
} for (var i=0; i<0x9800; i++)
if (watchdog_counter-- <= 0) { drawDisplayByte(i, ram.mem[i]);
console.log("WATCHDOG FIRED, PC =", cpu.getPC().toString(16)); // TODO: alert on video screenNeedsRefresh = false;
// TODO: self.breakpointHit(cpu.T()); }
self.reset(); if (watchdog_counter-- <= 0) {
} console.log("WATCHDOG FIRED, PC =", cpu.getPC().toString(16)); // TODO: alert on video
self.restartDebugState(); // TODO: this.breakpointHit(cpu.T());
}); this.reset();
}
this.restartDebugState();
} }
this.loadSoundROM = function(data) { this.loadSoundROM = function(data) {

View File

@ -12,6 +12,7 @@ import { PLATFORMS } from "./emu";
import * as Views from "./views"; import * as Views from "./views";
import { createNewPersistentStore } from "./store"; import { createNewPersistentStore } from "./store";
import { getFilenameForPath, getFilenamePrefix, highlightDifferences, invertMap } from "./util"; import { getFilenameForPath, getFilenamePrefix, highlightDifferences, invertMap } from "./util";
import { StateRecorderImpl } from "./recorder";
// external libs (TODO) // external libs (TODO)
declare var Octokat, ga, Tour, GIF, saveAs; declare var Octokat, ga, Tour, GIF, saveAs;
@ -29,6 +30,7 @@ var current_project : CodeProject; // current CodeProject object
var projectWindows : ProjectWindows; // window manager var projectWindows : ProjectWindows; // window manager
var stateRecorder : StateRecorderImpl = new StateRecorderImpl();
// TODO: codemirror multiplex support? // TODO: codemirror multiplex support?
var TOOL_TO_SOURCE_STYLE = { var TOOL_TO_SOURCE_STYLE = {
@ -460,6 +462,7 @@ function setDebugButtonState(btnid:string, btnstate:string) {
} }
function setupBreakpoint(btnid? : string) { function setupBreakpoint(btnid? : string) {
_disableRecording();
platform.setupDebug(function(state) { platform.setupDebug(function(state) {
lastDebugState = state; lastDebugState = state;
showDebugInfo(state); showDebugInfo(state);
@ -541,6 +544,7 @@ function runStepBackwards() {
} }
function clearBreakpoint() { function clearBreakpoint() {
_disableRecording();
lastDebugState = null; lastDebugState = null;
if (platform.clearDebug) platform.clearDebug(); if (platform.clearDebug) platform.clearDebug();
showDebugInfo(); showDebugInfo();
@ -679,6 +683,33 @@ function traceTiming() {
} }
} }
var recorderActive = false;
function _disableRecording() {
if (recorderActive) {
platform.setRecorder(null);
$("#dbg_record").removeClass("btn_recording");
$("#replaydiv").hide();
recorderActive = false;
}
}
function _enableRecording() {
stateRecorder.reset();
platform.setRecorder(stateRecorder);
$("#dbg_record").addClass("btn_recording");
$("#replaydiv").show();
recorderActive = true;
}
function _toggleRecording() {
if (recorderActive) {
_disableRecording();
} else {
_enableRecording();
}
}
function setupDebugControls(){ function setupDebugControls(){
$("#dbg_reset").click(resetAndDebug); $("#dbg_reset").click(resetAndDebug);
$("#dbg_pause").click(pause); $("#dbg_pause").click(pause);
@ -730,6 +761,21 @@ function setupDebugControls(){
$("#dbg_fastest").click(_fastestFrameRate); $("#dbg_fastest").click(_fastestFrameRate);
} }
updateDebugWindows(); updateDebugWindows();
// setup replay slider
if (platform.advance) {
$("#dbg_record").click(_toggleRecording);
var replayslider = $("#replayslider");
stateRecorder.callbackStateChanged = () => {
replayslider.attr('min', 0);
replayslider.attr('max', stateRecorder.numFrames()-1);
replayslider.attr('value', stateRecorder.numFrames()-1); // TODO: doesn't always move
};
replayslider.on('input', function(e) {
_pause();
stateRecorder.loadFrame(platform, (<any>e.target).value);
});
$("#replay_bar").show();
}
} }
function showWelcomeMessage() { function showWelcomeMessage() {