summaryrefslogtreecommitdiffstatshomepage
path: root/src/mess/video/apple1.c
blob: f6485dc7ee50a9f7d36d5b80b6221c4de9d6edf7 (plain) (blame)
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
// license:???
// copyright-holders:Paul Daniels, Colin Howell, R. Belmont
/***************************************************************************

  apple1.c

  Functions to emulate the video hardware of the Apple I.

  The Apple I video hardware was basically a dumb video terminal; in
  fact it was based on Steve Wozniak's own design for a simple video
  terminal.  It had 40 columns by 24 lines of uppercase-only text.
  Text could only be output at 60 characters per second, one character
  per video frame.  The cursor (a blinking @) could only be advanced
  using spaces or carriage returns.  Carriage returns were the only
  control characters recognized.  Previously written text could not be
  altered, only scrolled off the top of the screen.

  The video memory used seven 1k-bit dynamic shift registers.  Six of
  these held the 6-bit visible character codes, and one stored the
  cursor location as a simple bitmap--the bit for the cursor position
  was set to 0, and all the other bits were 1.

  These shift registers were continuously recirculated, completing one
  cycle per video frame.  As a new line of characters was about to be
  scanned by the video beam, that character line would be recirculated
  into the shift registers and would simultaneously be stored into a
  6x40-bit line buffer (also a shift register).  At this point, if the
  cursor location was in this line, a new character could be written
  into that location in the shift registers and the cursor could be
  advanced.  (Carriage returns were not written into the shift
  registers; they only advanced the cursor.)

  The characters in the line buffer were recirculated 7 times to
  display the 8 scan lines of the characters, before being replaced by
  a new line of characters from the main shift registers.

  Cursor blinking was performed by a Signetics 555 timer IC whose
  output was gated into the character code signals as they passed into
  the line buffer.

  Character images were provided by a Signetics 2513 character
  generator ROM, a chip also used in computer terminals such as the
  ADM-3A.  This ROM had 9 address lines and 5 data lines; it contained
  64 character images, each 5 pixels wide by 8 pixels high, with one
  line of pixels being blank for vertical separation.  The video
  circuitry added the 2 pixels of horizontal separation for each
  character.

  A special CLEAR SCREEN switch on the keyboard, directly connected to
  the video hardware, could be used to clear the video memory and
  return the cursor to the home position.  This was completely
  independent of the processor.

  A schematic of the Apple I video hardware can be found in the
  Apple-1 Operation Manual; look for the schematic titled "Terminal
  Section".  Most of the functionality modeled here was determined by
  reading this schematic.  Many of the chips used were standard 74xx
  TTL chips, but the shift registers used for the video memory and
  line buffer were Signetics 25xx PMOS ICs.  These were already
  becoming obsolete when the Apple I was built, and detailed
  information on them is very hard to find today.

***************************************************************************/

#include "emu.h"
#include "includes/apple1.h"


/***************************************************************************

    Terminal code

***************************************************************************/

TILE_GET_INFO_MEMBER(apple1_state::terminal_gettileinfo)
{
	int ch, gfxfont, code, color;

	ch = m_current_terminal->mem[tile_index];
	code = ch & ((1 << m_current_terminal->char_bits) - 1);
	color = ch >> m_current_terminal->char_bits;
	gfxfont = m_current_terminal->gfx;

	if ((tile_index == m_current_terminal->cur_offset) && !m_current_terminal->cur_hidden && m_current_terminal->getcursorcode)
		code = m_current_terminal->getcursorcode(code);

	SET_TILE_INFO_MEMBER(gfxfont,    /* gfx */
		code,       /* character */
		color,      /* color */
		0);         /* flags */
}

void apple1_state::terminal_draw(screen_device &screen, bitmap_ind16 &dest, const rectangle &cliprect, terminal_t *terminal)
{
	m_current_terminal = terminal;
	terminal->tm->draw(screen, dest, cliprect, 0, 0);
	m_current_terminal = NULL;
}

void apple1_state::verify_coords(terminal_t *terminal, int x, int y)
{
	assert(x >= 0);
	assert(y >= 0);
	assert(x < terminal->num_cols);
	assert(y < terminal->num_rows);
}

void apple1_state::terminal_putchar(terminal_t *terminal, int x, int y, int ch)
{
	int offs;

	verify_coords(terminal, x, y);

	offs = y * terminal->num_cols + x;
	if (terminal->mem[offs] != ch)
	{
		terminal->mem[offs] = ch;
		terminal->tm->mark_tile_dirty(offs);
	}
}

int apple1_state::terminal_getchar(terminal_t *terminal, int x, int y)
{
	int offs;

	verify_coords(terminal, x, y);
	offs = y * terminal->num_cols + x;
	return terminal->mem[offs];
}

void apple1_state::terminal_putblank(terminal_t *terminal, int x, int y)
{
	terminal_putchar(terminal, x, y, terminal->blank_char);
}

void apple1_state::terminal_dirtycursor(terminal_t *terminal)
{
	if (terminal->cur_offset >= 0)
		terminal->tm->mark_tile_dirty(terminal->cur_offset);
}

void apple1_state::terminal_setcursor(terminal_t *terminal, int x, int y)
{
	terminal_dirtycursor(terminal);
	terminal->cur_offset = y * terminal->num_cols + x;
	terminal_dirtycursor(terminal);
}

void apple1_state::terminal_hidecursor(terminal_t *terminal)
{
	terminal->cur_hidden = 1;
	terminal_dirtycursor(terminal);
}

void apple1_state::terminal_showcursor(terminal_t *terminal)
{
	terminal->cur_hidden = 0;
	terminal_dirtycursor(terminal);
}

void apple1_state::terminal_getcursor(terminal_t *terminal, int *x, int *y)
{
	*x = terminal->cur_offset % terminal->num_cols;
	*y = terminal->cur_offset / terminal->num_cols;
}

void apple1_state::terminal_fill(terminal_t *terminal, int val)
{
	int i;
	for (i = 0; i < terminal->num_cols * terminal->num_rows; i++)
		terminal->mem[i] = val;
	terminal->tm->mark_all_dirty();
}

void apple1_state::terminal_clear(terminal_t *terminal)
{
	terminal_fill(terminal, terminal->blank_char);
}

terminal_t *apple1_state::terminal_create(
	int gfx, int blank_char, int char_bits,
	int (*getcursorcode)(int original_code),
	int num_cols, int num_rows)
{
	terminal_t *term;
	int char_width, char_height;

	char_width = m_gfxdecode->gfx(gfx)->width();
	char_height = m_gfxdecode->gfx(gfx)->height();

	term = (terminal_t *) auto_alloc_array(machine(), char, sizeof(terminal_t) - sizeof(term->mem)
		+ (num_cols * num_rows * sizeof(termchar_t)));

	term->tm = &machine().tilemap().create(m_gfxdecode, tilemap_get_info_delegate(FUNC(apple1_state::terminal_gettileinfo),this), TILEMAP_SCAN_ROWS,
		char_width, char_height, num_cols, num_rows);

	term->gfx = gfx;
	term->blank_char = blank_char;
	term->char_bits = char_bits;
	term->num_cols = num_cols;
	term->num_rows = num_rows;
	term->getcursorcode = getcursorcode;
	term->cur_offset = -1;
	term->cur_hidden = 0;
	terminal_clear(term);
	return term;
}


/**************************************************************************/



/* The cursor blinking is generated by a free-running timer with a
   0.52-second period.  It is on for 2/3 of this period and off for
   1/3. */
#define CURSOR_OFF_LENGTH           (0.52/3)

/**************************************************************************/

static int apple1_getcursorcode(int original_code)
{
	/* Cursor uses symbol 0 (an @ sign) in the character generator ROM. */
	return 0;
}

/**************************************************************************/

void apple1_state::video_start()
{
	m_blink_on = 1;     /* cursor is visible initially */
	m_terminal = terminal_create(
		0,          /* graphics font 0 (the only one we have) */
		32,         /* Blank character is symbol 32 in the ROM */
		8,          /* use 8 bits for the character code */
		apple1_getcursorcode,
		40, 24);    /* 40 columns, 24 rows */

	terminal_setcursor(m_terminal, 0, 0);
}

/* This function handles all writes to the video display. */
void apple1_state::apple1_vh_dsp_w (int data)
{
	int x, y;
	int cursor_x, cursor_y;

	/* While CLEAR SCREEN is being held down, the hardware is forced
	   to clear the video memory, so video writes have no effect. */
	if (m_vh_clrscrn_pressed)
		return;

	/* The video display port only accepts the 7 lowest bits of the char. */
	data &= 0x7f;

	terminal_getcursor(m_terminal, &cursor_x, &cursor_y);

	if (data == '\r') {
		/* Carriage-return moves the cursor to the start of the next
		   line. */
		cursor_x = 0;
		cursor_y++;
	}
	else if (data < ' ') {
		/* Except for carriage-return, the video hardware completely
		   ignores all control characters. */
		return;
	}
	else {
		/* For visible characters, only 6 bits of the ASCII code are
		   used, because the 2513 character generator ROM only
		   contains 64 symbols.  The low 5 bits of the ASCII code are
		   used directly.  Bit 6 is ignored, since it is the same for
		   all the available characters in the ROM.  Bit 7 is inverted
		   before being used as the high bit of the 6-bit ROM symbol
		   index, because the block of 32 ASCII symbols containing the
		   uppercase letters comes first in the ROM. */

		int romindx = (data & 0x1f) | (((data ^ 0x40) & 0x40) >> 1);

		terminal_putchar(m_terminal, cursor_x, cursor_y, romindx);
		if (cursor_x < 39)
		{
			cursor_x++;
		}
		else
		{
			cursor_x = 0;
			cursor_y++;
		}
	}

	/* If the cursor went past the bottom line, scroll the text up one line. */
	if (cursor_y == 24)
	{
		for (y = 1; y < 24; y++)
			for (x = 0; x < 40; x++)
				terminal_putchar(m_terminal, x, y-1,
									terminal_getchar(m_terminal, x, y));

		for (x = 0; x < 40; x++)
			terminal_putblank(m_terminal, x, 23);

		cursor_y--;
	}

	terminal_setcursor(m_terminal, cursor_x, cursor_y);
}

/* This function handles clearing the video display on cold-boot or in
   response to a press of the CLEAR SCREEN switch. */
void apple1_state::apple1_vh_dsp_clr ()
{
	terminal_setcursor(m_terminal, 0, 0);
	terminal_clear(m_terminal);
}

/* Calculate how long it will take for the display to assert the RDA
   signal in response to a video display write.  This signal indicates
   the display has completed the write and is ready to accept another
   write. */
attotime  apple1_state::apple1_vh_dsp_time_to_ready ()
{
	int cursor_x, cursor_y;
	int cursor_scanline;
	double scanline_period = m_screen->scan_period().as_double();
	double cursor_hfrac;

	/* The video hardware refreshes the screen by reading the
	   character codes from its circulating shift-register memory.
	   Because of the way this memory works, a new character can only
	   be written into the cursor location at the moment this location
	   is about to be read.  This happens during the first scanline of
	   the cursor's character line, when the beam reaches the cursor's
	   horizontal position. */

	terminal_getcursor(m_terminal, &cursor_x, &cursor_y);
	cursor_scanline = cursor_y * apple1_charlayout.height;

	/* Each scanline is composed of 455 pixel times.  The first 175 of
	   these are the horizontal blanking period; the remaining 280 are
	   for the visible part of the scanline. */
	cursor_hfrac = (175 + cursor_x * apple1_charlayout.width) / 455;

	if (m_screen->vpos() == cursor_scanline) {
		/* video_screen_get_hpos() doesn't account for the horizontal
		   blanking interval; it acts as if the scanline period is
		   entirely composed of visible pixel times.  However, we can
		   still use it to find what fraction of the current scanline
		   period has elapsed. */
		double current_hfrac = m_screen->hpos() /
								m_screen->width();
		if (current_hfrac < cursor_hfrac)
			return attotime::from_double(scanline_period * (cursor_hfrac - current_hfrac));
	}

	return attotime::from_double(
		m_screen->time_until_pos(cursor_scanline, 0).as_double() +
		scanline_period * cursor_hfrac);
}

/* Blink the cursor on or off, as appropriate. */
void apple1_state::apple1_vh_cursor_blink ()
{
	int new_blink_on;

	/* The cursor is on for 2/3 of its blink period and off for 1/3.
	   This is most easily handled by dividing the total elapsed time
	   by the length of the off-portion of the cycle, giving us the
	   number of one-third-cycles elapsed, then checking the result
	   modulo 3. */

	if (((int) (machine().time().as_double() / CURSOR_OFF_LENGTH)) % 3 < 2)
		new_blink_on = 1;
	else
		new_blink_on = 0;

	if (new_blink_on != m_blink_on) {        /* have we changed state? */
		if (new_blink_on)
			terminal_showcursor(m_terminal);
		else
			terminal_hidecursor(m_terminal);
		m_blink_on = new_blink_on;
	}
}

UINT32 apple1_state::screen_update_apple1(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
	apple1_vh_cursor_blink();
	terminal_draw(screen, bitmap, cliprect, m_terminal);
	return 0;
}