1/******************************************************************************
2/
3/	File:			CC.cpp
4/
5/	Description:	Closed caption module.
6/
7/	Copyright 2001, Carlos Hasan
8/
9*******************************************************************************/
10
11#include <stdio.h>
12#include <Debug.h>
13#include "CC.h"
14
15CCaption::CCaption(CCapture & capture)
16	:	fCapture(capture),
17		fChannel(C_RADEON_CC1),
18		fRow(0),
19		fColumn(0),
20		fColor(0),
21		fLastControlCode(0)
22{
23	for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
24		for (int column = 0; column < C_RADEON_CC_COLUMNS; column++) {
25			fText[row][column] = fDisplayText[row][column] = 0x0000;
26		}
27	}
28}
29
30CCaption::~CCaption()
31{
32}
33
34status_t CCaption::InitCheck() const
35{
36	return fCapture.InitCheck();
37}
38
39void CCaption::SetMode(cc_mode mode)
40{
41	fChannel = mode;
42	fLastControlCode = 0;
43}
44
45bool CCaption::DecodeBits(const unsigned char * buffer, int & data)
46{
47	// assume the buffer is long enough (at least VBI line width bytes)
48	if (buffer == NULL)
49		return false;
50
51	// compute low/high levels
52	int low = 0xff, high = 0x00;
53	for (int offset = C_RADEON_CC_BLANK_START; offset < C_RADEON_CC_BLANK_START + 4 * C_RADEON_CC_BIT_DURATION; offset++) {
54		if (low > buffer[offset])
55			low = buffer[offset];
56		if (high < buffer[offset])
57			high = buffer[offset];
58	}
59	if (low + C_RADEON_CC_LEVEL_THRESHOLD >= high)
60		return false;
61
62	const int middle = (low + high) >> 1;
63
64	// find position of the first pulse
65	int start = C_RADEON_CC_BLANK_START + C_RADEON_CC_BIT_DURATION;
66	while (start <= C_RADEON_CC_BLANK_START + 4 * C_RADEON_CC_BIT_DURATION) {
67		if (buffer[start + 0] < middle && buffer[start + 1] < middle &&
68			buffer[start + 3] > middle && buffer[start + 4] > middle)
69			break;
70		start++;
71	}
72	if (start >= C_RADEON_CC_BLANK_START + 4 * C_RADEON_CC_BIT_DURATION)
73		return false;
74
75	// compute position of the last pulse
76	int end = start + 17 * C_RADEON_CC_BIT_DURATION;
77
78	// start in middle of first pulse
79	int bit = 0;
80	bool one = true;
81	for (int offset = start + C_RADEON_CC_BIT_DURATION / 2; offset < end; offset++) {
82		int width = 1;
83
84		// search the next pulse front
85		while (offset < end) {
86			if (one) {
87				if (buffer[offset + 0] > middle && buffer[offset + 1] > middle &&
88					buffer[offset + 3] < middle && buffer[offset + 4] < middle)
89					break;
90			}
91			else {
92				if (buffer[offset + 0] < middle && buffer[offset + 1] < middle &&
93					buffer[offset + 3] > middle && buffer[offset + 4] > middle)
94					break;
95			}
96			offset++;
97			width++;
98		}
99
100		// compute pulse width in bits
101		const int nbits = (width + (bit == 0 ? 0 : bit == 15 ? C_RADEON_CC_BIT_DURATION :
102			C_RADEON_CC_BIT_DURATION / 2)) / C_RADEON_CC_BIT_DURATION;
103		data >>= nbits;
104		if (one)
105			data |= (0xffff << (16 - nbits)) & 0xffff;
106
107		if ((bit += nbits) >= C_RADEON_CC_BITS_PER_FIELD)
108			break;
109
110		one = !one;
111	}
112
113	if (bit != C_RADEON_CC_BITS_PER_FIELD)
114		return false;
115
116	return true;
117}
118
119bool CCaption::Decode(const unsigned char * buffer0,
120							const unsigned char * buffer1)
121{
122	enum caption_code {
123		/* channel */
124		C_CC1		= 0x0000,
125		C_CC2		= 0x0800,
126
127		/* address */
128		C_ROW_1		= 0x0100,
129		C_ROW_2		= 0x0120,
130		C_ROW_3		= 0x0200,
131		C_ROW_4		= 0x0220,
132		C_ROW_5		= 0x0500,
133		C_ROW_6		= 0x0520,
134		C_ROW_7		= 0x0600,
135		C_ROW_8		= 0x0620,
136		C_ROW_9		= 0x0700,
137		C_ROW_10	= 0x0720,
138		C_ROW_11	= 0x0000,
139		C_ROW_12	= 0x0300,
140		C_ROW_13	= 0x0320,
141		C_ROW_14	= 0x0400,
142		C_ROW_15	= 0x0420,
143
144		/* color */
145		C_WHITE		= 0x0000,
146		C_GREEN		= 0x0002,
147		C_BLUE		= 0x0004,
148		C_CYAN		= 0x0006,
149		C_RED		= 0x0008,
150		C_YELLOW	= 0x000a,
151		C_MAGENTA	= 0x000c,
152		C_ITALICS	= 0x000e,
153
154		/* underline */
155		C_NORMAL	= 0x0000,
156		C_UNDERLINE	= 0x0001,
157
158		/* control */
159		C_RCL		= 0x0000,	// resume caption loading
160		C_BS		= 0x0001,	// backspace
161		C_AOF		= 0x0002,	// alarm off
162		C_AON		= 0x0003,	// alarm on
163		C_DER		= 0x0004,	// delete to end of row
164		C_RU2		= 0x0005,	// roll-up captions (2 rows)
165		C_RU3		= 0x0006,	// roll-up captions (3 rows)
166		C_RU4		= 0x0007,	// roll-up captions (4 rows)
167		C_FON		= 0x0008,	// flash on
168		C_RDC		= 0x0009,	// resume direct captioning
169		C_TR		= 0x000a,	// text restart
170		C_RTD		= 0x000b,	// resume text display
171		C_EDM		= 0x000c,	// erase displayed memory
172		C_CR		= 0x000d,	// carriage return
173		C_ENM		= 0x000e,	// erase non-displayed memory
174		C_EOC		= 0x000f,	// end of caption
175
176		/* special character */
177		C_reg		= 0x0000,	// registered
178		C_deg		= 0x0001,	// degree
179		C_frac12	= 0x0002,	// fraction 1/2
180		C_iquest	= 0x0003,	// invert question
181		C_trademark	= 0x0004,	// trademark
182		C_cent		= 0x0005,	// cent
183		C_pound		= 0x0006,	// pound
184		C_music		= 0x0007,	// music note
185		C_agrave	= 0x0008,	// agrave
186		C_tspace	= 0x0009,	// transparent space
187		C_egrave	= 0x000a,	// egrave
188		C_acirc		= 0x000b,	// a circ
189		C_ecirc		= 0x000c,	// e circ
190		C_icirc		= 0x000d,	// i circ
191		C_ocirc		= 0x000e,	// o circ
192		C_ucirc		= 0x000f,	// u circ
193
194		/* standard character (ASCII) */
195		C_aacute	= 0x002a,	// a acute accent
196		C_eacute	= 0x005c,	// e acute accent
197		C_iacute	= 0x005e,	// i acute accent
198		C_oacute	= 0x005f,	// o acute accent
199		C_uacute	= 0x0060,	// u acute accent
200		C_ccedil	= 0x007b,	// c cedilla
201		C_division	= 0x007c,	// division sign
202		C_Ntilde	= 0x007d,	// N tilde
203		C_ntilde	= 0x007e,	// n tilde
204		C_block		= 0x007f	// solid block
205	};
206
207	int code, channel, character;
208
209	/* decode next pair of bytes from the odd or even field buffer */
210	if (!DecodeBits(fChannel == C_RADEON_CC1 || fChannel == C_RADEON_CC2 ? buffer0 : buffer1, code))
211		return false;
212
213	/* swap decoded bytes */
214	code = ((code << 8) & 0xff00) | ((code >> 8) & 0x00ff);
215
216	/* check parity */
217	for (int bit = 0; bit < 7; bit++) {
218		if ((code & (0x0001 << bit)) != 0)
219			code ^= 0x0080;
220		if ((code & (0x0100 << bit)) != 0)
221			code ^= 0x8000;
222	}
223	if ((code & 0x8080) != 0x8080) {
224		PRINT(("CCaption::Decode() - parity error (%04X)\n", code));
225		return false;
226	}
227
228	/* check channel number */
229	channel = (code & 0x0800) >> 12;
230	if (channel == 0) {
231		if (fChannel != C_RADEON_CC1 && fChannel != C_RADEON_CC3) {
232			PRINT(("CCaption::Decode() - ignore channel (%02X)\n", code & 0x7f7f));
233			return false;
234		}
235	}
236	else {
237		if (fChannel != C_RADEON_CC2 && fChannel != C_RADEON_CC4) {
238			PRINT(("CCaption::Decode() - ignore channel (%02X)\n", code & 0x7f7f));
239			return false;
240		}
241	}
242
243	if ((code & 0x7000) == 0x0000) {
244		/* one-byte standard character (0?XX) */
245		character = code & 0x007f;
246
247		if (character >= 0x20) {
248
249			PRINT(("%c", character));
250
251			fText[fRow][fColumn] = (fColor << 8) + character;
252			if (fColumn < C_RADEON_CC_COLUMNS - 1)
253				fColumn++;
254		}
255		else if (character != 0x00) {
256			PRINT(("<%04X>", code & 0x7f7f));
257		}
258	}
259	else if ((code & 0x7770) == 0x1120) {
260		/* middle row code (112X) */
261		fColor = code & 0x000f;
262
263		fText[fRow][fColumn] = (fColor << 8) + ' ';
264		if (fColumn < C_RADEON_CC_COLUMNS - 1)
265			fColumn++;
266
267		switch (fColor & 0x000e) {
268		case C_WHITE:
269			PRINT(("<white"));
270			break;
271		case C_GREEN:
272			PRINT(("<green"));
273			break;
274		case C_BLUE:
275			PRINT(("<blue"));
276			break;
277		case C_CYAN:
278			PRINT(("<cyan"));
279			break;
280		case C_RED:
281			PRINT(("<red"));
282			break;
283		case C_YELLOW:
284			PRINT(("<yellow"));
285			break;
286		case C_MAGENTA:
287			PRINT(("<magenta"));
288			break;
289		case C_ITALICS:
290			PRINT(("<italics"));
291			break;
292		}
293		if ((fColor & 0x0001) != 0)
294			PRINT((",underline>"));
295		else
296			PRINT((">"));
297	}
298	else if ((code & 0x7770) == 0x1130) {
299		/* two-byte special character (113X) */
300		character = (code & 0x000f);
301
302		fText[fRow][fColumn] = (fColor << 8) + character + 0x0080;
303		if (fColumn < C_RADEON_CC_COLUMNS - 1)
304			fColumn++;
305
306		switch (character) {
307		case C_reg:
308			PRINT(("&reg;"));
309			break;
310		case C_deg:
311			PRINT(("&deg;"));
312			break;
313		case C_frac12:
314			PRINT(("&frac12;"));
315			break;
316		case C_iquest:
317			PRINT(("&iquest;"));
318			break;
319		case C_trademark:
320			PRINT(("&trademark;"));
321			break;
322		case C_cent:
323			PRINT(("&cent;"));
324			break;
325		case C_pound:
326			PRINT(("&pound;"));
327			break;
328		case C_music:
329			PRINT(("&music;"));
330			break;
331		case C_agrave:
332			PRINT(("&agrave;"));
333			break;
334		case C_tspace:
335			PRINT(("&tspace;"));
336			break;
337		case C_egrave:
338			PRINT(("&egrave;"));
339			break;
340		case C_acirc:
341			PRINT(("&acirc;"));
342			break;
343		case C_ecirc:
344			PRINT(("&ecirc;"));
345			break;
346		case C_icirc:
347			PRINT(("&icirc;"));
348			break;
349		case C_ocirc:
350			PRINT(("&ocirc;"));
351			break;
352		case C_ucirc:
353			PRINT(("&ucirc;"));
354			break;
355		default:
356			PRINT(("<special=%04X>", code & 0x7f7f));
357			break;
358		}
359	}
360	else if ((code & 0x7770) == 0x1420) {
361
362		if (code == fLastControlCode)
363			return false;
364		fLastControlCode = code;
365
366		/* miscellaneous control code (142X) */
367		switch (code & 0x000f) {
368		case C_RCL:
369			// resume caption loading
370			PRINT(("<rcl>"));
371			break;
372
373		case C_BS:
374			// backspace
375			PRINT(("<bs>"));
376			if (fColumn > 0)
377				fText[fRow][--fColumn] = 0x0000;
378			break;
379
380		case C_AOF:
381			// alarm off
382			PRINT(("<aof>"));
383			break;
384
385		case C_AON:
386			// alarm on
387			PRINT(("<aon>"));
388			break;
389
390		case C_DER:
391			// delete to end of row
392			PRINT(("<der>"));
393			for (int column = fColumn; column < C_RADEON_CC_COLUMNS; column++)
394				fText[fRow][column] = 0x0000;
395			break;
396
397		case C_RU2:
398			// rollup captions (2 rows)
399			PRINT(("<ru2>"));
400			for (int row = 0; row < C_RADEON_CC_ROWS - 2; row++) {
401				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
402					fText[row][column] = fText[row + 2][column];
403			}
404			for (int row = C_RADEON_CC_ROWS - 2; row < C_RADEON_CC_ROWS; row++) {
405				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
406					fText[row][column] = 0x0000;
407			}
408			break;
409
410		case C_RU3:
411			// rollup captions (3 rows)
412			PRINT(("<ru3>"));
413			for (int row = 0; row < C_RADEON_CC_ROWS - 3; row++) {
414				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
415					fText[row][column] = fText[row + 2][column];
416			}
417			for (int row = C_RADEON_CC_ROWS - 3; row < C_RADEON_CC_ROWS; row++) {
418				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
419					fText[row][column] = 0x0000;
420			}
421			break;
422
423		case C_RU4:
424			// rollup captions (4 rows)
425			PRINT(("<ru4>"));
426			for (int row = 0; row < C_RADEON_CC_ROWS - 4; row++) {
427				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
428					fText[row][column] = fText[row + 2][column];
429			}
430			for (int row = C_RADEON_CC_ROWS - 4; row < C_RADEON_CC_ROWS; row++) {
431				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
432					fText[row][column] = 0x0000;
433			}
434			break;
435
436		case C_FON:
437			// flash on
438			PRINT(("<fon>"));
439			break;
440
441		case C_RDC:
442			// resume direct captioning
443			PRINT(("<rdc>"));
444			break;
445
446		case C_TR:
447			// text restart
448			PRINT(("<tr>"));
449
450			fRow = fColumn = 0;
451			break;
452
453		case C_RTD:
454			// resume text display
455			PRINT(("<rtd>"));
456
457			fRow = fColumn = 0;
458			break;
459
460		case C_EDM:
461			// erase displayed memory
462			PRINT(("<edm>\n"));
463			for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
464				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
465					fDisplayText[row][column] = 0x0000;
466			}
467			DisplayCaptions();
468			break;
469
470		case C_CR:
471			// carriage return
472			PRINT(("<cr>"));
473
474			/* has no effect in caption loading mode */
475			break;
476
477		case C_ENM:
478			// erase non-displayed memory
479			PRINT(("<enm>"));
480			for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
481				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
482					fText[row][column] = 0x0000;
483			}
484			break;
485
486		case C_EOC:
487			// end of caption
488			PRINT(("<eoc>\n"));
489			for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
490				for (int column = 0; column < C_RADEON_CC_COLUMNS; column++) {
491					const int code = fText[row][column];
492					fText[row][column] = fDisplayText[row][column];
493					fDisplayText[row][column] = code;
494				}
495			}
496
497			DisplayCaptions();
498			break;
499		}
500	}
501	else if ((code & 0x7770) == 0x1720) {
502		/* tab offset (172X) */
503		const int offset = code & 0x000f;
504
505		if (offset >= 1 && offset <= 3) {
506			PRINT(("<tab%d>", offset));
507
508			if ((fColumn += offset) >= C_RADEON_CC_COLUMNS - 1)
509				fColumn = C_RADEON_CC_COLUMNS - 1;
510		}
511		else {
512			PRINT(("<tab=%04X>", code & 0x7f7f));
513		}
514	}
515	else if ((code & 0x7040) == 0x1040) {
516		/* preamble address code (1Y4X, 1Y6X) */
517
518		switch (code & 0x0720) {
519		case C_ROW_1:
520			PRINT(("\n<row1"));
521			fRow = 0;
522			break;
523		case C_ROW_2:
524			PRINT(("\n<row2"));
525			fRow = 1;
526			break;
527		case C_ROW_3:
528			PRINT(("\n<row3"));
529			fRow = 2;
530			break;
531		case C_ROW_4:
532			PRINT(("\n<row4"));
533			fRow = 3;
534			break;
535		case C_ROW_5:
536			PRINT(("\n<row5"));
537			fRow = 4;
538			break;
539		case C_ROW_6:
540			PRINT(("\n<row6"));
541			fRow = 5;
542			break;
543		case C_ROW_7:
544			PRINT(("\n<row7"));
545			fRow = 6;
546			break;
547		case C_ROW_8:
548			PRINT(("\n<row8"));
549			fRow = 7;
550			break;
551		case C_ROW_9:
552			PRINT(("\n<row9"));
553			fRow = 8;
554			break;
555		case C_ROW_10:
556			PRINT(("\n<row10"));
557			fRow = 9;
558			break;
559		case C_ROW_11:
560			PRINT(("\n<row11"));
561			fRow = 10;
562			break;
563		case C_ROW_12:
564			PRINT(("\n<row12"));
565			fRow = 11;
566			break;
567		case C_ROW_13:
568			PRINT(("\n<row13"));
569			fRow = 12;
570			break;
571		case C_ROW_14:
572			PRINT(("\n<row14"));
573			fRow = 13;
574			break;
575		case C_ROW_15:
576			PRINT(("\n<row15"));
577			fRow = 14;
578			break;
579		default:
580			PRINT(("\n<pac=%04X>", code & 0x7f7f));
581			return false;
582		}
583
584		if ((code & 0x0010) == 0x0000) {
585			/* change color */
586			fColor = (code & 0x000f);
587			fColumn = 0;
588
589			switch (fColor & 0x000e) {
590			case C_WHITE:
591				PRINT((",white"));
592				break;
593			case C_GREEN:
594				PRINT((",green"));
595				break;
596			case C_BLUE:
597				PRINT((",blue"));
598				break;
599			case C_CYAN:
600				PRINT((",cyan"));
601				break;
602			case C_RED:
603				PRINT((",red"));
604				break;
605			case C_YELLOW:
606				PRINT((",yellow"));
607				break;
608			case C_MAGENTA:
609				PRINT((",magenta"));
610				break;
611			case C_ITALICS:
612				PRINT((",italics"));
613				break;
614			}
615			if ((fColor & 0x0001) != 0)
616				PRINT((",underline>"));
617			else
618				PRINT((">"));
619
620		}
621		else {
622			/* indent, white */
623			fColor = C_WHITE | (code & 0x0001);
624			fColumn = (code & 0x000e) << 1;
625			PRINT((",col%d>", fColumn));
626		}
627	}
628	else {
629		/* two one-byte standard characters */
630		character = (code >> 8) & 0x7f;
631		if (character >= 0x20) {
632
633			PRINT(("%c", character));
634
635			fText[fRow][fColumn] = (fColor << 8) + character;
636			if (fColumn < C_RADEON_CC_COLUMNS - 1)
637				fColumn++;
638		}
639		else if (character != 0x00) {
640			PRINT(("<%02X>", character));
641		}
642
643		character = (code >> 0) & 0x7f;
644		if (character >= 0x20) {
645
646			PRINT(("%c", character));
647
648			fText[fRow][fColumn] = (fColor << 8) + character;
649			if (fColumn < C_RADEON_CC_COLUMNS - 1)
650				fColumn++;
651		}
652		else if (character != 0x00) {
653			PRINT(("<%02X>", character));
654		}
655	}
656
657	return true;
658}
659
660void CCaption::DisplayCaptions() const
661{
662	printf("\x1b[H\x1b[J");
663
664	for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
665		for (int column = 0; column < C_RADEON_CC_COLUMNS; column++) {
666			if (fDisplayText[row][column] == 0x0000) {
667				printf("\x1b[0;47;37m ");
668				continue;
669			}
670
671			const int code = (fDisplayText[row][column] >> 0) & 0xff;
672			const int color = (fDisplayText[row][column] >> 8) & 0xff;
673
674			switch (color & 0x000e) {
675			case 0x0000: // WHITE
676				printf("\x1b[0;%d;40;37m", (color & 1) << 2);
677				break;
678			case 0x0002: // GREEN
679				printf("\x1b[0;%d;40;32m", (color & 1) << 2);
680				break;
681			case 0x0004: // BLUE
682				printf("\x1b[0;%d;40;34m", (color & 1) << 2);
683				break;
684			case 0x0006: // CYAN
685				printf("\x1b[0;%d;40;35m", (color & 1) << 2);
686				break;
687			case 0x0008: // RED
688				printf("\x1b[0;%d;40;31m", (color & 1) << 2);
689				break;
690			case 0x000a: // YELLOW
691				printf("\x1b[0;%d;40;33m", (color & 1) << 2);
692				break;
693			case 0x000c: // MAGENTA
694				printf("\x1b[0;%d;40;36m", (color & 1) << 2);
695				break;
696			case 0x000e: // WHITE ITALICS
697				if ((color & 1) == 0)
698					printf("\x1b[1;40;37m");
699				else
700					printf("\x1b[1;4;40;37m");
701				break;
702			}
703
704			if (code >= 0x20 && code <= 0x7f) {
705				switch (code) {
706				case 0x002a:
707					// aacute
708					printf("\xc3\xa1");
709					break;
710				case 0x005c:
711					// eacute
712					printf("\xc3\xa9");
713					break;
714				case 0x005e:
715					// iacute
716					printf("\xc3\xad");
717					break;
718				case 0x005f:
719					// oacute
720					printf("\xc3\xb3");
721					break;
722				case 0x0060:
723					// uacute
724					printf("\xc3\xba");
725					break;
726				case 0x007b:
727					// ccedil
728					printf("\xc3\xa7");
729					break;
730				case 0x007c:
731					// division
732					printf("\xc3\xb7");
733					break;
734				case 0x007d:
735					// Ntilde
736					printf("\xc3\x91");
737					break;
738				case 0x007e:
739					// ntilde
740					printf("\xc3\xb1");
741					break;
742				case 0x007f:
743					// block
744					printf("\xc1\xbf");
745					break;
746				default:
747					// ASCII character
748					printf("%c", code);
749					break;
750				}
751			}
752			else {
753				switch (code) {
754				case 0x0080:
755					// reg
756					printf("\xc2\xae");
757					break;
758				case 0x0081:
759					// deg
760					printf("\xc2\xb0");
761					break;
762				case 0x0082:
763					// frac12
764					printf("\xc2\xbd");
765					break;
766				case 0x0083:
767					// iquest
768					printf("\xc2\xbf");
769					break;
770				case 0x0084:
771					// trademark
772					printf("\xe2\x84");
773					break;
774				case 0x0085:
775					// cent
776					printf("\xc2\xa2");
777					break;
778				case 0x0086:
779					// pound
780					printf("\xc2\xa3");
781					break;
782				case 0x0087:
783					// music
784					printf("\xc2\xa7");
785					break;
786				case 0x0088:
787					// agrave
788					printf("\xc3\xa0");
789					break;
790				case 0x0089:
791					// tspace
792					printf("\x1b[0;47;37m ");
793					break;
794				case 0x008a:
795					// egrave
796					printf("\xc3\xa8");
797					break;
798				case 0x008b:
799					// acirc
800					printf("\xc3\xa2");
801					break;
802				case 0x008c:
803					// ecirc
804					printf("\xc3\xaa");
805					break;
806				case 0x008d:
807					// icirc
808					printf("\xc3\xae");
809					break;
810				case 0x008e:
811					// ocirc
812					printf("\xc3\xb4");
813					break;
814				case 0x008f:
815					// ucirc
816					printf("\xc3\xbb");
817					break;
818				default:
819					// buggy code
820					printf("<%02X>", code);
821					break;
822				}
823			}
824		}
825		printf("\n");
826	}
827	printf("\x1b[0;30;47m");
828}
829