-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathUiCrt.cpp
594 lines (506 loc) · 17.9 KB
/
UiCrt.cpp
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
// This file implements the Crt class.
// Logically, it implements the character generator and CRT tube.
// It is used by the CrtFrame class.
#include "TerminalState.h" // state needed to create crt image
#include "Ui.h" // emulator interface
#include "UiCrt.h" // this module's defines
#include "UiCrtErrorDlg.h" // error code decoder
#include "UiCrtFrame.h" // this module's owner
#include "UiSystem.h" // sharing info between Ui* wxgui modules
#include <wx/sound.h> // "beep!"
#define USE_STRETCH_BLIT 0
// ----------------------------------------------------------------------------
// Crt
// ----------------------------------------------------------------------------
enum {
Timer_Beep = 100,
};
Crt::Crt(CrtFrame *parent, crt_state_t *crt_state) :
wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize),
m_parent(parent),
m_crt_state(crt_state)
{
createBeep();
#if 0
if (!m_beep) {
UI_warn("Emulator was unable to create the beep sound.\n"
"HEX(07) will produce the host bell sound.");
}
#endif
m_beep_tmr = std::make_unique<wxTimer>(this, Timer_Beep);
// event routing table
Bind(wxEVT_ERASE_BACKGROUND, &Crt::OnEraseBackground, this);
Bind(wxEVT_PAINT, &Crt::OnPaint, this);
Bind(wxEVT_CHAR, &Crt::OnChar, this);
Bind(wxEVT_SIZE, &Crt::OnSize, this);
Bind(wxEVT_LEFT_DCLICK, &Crt::OnLeftDClick, this);
Bind(wxEVT_TIMER, &Crt::OnTimer, this, Timer_Beep);
}
void
Crt::setFontDirty(bool dirty)
{
m_font_dirty = dirty;
m_dirty |= dirty;
if (dirty) {
// invalidate all, not just the text area, to ensure borders get redrawn
invalidateAll();
}
}
bool
Crt::isFontDirty() const noexcept
{
return m_font_dirty;
};
// set the point size of the text as well as the font style.
void
Crt::setFontSize(const int size)
{
m_font_size = size;
setFontDirty();
}
int
Crt::getFontSize() const noexcept
{
return m_font_size;
}
// set display attributes
void
Crt::setDisplayContrast(int n)
{
m_display_contrast = n;
setFontDirty();
}
void
Crt::setDisplayBrightness(int n)
{
m_display_brightness = n;
setFontDirty();
}
// set the display fg/bg colors
void
Crt::setColor(const wxColor &FG, const wxColor &BG)
{
m_fg_color = FG;
m_bg_color = BG;
setFontDirty();
}
// if the display has changed, updated it
void
Crt::refreshWindow()
{
if (isDirty() || m_crt_state->dirty) {
#if USE_STRETCH_BLIT
// FIXME: needed for stretchblit mode until I redo border stuff
invalidateAll();
#else
invalidateText();
#endif
setDirty(false);
}
}
// return a pointer to the screen image
wxBitmap*
Crt::grabScreen()
{
generateScreen(); // update the screen image bitmap
return &m_scrbits; // return a pointer to the bitmap
}
void
Crt::OnPaint(wxPaintEvent &WXUNUSED(event))
{
wxPaintDC dc(this);
#if 0 // for debugging
int vX, vY, vW, vH;
wxRegionIterator upd(GetUpdateRegion());
while (upd) {
vX = upd.GetX();
vY = upd.GetY();
vW = upd.GetW();
vH = upd.GetH();
upd++;
}
#endif
generateScreen(); // update the screen image bitmap
#if USE_STRETCH_BLIT
wxMemoryDC memDC(m_scrbits);
dc.StretchBlit(
0, // xdest
0, // ydest
m_screen_pix_w, // dstWidth
m_screen_pix_h, // dstHeight
&memDC, // source
0, // xsrc
0, // ysrc
m_screen_rc.GetWidth(), // srcWidth
m_screen_rc.GetHeight() // srcHeight
);
memDC.SelectObject(wxNullBitmap);
#else
dc.DrawBitmap(m_scrbits, m_screen_rc.GetX(), m_screen_rc.GetY());
// draw borders around active text area.
// if we are doing an incremental update, supposedly
// this all gets clipped against the damaged region,
// so we don't actually draw it if it isn't necessary.
{
const int left = m_screen_rc.GetLeft();
const int top = m_screen_rc.GetTop();
const int bottom = m_screen_rc.GetBottom() + 1;
const int right = m_screen_rc.GetRight() + 1;
// hmm, I was wondering how the bottom & right edges were treated.
// dumping various m_screen_rc.GetFoo() calls, I got this example:
// top=12, bottom=300, height=289.
// Thus, height isn't (bottom-top). the bottom returned appears to be
// included in the height. when drawing, though, I think it isn't included.
// When I set up m_screen_rc, I set top and height. thus, the use of
// Bottom (and Right) are suspect here. bottom and right are inclusive
// of the active text area. I fix that by adding 1 to bottom and right.
const int bottom_h = (m_screen_pix_h - bottom);
const int right_w = (m_screen_pix_w - right);
wxColor bg(intensityToColor(0.0f));
dc.SetBrush(wxBrush(bg, wxBRUSHSTYLE_SOLID));
dc.SetPen(wxPen(bg, 1, wxPENSTYLE_SOLID));
if (top > 0) { // top border is required
dc.DrawRectangle(0, 0, m_screen_pix_w, top);
}
if (bottom_h > 0) { // bottom border is required
dc.DrawRectangle(0, bottom, m_screen_pix_w, bottom_h);
}
if (left > 0) { // left border is required
dc.DrawRectangle(0, top, left, bottom-top);
}
if (right_w > 0) { // right border is required
dc.DrawRectangle(right, top, right_w, bottom-top);
}
dc.SetPen(wxNullPen);
dc.SetBrush(wxNullBrush);
}
#endif
setFrameCount(getFrameCount() + 1);
}
void
Crt::OnSize(wxSizeEvent &event)
{
int width, height;
GetClientSize(&width, &height);
m_screen_pix_w = width;
m_screen_pix_h = height;
recalcBorders();
invalidateAll();
event.Skip();
}
// catch and ignore erasebackground events so we don't get redraw
// flicker on window resize events
void
Crt::OnEraseBackground(wxEraseEvent &WXUNUSED(event))
{
// do nothing
}
// the user has double clicked on the screen.
// see if the line contains a Wang BASIC error code, and if so
// pop open a help message describing the error in more detail.
void
Crt::OnLeftDClick(wxMouseEvent &event)
{
const wxPoint pos = event.GetPosition(); // client window coordinates
wxPoint abs_pos = ClientToScreen(pos); // absolute screen coordinates
if (m_charcell_w == 0 || m_charcell_h == 0) {
return;
}
const int cell_x = (pos.x - m_screen_rc.GetX()) / m_charcell_w;
const int cell_y = (pos.y - m_screen_rc.GetY()) / m_charcell_h;
if (cell_x < 0 || cell_x > m_crt_state->chars_w) {
return;
}
if (cell_y < 0 || cell_y > m_crt_state->chars_h) {
return;
}
// scan line to see if it is an error line. Although most errors
// are of the form:
// Wang BASIC: <spaces>^ERR <number><spaces>
// or <spaces>^ERR =<number><spaces>
// BASIC-2: <spaces>^ERR <letter><number><spaces>
// some are not. Some have arbitrary garbage on the line before
// the appearance of the "^ERR ..." string.
// first char of row
char *p = reinterpret_cast<char *>(&m_crt_state->display[cell_y * m_crt_state->chars_w]);
// one past final char of row
const char *e = (p + m_crt_state->chars_w);
// scan entire line looking for first appearance of one of these forms.
// Wang BASIC:
// ^ERR <number><spaces>
// ^ERR =<number><spaces>
// BASIC-2:
// ^ERR (<letter>|'=')*<number>+
// this scanner is lax and scans for both with this pattern:
for ( ; p < e-7; p++) {
if (strncmp(p, "^ERR ", 5) != 0) {
continue;
}
char *pp = p + 5;
std::string errcode;
// check for optional initial letter or '='
if ((*pp >= 'A' && *pp <= 'Z') || (*pp == '=')) {
errcode = *pp++;
}
// make sure there is at least one digit
if (isdigit(static_cast<unsigned char>(*pp)) == 0) {
continue;
}
// grab the number
while ((pp < e) && (isdigit(static_cast<unsigned char>(*pp)) != 0)) {
errcode += *pp++;
}
#if 0
// launch an HTML browser and look up the error code
std::string helpfile = "errors.html#Code-" + errcode;
::wxLaunchDefaultBrowser(helpfile);
#else
// pop open a dialog with the relevant information
// this is a *lot* faster than launching a browser.
abs_pos.y += m_charcell_h; // move it down a row to not obscure the err
explainError(errcode, abs_pos);
#endif
}
#ifdef REVIEW_ALL_ERROR_MESSAGES
if (false) {
for (int ec=0; ec < 100; ec++) {
wxString ecs;
ecs.Printf("%02d", ec);
ExplainError(ecs, abs_pos);
}
ExplainError("=1", abs_pos);
ExplainError("=2", abs_pos);
ExplainError("=3", abs_pos);
} else {
int ec;
for (ec=1; ec < 9; ec++) {
wxString ecs;
ecs.Printf("A%02d", ec);
ExplainError(ecs, abs_pos);
}
for (ec=10; ec < 29; ec++) {
wxString ecs;
ecs.Printf("S%02d", ec);
ExplainError(ecs, abs_pos);
}
for (ec=32; ec < 59; ec++) {
wxString ecs;
ecs.Printf("P%02d", ec);
ExplainError(ecs, abs_pos);
}
for (ec=60; ec < 69; ec++) {
wxString ecs;
ecs.Printf("C%02d", ec);
ExplainError(ecs, abs_pos);
}
for (ec=70; ec < 77; ec++) {
wxString ecs;
ecs.Printf("X%02d", ec);
ExplainError(ecs, abs_pos);
}
for (ec=80; ec < 89; ec++) {
wxString ecs;
ecs.Printf("D%02d", ec);
ExplainError(ecs, abs_pos);
}
for (ec=190; ec < 199; ec++) {
wxString ecs;
ecs.Printf("%03d", ec);
ExplainError(ecs, abs_pos);
}
}
#endif
SetFocus(); // recapture focus
}
// do the dialog part of the error message decoder
void
Crt::explainError(const std::string &errcode, const wxPoint &orig)
{
// launch it as a popup
CrtErrorDlg dlg(this, errcode, wxPoint(orig.x, orig.y));
dlg.ShowModal();
}
// any time the screen size changes or the font options change,
// we recalculate where the active part of the screen.
void
Crt::recalcBorders()
{
// figure out where the active drawing area is
const int width = m_charcell_w*m_crt_state->chars_w;
const int height = m_charcell_h*m_crt_state->chars_h2;
const int orig_x = (width < m_screen_pix_w) ? (m_screen_pix_w-width)/2 : 0;
const int orig_y = (height < m_screen_pix_h) ? (m_screen_pix_h-height)/2 : 0;
assert(width >= 0 && width < 4096 && height >= 0 && height < 4096);
m_screen_rc.SetX(orig_x);
m_screen_rc.SetY(orig_y);
m_screen_rc.SetWidth(width);
m_screen_rc.SetHeight(height);
// resize the bitmap for the new screen dimensions.
// we can skip the malloc when the user simply changes the screen
// size, or changes the font color, contrast, or brightness.
// only a font change requires a new wxBitmap.
if (!m_scrbits.IsOk() || (m_scrbits.GetWidth() != width) ||
(m_scrbits.GetHeight() != height)) {
#if !(__WXMAC__) && DRAW_WITH_RAWBMP
m_scrbits = wxBitmap(width, height, 24);
#else
m_scrbits = wxBitmap(width, height, wxBITMAP_SCREEN_DEPTH);
#endif
}
}
void
Crt::ding()
{
if (!m_beep) {
wxBell();
} else {
if (!m_beep_tmr->IsRunning()) {
// start it going
m_beep->Play(wxSOUND_ASYNC | wxSOUND_LOOP);
}
// schedule the beep end time
m_beep_tmr->Start(100, wxTIMER_ONE_SHOT);
}
}
// the beep timer is has ended
void
Crt::OnTimer(wxTimerEvent &event)
{
if (event.GetId() == Timer_Beep) {
m_beep->Stop();
}
}
#ifdef __WXMSW__
// ----------------------------------------------------------------------------
// RIFF WAV file format
// ----------------------------------------------------------------------------
// __________________________
// | RIFF WAVE Chunk |
// | groupID = 'RIFF' |
// | riffType = 'WAVE' |
// | __________________ |
// | | Format Chunk | |
// | | ckID = 'fmt ' | |
// | |__________________| |
// | __________________ |
// | | Sound Data Chunk | |
// | | ckID = 'data' | |
// | |__________________| |
// |__________________________|
//
// although it is legal to have more than one data chunk, this
// program assumes there is only one.
static constexpr uint32 riffID = ('R' << 24) | ('I' << 16) | ('F' << 8) | ('F' << 0);
static constexpr uint32 waveID = ('W' << 24) | ('A' << 16) | ('V' << 8) | ('E' << 0);
struct RIFF_t {
uint32 groupID; // should be 'RIFF'
uint32 riffBytes; // number of bytes in file after this header
uint32 riffType; // should be 'WAVE'
};
static constexpr uint32 fmtID = ('f' << 24) | ('m' << 16) | ('t' << 8) | (' ' << 0);
struct FormatChunk_t {
uint32 chunkID; // should be 'fmt '
int32 chunkSize; // not including first 8 bytes of header
int16 formatTag; // data format
uint16 channels; // number of audio channels
uint32 sampleRate; // samples/sec
uint32 bytesPerSec; // samples/sec * #channels * bytes/sample
uint16 blockAlign; // # bytes per (sample*channels)
uint16 bitsPerSample;
};
static constexpr uint32 dataID = ('d' << 24) | ('a' << 16) | ('t' << 8) | ('a' << 0);
struct DataChunk_t {
uint32 chunkID; // must be 'data'
int32 chunkSize; // not including first 8 bytes of header
// unsigned char data[]; // everything that follows
};
#endif // __WXMSW__
// most of the data fields are stored little endian, but the ID tags
// are big endian.
#define LE16(v) wxUINT16_SWAP_ON_BE(v)
#define LE32(v) wxUINT32_SWAP_ON_BE(v)
#define BE16(v) wxUINT16_SWAP_ON_LE(v)
#define BE32(v) wxUINT32_SWAP_ON_LE(v)
// create a beep sound which chr(0x07) produces
void
Crt::createBeep()
{
#if USE_FILE_BEEPS
m_beep = std::make_unique<wxSound>();
wxString sound_file =
(m_crt_state->screen_type == UI_SCREEN_2236DE) ? "sounds/beep_1940.wav"
: "sounds/beep_1100.wav";
const bool success = m_beep->Create(sound_file);
if (!success) {
m_beep = nullptr;
}
#else
// generate the beep in memory (not supported by osx)
const float sample_rate = 44100.0f;
// the schematics for the dumb terminal seem to show about a 1100 Hz tone,
// while the 2336 I have on hand seems to be about 1940 Hz
const float target_freq =
(m_crt_state->screen_type == UI_SCREEN_2236DE) ? 1940.0f // 2336
: 1100.0f; // dumb crt schematics indicate this
// we want the buffer to have an integral number of complete cycles
// so fudge the buffer size to make that happen
const int cycles_per_tenth = static_cast<int>(target_freq / 10.0f);
const int num_samples = static_cast<int>(sample_rate * 0.1f); // 1/10th of a second
const float act_freq = 10.0 * cycles_per_tenth;
const int total_bytes = sizeof(RIFF_t)
+ sizeof(FormatChunk_t)
+ sizeof(DataChunk_t)
+ 1*num_samples; // 1 byte per sample
// chunk description
RIFF_t RiffHdr;
RiffHdr.groupID = static_cast<uint32>(BE32(riffID));
RiffHdr.riffBytes = static_cast<uint32>(LE32(total_bytes-8));
RiffHdr.riffType = static_cast<uint32>(BE32(waveID));
// first subchunk, format description
FormatChunk_t FormatHdr;
FormatHdr.chunkID = static_cast<uint32>(BE32(fmtID));
FormatHdr.chunkSize = static_cast< int32>(LE32(sizeof(FormatHdr)-8));
FormatHdr.formatTag = static_cast< int16>(LE16(1)); // pcm
FormatHdr.channels = static_cast<uint16>(LE16(1)); // mono
FormatHdr.sampleRate = static_cast<uint32>(LE32( sample_rate));
FormatHdr.bytesPerSec = static_cast<uint32>(LE32(1*sample_rate));
FormatHdr.blockAlign = static_cast<uint16>(LE16(1));
FormatHdr.bitsPerSample = static_cast<uint16>(LE16(8)); // good enough for a beep
// second subchunk, data
DataChunk_t DataHdr;
DataHdr.chunkID = static_cast<uint32>(BE32(dataID));
DataHdr.chunkSize = static_cast<uint32>(LE32(1*num_samples));
// create a contiguous block, copy the headers into it,
// then append the sample data
uint8 *wav = new uint8 [total_bytes];
if (wav == nullptr) {
return;
}
uint8 *wp = wav;
memcpy(wp, &RiffHdr, sizeof(RiffHdr)); wp += sizeof(RiffHdr);
memcpy(wp, &FormatHdr, sizeof(FormatHdr)); wp += sizeof(FormatHdr);
memcpy(wp, &DataHdr, sizeof(DataHdr)); wp += sizeof(DataHdr);
// make a clipped sine wave. the nominal 1940 Hz frequency was chosen
// to match my real 2336DE terminal. the frequency generated is stretched
// to ensure an exact number of cycles is generated to produce seamless
// looping.
const float phase_scale = 2*3.14159f * act_freq / sample_rate;
const float clip = 0.70f; // chop off top/bottom of wave
const float amplitude = 40.0f; // loudness
for (int n=0; n < num_samples; n++) {
float s = sin(phase_scale * n);
s = (s > clip) ? clip
: (s < -clip) ? -clip
: s;
int sample = static_cast<int>(128.0f + amplitude * s);
*wp++ = sample;
}
m_beep = std::make_unique<wxSound>();
const bool success = m_beep->Create(total_bytes, wav);
if (!success) {
m_beep = nullptr;
}
delete [] wav;
#endif
}
// vim: ts=8:et:sw=4:smarttab