// Copyright 2011-2012 Paulo Augusto Peccin. See licence.txt distributed with this file. package org.javatari.atari.console; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.javatari.atari.board.BUS; import org.javatari.atari.cartridge.Cartridge; import org.javatari.atari.cartridge.CartridgeDatabase; import org.javatari.atari.cartridge.CartridgeFormatOption; import org.javatari.atari.cartridge.CartridgeInsertionListener; import org.javatari.atari.cartridge.CartridgeSocket; import org.javatari.atari.cartridge.ROMFormatUnsupportedException; import org.javatari.atari.cartridge.formats.CartridgeSavestate; import org.javatari.atari.console.savestate.ConsoleState; import org.javatari.atari.console.savestate.SaveStateMedia; import org.javatari.atari.console.savestate.SaveStateSocket; import org.javatari.atari.controls.ConsoleControls; import org.javatari.atari.controls.ConsoleControls.Control; import org.javatari.atari.controls.ConsoleControlsInput; import org.javatari.atari.controls.ConsoleControlsSocket; import org.javatari.atari.pia.PIA; import org.javatari.atari.pia.RAM; import org.javatari.atari.tia.TIA; import org.javatari.general.av.audio.AudioSignal; import org.javatari.general.av.video.VideoSignal; import org.javatari.general.av.video.VideoStandard; import org.javatari.general.board.Clock; import org.javatari.general.m6502.M6502; import org.javatari.parameters.Parameters; public class Console { public Console() { mainComponentsCreate(); socketsCreate(); mainClockCreate(); videoStandardAuto(); } public VideoSignal videoOutput() { return tia.videoOutput(); } public AudioSignal audioOutput() { return tia.audioOutput(); } public ConsoleControlsSocket controlsSocket() { return controlsSocket; } public CartridgeSocket cartridgeSocket() { return cartridgeSocket; } public SaveStateSocket saveStateSocket() { return saveStateSocket; } public void powerOn() { if (powerOn) powerOff(); cpu.powerOn(); bus.powerOn(); powerOn = true; controlsSocket.controlsStatesRedefined(); go(); videoStandardAutoDetectionStart(); } public void powerOff() { pause(); bus.powerOff(); cpu.powerOff(); powerOn = false; controlsSocket.controlsStatesRedefined(); } public void extendedPowerOff() { powerOff(); } public void destroy() { extendedPowerOff(); mainClockDestroy(); } public void showOSD(String message, boolean overlap) { tia.videoOutput().showOSD(message, overlap); } public VideoStandard videoStandard() { return videoStandard; } public void videoStandard(VideoStandard videoStandard) { if (videoStandard != this.videoStandard) { this.videoStandard = videoStandard; tia.videoStandard(this.videoStandard); mainClockAdjustToNormal(); } showOSD((videoStandardAuto ? "AUTO: " : "") + videoStandard.toString(), false); } public void go() { mainClock.go(); } public void pause() { mainClock.pause(); } // For debug purposes public Clock mainClock() { return mainClock; } protected Cartridge cartridge() { return bus.cartridge; } protected void cartridge(Cartridge cartridge) { controlsSocket.removeForwardedInput(cartridge()); bus.cartridge(cartridge); cartridgeSocket.cartridgeInserted(cartridge); if (cartridge != null) { controlsSocket.addForwardedInput(cartridge); saveStateSocket.connectCartridge(cartridge); } } protected void videoStandardAuto() { videoStandardAuto = true; if (powerOn) videoStandardAutoDetectionStart(); else videoStandard(VideoStandard.NTSC); } protected void videoStandardAutoDetectionStart() { if (!videoStandardAuto || videoStandardAutoDetectionInProgress) return; // If no Cartridge present, use NTSC if (bus.cartridge == null) { videoStandard(VideoStandard.NTSC); return; } // Otherwise use the VideoStandard detected by the monitor if (tia.videoOutput().monitor() == null) return; videoStandardAutoDetectionInProgress = true; tia.videoOutput().monitor().videoStandardDetectionStart(); // TODO This thread is a source of indeterminism. Potential problem in multiplayer sync new Thread("Console VideoStd Detection") { public void run() { VideoStandard std; int tries = 0; do { try { Thread.sleep(20); } catch (InterruptedException e) {}; std = tia.videoOutput().monitor().videoStandardDetected(); } while (std == null && ++tries < 1500/20); if (std != null) videoStandard(std); else showOSD("AUTO: FAILED", false); videoStandardAutoDetectionInProgress = false; }}.start(); } protected void videoStandardForced(VideoStandard forcedVideoStandard) { videoStandardAuto = false; videoStandard(forcedVideoStandard); } protected void mainComponentsCreate() { cpu = new M6502(); tia = new TIA(); pia = new PIA(); ram = new RAM(); bus = new BUS(cpu, tia, pia, ram); } protected void mainClockCreate() { mainClock = new Clock("Console(TIA)", tia, 0); } protected void mainClockAdjustToNormal() { double fps = tia.desiredClockForVideoStandard(); mainClock.speed(fps); tia.audioOutput().fps(fps); } protected void mainClockAdjustToAlternate() { double fps = tia.desiredClockForVideoStandard() * ALTERNATE_CLOCK_FACTOR; mainClock.speed(fps); tia.audioOutput().fps(fps); } protected void mainClockDestroy() { mainClock.terminate(); } protected void socketsCreate() { controlsSocket = new ConsoleControlsSocket(); controlsSocket.addForwardedInput(new ConsoleControlsInputAdapter()); controlsSocket.addForwardedInput(tia); controlsSocket.addForwardedInput(pia); cartridgeSocket = new CartridgeSocketAdapter(); saveStateSocket = new SaveStateSocketAdapter(); } protected void loadState(ConsoleState state) { tia.loadState(state.tiaState); pia.loadState(state.piaState); ram.loadState(state.ramState); cpu.loadState(state.cpuState); cartridge(state.cartridge); videoStandard(state.videoStandard); controlsSocket.controlsStatesRedefined(); } protected ConsoleState saveState() { return new ConsoleState( tia.saveState(), pia.saveState(), ram.saveState(), cpu.saveState(), cartridge() != null ? cartridge().saveState() : null, videoStandard() ); } protected void cycleCartridgeFormat() { if (cartridge() == null) { showOSD("NO CARTRIDGE INSERTED!", true); return; } ArrayList<CartridgeFormatOption> options; try { options = CartridgeDatabase.getFormatOptions(cartridge().rom()); } catch (ROMFormatUnsupportedException e) { return; } CartridgeFormatOption currOption = null; for (CartridgeFormatOption option : options) if (option.format.equals(cartridge().format())) currOption = option; int pos = options.indexOf(currOption) + 1; // cycle through options if (pos >= options.size()) pos = 0; CartridgeFormatOption newOption = options.get(pos); Cartridge newCart = newOption.format.createCartridge(cartridge().rom()); cartridgeSocket().insert(newCart, true); showOSD(newOption.format.toString(), true); } protected void powerFry() { ram.powerFry(); } private ConsoleState pauseAndSaveState() { pause(); ConsoleState state = saveState(); go(); return state; } protected void pauseAndLoadState(ConsoleState state) { if (powerOn) { pause(); loadState(state); go(); } else { powerOn(); pauseAndLoadState(state); } } public boolean powerOn = false; protected BUS bus; protected M6502 cpu; protected TIA tia; protected PIA pia; protected RAM ram; protected VideoStandard videoStandard; protected boolean videoStandardAuto = true; private boolean videoStandardAutoDetectionInProgress = false; protected ConsoleControlsSocket controlsSocket; protected CartridgeSocketAdapter cartridgeSocket; protected SaveStateSocketAdapter saveStateSocket; protected Clock mainClock; public static final float ALTERNATE_CLOCK_FACTOR = Parameters.CONSOLE_ALTERNATE_CLOCK_FACTOR; protected class ConsoleControlsInputAdapter implements ConsoleControlsInput { public ConsoleControlsInputAdapter() { } @Override public void controlStateChanged(Control control, boolean state) { // Normal state controls if (control == Control.FAST_SPEED) { if (state) { showOSD("FAST FORWARD", true); mainClockAdjustToAlternate(); } else { showOSD(null, true); mainClockAdjustToNormal(); } return; } // Toggles if (!state) return; switch (control) { case POWER: if (powerOn) powerOff(); else powerOn(); break; case POWER_FRY: powerFry(); break; case SAVE_STATE_0: case SAVE_STATE_1: case SAVE_STATE_2: case SAVE_STATE_3: case SAVE_STATE_4: case SAVE_STATE_5: case SAVE_STATE_6: case SAVE_STATE_7: case SAVE_STATE_8: case SAVE_STATE_9: case SAVE_STATE_10: case SAVE_STATE_11: case SAVE_STATE_12: saveStateSocket.saveState(control.slot); break; case LOAD_STATE_0: case LOAD_STATE_1: case LOAD_STATE_2: case LOAD_STATE_3: case LOAD_STATE_4: case LOAD_STATE_5: case LOAD_STATE_6: case LOAD_STATE_7: case LOAD_STATE_8: case LOAD_STATE_9: case LOAD_STATE_10: case LOAD_STATE_11: case LOAD_STATE_12: saveStateSocket.loadState(control.slot); break; case VIDEO_STANDARD: showOSD(null, true); // Prepares for the upcoming "AUTO" OSD to always show if (videoStandardAuto) videoStandardForced(VideoStandard.NTSC); else if (videoStandard() == VideoStandard.NTSC) videoStandardForced(VideoStandard.PAL); else videoStandardAuto(); break; case CARTRIDGE_FORMAT: cycleCartridgeFormat(); } } @Override public void controlStateChanged(ConsoleControls.Control control, int position) { // No positional controls here } @Override public void controlsStateReport(Map<ConsoleControls.Control, Boolean> report) { // Only Power Control is visible from outside report.put(Control.POWER, powerOn); } } protected class CartridgeSocketAdapter implements CartridgeSocket { @Override public void insert(Cartridge cartridge, boolean autoPower) { // Special case for Savestates if (cartridge != null && cartridge instanceof CartridgeSavestate) { insertSavestateCartridge((CartridgeSavestate) cartridge); return; } // Normal case if (autoPower && powerOn) powerOff(); cartridge(cartridge); if (autoPower && !powerOn) powerOn(); } @Override public Cartridge inserted() { return cartridge(); } @Override public void cartridgeInserted(Cartridge cartridge) { for (CartridgeInsertionListener listener : insertionListeners) listener.cartridgeInserted(cartridge); } @Override public void addInsertionListener(CartridgeInsertionListener listener) { if (!insertionListeners.contains(listener)) { insertionListeners.add(listener); listener.cartridgeInserted(inserted()); // Fire a insertion event } } @Override public void removeInsertionListener(CartridgeInsertionListener listener) { insertionListeners.remove(listener); } protected void insertSavestateCartridge(CartridgeSavestate cartridge) { ConsoleState state = cartridge.getConsoleState(); if (state != null) { pauseAndLoadState(state); showOSD("Savestate Cartridge loaded", true); } } private List<CartridgeInsertionListener> insertionListeners = new ArrayList<CartridgeInsertionListener>(); } protected class SaveStateSocketAdapter implements SaveStateSocket { @Override public void connectMedia(SaveStateMedia media) { this.media = media; } @Override public SaveStateMedia media() { return media; } @Override public void externalStateChange() { // Nothing } public void connectCartridge(Cartridge cartridge) { cartridge.connectSaveStateSocket(this); } @Override public void saveStateFile() { if (!powerOn || media == null) return; ConsoleState state = pauseAndSaveState(); if (media.saveStateFile(state)) showOSD("State file saved", true); else showOSD("State file save failed", true); } public void saveState(int slot) { if (!powerOn || media == null) return; ConsoleState state = pauseAndSaveState(); if (media.saveState(slot, state)) showOSD("State " + slot + " saved", true); else showOSD("State " + slot + " save failed", true); } public void loadState(int slot) { if (media == null) return; ConsoleState state = media.loadState(slot); if (state == null) { showOSD("State " + slot + " load failed", true); return; } pauseAndLoadState(state); showOSD("State " + slot + " loaded", true); } private SaveStateMedia media; } }