ac6502 1.3.0 → 1.4.1

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 (141) hide show
  1. package/README.md +139 -32
  2. package/dist/components/IO/ACIA.d.ts +76 -0
  3. package/dist/components/IO/ACIA.js +282 -0
  4. package/dist/components/IO/ACIA.js.map +1 -0
  5. package/dist/components/IO/Attachments/Attachment.d.ts +112 -0
  6. package/dist/components/IO/Attachments/Attachment.js +71 -0
  7. package/dist/components/IO/Attachments/Attachment.js.map +1 -0
  8. package/dist/components/IO/Attachments/JoystickAttachment.d.ts +53 -0
  9. package/dist/components/IO/Attachments/JoystickAttachment.js +90 -0
  10. package/dist/components/IO/Attachments/JoystickAttachment.js.map +1 -0
  11. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.d.ts +63 -0
  12. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +489 -0
  13. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -0
  14. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.d.ts +44 -0
  15. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/Attachments/KeypadAttachment.d.ts +47 -0
  18. package/dist/components/IO/Attachments/KeypadAttachment.js +141 -0
  19. package/dist/components/IO/Attachments/KeypadAttachment.js.map +1 -0
  20. package/dist/components/IO/Attachments/LCDAttachment.d.ts +110 -0
  21. package/dist/components/IO/Attachments/LCDAttachment.js +716 -0
  22. package/dist/components/IO/Attachments/LCDAttachment.js.map +1 -0
  23. package/dist/components/IO/Attachments/SNESAttachment.d.ts +85 -0
  24. package/dist/components/IO/Attachments/SNESAttachment.js +184 -0
  25. package/dist/components/IO/Attachments/SNESAttachment.js.map +1 -0
  26. package/dist/components/IO/Empty.d.ts +9 -0
  27. package/dist/components/IO/Empty.js +5 -7
  28. package/dist/components/IO/Empty.js.map +1 -1
  29. package/dist/components/IO/GPIOCard.d.ts +5 -5
  30. package/dist/components/IO/GPIOCard.js.map +1 -1
  31. package/dist/components/IO/RAMBank.d.ts +37 -0
  32. package/dist/components/IO/RAMBank.js +63 -0
  33. package/dist/components/IO/RAMBank.js.map +1 -0
  34. package/dist/components/IO/RTC.d.ts +107 -0
  35. package/dist/components/IO/RTC.js +483 -0
  36. package/dist/components/IO/RTC.js.map +1 -0
  37. package/dist/components/IO/Sound.d.ts +120 -0
  38. package/dist/components/IO/Sound.js +622 -0
  39. package/dist/components/IO/Sound.js.map +1 -0
  40. package/dist/components/IO/Storage.d.ts +74 -0
  41. package/dist/components/IO/Storage.js +409 -0
  42. package/dist/components/IO/Storage.js.map +1 -0
  43. package/dist/components/IO/Terminal.d.ts +19 -0
  44. package/dist/components/IO/Terminal.js +33 -0
  45. package/dist/components/IO/Terminal.js.map +1 -0
  46. package/dist/components/IO/VIA.d.ts +105 -0
  47. package/dist/components/IO/VIA.js +597 -0
  48. package/dist/components/IO/VIA.js.map +1 -0
  49. package/dist/components/IO/Video.d.ts +141 -0
  50. package/dist/components/IO/Video.js +630 -0
  51. package/dist/components/IO/Video.js.map +1 -0
  52. package/dist/components/Machine.d.ts +20 -24
  53. package/dist/components/Machine.js +249 -166
  54. package/dist/components/Machine.js.map +1 -1
  55. package/dist/index.js +28 -14
  56. package/dist/index.js.map +1 -1
  57. package/dist/lib.d.ts +16 -16
  58. package/dist/lib.js +32 -32
  59. package/dist/lib.js.map +1 -1
  60. package/dist/tests/IO/ACIA.test.d.ts +1 -0
  61. package/dist/tests/IO/ACIA.test.js +423 -0
  62. package/dist/tests/IO/ACIA.test.js.map +1 -0
  63. package/dist/tests/IO/Attachments/Attachment.test.d.ts +1 -0
  64. package/dist/tests/IO/Attachments/Attachment.test.js +339 -0
  65. package/dist/tests/IO/Attachments/Attachment.test.js.map +1 -0
  66. package/dist/tests/IO/Attachments/JoystickAttachment.test.d.ts +1 -0
  67. package/dist/tests/IO/Attachments/JoystickAttachment.test.js +126 -0
  68. package/dist/tests/IO/Attachments/JoystickAttachment.test.js.map +1 -0
  69. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.d.ts +1 -0
  70. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +779 -0
  71. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -0
  72. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.d.ts +1 -0
  73. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js +355 -0
  74. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js.map +1 -0
  75. package/dist/tests/IO/Attachments/KeypadAttachment.test.d.ts +1 -0
  76. package/dist/tests/IO/Attachments/KeypadAttachment.test.js +323 -0
  77. package/dist/tests/IO/Attachments/KeypadAttachment.test.js.map +1 -0
  78. package/dist/tests/IO/Attachments/LCDAttachment.test.d.ts +1 -0
  79. package/dist/tests/IO/Attachments/LCDAttachment.test.js +627 -0
  80. package/dist/tests/IO/Attachments/LCDAttachment.test.js.map +1 -0
  81. package/dist/tests/IO/Attachments/SNESAttachment.test.d.ts +1 -0
  82. package/dist/tests/IO/Attachments/SNESAttachment.test.js +331 -0
  83. package/dist/tests/IO/Attachments/SNESAttachment.test.js.map +1 -0
  84. package/dist/tests/IO/Empty.test.d.ts +1 -0
  85. package/dist/tests/IO/Empty.test.js +121 -0
  86. package/dist/tests/IO/Empty.test.js.map +1 -0
  87. package/dist/tests/IO/GPIOCard.test.js.map +1 -1
  88. package/dist/tests/IO/RAMBank.test.d.ts +1 -0
  89. package/dist/tests/IO/RAMBank.test.js +229 -0
  90. package/dist/tests/IO/RAMBank.test.js.map +1 -0
  91. package/dist/tests/IO/RTC.test.d.ts +1 -0
  92. package/dist/tests/IO/RTC.test.js +177 -0
  93. package/dist/tests/IO/RTC.test.js.map +1 -0
  94. package/dist/tests/IO/Sound.test.d.ts +1 -0
  95. package/dist/tests/IO/Sound.test.js +528 -0
  96. package/dist/tests/IO/Sound.test.js.map +1 -0
  97. package/dist/tests/IO/Storage.test.d.ts +1 -0
  98. package/dist/tests/IO/Storage.test.js +656 -0
  99. package/dist/tests/IO/Storage.test.js.map +1 -0
  100. package/dist/tests/IO/VIA.test.d.ts +1 -0
  101. package/dist/tests/IO/VIA.test.js +503 -0
  102. package/dist/tests/IO/VIA.test.js.map +1 -0
  103. package/dist/tests/IO/Video.test.d.ts +1 -0
  104. package/dist/tests/IO/Video.test.js +549 -0
  105. package/dist/tests/IO/Video.test.js.map +1 -0
  106. package/dist/tests/Machine.test.js +27 -42
  107. package/dist/tests/Machine.test.js.map +1 -1
  108. package/package.json +1 -1
  109. package/src/components/IO/{SerialCard.ts → ACIA.ts} +2 -2
  110. package/src/components/IO/{GPIOAttachments/GPIOAttachment.ts → Attachments/Attachment.ts} +2 -2
  111. package/src/components/IO/{GPIOAttachments/GPIOJoystickAttachment.ts → Attachments/JoystickAttachment.ts} +3 -3
  112. package/src/components/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.ts → Attachments/KeyboardEncoderAttachment.ts} +3 -3
  113. package/src/components/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.ts → Attachments/KeyboardMatrixAttachment.ts} +5 -5
  114. package/src/components/IO/{GPIOAttachments/GPIOKeypadAttachment.ts → Attachments/KeypadAttachment.ts} +3 -3
  115. package/src/components/IO/{GPIOAttachments/GPIOLCDAttachment.ts → Attachments/LCDAttachment.ts} +7 -7
  116. package/src/components/IO/{EmptyCard.ts → Empty.ts} +1 -1
  117. package/src/components/IO/{RAMCard.ts → RAMBank.ts} +8 -8
  118. package/src/components/IO/{RTCCard.ts → RTC.ts} +1 -1
  119. package/src/components/IO/{SoundCard.ts → Sound.ts} +2 -2
  120. package/src/components/IO/{StorageCard.ts → Storage.ts} +70 -73
  121. package/src/components/IO/{DevOutputBoard.ts → Terminal.ts} +2 -2
  122. package/src/components/IO/{GPIOCard.ts → VIA.ts} +64 -64
  123. package/src/components/IO/{VideoCard.ts → Video.ts} +1 -1
  124. package/src/components/Machine.ts +276 -176
  125. package/src/index.ts +34 -21
  126. package/src/lib.ts +16 -16
  127. package/src/tests/IO/{SerialCard.test.ts → ACIA.test.ts} +5 -5
  128. package/src/tests/IO/{GPIOAttachments/GPIOAttachment.test.ts → Attachments/Attachment.test.ts} +12 -12
  129. package/src/tests/IO/{GPIOAttachments/GPIOJoystickAttachment.test.ts → Attachments/JoystickAttachment.test.ts} +23 -23
  130. package/src/tests/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts → Attachments/KeyboardEncoderAttachment.test.ts} +4 -4
  131. package/src/tests/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts → Attachments/KeyboardMatrixAttachment.test.ts} +5 -5
  132. package/src/tests/IO/{GPIOAttachments/GPIOKeypadAttachment.test.ts → Attachments/KeypadAttachment.test.ts} +38 -38
  133. package/src/tests/IO/{GPIOAttachments/GPIOLCDAttachment.test.ts → Attachments/LCDAttachment.test.ts} +12 -12
  134. package/src/tests/IO/Empty.test.ts +143 -0
  135. package/src/tests/IO/{RAMCard.test.ts → RAMBank.test.ts} +33 -33
  136. package/src/tests/IO/{RTCCard.test.ts → RTC.test.ts} +6 -6
  137. package/src/tests/IO/{SoundCard.test.ts → Sound.test.ts} +6 -6
  138. package/src/tests/IO/{StorageCard.test.ts → Storage.test.ts} +34 -25
  139. package/src/tests/IO/{GPIOCard.test.ts → VIA.test.ts} +7 -7
  140. package/src/tests/IO/{VideoCard.test.ts → Video.test.ts} +13 -13
  141. package/src/tests/Machine.test.ts +31 -38
@@ -2,22 +2,21 @@ import { CPU } from './CPU'
2
2
  import { RAM } from './RAM'
3
3
  import { ROM } from './ROM'
4
4
  import { Cart } from './Cart'
5
- import { GPIOCard } from './IO/GPIOCard'
6
- import { RAMCard } from './IO/RAMCard'
7
- import { RTCCard } from './IO/RTCCard'
8
- import { SerialCard } from './IO/SerialCard'
9
- import { SoundCard } from './IO/SoundCard'
10
- import { StorageCard } from './IO/StorageCard'
11
- import { VideoCard } from './IO/VideoCard'
12
- import { GPIOKeyboardMatrixAttachment } from './IO/GPIOAttachments/GPIOKeyboardMatrixAttachment'
13
- import { GPIOKeyboardEncoderAttachment } from './IO/GPIOAttachments/GPIOKeyboardEncoderAttachment'
14
- import { GPIOJoystickAttachment } from './IO/GPIOAttachments/GPIOJoystickAttachment'
15
- import { GPIOLCDAttachment } from './IO/GPIOAttachments/GPIOLCDAttachment'
16
- import { GPIOKeypadAttachment } from './IO/GPIOAttachments/GPIOKeypadAttachment'
17
- import { EmptyCard } from './IO/EmptyCard'
18
- import { DevOutputBoard } from './IO/DevOutputBoard'
5
+ import { VIA } from './IO/VIA'
6
+ import { RAMBank } from './IO/RAMBank'
7
+ import { RTC } from './IO/RTC'
8
+ import { ACIA } from './IO/ACIA'
9
+ import { Sound } from './IO/Sound'
10
+ import { Storage } from './IO/Storage'
11
+ import { Video } from './IO/Video'
12
+ import { KeyboardMatrixAttachment } from './IO/Attachments/KeyboardMatrixAttachment'
13
+ import { KeyboardEncoderAttachment } from './IO/Attachments/KeyboardEncoderAttachment'
14
+ import { JoystickAttachment } from './IO/Attachments/JoystickAttachment'
15
+ import { LCDAttachment } from './IO/Attachments/LCDAttachment'
16
+ import { KeypadAttachment } from './IO/Attachments/KeypadAttachment'
17
+ import { Empty } from './IO/Empty'
18
+ import { Terminal } from './IO/Terminal'
19
19
  import { IO } from './IO'
20
- import { readFile } from 'fs/promises'
21
20
 
22
21
  export class Machine {
23
22
 
@@ -26,209 +25,304 @@ export class Machine {
26
25
 
27
26
  private ioCycleAccumulator: number = 0
28
27
  private ioTickInterval: number = 128 // adjust (64/128/256)
28
+ private loopHandle?: ReturnType<typeof setImmediate> | ReturnType<typeof setTimeout>
29
29
 
30
30
  cpu: CPU
31
31
  ram: RAM
32
32
  rom: ROM
33
+ cart?: Cart
34
+
33
35
  io1: IO
34
36
  io2: IO
35
37
  io3: IO
36
38
  io4: IO
37
- io5: SerialCard
39
+ io5: IO
38
40
  io6: IO
39
41
  io7: IO
40
42
  io8: IO
41
43
 
42
- cart?: Cart
43
- target: string
44
-
45
- // GPIO Attachments
46
- keyboardMatrixAttachment: GPIOKeyboardMatrixAttachment
47
- keyboardEncoderAttachment: GPIOKeyboardEncoderAttachment
48
- joystickAttachmentA: GPIOJoystickAttachment
49
- joystickAttachmentB: GPIOJoystickAttachment
44
+ // VIA Attachments
45
+ keyboardMatrixAttachment?: KeyboardMatrixAttachment
46
+ keyboardEncoderAttachment?: KeyboardEncoderAttachment
47
+ joystickAttachmentA?: JoystickAttachment
48
+ joystickAttachmentB?: JoystickAttachment
50
49
 
51
50
  // KIM mode attachments
52
- lcdAttachment?: GPIOLCDAttachment
53
- keypadAttachment?: GPIOKeypadAttachment
51
+ lcdAttachment?: LCDAttachment
52
+ keypadAttachment?: KeypadAttachment
54
53
 
55
- isAlive: boolean = false
54
+ target: string
56
55
  isRunning: boolean = false
57
56
  frequency: number = 2000000 // 2 MHz
58
57
  scale: number = 2
59
58
  frames: number = 0
60
- frameDelay: number = 0
61
- frameDelayCount: number = 0
62
59
  startTime: number = Date.now()
63
60
  previousTime: number = performance.now()
64
61
 
65
62
  transmit?: (data: number) => void
66
63
  render?: () => void
67
- pushAudioSamples?: (samples: Float32Array) => void
64
+ play?: (samples: Float32Array) => void
68
65
 
69
66
  //
70
67
  // Initialization
71
68
  //
72
69
 
73
- constructor(target: string = 'cob') {
70
+ constructor(target: string) {
74
71
  this.target = target
75
72
  this.cpu = new CPU(this.read.bind(this), this.write.bind(this))
76
73
  this.ram = new RAM()
77
74
  this.rom = new ROM()
78
75
 
79
- this.io5 = new SerialCard()
76
+ this.io1 = new Empty()
77
+ this.io2 = new Empty()
78
+ this.io3 = new Empty()
79
+ this.io4 = new Empty()
80
+ this.io5 = new Empty()
81
+ this.io6 = new Empty()
82
+ this.io7 = new Empty()
83
+ this.io8 = new Empty()
80
84
 
81
- // Connect SerialCard IRQ/NMI to CPU
82
- this.io5.raiseIRQ = () => this.cpu.irq()
83
- this.io5.raiseNMI = () => this.cpu.nmi()
85
+ this.configureTarget(target)
86
+
87
+ this.startTime = Date.now()
88
+ this.cpu.reset()
89
+ }
84
90
 
85
- // Connect SerialCard transmit callback
86
- this.io5.transmit = (data: number) => {
87
- if (this.transmit) {
88
- this.transmit(data)
89
- }
90
- }
91
+ configureTarget(target: string): void {
92
+ if (target === 'kim') {
93
+ const acia = new ACIA()
94
+ this.io5 = acia
91
95
 
92
- // Always create standard GPIO attachments (for type stability)
93
- this.keyboardMatrixAttachment = new GPIOKeyboardMatrixAttachment(10)
94
- this.keyboardEncoderAttachment = new GPIOKeyboardEncoderAttachment(20)
95
- this.joystickAttachmentA = new GPIOJoystickAttachment(false, 100)
96
- this.joystickAttachmentB = new GPIOJoystickAttachment(false, 100)
96
+ // Connect ACIA IRQ/NMI to CPU
97
+ acia.raiseIRQ = () => this.cpu.irq()
98
+ acia.raiseNMI = () => this.cpu.nmi()
97
99
 
98
- if (target === 'kim') {
99
- this.io1 = new EmptyCard()
100
- this.io2 = new EmptyCard()
101
- this.io3 = new EmptyCard()
102
- this.io4 = new EmptyCard()
103
- this.io6 = new EmptyCard()
104
- this.io7 = new EmptyCard()
100
+ // Connect ACIA transmit callback
101
+ acia.transmit = (data: number) => {
102
+ if (this.transmit) {
103
+ this.transmit(data)
104
+ }
105
+ }
105
106
 
106
- const gpioCard = new GPIOCard()
107
- this.io8 = gpioCard
107
+ this.io1 = new Empty()
108
+ this.io2 = new Empty()
109
+ this.io3 = new Empty()
110
+ this.io4 = new Empty()
111
+ this.io6 = new Empty()
112
+ this.io7 = new Empty()
108
113
 
109
- // Connect GPIOCard IRQ/NMI to CPU
110
- gpioCard.raiseIRQ = () => this.cpu.irq()
111
- gpioCard.raiseNMI = () => this.cpu.nmi()
114
+ const via = new VIA()
115
+ this.io8 = via
116
+
117
+ // Connect VIA IRQ/NMI to CPU
118
+ via.raiseIRQ = () => this.cpu.irq()
119
+ via.raiseNMI = () => this.cpu.nmi()
112
120
 
113
121
  // Create KIM GPIO Attachments
114
- this.lcdAttachment = new GPIOLCDAttachment(16, 2, 10)
115
- this.keypadAttachment = new GPIOKeypadAttachment(true, 20)
122
+ this.lcdAttachment = new LCDAttachment(16, 2, 10)
123
+ this.keypadAttachment = new KeypadAttachment(true, 20)
116
124
 
117
125
  // Attach LCD to Port A (control: RS/RW/E on bits 5-7) and Port B (data bus)
118
- gpioCard.attachToPortA(this.lcdAttachment)
119
- gpioCard.attachToPortB(this.lcdAttachment)
126
+ via.attachToPortA(this.lcdAttachment)
127
+ via.attachToPortB(this.lcdAttachment)
120
128
 
121
129
  // Attach keypad to Port A (bits 0-4)
122
- gpioCard.attachToPortA(this.keypadAttachment)
130
+ via.attachToPortA(this.keypadAttachment)
123
131
  } else if (target === 'dev') {
124
- const rtcCard = new RTCCard()
125
- const storageCard = new StorageCard()
126
- const gpioCard = new GPIOCard()
127
-
128
- this.io1 = new RAMCard()
129
- this.io2 = new RAMCard()
130
- this.io3 = rtcCard
131
- this.io4 = storageCard
132
- this.io6 = gpioCard
133
- this.io7 = new EmptyCard()
134
- this.io8 = new DevOutputBoard()
135
-
136
- // Connect RTCCard IRQ/NMI to CPU
137
- rtcCard.raiseIRQ = () => this.cpu.irq()
138
- rtcCard.raiseNMI = () => this.cpu.nmi()
132
+ const acia = new ACIA()
133
+ this.io5 = acia
134
+
135
+ // Connect ACIA IRQ/NMI to CPU
136
+ acia.raiseIRQ = () => this.cpu.irq()
137
+ acia.raiseNMI = () => this.cpu.nmi()
138
+
139
+ // Connect ACIA transmit callback
140
+ acia.transmit = (data: number) => {
141
+ if (this.transmit) {
142
+ this.transmit(data)
143
+ }
144
+ }
145
+
146
+ const rtc = new RTC()
147
+ const storage = new Storage()
148
+ const via = new VIA()
149
+
150
+ this.io1 = new RAMBank()
151
+ this.io2 = new RAMBank()
152
+ this.io3 = rtc
153
+ this.io4 = storage
154
+ this.io6 = via
155
+ this.io7 = new Empty()
156
+ this.io8 = new Terminal()
157
+
158
+ // Connect RTC IRQ/NMI to CPU
159
+ rtc.raiseIRQ = () => this.cpu.irq()
160
+ rtc.raiseNMI = () => this.cpu.nmi()
161
+
162
+ // Create standard GPIO attachments
163
+ this.keyboardMatrixAttachment = new KeyboardMatrixAttachment(10)
164
+ this.keyboardEncoderAttachment = new KeyboardEncoderAttachment(20)
165
+ this.joystickAttachmentA = new JoystickAttachment(false, 100)
166
+ this.joystickAttachmentB = new JoystickAttachment(false, 100)
139
167
 
140
168
  // Attach peripherals to GPIO Card
141
- gpioCard.attachToPortA(this.keyboardMatrixAttachment)
142
- gpioCard.attachToPortB(this.keyboardMatrixAttachment)
143
- gpioCard.attachToPortA(this.keyboardEncoderAttachment)
144
- gpioCard.attachToPortB(this.keyboardEncoderAttachment)
145
- gpioCard.attachToPortA(this.joystickAttachmentA)
146
- gpioCard.attachToPortB(this.joystickAttachmentB)
147
- } else {
148
- // COB / VCS
149
- const rtcCard = new RTCCard()
150
- const storageCard = new StorageCard()
151
- const gpioCard = new GPIOCard()
152
- const soundCard = new SoundCard()
153
- const videoCard = new VideoCard()
154
-
155
- this.io1 = new RAMCard()
156
- this.io2 = new RAMCard()
157
- this.io3 = rtcCard
158
- this.io4 = storageCard
159
- this.io6 = gpioCard
160
- this.io7 = soundCard
161
- this.io8 = videoCard
162
-
163
- // Connect RTCCard IRQ/NMI to CPU
164
- rtcCard.raiseIRQ = () => this.cpu.irq()
165
- rtcCard.raiseNMI = () => this.cpu.nmi()
166
-
167
- // Connect VideoCard IRQ/NMI to CPU
168
- videoCard.raiseIRQ = () => this.cpu.irq()
169
- videoCard.raiseNMI = () => this.cpu.nmi()
170
-
171
- // Connect SoundCard pushSamples callback
172
- soundCard.pushSamples = (samples: Float32Array) => {
173
- if (this.pushAudioSamples) {
174
- this.pushAudioSamples(samples)
169
+ via.attachToPortA(this.keyboardMatrixAttachment)
170
+ via.attachToPortB(this.keyboardMatrixAttachment)
171
+ via.attachToPortA(this.keyboardEncoderAttachment)
172
+ via.attachToPortB(this.keyboardEncoderAttachment)
173
+ via.attachToPortA(this.joystickAttachmentA)
174
+ via.attachToPortB(this.joystickAttachmentB)
175
+ } else if (target === 'vcs') {
176
+ this.io5 = new Empty()
177
+
178
+ const via = new VIA()
179
+ const sound = new Sound()
180
+ const video = new Video()
181
+
182
+ this.io1 = new Empty()
183
+ this.io2 = new Empty()
184
+ this.io3 = new Empty()
185
+ this.io4 = new Empty()
186
+ this.io6 = via
187
+ this.io7 = sound
188
+ this.io8 = video
189
+
190
+ // Connect Video IRQ/NMI to CPU
191
+ video.raiseIRQ = () => this.cpu.irq()
192
+ video.raiseNMI = () => this.cpu.nmi()
193
+
194
+ // Connect Sound pushSamples callback
195
+ sound.pushSamples = (samples: Float32Array) => {
196
+ if (this.play) {
197
+ this.play(samples)
175
198
  }
176
199
  }
177
200
 
201
+ // Create standard GPIO attachments
202
+ this.keyboardMatrixAttachment = new KeyboardMatrixAttachment(10)
203
+ this.keyboardEncoderAttachment = new KeyboardEncoderAttachment(20)
204
+ this.joystickAttachmentA = new JoystickAttachment(false, 100)
205
+ this.joystickAttachmentB = new JoystickAttachment(false, 100)
206
+
178
207
  // Attach peripherals to GPIO Card
179
- gpioCard.attachToPortA(this.keyboardMatrixAttachment)
180
- gpioCard.attachToPortB(this.keyboardMatrixAttachment)
181
- gpioCard.attachToPortA(this.keyboardEncoderAttachment)
182
- gpioCard.attachToPortB(this.keyboardEncoderAttachment)
183
- gpioCard.attachToPortA(this.joystickAttachmentA)
184
- gpioCard.attachToPortB(this.joystickAttachmentB)
185
- }
208
+ via.attachToPortA(this.keyboardMatrixAttachment)
209
+ via.attachToPortB(this.keyboardMatrixAttachment)
210
+ via.attachToPortA(this.keyboardEncoderAttachment)
211
+ via.attachToPortB(this.keyboardEncoderAttachment)
212
+ via.attachToPortA(this.joystickAttachmentA)
213
+ via.attachToPortB(this.joystickAttachmentB)
214
+ } else if (target === 'cob') {
215
+ const acia = new ACIA()
216
+ this.io5 = acia
217
+
218
+ // Connect ACIA IRQ/NMI to CPU
219
+ acia.raiseIRQ = () => this.cpu.irq()
220
+ acia.raiseNMI = () => this.cpu.nmi()
221
+
222
+ // Connect ACIA transmit callback
223
+ acia.transmit = (data: number) => {
224
+ if (this.transmit) {
225
+ this.transmit(data)
226
+ }
227
+ }
186
228
 
187
- this.cpu.reset()
229
+ const rtc = new RTC()
230
+ const storage = new Storage()
231
+ const gpio = new VIA()
232
+ const sound = new Sound()
233
+ const video = new Video()
234
+
235
+ this.io1 = new RAMBank()
236
+ this.io2 = new RAMBank()
237
+ this.io3 = rtc
238
+ this.io4 = storage
239
+ this.io6 = gpio
240
+ this.io7 = sound
241
+ this.io8 = video
242
+
243
+ // Connect RTC IRQ/NMI to CPU
244
+ rtc.raiseIRQ = () => this.cpu.irq()
245
+ rtc.raiseNMI = () => this.cpu.nmi()
246
+
247
+ // Connect Video IRQ/NMI to CPU
248
+ video.raiseIRQ = () => this.cpu.irq()
249
+ video.raiseNMI = () => this.cpu.nmi()
250
+
251
+ // Connect Sound pushSamples callback
252
+ sound.pushSamples = (samples: Float32Array) => {
253
+ if (this.play) {
254
+ this.play(samples)
255
+ }
256
+ }
257
+
258
+ // Create standard GPIO attachments
259
+ this.keyboardMatrixAttachment = new KeyboardMatrixAttachment(10)
260
+ this.keyboardEncoderAttachment = new KeyboardEncoderAttachment(20)
261
+ this.joystickAttachmentA = new JoystickAttachment(false, 100)
262
+ this.joystickAttachmentB = new JoystickAttachment(false, 100)
263
+
264
+ // Attach peripherals to GPIO Card
265
+ gpio.attachToPortA(this.keyboardMatrixAttachment)
266
+ gpio.attachToPortB(this.keyboardMatrixAttachment)
267
+ gpio.attachToPortA(this.keyboardEncoderAttachment)
268
+ gpio.attachToPortB(this.keyboardEncoderAttachment)
269
+ gpio.attachToPortA(this.joystickAttachmentA)
270
+ gpio.attachToPortB(this.joystickAttachmentB)
271
+ } else {
272
+ this.io1 = new Empty()
273
+ this.io2 = new Empty()
274
+ this.io3 = new Empty()
275
+ this.io4 = new Empty()
276
+ this.io5 = new Empty()
277
+ this.io6 = new Empty()
278
+ this.io7 = new Empty()
279
+ this.io8 = new Empty()
280
+ }
188
281
  }
189
282
 
190
283
  //
191
284
  // Methods
192
285
  //
193
286
 
194
- loadROM = async (path: string) => {
195
- try {
196
- this.rom.load(Array.from(new Uint8Array(await readFile(path))))
197
- } catch (error) {
198
- console.error('Error reading file:', error)
287
+ loadROM = (data: Uint8Array | number[] | ArrayBuffer) => {
288
+ if (data instanceof ArrayBuffer) {
289
+ this.rom.load(Array.from(new Uint8Array(data)))
290
+ } else if (data instanceof Uint8Array) {
291
+ this.rom.load(Array.from(data))
292
+ } else {
293
+ this.rom.load(data)
199
294
  }
200
295
  }
201
296
 
202
- loadCart = async (path: string) => {
203
- try {
204
- const data = Array.from(new Uint8Array(await readFile(path)))
205
- const cart = new Cart()
206
- cart.load(data)
207
- this.cart = cart
208
- } catch (error) {
209
- console.error('Error reading file:', error)
297
+ loadCart = (data: Uint8Array | number[] | ArrayBuffer) => {
298
+ let dataArray: number[]
299
+ if (data instanceof ArrayBuffer) {
300
+ dataArray = Array.from(new Uint8Array(data))
301
+ } else if (data instanceof Uint8Array) {
302
+ dataArray = Array.from(data)
303
+ } else {
304
+ dataArray = data
210
305
  }
211
- }
212
-
213
- start(): void {
214
- this.cpu.reset()
215
- this.startTime = Date.now()
216
- this.isRunning = true
217
- this.isAlive = true
218
- this.loop()
219
- }
220
-
221
- end(): void {
222
- this.isRunning = false
223
- this.isAlive = false
306
+ const cart = new Cart()
307
+ cart.load(dataArray)
308
+ this.cart = cart
224
309
  }
225
310
 
226
311
  run(): void {
227
312
  this.isRunning = true
313
+ this.loop()
228
314
  }
229
315
 
230
316
  stop(): void {
231
317
  this.isRunning = false
318
+ if (this.loopHandle) {
319
+ if (typeof clearImmediate !== 'undefined') {
320
+ clearImmediate(this.loopHandle as any)
321
+ } else {
322
+ clearTimeout(this.loopHandle as any)
323
+ }
324
+ this.loopHandle = undefined
325
+ }
232
326
  }
233
327
 
234
328
  step(): void {
@@ -237,12 +331,12 @@ export class Machine {
237
331
 
238
332
  // Tick IO cards for each cycle of the instruction
239
333
  for (let i = 0; i < cyclesExecuted; i++) {
240
- // SerialCard must be cycle-accurate
334
+ // ACIA must be cycle-accurate
241
335
  this.io5.tick(this.frequency)
242
336
 
243
337
  this.ioCycleAccumulator++
244
338
  if (this.ioCycleAccumulator >= this.ioTickInterval) {
245
- // Skip ticking RAMCard IO1 and IO2 since they have no timing behavior
339
+ // Skip ticking RAMBank IO1 and IO2 since they have no timing behavior
246
340
  this.io3.tick(this.frequency)
247
341
  this.io4.tick(this.frequency)
248
342
  this.io6.tick(this.frequency)
@@ -253,17 +347,30 @@ export class Machine {
253
347
  }
254
348
  }
255
349
 
350
+ reset(coldStart: boolean): void {
351
+ this.cpu.reset()
352
+ this.ram.reset(coldStart)
353
+ this.io1.reset(coldStart)
354
+ this.io2.reset(coldStart)
355
+ this.io3.reset(coldStart)
356
+ this.io4.reset(coldStart)
357
+ this.io5.reset(coldStart)
358
+ this.io6.reset(coldStart)
359
+ this.io7.reset(coldStart)
360
+ this.io8.reset(coldStart)
361
+ }
362
+
256
363
  tick(): void {
257
364
  // Execute one CPU clock cycle
258
365
  this.cpu.tick()
259
366
 
260
- // SerialCard must be cycle-accurate
367
+ // ACIA must be cycle-accurate
261
368
  this.io5.tick(this.frequency)
262
369
 
263
370
  // Tick other IO cards at intervals
264
371
  this.ioCycleAccumulator++
265
372
  if (this.ioCycleAccumulator >= this.ioTickInterval) {
266
- // Skip ticking RAMCard IO1 and IO2 since they have no timing behavior
373
+ // Skip ticking RAMBank IO1 and IO2 since they have no timing behavior
267
374
  this.io3.tick(this.frequency)
268
375
  this.io4.tick(this.frequency)
269
376
  this.io6.tick(this.frequency)
@@ -274,22 +381,24 @@ export class Machine {
274
381
  }
275
382
 
276
383
  onReceive(data: number): void {
277
- this.io5.onData(data) // Pass data to Serial card
384
+ if (this.target !== 'vcs') {
385
+ (this.io5 as ACIA).onData(data) // Pass data to Serial card
386
+ }
278
387
  }
279
388
 
280
389
  onKeyDown(scancode: number): void {
281
390
  if (this.target === 'kim') {
282
391
  this.keypadAttachment?.updateKey(scancode, true)
283
392
  } else {
284
- this.keyboardMatrixAttachment.updateKey(scancode, true)
285
- this.keyboardEncoderAttachment.updateKey(scancode, true)
393
+ this.keyboardMatrixAttachment?.updateKey(scancode, true)
394
+ this.keyboardEncoderAttachment?.updateKey(scancode, true)
286
395
  }
287
396
  }
288
397
 
289
398
  onKeyUp(scancode: number): void {
290
399
  if (this.target !== 'kim') {
291
- this.keyboardMatrixAttachment.updateKey(scancode, false)
292
- this.keyboardEncoderAttachment.updateKey(scancode, false)
400
+ this.keyboardMatrixAttachment?.updateKey(scancode, false)
401
+ this.keyboardEncoderAttachment?.updateKey(scancode, false)
293
402
  }
294
403
  }
295
404
 
@@ -306,8 +415,6 @@ export class Machine {
306
415
  //
307
416
 
308
417
  private loop(): void {
309
- if (!this.isAlive) { return }
310
-
311
418
  const now = performance.now()
312
419
  const elapsedMs = now - this.previousTime
313
420
  this.previousTime = now
@@ -325,12 +432,12 @@ export class Machine {
325
432
  for (let i = 0; i < ticksToRun; i++) {
326
433
  this.cpu.tick()
327
434
 
328
- // SerialCard must be cycle-accurate
435
+ // ACIA must be cycle-accurate
329
436
  this.io5.tick(this.frequency)
330
437
 
331
438
  this.ioCycleAccumulator++
332
439
  if (this.ioCycleAccumulator >= this.ioTickInterval) {
333
- // Skip ticking RAMCard IO1 and IO2 since they have no timing behavior
440
+ // Skip ticking RAMBank IO1 and IO2 since they have no timing behavior
334
441
  this.io3.tick(this.frequency)
335
442
  this.io4.tick(this.frequency)
336
443
  this.io6.tick(this.frequency)
@@ -349,34 +456,27 @@ export class Machine {
349
456
  this.render()
350
457
  this.frames += 1
351
458
  } else if (this.render && (this.target === 'cob' || this.target === 'vcs')) {
352
- const videoCard = this.io8 as VideoCard
353
- if (videoCard.frameReady) {
354
- videoCard.frameReady = false
459
+ const Video = this.io8 as Video
460
+ if (Video.frameReady) {
461
+ Video.frameReady = false
355
462
  this.render()
356
463
  this.frames += 1
357
464
  }
358
465
  }
359
466
 
360
- setImmediate(() => this.loop())
467
+ if (this.isRunning) {
468
+ if (typeof setImmediate !== 'undefined') {
469
+ this.loopHandle = setImmediate(() => this.loop())
470
+ } else {
471
+ this.loopHandle = setTimeout(() => this.loop(), 0)
472
+ }
473
+ }
361
474
  }
362
475
 
363
476
  //
364
477
  // Bus Operations
365
478
  //
366
479
 
367
- reset(coldStart: boolean): void {
368
- this.cpu.reset()
369
- this.ram.reset(coldStart)
370
- this.io1.reset(coldStart)
371
- this.io2.reset(coldStart)
372
- this.io3.reset(coldStart)
373
- this.io4.reset(coldStart)
374
- this.io5.reset(coldStart)
375
- this.io6.reset(coldStart)
376
- this.io7.reset(coldStart)
377
- this.io8.reset(coldStart)
378
- }
379
-
380
480
  read(address: number): number {
381
481
  switch(true) {
382
482
  case (this.cart && address >= Cart.CODE && address <= Cart.END):