forked from mattgodbolt/jsbeeb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
teletext_adaptor.js
172 lines (147 loc) · 5.17 KB
/
teletext_adaptor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"use strict";
import * as utils from "./utils.js";
// Code ported from Beebem (C to .js) by Jason Robson
const TELETEXT_IRQ = 5;
const TELETEXT_FRAME_SIZE = 860;
const TELETEXT_UPDATE_FREQ = 50000;
/*
Offset Description Access
+00 Status register R/W
+01 Row register
+02 Data register
+03 Clear status register
Status register:
Read
Bits Function
0-3 Link settings
4 FSYN (Latches high on Field sync)
5 DEW (Data entry window)
6 DOR (Latches INT on end of DEW)
7 INT (latches high on end of DEW)
Write
Bits Function
0-1 Channel select
2 Teletext Enable
3 Enable Interrupts
4 Enable AFC (and mystery links A)
5 Mystery links B
*/
export class TeletextAdaptor {
constructor(cpu) {
this.cpu = cpu;
this.teletextStatus = 0x0f; /* low nibble comes from LK4-7 and mystery links which are left floating */
this.teletextInts = false;
this.teletextEnable = false;
this.channel = 0;
this.currentFrame = 0;
this.totalFrames = 0;
this.rowPtr = 0x00;
this.colPtr = 0x00;
this.frameBuffer = new Array(16).fill(0).map(() => new Array(64).fill(0));
this.streamData = null;
this.pollCount = 0;
}
reset(hard) {
if (hard) {
console.log("Teletext adaptor: initialisation");
this.loadChannelStream(this.channel);
}
}
loadChannelStream(channel) {
console.log("Teletext adaptor: switching to channel " + channel);
const teletextRef = this;
utils.loadData("teletext/txt" + channel + ".dat").then(function (data) {
teletextRef.streamData = data;
teletextRef.totalFrames = data.length / TELETEXT_FRAME_SIZE;
teletextRef.currentFrame = 0;
});
}
read(addr) {
let data = 0x00;
switch (addr) {
case 0x00: // Status Register
data = this.teletextStatus;
break;
case 0x01: // Row Register
break;
case 0x02: // Data Register
data = this.frameBuffer[this.rowPtr][this.colPtr++];
break;
case 0x03:
this.teletextStatus &= ~0xd0; // Clear INT, DOR, and FSYN latches
this.cpu.interrupt &= ~(1 << TELETEXT_IRQ);
break;
}
return data;
}
write(addr, value) {
switch (addr) {
case 0x00:
// Status register
this.teletextInts = (value & 0x08) === 0x08;
if (this.teletextInts && this.teletextStatus & 0x80) {
this.cpu.interrupt |= 1 << TELETEXT_IRQ; // Interrupt if INT and interrupts enabled
} else {
this.cpu.interrupt &= ~(1 << TELETEXT_IRQ); // Clear interrupt
}
this.teletextEnable = (value & 0x04) === 0x04;
if ((value & 0x03) !== this.channel && this.teletextEnable) {
this.channel = value & 0x03;
this.loadChannelStream(this.channel);
}
break;
case 0x01:
this.rowPtr = value;
this.colPtr = 0x00;
break;
case 0x02:
this.frameBuffer[this.rowPtr][this.colPtr++] = value & 0xff;
break;
case 0x03:
this.teletextStatus &= ~0xd0; // Clear INT, DOR, and FSYN latches
this.cpu.interrupt &= ~(1 << TELETEXT_IRQ); // Clear interrupt
break;
}
}
// Attempt to emulate the TV broadcast
polltime(cycles) {
this.pollCount += cycles;
if (this.pollCount > TELETEXT_UPDATE_FREQ) {
this.pollCount = 0;
// Don't flood the processor with teletext interrupts during a reset
if (this.cpu.resetLine) {
this.update();
} else {
// Grace period before we start up again
this.pollCount = -TELETEXT_UPDATE_FREQ * 10;
}
}
}
update() {
if (this.currentFrame >= this.totalFrames) {
this.currentFrame = 0;
}
const offset = this.currentFrame * TELETEXT_FRAME_SIZE + 3 * 43;
this.teletextStatus &= 0x0f;
this.teletextStatus |= 0xd0; // data ready so latch INT, DOR, and FSYN
if (this.teletextEnable) {
// Copy current stream position into the frame buffer
for (let i = 0; i < 16; ++i) {
if (this.streamData[offset + i * 43] !== 0) {
this.frameBuffer[i][0] = 0x67;
for (let j = 1; j <= 42; j++) {
this.frameBuffer[i][j] = this.streamData[offset + (i * 43 + (j - 1))];
}
} else {
this.frameBuffer[i][0] = 0x00;
}
}
}
this.currentFrame++;
this.rowPtr = 0x00;
this.colPtr = 0x00;
if (this.teletextInts) {
this.cpu.interrupt |= 1 << TELETEXT_IRQ;
}
}
}