Skip to content

Commit

Permalink
[#314] Add BS + DELETE to keyboard mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
vbmacher committed Jan 2, 2024
1 parent 8994b6e commit 991f962
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
@NotThreadSafe
public class ZxSpectrumBusImpl extends AbstractMemoryContext<Byte> implements ZxSpectrumBus, CPUContext.PassedCyclesListener {
private static final long LINE_TSTATES = 224;
private static final long FRAME_TSTATES = 69888;

// from 14335 to 14463, then 96 tstates pause to reach "end of line", then repeat.
private final static Map<Long, Integer> CONTENTION_MAP = new HashMap<>();
Expand Down Expand Up @@ -129,6 +130,7 @@ public void initialize(ContextZ80 cpu, MemoryContext<Byte> memory) {
for (CPUContext.PassedCyclesListener listener : deferredListeners) {
cpu.addPassedCyclesListener(listener);
}
cpu.addPassedCyclesListener(this);

deferredAttachments.clear();
deferredListeners.clear();
Expand Down Expand Up @@ -248,6 +250,7 @@ private void contendedMemory(int location) {
if (location >= 0x4000 && location <= 0x7FFF) {
Integer cycles = CONTENTION_MAP.get(contentionCycles);
if (cycles != null) {
// System.out.printf("%04x: %d, tstates=%d\n", location, cycles, contentionCycles);
cpu.addCycles(cycles);
}
}
Expand Down Expand Up @@ -306,7 +309,7 @@ private void contendedPort(int portAddress) {

@Override
public void passedCycles(long tstates) {
contentionCycles = (contentionCycles + tstates) % (LINE_TSTATES + 14335);
contentionCycles = (contentionCycles + tstates) % FRAME_TSTATES;
}

private class ContendedDeviceProxy implements Context8080.CpuPortDevice {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,53 +72,59 @@ public class ULA implements Context8080.CpuPortDevice, Keyboard.OnKeyListener {
public static final int ATTRIBUTE_HEIGHT = SCREEN_HEIGHT / 8;

private final static byte[] RST_7 = new byte[0x38]; // works for IM1 and IM2 modes
private final static byte[] KEY_SHIFT = new byte[]{0, 1};
private final static byte[] KEY_SYM_SHIFT = new byte[]{7, 2};

private final byte[] keymap = new byte[8]; // keyboard state
public final byte[][] videoMemory = new byte[SCREEN_WIDTH][SCREEN_HEIGHT];
public final byte[][] attributeMemory = new byte[SCREEN_WIDTH][ATTRIBUTE_HEIGHT];
private final static int[] lineStartOffsets = computeLineStartOffsets();

// maps host characters to ZX Spectrum key "commands" (keymap index, "zero" value)
// maps host characters to ZX Spectrum key "commands"
// Byte[] = {keymap index, "zero" value, shift, symshift}
private final static Map<Character, Byte[]> CHAR_MAPPING = new HashMap<>();

static {
CHAR_MAPPING.put('z', new Byte[]{0, 2}); // z, COPY, ":"
CHAR_MAPPING.put('x', new Byte[]{0, 4}); // x, CLEAR, "£"
CHAR_MAPPING.put('c', new Byte[]{0, 8}); // c, CONT, "?"
CHAR_MAPPING.put('v', new Byte[]{0, 16}); // v, CLS, "/"
CHAR_MAPPING.put('b', new Byte[]{7, 16}); // b, BORDER, "*"
CHAR_MAPPING.put('n', new Byte[]{7, 8}); // n, NEXT, ","
CHAR_MAPPING.put('m', new Byte[]{7, 4}); // m, PAUSE, "."
CHAR_MAPPING.put(' ', new Byte[]{7, 1}); // " "
CHAR_MAPPING.put('a', new Byte[]{1, 1}); // a, NEW, "STOP"
CHAR_MAPPING.put('s', new Byte[]{1, 2}); // s, SAVE, "NOT"
CHAR_MAPPING.put('d', new Byte[]{1, 4}); // d, DIM, "STEP"
CHAR_MAPPING.put('f', new Byte[]{1, 8}); // f, FOR, "TO"
CHAR_MAPPING.put('g', new Byte[]{1, 16}); // g, GOTO, "THEN"
CHAR_MAPPING.put('h', new Byte[]{6, 16}); // h, GOSUB, "↑"
CHAR_MAPPING.put('j', new Byte[]{6, 8}); // j, LOAD, "-"
CHAR_MAPPING.put('k', new Byte[]{6, 4}); // k, LIST, "+"
CHAR_MAPPING.put('l', new Byte[]{6, 2}); // l, LET, "="
CHAR_MAPPING.put('q', new Byte[]{2, 1}); // q, PLOT, "<="
CHAR_MAPPING.put('w', new Byte[]{2, 2}); // w, DRAW, "<>"
CHAR_MAPPING.put('e', new Byte[]{2, 4}); // e, REM, ">="
CHAR_MAPPING.put('r', new Byte[]{2, 8}); // r, RUN, "<"
CHAR_MAPPING.put('t', new Byte[]{2, 16}); // t, RAND, ">"
CHAR_MAPPING.put('y', new Byte[]{5, 16}); // y, RETURN, "AND"
CHAR_MAPPING.put('u', new Byte[]{5, 8}); // u, IF, "OR"
CHAR_MAPPING.put('i', new Byte[]{5, 4}); // i, INPUT, "AT"
CHAR_MAPPING.put('o', new Byte[]{5, 2}); // o, POKE, ";"
CHAR_MAPPING.put('p', new Byte[]{5, 1}); // p, PRINT, "
CHAR_MAPPING.put('1', new Byte[]{3, 1}); // 1, "!"
CHAR_MAPPING.put('2', new Byte[]{3, 2}); // 2, "@"
CHAR_MAPPING.put('3', new Byte[]{3, 4}); // 3, "#"
CHAR_MAPPING.put('4', new Byte[]{3, 8}); // 4, "$"
CHAR_MAPPING.put('5', new Byte[]{3, 16}); // 5, "%"
CHAR_MAPPING.put('6', new Byte[]{4, 16}); // 6, "&"
CHAR_MAPPING.put('7', new Byte[]{4, 8}); // 7, "'"
CHAR_MAPPING.put('8', new Byte[]{4, 4}); // 8, "("
CHAR_MAPPING.put('9', new Byte[]{4, 2}); // 9, ")"
CHAR_MAPPING.put('0', new Byte[]{4, 1}); // 0, "_"
CHAR_MAPPING.put('Z', new Byte[]{0, 2, -1, -1}); // z, COPY, ":"
CHAR_MAPPING.put('X', new Byte[]{0, 4, -1, -1}); // x, CLEAR, "£"
CHAR_MAPPING.put('C', new Byte[]{0, 8, -1, -1}); // c, CONT, "?"
CHAR_MAPPING.put('V', new Byte[]{0, 16, -1, -1}); // v, CLS, "/"
CHAR_MAPPING.put('B', new Byte[]{7, 16, -1, -1}); // b, BORDER, "*"
CHAR_MAPPING.put('N', new Byte[]{7, 8, -1, -1}); // n, NEXT, ","
CHAR_MAPPING.put('M', new Byte[]{7, 4, -1, -1}); // m, PAUSE, "."
CHAR_MAPPING.put(' ', new Byte[]{7, 1, -1, -1}); // " "
CHAR_MAPPING.put('\n', new Byte[]{6, 1, -1, -1}); // ENTER
CHAR_MAPPING.put('A', new Byte[]{1, 1, -1, -1}); // a, NEW, "STOP"
CHAR_MAPPING.put('S', new Byte[]{1, 2, -1, -1}); // s, SAVE, "NOT"
CHAR_MAPPING.put('D', new Byte[]{1, 4, -1, -1}); // d, DIM, "STEP"
CHAR_MAPPING.put('F', new Byte[]{1, 8, -1, -1}); // f, FOR, "TO"
CHAR_MAPPING.put('G', new Byte[]{1, 16, -1, -1}); // g, GOTO, "THEN"
CHAR_MAPPING.put('H', new Byte[]{6, 16, -1, -1}); // h, GOSUB, "↑"
CHAR_MAPPING.put('J', new Byte[]{6, 8, -1, -1}); // j, LOAD, "-"
CHAR_MAPPING.put('K', new Byte[]{6, 4, -1, -1}); // k, LIST, "+"
CHAR_MAPPING.put('L', new Byte[]{6, 2, -1, -1}); // l, LET, "="
CHAR_MAPPING.put('Q', new Byte[]{2, 1, -1, -1}); // q, PLOT, "<="
CHAR_MAPPING.put('W', new Byte[]{2, 2, -1, -1}); // w, DRAW, "<>"
CHAR_MAPPING.put('E', new Byte[]{2, 4, -1, -1}); // e, REM, ">="
CHAR_MAPPING.put('R', new Byte[]{2, 8, -1, -1}); // r, RUN, "<"
CHAR_MAPPING.put('T', new Byte[]{2, 16, -1, -1}); // t, RAND, ">"
CHAR_MAPPING.put('Y', new Byte[]{5, 16, -1, -1}); // y, RETURN, "AND"
CHAR_MAPPING.put('U', new Byte[]{5, 8, -1, -1}); // u, IF, "OR"
CHAR_MAPPING.put('I', new Byte[]{5, 4, -1, -1}); // i, INPUT, "AT"
CHAR_MAPPING.put('O', new Byte[]{5, 2, -1, -1}); // o, POKE, ";"
CHAR_MAPPING.put('P', new Byte[]{5, 1, -1, -1}); // p, PRINT, "
CHAR_MAPPING.put('1', new Byte[]{3, 1, -1, -1}); // 1, "!"
CHAR_MAPPING.put('2', new Byte[]{3, 2, -1, -1}); // 2, "@"
CHAR_MAPPING.put('3', new Byte[]{3, 4, -1, -1}); // 3, "#"
CHAR_MAPPING.put('4', new Byte[]{3, 8, -1, -1}); // 4, "$"
CHAR_MAPPING.put('5', new Byte[]{3, 16, -1, -1}); // 5, "%"
CHAR_MAPPING.put('6', new Byte[]{4, 16, -1, -1}); // 6, "&"
CHAR_MAPPING.put('7', new Byte[]{4, 8, -1, -1}); // 7, "'"
CHAR_MAPPING.put('8', new Byte[]{4, 4, -1, -1}); // 8, "("
CHAR_MAPPING.put('9', new Byte[]{4, 2, -1, -1}); // 9, ")"
CHAR_MAPPING.put('0', new Byte[]{4, 1, -1, -1}); // 0, "_"
CHAR_MAPPING.put('\b', new Byte[]{4, 1, 1, -1}); // backspace
CHAR_MAPPING.put((char) 127, new Byte[]{4, 1, 1, -1}); // delete
}

// The Spectrum's 'FLASH' effect is also produced by the ULA: Every 16 frames, the ink and paper of all flashing
Expand Down Expand Up @@ -219,10 +225,7 @@ public byte read(int portAddress) {
}

// LINE IN?
if ((bus.readData() & 1) == 1) {
result |= 0x40;
}
return result;
return (byte) (result | ((bus.readData() & 1) << 6));
}

@Override
Expand All @@ -245,42 +248,57 @@ public String toString() {
@Override
public void onKeyUp(KeyEvent evt) {
int keyCode = evt.getExtendedKeyCode();

if (keyCode == KeyEvent.VK_CONTROL) {
keymap[7] |= 0x2; // symshift
} else if (evt.getKeyCode() == KeyEvent.VK_SHIFT) {
keymap[0] |= 0x1; // shift
switch (keyCode) {
case KeyEvent.VK_CONTROL:
keymap[KEY_SYM_SHIFT[0]] |= KEY_SYM_SHIFT[1];
break;
case KeyEvent.VK_SHIFT:
keymap[KEY_SHIFT[0]] |= KEY_SHIFT[1];
break;
default:
Byte[] command = CHAR_MAPPING.get((char) keyCode);
if (command != null) {
if (command[2] == 1) {
keymap[KEY_SHIFT[0]] |= KEY_SHIFT[1];
} else if (command[2] == 0) {
keymap[KEY_SHIFT[0]] &= (byte) ((~KEY_SHIFT[1]) & 0xFF);
}
if (command[3] == 1) {
keymap[KEY_SYM_SHIFT[0]] |= KEY_SYM_SHIFT[1];
} else if (command[3] == 0) {
keymap[KEY_SYM_SHIFT[0]] &= (byte) ((~KEY_SYM_SHIFT[1]) & 0xFF);
}
keymap[command[0]] |= command[1];
}
}

if (keyCode == KeyEvent.VK_ENTER) {
keymap[6] |= 0x1; // ENTER
} else {
char c = (char) Character.toLowerCase(keyCode);
Byte[] command = CHAR_MAPPING.get(c);
if (command != null) {
keymap[command[0]] |= command[1];
}
}
}

@Override
public void onKeyDown(KeyEvent evt) {
int keyCode = evt.getExtendedKeyCode();

if (keyCode == KeyEvent.VK_CONTROL) {
keymap[7] &= (byte) 0xFD; // symshift
} else if (keyCode == KeyEvent.VK_SHIFT) {
keymap[0] &= (byte) 0xFE; // shift
}

if (keyCode == KeyEvent.VK_ENTER) {
keymap[6] &= (byte) 0xFE; // ENTER
} else {
char c = (char) Character.toLowerCase(keyCode);
Byte[] command = CHAR_MAPPING.get(c);
if (command != null) {
keymap[command[0]] &= (byte) ((~command[1]) & 0xFF);
}
switch (keyCode) {
case KeyEvent.VK_CONTROL:
keymap[KEY_SYM_SHIFT[0]] &= (byte) ((~KEY_SYM_SHIFT[1]) & 0xFF);
break;
case KeyEvent.VK_SHIFT:
keymap[KEY_SHIFT[0]] &= (byte) ((~KEY_SHIFT[1]) & 0xFF);
break;
default:
Byte[] command = CHAR_MAPPING.get((char) keyCode);
if (command != null) {
if (command[2] == 1) {
keymap[KEY_SHIFT[0]] &= (byte) ((~KEY_SHIFT[1]) & 0xFF);
} else if (command[2] == 0) {
keymap[KEY_SHIFT[0]] |= KEY_SHIFT[1];
}
if (command[3] == 1) {
keymap[KEY_SYM_SHIFT[0]] &= (byte) ((~KEY_SYM_SHIFT[1]) & 0xFF);
} else if (command[3] == 0) {
keymap[KEY_SYM_SHIFT[0]] |= KEY_SYM_SHIFT[1];
}
keymap[command[0]] &= (byte) ((~command[1]) & 0xFF);
}
}
}

Expand Down

0 comments on commit 991f962

Please sign in to comment.