Skip to content

Commit

Permalink
Support Family BASIC and keyboard
Browse files Browse the repository at this point in the history
TODO: Keyboard layout.
  • Loading branch information
tyfkda committed Jul 1, 2023
1 parent 9115cfa commit d9e1385
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 8 deletions.
12 changes: 12 additions & 0 deletions src/app/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Nes, NesEvent} from '../nes/nes'
import {Cartridge} from '../nes/cartridge'
import {Keyboard} from '../nes/peripheral/keyboard'

import {AppEvent} from './app_event'
import {AudioManager} from '../util/audio_manager'
Expand Down Expand Up @@ -113,6 +114,17 @@ export class App {
this.nes.reset()
this.screenWnd.getContentHolder().focus()

// Set up keyboard.
const romHash = cartridge.calcHashValue()
switch (romHash) {
case '2ba1dbbb774118eb903465f8e66f92a2': // Family BASIC v3
case 'b6fd590c5e833e3ab6b8462e40335842': // Family BASIC v2.1a
case 'fc1668b428b5012e61e2de204164f24c': // Family BASIC v2.0a
this.screenWnd.setKeyboard(new Keyboard())
break
default: break
}

return null
}

Expand Down
97 changes: 91 additions & 6 deletions src/app/screen_wnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {PadBit, PadValue} from '../nes/apu'
import {PadKeyHandler} from '../util/pad_key_handler'
import {KeyboardManager} from '../util/keyboard_manager'
import {GamepadManager} from '../util/gamepad_manager'
import {Keyboard as NesKeyboard, KeyType} from '../nes/peripheral/keyboard'
import {RegisterWnd, RamWnd, TraceWnd, ControlWnd} from './debug_wnd'
import {FpsWnd, PaletWnd, NameTableWnd, PatternTableWnd, AudioWnd} from './other_wnd'
import {Fds} from '../nes/fds/fds'
Expand Down Expand Up @@ -45,6 +46,79 @@ const enum WndType {
FDS_CTRL,
}

const kKeyMapping: {[key: string]: KeyType} = {
KeyA: KeyType.A,
KeyB: KeyType.B,
KeyC: KeyType.C,
KeyD: KeyType.D,
KeyE: KeyType.E,
KeyF: KeyType.F,
KeyG: KeyType.G,
KeyH: KeyType.H,
KeyI: KeyType.I,
KeyJ: KeyType.J,
KeyK: KeyType.K,
KeyL: KeyType.L,
KeyM: KeyType.M,
KeyN: KeyType.N,
KeyO: KeyType.O,
KeyP: KeyType.P,
KeyQ: KeyType.Q,
KeyR: KeyType.R,
KeyS: KeyType.S,
KeyT: KeyType.T,
KeyU: KeyType.U,
KeyV: KeyType.V,
KeyW: KeyType.W,
KeyX: KeyType.X,
KeyY: KeyType.Y,
KeyZ: KeyType.Z,
Digit0: KeyType.NUM0,
Digit1: KeyType.NUM1,
Digit2: KeyType.NUM2,
Digit3: KeyType.NUM3,
Digit4: KeyType.NUM4,
Digit5: KeyType.NUM5,
Digit6: KeyType.NUM6,
Digit7: KeyType.NUM7,
Digit8: KeyType.NUM8,
Digit9: KeyType.NUM9,
Enter: KeyType.RETURN,
Space: KeyType.SPACE,
Comma: KeyType.COMMA,
Period: KeyType.PERIOD,
Slash: KeyType.SLASH,
BracketLeft: KeyType.LBRACKET,
BracketRight: KeyType.RBRACKET,
Escape: KeyType.ESC,
Semicolon: KeyType.SEMICOLON,
Minus: KeyType.MINUS,
Equal: KeyType.HAT,
Backslash: KeyType.YEN,
Insert: KeyType.INS,
Backspace: KeyType.DEL,
ShiftLeft: KeyType.LSHIFT,
ShiftRight: KeyType.RSHIFT,
ControlLeft: KeyType.CTR,
ControlRight: KeyType.CTR,
AltLeft: KeyType.GRPH,
AltRight: KeyType.KANA,
F1: KeyType.F1,
F2: KeyType.F2,
F3: KeyType.F3,
F4: KeyType.F4,
F5: KeyType.F5,
F6: KeyType.F6,
F7: KeyType.F7,
F8: KeyType.F8,
F11: KeyType.STOP,
F12: KeyType.CLR_HOME,
ArrowUp: KeyType.UP,
ArrowDown: KeyType.DOWN,
ArrowLeft: KeyType.LEFT,
ArrowRight: KeyType.RIGHT,
}

type Size = {width: number, height: number}

function takeScreenshot(wndMgr: WindowManager, screenWnd: ScreenWnd): Wnd {
Expand Down Expand Up @@ -97,7 +171,8 @@ export class ScreenWnd extends Wnd {
private menuItems: Array<MenuItemInfo>
private scalerType: ScalerType = ScalerType.NEAREST
private padKeyHandler = new PadKeyHandler()
private keyboardManager = new KeyboardManager()
private domKeyboardManager = new KeyboardManager()
private nesKeyboard: NesKeyboard
private timeScale = 1
private fullscreenResizeFunc: () => void
private repeatBtnFrame = false
Expand Down Expand Up @@ -249,23 +324,27 @@ export class ScreenWnd extends Wnd {
if (!param) {
this.timeScale = TIME_SCALE_NORMAL
this.padKeyHandler.clearAll()
this.keyboardManager.clear()
this.domKeyboardManager.clear()
}
break

case WndEvent.KEY_DOWN:
{
const event = param as KeyboardEvent
if (!(event.ctrlKey || event.altKey || event.metaKey))
this.keyboardManager.onKeyDown(event)
this.domKeyboardManager.onKeyDown(event)
if (this.nesKeyboard != null && event.code in kKeyMapping)
this.nesKeyboard.setKeyState(kKeyMapping[event.code], true)
}
break

case WndEvent.KEY_UP:
{
const event = param as KeyboardEvent
if (!(event.ctrlKey || event.altKey || event.metaKey))
this.keyboardManager.onKeyUp(event)
this.domKeyboardManager.onKeyUp(event)
if (this.nesKeyboard != null && event.code in kKeyMapping)
this.nesKeyboard.setKeyState(kKeyMapping[event.code], false)
}
break

Expand Down Expand Up @@ -346,6 +425,11 @@ export class ScreenWnd extends Wnd {
})
}

public setKeyboard(nesKeyboard: NesKeyboard): void {
this.nesKeyboard = nesKeyboard
this.nes.setPeripheral(this.nesKeyboard.getIoMap())
}

protected closeChildrenWindows(): void {
for (const wnd of Object.values(this.wndMap))
if (wnd != null)
Expand Down Expand Up @@ -685,8 +769,9 @@ export class ScreenWnd extends Wnd {
}

private update(elapsedTime: number) {
this.padKeyHandler.update(this.keyboardManager)
const speedUp = this.keyboardManager.getKeyPressing('ShiftLeft')
this.padKeyHandler.update(this.domKeyboardManager)
const speedUp = (this.nesKeyboard == null &&
this.domKeyboardManager.getKeyPressing('ShiftLeft'))
this.timeScale = speedUp ? TIME_SCALE_FAST : TIME_SCALE_NORMAL

this.stream.triggerStartCalc()
Expand Down
21 changes: 19 additions & 2 deletions src/nes/nes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export class Nes {
private channelWaveTypes: WaveType[]
private gamePads = [new GamePad(), new GamePad()]

private peripheralMap = new Map<number, (adr: Address, value?: Byte) => any>()

public static isMapperSupported(mapperNo: number): boolean {
return mapperNo in kMapperTable
}
Expand Down Expand Up @@ -96,6 +98,11 @@ export class Nes {
this.channelWaveTypes = channels
}

public setPeripheral(ioMap: Map<number, (adr: Address, value?: Byte) => any>): void {
for (const [key, value] of ioMap)
this.peripheralMap.set(key, value)
}

public setMapper(mapper: Mapper): void {
this.mapper = mapper
}
Expand Down Expand Up @@ -236,8 +243,18 @@ export class Nes {
this.ppu.write(reg, value)
})

bus.setReadMemory(0x4000, 0x5fff, adr => this.readFromApu(adr)) // APU
bus.setWriteMemory(0x4000, 0x5fff, (adr, value) => this.writeToApu(adr, value)) // APU
bus.setReadMemory(0x4000, 0x5fff, adr => {
if (this.peripheralMap.has(adr))
return this.peripheralMap.get(adr)!(adr)
return this.readFromApu(adr) // APU
})
bus.setWriteMemory(0x4000, 0x5fff, (adr, value) => {
if (this.peripheralMap.has(adr)) {
this.peripheralMap.get(adr)!(adr, value)
return
}
this.writeToApu(adr, value) // APU
})

// PRG ROM
const prgMask = (this.prgRom.length - 1) | 0
Expand Down
57 changes: 57 additions & 0 deletions src/nes/peripheral/keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Family BASIC Keyboard

import {Address, Byte} from "../types";

export interface Peripheral {
getIoMap(): Map<number, (adr: Address, value?: Byte) => any>
}

export const enum KeyType {
F8, RETURN, LBRACKET, RBRACKET, KANA, RSHIFT, YEN, STOP,
F7, ATMARK, COLON, SEMICOLON, UNDERSCORE, SLASH, MINUS, HAT,
F6, O, L, K, PERIOD, COMMA, P, NUM0,
F5, I, U, J, M, N, NUM9, NUM8,
F4, Y, G, H, B, V, NUM7, NUM6,
F3, T, R, D, F, C, NUM5, NUM4,
F2, W, S, A, X, Z, E, NUM3,
F1, ESC, Q, CTR, LSHIFT, GRPH, NUM1, NUM2,
CLR_HOME, UP, RIGHT, LEFT, DOWN, SPACE, DEL, INS,
}

export class Keyboard implements Peripheral {
private ioMap: Map<number, (adr: Address, value?: Byte) => any>
private state = new Uint8Array(10 * 2)
private rowcol = 0

constructor() {
this.state.fill(0x1e)

this.ioMap = new Map<number, (adr: Address, value?: Byte) => any>()
this.ioMap.set(0x4016, (_adr: Address, value?: Byte) => {
const prevcol = this.rowcol & 1
const col = (value! >> 1) & 1
this.rowcol = (this.rowcol & ~1) | col
if (col === 0 && prevcol !== 0) // High to low.
this.rowcol = (this.rowcol + 2) % (10 * 2)
if ((value! & 1) !== 0) {
this.rowcol = 0
}
})
this.ioMap.set(0x4017, (_adr: Address) => {
const result = this.state[this.rowcol]
this.rowcol += 1
return result
})
}

public getIoMap(): Map<number, (adr: number, value?: Byte) => any> {
return this.ioMap
}

public setKeyState(type: KeyType, pressed: boolean): void {
const i = type >> 2
const b = 2 << (type & 3)
const s = this.state[i]
this.state[i] = pressed ? (s & ~b) : (s | b) // Pressed=>clear, not pressed=>set
}
}

0 comments on commit d9e1385

Please sign in to comment.