ac6502 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/CPU.d.ts +4 -0
  2. package/dist/components/CPU.js +87 -30
  3. package/dist/components/CPU.js.map +1 -1
  4. package/dist/components/IO/ACIA.d.ts +13 -21
  5. package/dist/components/IO/ACIA.js +53 -151
  6. package/dist/components/IO/ACIA.js.map +1 -1
  7. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +1 -1
  8. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -1
  9. package/dist/components/IO/Empty.d.ts +1 -3
  10. package/dist/components/IO/Empty.js +1 -5
  11. package/dist/components/IO/Empty.js.map +1 -1
  12. package/dist/components/IO/RAMBank.d.ts +3 -4
  13. package/dist/components/IO/RAMBank.js +4 -13
  14. package/dist/components/IO/RAMBank.js.map +1 -1
  15. package/dist/components/IO/RTC.d.ts +2 -3
  16. package/dist/components/IO/RTC.js +17 -7
  17. package/dist/components/IO/RTC.js.map +1 -1
  18. package/dist/components/IO/Sound.d.ts +1 -3
  19. package/dist/components/IO/Sound.js +13 -23
  20. package/dist/components/IO/Sound.js.map +1 -1
  21. package/dist/components/IO/Storage.d.ts +1 -3
  22. package/dist/components/IO/Storage.js +1 -3
  23. package/dist/components/IO/Storage.js.map +1 -1
  24. package/dist/components/IO/VIA.d.ts +1 -3
  25. package/dist/components/IO/VIA.js +6 -7
  26. package/dist/components/IO/VIA.js.map +1 -1
  27. package/dist/components/IO/Video.d.ts +1 -3
  28. package/dist/components/IO/Video.js +3 -5
  29. package/dist/components/IO/Video.js.map +1 -1
  30. package/dist/components/IO.d.ts +1 -3
  31. package/dist/components/Machine.d.ts +1 -2
  32. package/dist/components/Machine.js +21 -74
  33. package/dist/components/Machine.js.map +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/tests/IO/ACIA.test.js +57 -108
  36. package/dist/tests/IO/ACIA.test.js.map +1 -1
  37. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +5 -0
  38. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -1
  39. package/dist/tests/IO/Empty.test.js +2 -14
  40. package/dist/tests/IO/Empty.test.js.map +1 -1
  41. package/dist/tests/IO/RAMBank.test.js +5 -12
  42. package/dist/tests/IO/RAMBank.test.js.map +1 -1
  43. package/dist/tests/IO/RTC.test.js +7 -16
  44. package/dist/tests/IO/RTC.test.js.map +1 -1
  45. package/dist/tests/IO/Sound.test.js +6 -8
  46. package/dist/tests/IO/Sound.test.js.map +1 -1
  47. package/dist/tests/IO/Storage.test.js +0 -6
  48. package/dist/tests/IO/Storage.test.js.map +1 -1
  49. package/dist/tests/IO/VIA.test.js +6 -10
  50. package/dist/tests/IO/VIA.test.js.map +1 -1
  51. package/dist/tests/IO/Video.test.js +7 -7
  52. package/dist/tests/IO/Video.test.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/components/CPU.ts +94 -31
  55. package/src/components/IO/ACIA.ts +57 -176
  56. package/src/components/IO/Attachments/KeyboardEncoderAttachment.ts +1 -1
  57. package/src/components/IO/Empty.ts +1 -4
  58. package/src/components/IO/RAMBank.ts +4 -15
  59. package/src/components/IO/RTC.ts +18 -7
  60. package/src/components/IO/Sound.ts +14 -27
  61. package/src/components/IO/Storage.ts +2 -5
  62. package/src/components/IO/VIA.ts +6 -8
  63. package/src/components/IO/Video.ts +5 -7
  64. package/src/components/IO.ts +1 -4
  65. package/src/components/Machine.ts +22 -90
  66. package/src/index.ts +1 -1
  67. package/src/tests/IO/ACIA.test.ts +60 -122
  68. package/src/tests/IO/Attachments/KeyboardEncoderAttachment.test.ts +6 -0
  69. package/src/tests/IO/Empty.test.ts +2 -17
  70. package/src/tests/IO/RAMBank.test.ts +5 -14
  71. package/src/tests/IO/RTC.test.ts +7 -20
  72. package/src/tests/IO/Sound.test.ts +6 -8
  73. package/src/tests/IO/Storage.test.ts +0 -7
  74. package/src/tests/IO/VIA.test.ts +6 -12
  75. package/src/tests/IO/Video.test.ts +7 -8
@@ -3,6 +3,9 @@ import { IO } from '../IO'
3
3
  /**
4
4
  * ACIA - Emulates a R6551 ACIA (Asynchronous Communications Interface Adapter)
5
5
  *
6
+ * Simplified to match real R6551 hardware: single-byte TX/RX registers,
7
+ * no buffers, no baud rate timing (USB serial operates at USB speeds).
8
+ *
6
9
  * Register Map:
7
10
  * $00: Data Register (read/write)
8
11
  * $01: Status Register (read) / Programmed Reset (write)
@@ -11,31 +14,24 @@ import { IO } from '../IO'
11
14
  */
12
15
  export class ACIA implements IO {
13
16
 
14
- raiseIRQ = () => {}
15
- raiseNMI = () => {}
16
17
  transmit?: (data: number) => void
17
18
 
18
19
  // Registers
19
- private dataRegister: number = 0
20
- private statusRegister: number = 0x10 // Transmit Data Register Empty
20
+ private txRegister: number = 0
21
+ private rxRegister: number = 0
21
22
  private commandRegister: number = 0
22
23
  private controlRegister: number = 0
23
24
 
24
- // Buffers
25
- private transmitBuffer: number[] = []
26
- private receiveBuffer: number[] = []
27
-
28
25
  // Status flags
26
+ private txRegEmpty: boolean = true
27
+ private rxRegFull: boolean = false
28
+ private txPending: boolean = false
29
+ private overrun: boolean = false
29
30
  private parityError: boolean = false
30
31
  private framingError: boolean = false
31
- private overrun: boolean = false
32
32
  private irqFlag: boolean = false
33
33
  private echoMode: boolean = false
34
34
 
35
- // Timing
36
- private cycleCounter: number = 0
37
- private baudRate: number = 115200
38
-
39
35
  /**
40
36
  * Read from ACIA register
41
37
  */
@@ -80,51 +76,32 @@ export class ACIA implements IO {
80
76
  break
81
77
 
82
78
  case 0x03: // Control Register
83
- this.writeControl(data)
79
+ this.controlRegister = data & 0xFF
84
80
  break
85
81
  }
86
82
  }
87
83
 
88
84
  /**
89
- * Read data from receive buffer
85
+ * Read data from receive register
90
86
  */
91
87
  private readData(): number {
92
- if (this.receiveBuffer.length > 0) {
93
- const data = this.receiveBuffer.shift()!
94
- this.dataRegister = data
95
-
96
- // Update status: clear Receive Data Register Full
97
- this.statusRegister &= ~0x08
98
-
99
- // Check for overrun if more data arrives
100
- if (this.receiveBuffer.length === 0) {
101
- this.overrun = false
102
- this.statusRegister &= ~0x04
103
- }
88
+ // Clear Receive Data Register Full
89
+ this.rxRegFull = false
90
+ this.overrun = false
104
91
 
105
- // If more bytes remain in the buffer, re-assert IRQ so the BIOS services them
106
- if (this.receiveBuffer.length > 0 && !(this.commandRegister & 0x02)) {
107
- this.irqFlag = true
108
- this.statusRegister |= 0x80
109
- } else {
110
- this.irqFlag = false
111
- this.statusRegister &= ~0x80
112
- }
92
+ // Clear IRQ if it was from RX
93
+ this.irqFlag = false
113
94
 
114
- return data
115
- }
116
-
117
- return this.dataRegister
95
+ return this.rxRegister
118
96
  }
119
97
 
120
98
  /**
121
- * Write data to transmit buffer
99
+ * Write data to transmit register
122
100
  */
123
101
  private writeData(data: number): void {
124
- this.transmitBuffer.push(data & 0xFF)
125
-
126
- // Clear Transmit Data Register Empty flag
127
- this.statusRegister &= ~0x10
102
+ this.txRegister = data & 0xFF
103
+ this.txRegEmpty = false
104
+ this.txPending = true
128
105
  }
129
106
 
130
107
  /**
@@ -148,27 +125,26 @@ export class ACIA implements IO {
148
125
  if (this.overrun) status |= 0x04
149
126
 
150
127
  // Bit 3: Receive Data Register Full
151
- if (this.receiveBuffer.length > 0) status |= 0x08
128
+ if (this.rxRegFull) status |= 0x08
152
129
 
153
130
  // Bit 4: Transmit Data Register Empty
154
- if (this.transmitBuffer.length === 0) status |= 0x10
131
+ if (this.txRegEmpty) status |= 0x10
155
132
 
156
- // Bit 5: Data Carrier Detect (DCD)
133
+ // Bit 5: Data Carrier Detect (DCD) - always connected
157
134
  status &= ~0x20
158
135
 
159
- // Bit 6: Data Set Ready (DSR)
136
+ // Bit 6: Data Set Ready (DSR) - always ready
160
137
  status |= 0x40
161
138
 
162
139
  // Bit 7: Interrupt (IRQ)
163
140
  if (this.irqFlag) status |= 0x80
164
141
 
165
- // Clear IRQ and error flags after building the status byte (R6551 spec)
142
+ // Clear IRQ and error flags after reading (R6551 spec)
166
143
  this.irqFlag = false
167
144
  this.parityError = false
168
145
  this.framingError = false
169
146
  this.overrun = false
170
147
 
171
- this.statusRegister = status
172
148
  return status
173
149
  }
174
150
 
@@ -178,82 +154,16 @@ export class ACIA implements IO {
178
154
  private writeCommand(data: number): void {
179
155
  this.commandRegister = data & 0xFF
180
156
 
181
- // Bits 0-1: DTR control
182
- // const dtrControl = data & 0x03
183
-
184
- // Bit 1: Receiver Interrupt Request Disable (RIIE) — 0 = IRQ enabled, 1 = disabled (active low)
185
- const receiveIRQEnabled = (data & 0x02) === 0
186
-
187
- // Bits 3-2: Transmitter Interrupt Control (TIC)
188
- // const transmitControl = (data >> 2) & 0x03
189
-
190
157
  // Bit 4: Echo Mode Enable (EME)
191
158
  this.echoMode = (data & 0x10) !== 0
192
-
193
- // Bits 6-7: Parity control
194
- // const parityControl = (data >> 6) & 0x03
195
-
196
- // Handle receive IRQ
197
- if (receiveIRQEnabled && this.receiveBuffer.length > 0) {
198
- this.irqFlag = true
199
- this.statusRegister |= 0x80
200
- this.raiseIRQ()
201
- } else if (!receiveIRQEnabled) {
202
- this.irqFlag = false
203
- this.statusRegister &= ~0x80
204
- }
205
- }
206
-
207
- /**
208
- * Write to control register
209
- */
210
- private writeControl(data: number): void {
211
- this.controlRegister = data & 0xFF
212
-
213
- // Bits 0-3: Baud rate
214
- const baudRateCode = data & 0x0F
215
- this.baudRate = this.getBaudRate(baudRateCode)
216
-
217
- // Bit 4: Receiver clock source (internal/external)
218
- const receiverClockSource = (data & 0x10) !== 0
219
-
220
- // Bits 5-6: Word length (5, 6, 7, or 8 bits)
221
- const wordLength = ((data >> 5) & 0x03) + 5
222
-
223
- // Bit 7: Stop bits (1 or 2)
224
- const stopBits = (data & 0x80) ? 2 : 1
225
- }
226
-
227
- /**
228
- * Get baud rate from control register code
229
- */
230
- private getBaudRate(code: number): number {
231
- const baudRates = [
232
- 115200, // 0000 (actually 16x external clock, using 115200 as default)
233
- 50, // 0001
234
- 75, // 0010
235
- 110, // 0011
236
- 135, // 0100
237
- 150, // 0101
238
- 300, // 0110
239
- 600, // 0111
240
- 1200, // 1000
241
- 1800, // 1001
242
- 2400, // 1010
243
- 3600, // 1011
244
- 4800, // 1100
245
- 7200, // 1101
246
- 9600, // 1110
247
- 19200 // 1111
248
- ]
249
- return baudRates[code] || 115200
250
159
  }
251
160
 
252
161
  /**
253
162
  * Programmed reset
254
163
  */
255
164
  private programmedReset(): void {
256
- this.statusRegister = 0x10 // Transmit Data Register Empty
165
+ this.txRegEmpty = true
166
+ this.txPending = false
257
167
  this.parityError = false
258
168
  this.framingError = false
259
169
  this.overrun = false
@@ -261,100 +171,71 @@ export class ACIA implements IO {
261
171
  }
262
172
 
263
173
  /**
264
- * Tick - emulate ACIA timing
174
+ * Tick - process TX/RX each cycle, return interrupt status
265
175
  */
266
- tick(frequency: number): void {
267
- // Re-evaluate receive interrupt condition.
268
- // After readStatus() clears irqFlag, the IRQ must be re-asserted if
269
- // the underlying condition (RDRF + receive IRQ enabled) is still active.
270
- if (!(this.commandRegister & 0x02) && this.receiveBuffer.length > 0) {
271
- this.irqFlag = true
272
- }
273
-
274
- if (this.irqFlag) {
275
- this.raiseIRQ()
276
- }
277
-
278
- this.cycleCounter++
176
+ tick(frequency: number): number {
177
+ // Handle pending transmit - send immediately (no baud timing)
178
+ if (this.txPending) {
179
+ this.txPending = false
279
180
 
280
- // Calculate cycles per byte: (CPU_CLOCK / baud_rate) * bits_per_frame
281
- // Assuming 10 bits per frame (1 start + 8 data + 1 stop)
282
- const cyclesPerByte = Math.floor((frequency / this.baudRate) * 10)
283
-
284
- // Simulate transmission based on actual baud rate
285
- if (this.cycleCounter >= cyclesPerByte && this.transmitBuffer.length > 0) {
286
- this.cycleCounter = 0
287
-
288
- // Transmit one byte
289
- const byte = this.transmitBuffer.shift()
290
-
291
- if (byte !== undefined && this.transmit) {
292
- this.transmit(byte)
181
+ if (this.transmit) {
182
+ this.transmit(this.txRegister)
293
183
  }
294
184
 
295
- // Set Transmit Data Register Empty if buffer is empty
296
- if (this.transmitBuffer.length === 0) {
297
- this.statusRegister |= 0x10
185
+ this.txRegEmpty = true
298
186
 
299
- // Trigger transmit complete IRQ if enabled (bits 3-2 = 01 means TxIRQ on TDRE)
300
- if ((this.commandRegister & 0x0C) === 0x04) {
301
- this.irqFlag = true
302
- this.statusRegister |= 0x80
303
- this.raiseIRQ()
304
- }
187
+ // Trigger transmit complete IRQ if enabled (TIC bits 3-2 = 01)
188
+ if ((this.commandRegister & 0x0C) === 0x04) {
189
+ this.irqFlag = true
305
190
  }
191
+
192
+ // Echo mode: received data echoed back
193
+ // (echo of transmitted data is handled in onData)
306
194
  }
195
+
196
+ // Return IRQ status
197
+ return this.irqFlag ? 0x80 : 0
307
198
  }
308
199
 
309
200
  /**
310
201
  * Reset the ACIA
311
202
  */
312
203
  reset(coldStart: boolean): void {
313
- this.dataRegister = 0
314
- this.statusRegister = 0x10 // Transmit Data Register Empty
204
+ this.txRegister = 0
205
+ this.rxRegister = 0
315
206
  this.commandRegister = 0
316
207
  this.controlRegister = 0
317
-
318
- this.transmitBuffer = []
319
- this.receiveBuffer = []
320
-
208
+
209
+ this.txRegEmpty = true
210
+ this.rxRegFull = false
211
+ this.txPending = false
212
+ this.overrun = false
321
213
  this.parityError = false
322
214
  this.framingError = false
323
- this.overrun = false
324
215
  this.irqFlag = false
325
216
  this.echoMode = false
326
-
327
- this.cycleCounter = 0
328
- this.baudRate = 115200
329
217
  }
330
218
 
331
219
  /**
332
220
  * Receive data from external source
333
221
  */
334
222
  onData(data: number): void {
335
- if (this.receiveBuffer.length > 0) {
223
+ if (this.rxRegFull) {
336
224
  // Overrun: new data arrived before the previous byte was read
337
225
  this.overrun = true
338
- this.statusRegister |= 0x04
339
226
  }
340
227
 
341
- this.receiveBuffer.push(data & 0xFF)
342
-
343
- // Set Receive Data Register Full flag
344
- this.statusRegister |= 0x08
228
+ this.rxRegister = data & 0xFF
229
+ this.rxRegFull = true
345
230
 
346
231
  // Trigger receive IRQ if enabled (bit 1 = 0 means enabled, active low)
347
232
  if (!(this.commandRegister & 0x02)) {
348
233
  this.irqFlag = true
349
- this.statusRegister |= 0x80
350
- this.raiseIRQ()
351
234
  }
352
235
 
353
236
  // Echo mode: automatically transmit received data
354
- if (this.echoMode) {
355
- this.transmitBuffer.push(data & 0xFF)
356
- // Clear Transmit Data Register Empty flag
357
- this.statusRegister &= ~0x10
237
+ if (this.echoMode && this.transmit) {
238
+ this.transmit(data & 0xFF)
358
239
  }
359
240
  }
360
241
  }
@@ -86,7 +86,7 @@ export class KeyboardEncoderAttachment extends AttachmentBase {
86
86
  // 'A' = PS/2 encoder on Port A (CA1 IRQ only)
87
87
  // 'B' = Matrix encoder on Port B (CB1 IRQ only)
88
88
  // 'both' = both ports active (default)
89
- activePort: 'A' | 'B' | 'both' = 'both'
89
+ activePort: 'A' | 'B' | 'both' = 'B'
90
90
 
91
91
  // Port A state
92
92
  private asciiDataA: number = 0x00
@@ -2,15 +2,12 @@ import { IO } from '../IO'
2
2
 
3
3
  export class Empty implements IO {
4
4
 
5
- raiseIRQ = () => {}
6
- raiseNMI = () => {}
7
-
8
5
  read(address: number): number {
9
6
  return 0
10
7
  }
11
8
 
12
9
  write(address: number, data: number): void {}
13
- tick(frequency: number): void {}
10
+ tick(frequency: number): number { return 0 }
14
11
  reset(coldStart: boolean): void {}
15
12
 
16
13
  }
@@ -20,40 +20,29 @@ export class RAMBank implements IO {
20
20
  data: number[] = [...Array(RAMBank.TOTAL_SIZE)].fill(0x00)
21
21
  currentBank: number = 0
22
22
 
23
- raiseIRQ = () => {}
24
- raiseNMI = () => {}
25
-
26
23
  /**
27
- * Read from RAM or bank control register
24
+ * Read from RAM - all addresses read from the data array
28
25
  */
29
26
  read(address: number): number {
30
- // Reading from bank control register returns current bank number
31
- if (address === RAMBank.BANK_CONTROL_REGISTER) {
32
- return this.currentBank
33
- }
34
-
35
- // Calculate actual address in RAM: bank * bank_size + offset and return data
36
27
  return this.data[this.currentBank * RAMBank.BANK_SIZE + address]
37
28
  }
38
29
 
39
30
  /**
40
31
  * Write to RAM or bank control register
32
+ * Writing to $3FF sets the bank AND writes through to the new bank's data
41
33
  */
42
34
  write(address: number, data: number): void {
43
- // Writing to bank control register switches banks
44
35
  if (address === RAMBank.BANK_CONTROL_REGISTER) {
45
- this.currentBank = data & 0xFF // Ensure 0-255 range
46
- return
36
+ this.currentBank = data & 0xFF
47
37
  }
48
38
 
49
- // Calculate actual address in RAM: bank * bank_size + offset and store data
50
39
  this.data[this.currentBank * RAMBank.BANK_SIZE + address] = data & 0xFF
51
40
  }
52
41
 
53
42
  /**
54
43
  * Tick - no timing behavior for RAM
55
44
  */
56
- tick(frequency: number): void {}
45
+ tick(frequency: number): number { return 0 }
57
46
 
58
47
  /**
59
48
  * Reset the RAM card
@@ -26,8 +26,6 @@ import { IO } from '../IO'
26
26
  * 0x13: RAM Data (Extended RAM Data at address pointed to by 0x10)
27
27
  */
28
28
  export class RTC implements IO {
29
- raiseIRQ = () => {}
30
- raiseNMI = () => {}
31
29
 
32
30
  // RTC Registers (user-visible)
33
31
  private userSeconds: number = 0 // 0x00
@@ -61,6 +59,7 @@ export class RTC implements IO {
61
59
  private watchdog2: number = 0 // 0x0D (0.1 Second and Second)
62
60
  private watchdogCounterCentis: number = 0
63
61
  private watchdogCycleCounter: number = 0
62
+ private nmiPending: boolean = false
64
63
 
65
64
  // Control registers
66
65
  private controlA: number = 0 // 0x0E
@@ -261,7 +260,6 @@ export class RTC implements IO {
261
260
  if ((this.controlA & flagMask) === 0) return
262
261
  if ((this.controlB & enableMask) === 0) return
263
262
  this.controlA |= 0x01 // Set IRQF flag (bit 0)
264
- this.raiseIRQ()
265
263
  }
266
264
 
267
265
  private setKickstartFlag(): void {
@@ -304,7 +302,7 @@ export class RTC implements IO {
304
302
  } else {
305
303
  // WDS=1 steers watchdog to reset; emulate by clearing WDE
306
304
  this.controlB &= ~0x02
307
- this.raiseNMI()
305
+ this.nmiPending = true
308
306
  }
309
307
  }
310
308
 
@@ -449,7 +447,7 @@ export class RTC implements IO {
449
447
 
450
448
 
451
449
 
452
- tick(frequency: number, cycles: number = 1): void {
450
+ tick(frequency: number): number {
453
451
  this.cpuFrequency = frequency > 0 ? frequency : 1000000
454
452
 
455
453
  const teEnabled = (this.controlB & 0x80) !== 0
@@ -457,12 +455,17 @@ export class RTC implements IO {
457
455
  if ((this.monthControl & 0x80) !== 0) {
458
456
  // EOSC=1: oscillator disabled (DS1511Y+ active-low enable)
459
457
  this.stepWatchdog()
460
- return
458
+ let status = (this.controlA & 0x01) ? 0x80 : 0
459
+ if (this.nmiPending) {
460
+ status |= 0x40
461
+ this.nmiPending = false
462
+ }
463
+ return status
461
464
  }
462
465
 
463
466
  // Advance the clock by counting CPU cycles.
464
467
  // One emulated second = cpuFrequency cycles.
465
- this.cycleAccumulator += cycles
468
+ this.cycleAccumulator++
466
469
  while (this.cycleAccumulator >= this.cpuFrequency) {
467
470
  this.cycleAccumulator -= this.cpuFrequency
468
471
  this.incrementTime()
@@ -482,6 +485,14 @@ export class RTC implements IO {
482
485
  }
483
486
 
484
487
  this.stepWatchdog()
488
+
489
+ // Return interrupt status: bit 7 = IRQ, bit 6 = NMI
490
+ let status = (this.controlA & 0x01) ? 0x80 : 0
491
+ if (this.nmiPending) {
492
+ status |= 0x40
493
+ this.nmiPending = false
494
+ }
495
+ return status
485
496
  }
486
497
 
487
498
  reset(coldStart: boolean): void {
@@ -40,9 +40,6 @@ export const SID_CLOCK_PAL = 985248
40
40
  /** Number of SID registers */
41
41
  const NUM_REGISTERS = 29
42
42
 
43
- /** Cycles per tick from Machine.ts ioTickInterval */
44
- const CYCLES_PER_TICK = 128
45
-
46
43
  // Register offsets within each voice (relative to voice base)
47
44
  const REG_FREQ_LO = 0
48
45
  const REG_FREQ_HI = 1
@@ -162,9 +159,6 @@ export class SIDVoice {
162
159
 
163
160
  export class Sound implements IO {
164
161
 
165
- raiseIRQ = () => {}
166
- raiseNMI = () => {}
167
-
168
162
  /** Callback to push audio samples to the host emulator */
169
163
  pushSamples?: (samples: Float32Array) => void
170
164
 
@@ -202,7 +196,7 @@ export class Sound implements IO {
202
196
  sidClock: number = SID_CLOCK_NTSC
203
197
 
204
198
  /** Internal sample buffer for pushing to host */
205
- private sampleBuffer: Float32Array = new Float32Array(4096)
199
+ private sampleBuffer: Float32Array = new Float32Array(128)
206
200
  private sampleBufferIndex: number = 0
207
201
 
208
202
  // ================================================================
@@ -247,35 +241,28 @@ export class Sound implements IO {
247
241
  }
248
242
  }
249
243
 
250
- tick(frequency: number): void {
244
+ tick(frequency: number): number {
251
245
  // SID clock runs at the CPU clock rate
252
246
  this.sidClock = frequency
253
247
 
254
- // Each tick represents CYCLES_PER_TICK SID clock cycles
255
- const cycles = CYCLES_PER_TICK
256
-
257
- for (let c = 0; c < cycles; c++) {
258
- this.clockOneCycle()
248
+ // Each tick represents 1 SID clock cycle
249
+ this.clockOneCycle()
259
250
 
260
- // Sample rate conversion: accumulate and downsample
261
- this.cycleAccumulator += this.sampleRate
262
- if (this.cycleAccumulator >= this.sidClock) {
263
- this.cycleAccumulator -= this.sidClock
251
+ // Sample rate conversion: accumulate and downsample
252
+ this.cycleAccumulator += this.sampleRate
253
+ if (this.cycleAccumulator >= this.sidClock) {
254
+ this.cycleAccumulator -= this.sidClock
264
255
 
265
- const sample = this.generateSample()
266
- this.sampleBuffer[this.sampleBufferIndex++] = sample
256
+ const sample = this.generateSample()
257
+ this.sampleBuffer[this.sampleBufferIndex++] = sample
267
258
 
268
- // Buffer full - push to host
269
- if (this.sampleBufferIndex >= this.sampleBuffer.length) {
270
- this.flushSampleBuffer()
271
- }
259
+ // Buffer full - push to host
260
+ if (this.sampleBufferIndex >= this.sampleBuffer.length) {
261
+ this.flushSampleBuffer()
272
262
  }
273
263
  }
274
264
 
275
- // Flush remaining samples
276
- if (this.sampleBufferIndex > 0) {
277
- this.flushSampleBuffer()
278
- }
265
+ return 0
279
266
  }
280
267
 
281
268
  reset(coldStart: boolean): void {
@@ -65,9 +65,6 @@ export class Storage implements IO {
65
65
  private isIdentifying: boolean = false
66
66
  private isTransferring: boolean = false
67
67
 
68
- raiseIRQ = () => {}
69
- raiseNMI = () => {}
70
-
71
68
  constructor() {
72
69
  // Initialize storage and identity buffers
73
70
  this.storage = new Uint8Array(Storage.STORAGE_SIZE)
@@ -129,8 +126,8 @@ export class Storage implements IO {
129
126
  }
130
127
  }
131
128
 
132
- tick(frequency: number): void {
133
- // No timing behavior needed for this implementation
129
+ tick(frequency: number): number {
130
+ return 0
134
131
  }
135
132
 
136
133
  reset(coldStart: boolean): void {
@@ -78,9 +78,6 @@ export class VIA implements IO {
78
78
  private portA_attachmentCount: number = 0
79
79
  private portB_attachmentCount: number = 0
80
80
 
81
- raiseIRQ = () => {}
82
- raiseNMI = () => {}
83
-
84
81
  constructor() {
85
82
  this.reset(true)
86
83
  }
@@ -333,12 +330,12 @@ export class VIA implements IO {
333
330
  }
334
331
  }
335
332
 
336
- tick(frequency: number, cycles: number = 1): void {
333
+ tick(frequency: number): number {
337
334
  this.tickCounter++
338
335
 
339
336
  // Update Timer 1
340
337
  if (this.T1_running && this.regT1C > 0) {
341
- this.regT1C -= cycles
338
+ this.regT1C--
342
339
  if (this.regT1C <= 0) {
343
340
  this.setIRQFlag(VIA.IRQ_T1)
344
341
 
@@ -359,7 +356,7 @@ export class VIA implements IO {
359
356
 
360
357
  // Update Timer 2
361
358
  if (this.T2_running && this.regT2C > 0) {
362
- this.regT2C -= cycles
359
+ this.regT2C--
363
360
  if (this.regT2C <= 0) {
364
361
  this.setIRQFlag(VIA.IRQ_T2)
365
362
  this.regT2C = 0
@@ -401,10 +398,11 @@ export class VIA implements IO {
401
398
  }
402
399
  }
403
400
 
404
- // Raise IRQ if any enabled interrupt is active
401
+ // Return IRQ status if any enabled interrupt is active
405
402
  if (this.regIFR & this.regIER & 0x7F) {
406
- this.raiseIRQ()
403
+ return 0x80
407
404
  }
405
+ return 0
408
406
  }
409
407
 
410
408
  private updateIRQ(): void {