1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | //============================================================================ |
17 | |
18 | #include "CartBUS.hxx" |
19 | #include "DataGridWidget.hxx" |
20 | #include "PopUpWidget.hxx" |
21 | #include "CartBUSWidget.hxx" |
22 | |
23 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
24 | CartridgeBUSWidget::CartridgeBUSWidget( |
25 | GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont, |
26 | int x, int y, int w, int h, CartridgeBUS& cart) |
27 | : CartDebugWidget(boss, lfont, nfont, x, y, w, h), |
28 | myCart(cart) |
29 | { |
30 | uInt16 size = 8 * 4096; |
31 | |
32 | ostringstream info; |
33 | info << "BUS Stuffing cartridge (EXPERIMENTAL)\n" |
34 | << "32K ROM, seven 4K banks are accessible to 2600\n" |
35 | << "8K BUS RAM\n" |
36 | << "BUS registers accessible @ $FFEE - $FFF3\n" |
37 | << "Banks accessible at hotspots $FFFF to $FFFB\n" |
38 | << "Startup bank = " << cart.startBank() << "\n" ; |
39 | |
40 | #if 0 |
41 | // Eventually, we should query this from the debugger/disassembler |
42 | for(uInt32 i = 0, offset = 0xFFC, spot = 0xFF5; i < 7; ++i, offset += 0x1000) |
43 | { |
44 | uInt16 start = (cart.myImage[offset+1] << 8) | cart.myImage[offset]; |
45 | start -= start % 0x1000; |
46 | info << "Bank " << i << " @ $" << HEX4 << (start + 0x80) << " - " |
47 | << "$" << (start + 0xFFF) << " (hotspot = $" << (spot+i) << ")\n" ; |
48 | } |
49 | #endif |
50 | |
51 | int xpos = 2, |
52 | ypos = addBaseInformation(size, "AtariAge" , info.str(), 4) + |
53 | myLineHeight; |
54 | |
55 | VariantList items; |
56 | VarList::push_back(items, "0 ($FFF5)" ); |
57 | VarList::push_back(items, "1 ($FFF6)" ); |
58 | VarList::push_back(items, "2 ($FFF7)" ); |
59 | VarList::push_back(items, "3 ($FFF8)" ); |
60 | VarList::push_back(items, "4 ($FFF9)" ); |
61 | VarList::push_back(items, "5 ($FFFA)" ); |
62 | VarList::push_back(items, "6 ($FFFB)" ); |
63 | myBank = |
64 | new PopUpWidget(boss, _font, xpos, ypos-2, _font.getStringWidth("0 ($FFFx)" ), |
65 | myLineHeight, items, "Set bank " , |
66 | 0, kBankChanged); |
67 | myBank->setTarget(this); |
68 | addFocusWidget(myBank); |
69 | |
70 | int lwidth = _font.getStringWidth("Datastream Increments " ); // get width of the widest label |
71 | |
72 | // Datastream Pointers |
73 | #define DS_X 30 |
74 | xpos = DS_X; |
75 | ypos += myLineHeight + 4; |
76 | new StaticTextWidget(boss, _font, xpos, ypos, lwidth, |
77 | myFontHeight, "Datastream Pointers" , TextAlign::Left); |
78 | xpos += lwidth; |
79 | |
80 | myDatastreamPointers = new DataGridWidget(boss, _nfont, DS_X, ypos+myLineHeight-2, 4, 4, 6, 32, Common::Base::F_16_3_2); |
81 | myDatastreamPointers->setTarget(this); |
82 | myDatastreamPointers->setEditable(false); |
83 | |
84 | myDatastreamPointers2 = new DataGridWidget(boss, _nfont, DS_X + myDatastreamPointers->getWidth() * 3 / 4, ypos+myLineHeight-2 + 4*myLineHeight, 1, 2, 6, 32, Common::Base::F_16_3_2); |
85 | myDatastreamPointers2->setTarget(this); |
86 | myDatastreamPointers2->setEditable(false); |
87 | |
88 | uInt32 row; |
89 | for(row = 0; row < 4; ++row) |
90 | { |
91 | myDatastreamLabels[row] = |
92 | new StaticTextWidget(_boss, _font, DS_X - _font.getStringWidth("xx " ), |
93 | ypos+myLineHeight-2 + row*myLineHeight + 2, |
94 | myFontWidth*2, myFontHeight, "" , TextAlign::Left); |
95 | myDatastreamLabels[row]->setLabel(Common::Base::toString(row * 4, Common::Base::F_16_2)); |
96 | } |
97 | lwidth = _font.getStringWidth("Write Data (stream 16)" ); |
98 | myDatastreamLabels[4] = |
99 | new StaticTextWidget(_boss, _font, DS_X - _font.getStringWidth("xx " ), |
100 | ypos+myLineHeight-2 + 4*myLineHeight + 2, |
101 | lwidth, myFontHeight, "Write Data (stream 16)" , TextAlign::Left); |
102 | myDatastreamLabels[5] = |
103 | new StaticTextWidget(_boss, _font, DS_X - _font.getStringWidth("xx " ), |
104 | ypos+myLineHeight-2 + 5*myLineHeight + 2, |
105 | lwidth, myFontHeight, "Jump Data (stream 17)" , TextAlign::Left); |
106 | |
107 | // Datastream Increments |
108 | xpos = DS_X + myDatastreamPointers->getWidth() + 20; |
109 | new StaticTextWidget(boss, _font, xpos, ypos, lwidth, |
110 | myFontHeight, "Datastream Increments" , TextAlign::Left); |
111 | |
112 | myDatastreamIncrements = new DataGridWidget(boss, _nfont, xpos, ypos+myLineHeight-2, 4, 4, 5, 32, Common::Base::F_16_2_2); |
113 | myDatastreamIncrements->setTarget(this); |
114 | myDatastreamIncrements->setEditable(false); |
115 | |
116 | myDatastreamIncrements2 = new DataGridWidget(boss, _nfont, xpos, ypos+myLineHeight-2 + 4*myLineHeight, 1, 2, 5, 32, Common::Base::F_16_2_2); |
117 | myDatastreamIncrements2->setTarget(this); |
118 | myDatastreamIncrements2->setEditable(false); |
119 | |
120 | // Datastream Maps |
121 | xpos = 0; ypos += myLineHeight*7 + 4; |
122 | new StaticTextWidget(boss, _font, xpos, ypos, lwidth, |
123 | myFontHeight, "Address Maps" , TextAlign::Left); |
124 | |
125 | myAddressMaps = new DataGridWidget(boss, _nfont, 0, ypos+myLineHeight-2, 8, 5, 8, 32, Common::Base::F_16_8); |
126 | myAddressMaps->setTarget(this); |
127 | myAddressMaps->setEditable(false); |
128 | |
129 | // Music counters |
130 | xpos = 10; ypos += myLineHeight*6 + 4; |
131 | new StaticTextWidget(boss, _font, xpos, ypos, lwidth, |
132 | myFontHeight, "Music Counters" , TextAlign::Left); |
133 | xpos += lwidth; |
134 | |
135 | myMusicCounters = new DataGridWidget(boss, _nfont, xpos, ypos-2, 3, 1, 8, 32, Common::Base::F_16_8); |
136 | myMusicCounters->setTarget(this); |
137 | myMusicCounters->setEditable(false); |
138 | |
139 | // Music frequencies |
140 | xpos = 10; ypos += myLineHeight + 4; |
141 | new StaticTextWidget(boss, _font, xpos, ypos, lwidth, |
142 | myFontHeight, "Music Frequencies" , TextAlign::Left); |
143 | xpos += lwidth; |
144 | |
145 | myMusicFrequencies = new DataGridWidget(boss, _nfont, xpos, ypos-2, 3, 1, 8, 32, Common::Base::F_16_8); |
146 | myMusicFrequencies->setTarget(this); |
147 | myMusicFrequencies->setEditable(false); |
148 | |
149 | // Music waveforms |
150 | xpos = 10; ypos += myLineHeight + 4; |
151 | new StaticTextWidget(boss, _font, xpos, ypos, lwidth, |
152 | myFontHeight, "Music Waveforms" , TextAlign::Left); |
153 | xpos += lwidth; |
154 | |
155 | myMusicWaveforms = new DataGridWidget(boss, _nfont, xpos, ypos-2, 3, 1, 4, 16, Common::Base::F_16_2); |
156 | myMusicWaveforms->setTarget(this); |
157 | myMusicWaveforms->setEditable(false); |
158 | |
159 | int xpossp = xpos + myMusicWaveforms->getWidth() + 20; |
160 | int lwidth2 = _font.getStringWidth("Sample Pointer " ); |
161 | new StaticTextWidget(boss, _font, xpossp, ypos, lwidth2, |
162 | myFontHeight, "Sample Pointer " , TextAlign::Left); |
163 | |
164 | mySamplePointer = new DataGridWidget(boss, _nfont, xpossp + lwidth2, ypos-2, 1, 1, 8, 32, Common::Base::F_16_8); |
165 | mySamplePointer->setTarget(this); |
166 | mySamplePointer->setEditable(false); |
167 | |
168 | // Music waveform sizes |
169 | xpos = 10; ypos += myLineHeight + 4; |
170 | new StaticTextWidget(boss, _font, xpos, ypos, lwidth, |
171 | myFontHeight, "Music Waveform Sizes" , TextAlign::Left); |
172 | xpos += lwidth; |
173 | |
174 | myMusicWaveformSizes = new DataGridWidget(boss, _nfont, xpos, ypos-2, 3, 1, 4, 16, Common::Base::F_16_2); |
175 | myMusicWaveformSizes->setTarget(this); |
176 | myMusicWaveformSizes->setEditable(false); |
177 | |
178 | |
179 | // BUS stuff and Digital Audio flags |
180 | xpos = 10; ypos += myLineHeight + 4; |
181 | myBusOverdrive = new CheckboxWidget(boss, _font, xpos, ypos, "BUS Overdrive enabled" ); |
182 | myBusOverdrive->setTarget(this); |
183 | myBusOverdrive->setEditable(false); |
184 | |
185 | myDigitalSample = new CheckboxWidget(boss, _font, xpossp, ypos, "Digital Sample mode" ); |
186 | myDigitalSample->setTarget(this); |
187 | myDigitalSample->setEditable(false); |
188 | } |
189 | |
190 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
191 | void CartridgeBUSWidget::saveOldState() |
192 | { |
193 | myOldState.tops.clear(); |
194 | myOldState.bottoms.clear(); |
195 | myOldState.datastreampointers.clear(); |
196 | myOldState.datastreamincrements.clear(); |
197 | myOldState.addressmaps.clear(); |
198 | myOldState.mcounters.clear(); |
199 | myOldState.mfreqs.clear(); |
200 | myOldState.mwaves.clear(); |
201 | myOldState.mwavesizes.clear(); |
202 | myOldState.internalram.clear(); |
203 | myOldState.samplepointer.clear(); |
204 | |
205 | for(uInt32 i = 0; i < 18; ++i) |
206 | { |
207 | // Pointers are stored as: |
208 | // PPPFF--- |
209 | // |
210 | // Increments are stored as |
211 | // ----IIFF |
212 | // |
213 | // P = Pointer |
214 | // I = Increment |
215 | // F = Fractional |
216 | |
217 | myOldState.datastreampointers.push_back(myCart.getDatastreamPointer(i)>>12); |
218 | if (i < 16) |
219 | myOldState.datastreamincrements.push_back(myCart.getDatastreamIncrement(i)); |
220 | else |
221 | myOldState.datastreamincrements.push_back(0x100); |
222 | } |
223 | |
224 | for(uInt32 i = 0; i < 37; ++i) // only 37 map values |
225 | myOldState.addressmaps.push_back(myCart.getAddressMap(i)); |
226 | |
227 | for(uInt32 i = 37; i < 40; ++i) // but need 40 for the grid |
228 | myOldState.addressmaps.push_back(0); |
229 | |
230 | for(uInt32 i = 0; i < 3; ++i) |
231 | myOldState.mcounters.push_back(myCart.myMusicCounters[i]); |
232 | |
233 | for(uInt32 i = 0; i < 3; ++i) |
234 | { |
235 | myOldState.mfreqs.push_back(myCart.myMusicFrequencies[i]); |
236 | myOldState.mwaves.push_back(myCart.getWaveform(i) >> 5); |
237 | myOldState.mwavesizes.push_back(myCart.getWaveformSize((i))); |
238 | } |
239 | |
240 | for(uInt32 i = 0; i < internalRamSize(); ++i) |
241 | myOldState.internalram.push_back(myCart.myBUSRAM[i]); |
242 | |
243 | myOldState.samplepointer.push_back(myCart.getSample()); |
244 | } |
245 | |
246 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
247 | void CartridgeBUSWidget::loadConfig() |
248 | { |
249 | myBank->setSelectedIndex(myCart.getBank()); |
250 | |
251 | // Get registers, using change tracking |
252 | IntArray alist; |
253 | IntArray vlist; |
254 | BoolArray changed; |
255 | |
256 | alist.clear(); vlist.clear(); changed.clear(); |
257 | for(int i = 0; i < 16; ++i) |
258 | { |
259 | // Pointers are stored as: |
260 | // PPPFF--- |
261 | // |
262 | // Increments are stored as |
263 | // ----IIFF |
264 | // |
265 | // P = Pointer |
266 | // I = Increment |
267 | // F = Fractional |
268 | |
269 | Int32 pointervalue = myCart.getDatastreamPointer(i) >> 12; |
270 | alist.push_back(0); vlist.push_back(pointervalue); |
271 | changed.push_back(pointervalue != myOldState.datastreampointers[i]); |
272 | } |
273 | myDatastreamPointers->setList(alist, vlist, changed); |
274 | |
275 | alist.clear(); vlist.clear(); changed.clear(); |
276 | for(int i = 16; i < 18; ++i) |
277 | { |
278 | Int32 pointervalue = myCart.getDatastreamPointer(i) >> 12; |
279 | alist.push_back(0); vlist.push_back(pointervalue); |
280 | changed.push_back(pointervalue != myOldState.datastreampointers[i]); |
281 | } |
282 | myDatastreamPointers2->setList(alist, vlist, changed); |
283 | |
284 | alist.clear(); vlist.clear(); changed.clear(); |
285 | for(int i = 0; i < 16; ++i) |
286 | { |
287 | Int32 incrementvalue = myCart.getDatastreamIncrement(i); |
288 | alist.push_back(0); vlist.push_back(incrementvalue); |
289 | changed.push_back(incrementvalue != myOldState.datastreamincrements[i]); |
290 | } |
291 | myDatastreamIncrements->setList(alist, vlist, changed); |
292 | |
293 | alist.clear(); vlist.clear(); changed.clear(); |
294 | for(int i = 16; i < 18; ++i) |
295 | { |
296 | Int32 incrementvalue = 0x100; |
297 | alist.push_back(0); vlist.push_back(incrementvalue); |
298 | changed.push_back(incrementvalue != myOldState.datastreamincrements[i]); |
299 | } |
300 | myDatastreamIncrements2->setList(alist, vlist, changed); |
301 | |
302 | alist.clear(); vlist.clear(); changed.clear(); |
303 | for(int i = 0; i < 37; ++i) // only 37 map values |
304 | { |
305 | Int32 mapvalue = myCart.getAddressMap(i); |
306 | alist.push_back(0); vlist.push_back(mapvalue); |
307 | changed.push_back(mapvalue != myOldState.addressmaps[i]); |
308 | } |
309 | for(int i = 37; i < 40; ++i) // but need 40 for the grid |
310 | { |
311 | Int32 mapvalue = 0; |
312 | alist.push_back(0); vlist.push_back(mapvalue); |
313 | changed.push_back(mapvalue != myOldState.addressmaps[i]); |
314 | } |
315 | myAddressMaps->setList(alist, vlist, changed); |
316 | |
317 | alist.clear(); vlist.clear(); changed.clear(); |
318 | for(int i = 0; i < 3; ++i) |
319 | { |
320 | alist.push_back(0); vlist.push_back(myCart.myMusicCounters[i]); |
321 | changed.push_back(myCart.myMusicCounters[i] != uInt32(myOldState.mcounters[i])); |
322 | } |
323 | myMusicCounters->setList(alist, vlist, changed); |
324 | |
325 | alist.clear(); vlist.clear(); changed.clear(); |
326 | for(int i = 0; i < 3; ++i) |
327 | { |
328 | alist.push_back(0); vlist.push_back(myCart.myMusicFrequencies[i]); |
329 | changed.push_back(myCart.myMusicFrequencies[i] != uInt32(myOldState.mfreqs[i])); |
330 | } |
331 | myMusicFrequencies->setList(alist, vlist, changed); |
332 | |
333 | alist.clear(); vlist.clear(); changed.clear(); |
334 | for(int i = 0; i < 3; ++i) |
335 | { |
336 | alist.push_back(0); vlist.push_back(myCart.getWaveform(i) >> 5); |
337 | changed.push_back((myCart.getWaveform(i) >> 5) != uInt32(myOldState.mwaves[i])); |
338 | } |
339 | myMusicWaveforms->setList(alist, vlist, changed); |
340 | |
341 | alist.clear(); vlist.clear(); changed.clear(); |
342 | for(int i = 0; i < 3; ++i) |
343 | { |
344 | alist.push_back(0); vlist.push_back(myCart.getWaveformSize(i)); |
345 | changed.push_back((myCart.getWaveformSize(i)) != uInt32(myOldState.mwavesizes[i])); |
346 | } |
347 | myMusicWaveformSizes->setList(alist, vlist, changed); |
348 | |
349 | alist.clear(); vlist.clear(); changed.clear(); |
350 | alist.push_back(0); vlist.push_back(myCart.getSample()); |
351 | changed.push_back((myCart.getSample()) != uInt32(myOldState.samplepointer[0])); |
352 | mySamplePointer->setList(alist, vlist, changed); |
353 | |
354 | myBusOverdrive->setState((myCart.myMode & 0x0f) == 0); |
355 | myDigitalSample->setState((myCart.myMode & 0xf0) == 0); |
356 | |
357 | if ((myCart.myMode & 0xf0) == 0) |
358 | { |
359 | myMusicWaveforms->setCrossed(true); |
360 | myMusicWaveformSizes->setCrossed(true); |
361 | mySamplePointer->setCrossed(false); |
362 | } |
363 | else |
364 | { |
365 | myMusicWaveforms->setCrossed(false); |
366 | myMusicWaveformSizes->setCrossed(false); |
367 | mySamplePointer->setCrossed(true); |
368 | } |
369 | |
370 | CartDebugWidget::loadConfig(); |
371 | } |
372 | |
373 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
374 | void CartridgeBUSWidget::handleCommand(CommandSender* sender, |
375 | int cmd, int data, int id) |
376 | { |
377 | if(cmd == kBankChanged) |
378 | { |
379 | myCart.unlockBank(); |
380 | myCart.bank(myBank->getSelected()); |
381 | myCart.lockBank(); |
382 | invalidate(); |
383 | } |
384 | } |
385 | |
386 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
387 | string CartridgeBUSWidget::bankState() |
388 | { |
389 | ostringstream& buf = buffer(); |
390 | |
391 | static const char* const spot[] = { |
392 | "$FFF5" , "$FFF6" , "$FFF7" , "$FFF8" , "$FFF9" , "$FFFA" , "$FFFB" |
393 | }; |
394 | buf << "Bank = " << std::dec << myCart.getBank() |
395 | << ", hotspot = " << spot[myCart.getBank()]; |
396 | |
397 | return buf.str(); |
398 | } |
399 | |
400 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
401 | uInt32 CartridgeBUSWidget::internalRamSize() |
402 | { |
403 | return 8*1024; |
404 | } |
405 | |
406 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
407 | uInt32 CartridgeBUSWidget::internalRamRPort(int start) |
408 | { |
409 | return 0x0000 + start; |
410 | } |
411 | |
412 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
413 | string CartridgeBUSWidget::internalRamDescription() |
414 | { |
415 | ostringstream desc; |
416 | desc << "$0000 - $07FF - BUS driver\n" |
417 | << " not accessible to 6507\n" |
418 | << "$0800 - $17FF - 4K Data Stream storage\n" |
419 | << " indirectly accessible to 6507\n" |
420 | << " via BUS's Data Stream registers\n" |
421 | << "$1800 - $1FFF - 2K C variable storage and stack\n" |
422 | << " not accessible to 6507" ; |
423 | |
424 | return desc.str(); |
425 | } |
426 | |
427 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
428 | const ByteArray& CartridgeBUSWidget::internalRamOld(int start, int count) |
429 | { |
430 | myRamOld.clear(); |
431 | for(int i = 0; i < count; i++) |
432 | myRamOld.push_back(myOldState.internalram[start + i]); |
433 | return myRamOld; |
434 | } |
435 | |
436 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
437 | const ByteArray& CartridgeBUSWidget::internalRamCurrent(int start, int count) |
438 | { |
439 | myRamCurrent.clear(); |
440 | for(int i = 0; i < count; i++) |
441 | myRamCurrent.push_back(myCart.myBUSRAM[start + i]); |
442 | return myRamCurrent; |
443 | } |
444 | |
445 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
446 | void CartridgeBUSWidget::internalRamSetValue(int addr, uInt8 value) |
447 | { |
448 | myCart.myBUSRAM[addr] = value; |
449 | } |
450 | |
451 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
452 | uInt8 CartridgeBUSWidget::internalRamGetValue(int addr) |
453 | { |
454 | return myCart.myBUSRAM[addr]; |
455 | } |
456 | |