-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
488 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,338 @@ | ||
/*****************************************************************************/ | ||
/* | ||
* NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation | ||
* | ||
* by EMMIR 2018-2023 | ||
* | ||
* YouTube: https://www.youtube.com/@EMMIR_KC/videos | ||
* Discord: https://discord.com/invite/hdYctSmyQJ | ||
*/ | ||
/*****************************************************************************/ | ||
|
||
#include "crt_core.h" | ||
|
||
#if (CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#if (CRT_CHROMA_PATTERN == 1) | ||
/* 227.5 subcarrier cycles per line means every other line has reversed phase */ | ||
#define CC_PHASE(ln) (((ln) & 1) ? -1 : 1) | ||
#else | ||
#define CC_PHASE(ln) (1) | ||
#endif | ||
|
||
#define EXP_P 11 | ||
#define EXP_ONE (1 << EXP_P) | ||
#define EXP_MASK (EXP_ONE - 1) | ||
#define EXP_PI 6434 | ||
#define EXP_MUL(x, y) (((x) * (y)) >> EXP_P) | ||
#define EXP_DIV(x, y) (((x) << EXP_P) / (y)) | ||
|
||
static int e11[] = { | ||
EXP_ONE, | ||
5567, /* e */ | ||
15133, /* e^2 */ | ||
41135, /* e^3 */ | ||
111817 /* e^4 */ | ||
}; | ||
|
||
/* fixed point e^x */ | ||
static int | ||
expx(int n) | ||
{ | ||
int neg, idx, res; | ||
int nxt, acc, del; | ||
int i; | ||
|
||
if (n == 0) { | ||
return EXP_ONE; | ||
} | ||
neg = n < 0; | ||
if (neg) { | ||
n = -n; | ||
} | ||
idx = n >> EXP_P; | ||
res = EXP_ONE; | ||
for (i = 0; i < idx / 4; i++) { | ||
res = EXP_MUL(res, e11[4]); | ||
} | ||
idx &= 3; | ||
if (idx > 0) { | ||
res = EXP_MUL(res, e11[idx]); | ||
} | ||
|
||
n &= EXP_MASK; | ||
nxt = EXP_ONE; | ||
acc = 0; | ||
del = 1; | ||
for (i = 1; i < 17; i++) { | ||
acc += nxt / del; | ||
nxt = EXP_MUL(nxt, n); | ||
del *= i; | ||
if (del > nxt || nxt <= 0 || del <= 0) { | ||
break; | ||
} | ||
} | ||
res = EXP_MUL(res, acc); | ||
|
||
if (neg) { | ||
res = EXP_DIV(EXP_ONE, res); | ||
} | ||
return res; | ||
} | ||
|
||
/*****************************************************************************/ | ||
/********************************* FILTERS ***********************************/ | ||
/*****************************************************************************/ | ||
|
||
/* infinite impulse response low pass filter for bandlimiting YIQ */ | ||
static struct IIRLP { | ||
int c; | ||
int h; /* history */ | ||
} iirY, iirI, iirQ; | ||
|
||
/* freq - total bandwidth | ||
* limit - max frequency | ||
*/ | ||
static void | ||
init_iir(struct IIRLP *f, int freq, int limit) | ||
{ | ||
int rate; /* cycles/pixel rate */ | ||
|
||
memset(f, 0, sizeof(struct IIRLP)); | ||
rate = (freq << 9) / limit; | ||
f->c = EXP_ONE - expx(-((EXP_PI << 9) / rate)); | ||
} | ||
|
||
static void | ||
reset_iir(struct IIRLP *f) | ||
{ | ||
f->h = 0; | ||
} | ||
|
||
/* hi-pass for debugging */ | ||
#define HIPASS 0 | ||
|
||
static int | ||
iirf(struct IIRLP *f, int s) | ||
{ | ||
f->h += EXP_MUL(s - f->h, f->c); | ||
#if HIPASS | ||
return s - f->h; | ||
#else | ||
return f->h; | ||
#endif | ||
} | ||
|
||
extern void | ||
crt_modulate(struct CRT *v, struct NTSC_SETTINGS *s) | ||
{ | ||
int x, y, xo, yo; | ||
int destw = AV_LEN; | ||
int desth = ((CRT_LINES * 64500) >> 16); | ||
int iccf[CRT_CC_SAMPLES]; | ||
int ccmodI[CRT_CC_SAMPLES]; /* color phase for mod */ | ||
int ccmodQ[CRT_CC_SAMPLES]; /* color phase for mod */ | ||
int ccburst[CRT_CC_SAMPLES]; /* color phase for burst */ | ||
int sn, cs, n, ph; | ||
int inv_phase = 0; | ||
int bpp; | ||
int aberration = 0; | ||
|
||
if (!s->iirs_initialized) { | ||
init_iir(&iirY, L_FREQ, Y_FREQ); | ||
init_iir(&iirI, L_FREQ, I_FREQ); | ||
init_iir(&iirQ, L_FREQ, Q_FREQ); | ||
s->iirs_initialized = 1; | ||
} | ||
#if CRT_DO_BLOOM | ||
if (s->raw) { | ||
destw = s->w; | ||
desth = s->h; | ||
if (destw > ((AV_LEN * 55500) >> 16)) { | ||
destw = ((AV_LEN * 55500) >> 16); | ||
} | ||
if (desth > ((CRT_LINES * 63500) >> 16)) { | ||
desth = ((CRT_LINES * 63500) >> 16); | ||
} | ||
} else { | ||
destw = (AV_LEN * 55500) >> 16; | ||
desth = (CRT_LINES * 63500) >> 16; | ||
} | ||
#else | ||
if (s->raw) { | ||
destw = s->w; | ||
desth = s->h; | ||
if (destw > AV_LEN) { | ||
destw = AV_LEN; | ||
} | ||
if (desth > ((CRT_LINES * 64500) >> 16)) { | ||
desth = ((CRT_LINES * 64500) >> 16); | ||
} | ||
} | ||
#endif | ||
if (s->as_color) { | ||
for (x = 0; x < CRT_CC_SAMPLES; x++) { | ||
n = s->hue + x * (360 / CRT_CC_SAMPLES); | ||
crt_sincos14(&sn, &cs, (n + 33) * 8192 / 180); | ||
ccburst[x] = sn >> 10; | ||
crt_sincos14(&sn, &cs, n * 8192 / 180); | ||
ccmodI[x] = sn >> 10; | ||
crt_sincos14(&sn, &cs, (n - 90) * 8192 / 180); | ||
ccmodQ[x] = sn >> 10; | ||
} | ||
} else { | ||
memset(ccburst, 0, sizeof(ccburst)); | ||
memset(ccmodI, 0, sizeof(ccmodI)); | ||
memset(ccmodQ, 0, sizeof(ccmodQ)); | ||
} | ||
|
||
bpp = crt_bpp4fmt(s->format); | ||
if (bpp == 0) { | ||
return; /* just to be safe */ | ||
} | ||
xo = AV_BEG + s->xoffset + (AV_LEN - destw) / 2; | ||
yo = CRT_TOP + s->yoffset + (CRT_LINES - desth) / 2; | ||
|
||
s->field &= 1; | ||
s->frame &= 1; | ||
inv_phase = (s->field == s->frame); | ||
ph = CC_PHASE(inv_phase); | ||
|
||
/* align signal */ | ||
xo = (xo & ~3); | ||
if (s->do_aberration) { | ||
aberration = ((rand() % 12) - 8) + 14; | ||
} | ||
for (n = 0; n < CRT_VRES; n++) { | ||
int t; /* time */ | ||
signed char *line = &v->analog[n * CRT_HRES]; | ||
|
||
t = LINE_BEG; | ||
|
||
if (n <= 3 || (n >= 7 && n <= 9)) { | ||
/* equalizing pulses - small blips of sync, mostly blank */ | ||
while (t < (4 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; | ||
while (t < (50 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; | ||
while (t < (54 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; | ||
while (t < (100 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; | ||
} else if (n >= 4 && n <= 6) { | ||
int even[4] = { 46, 50, 96, 100 }; | ||
int odd[4] = { 4, 50, 96, 100 }; | ||
int *offs = even; | ||
if (s->field == 1) { | ||
offs = odd; | ||
} | ||
/* vertical sync pulse - small blips of blank, mostly sync */ | ||
while (t < (offs[0] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; | ||
while (t < (offs[1] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; | ||
while (t < (offs[2] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; | ||
while (t < (offs[3] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; | ||
} else { | ||
int cb; | ||
if (n < (CRT_VRES - aberration)) { | ||
/* video line */ | ||
while (t < SYNC_BEG) line[t++] = BLANK_LEVEL; /* FP */ | ||
while (t < BW_BEG) line[t++] = SYNC_LEVEL; /* SYNC */ | ||
} | ||
while (t < AV_BEG) line[t++] = BLANK_LEVEL; /* BW + CB + BP */ | ||
|
||
if (n < CRT_TOP) { | ||
while (t < CRT_HRES) line[t++] = BLANK_LEVEL; | ||
} | ||
|
||
/* CB_CYCLES of color burst at 3.579545 Mhz */ | ||
for (t = CB_BEG; t < CB_BEG + (CB_CYCLES * CRT_CB_FREQ); t++) { | ||
#if (CRT_CHROMA_PATTERN == 1) | ||
int off180 = CRT_CC_SAMPLES / 2; | ||
cb = ccburst[(t + inv_phase * off180) % CRT_CC_SAMPLES]; | ||
#else | ||
cb = ccburst[t % CRT_CC_SAMPLES]; | ||
#endif | ||
line[t] = (BLANK_LEVEL + (cb * BURST_LEVEL)) >> 5; | ||
iccf[t % CRT_CC_SAMPLES] = line[t]; | ||
} | ||
} | ||
} | ||
/* reset hsync every frame so only the bottom part is warped */ | ||
v->hsync = 0; | ||
|
||
for (y = 0; y < desth; y++) { | ||
int field_offset; | ||
int sy; | ||
|
||
field_offset = (s->field * s->h + desth) / desth / 2; | ||
sy = (y * s->h) / desth; | ||
|
||
sy += field_offset; | ||
|
||
if (sy >= s->h) sy = s->h; | ||
|
||
sy *= s->w; | ||
|
||
reset_iir(&iirY); | ||
reset_iir(&iirI); | ||
reset_iir(&iirQ); | ||
|
||
for (x = 0; x < destw; x++) { | ||
int fy, fi, fq; | ||
int rA, gA, bA; | ||
const unsigned char *pix; | ||
int ire; /* composite signal */ | ||
int xoff; | ||
|
||
pix = s->data + ((((x * s->w) / destw) + sy) * bpp); | ||
switch (s->format) { | ||
case CRT_PIX_FORMAT_RGB: | ||
case CRT_PIX_FORMAT_RGBA: | ||
rA = pix[0]; | ||
gA = pix[1]; | ||
bA = pix[2]; | ||
break; | ||
case CRT_PIX_FORMAT_BGR: | ||
case CRT_PIX_FORMAT_BGRA: | ||
rA = pix[2]; | ||
gA = pix[1]; | ||
bA = pix[0]; | ||
break; | ||
case CRT_PIX_FORMAT_ARGB: | ||
rA = pix[1]; | ||
gA = pix[2]; | ||
bA = pix[3]; | ||
break; | ||
case CRT_PIX_FORMAT_ABGR: | ||
rA = pix[3]; | ||
gA = pix[2]; | ||
bA = pix[1]; | ||
break; | ||
default: | ||
rA = gA = bA = 0; | ||
break; | ||
} | ||
|
||
/* RGB to YIQ */ | ||
fy = (19595 * rA + 38470 * gA + 7471 * bA) >> 14; | ||
fi = (39059 * rA - 18022 * gA - 21103 * bA) >> 14; | ||
fq = (13894 * rA - 34275 * gA + 20382 * bA) >> 14; | ||
ire = BLACK_LEVEL + v->black_point; | ||
|
||
xoff = (x + xo) % CRT_CC_SAMPLES; | ||
/* bandlimit Y,I,Q */ | ||
fy = iirf(&iirY, fy); | ||
fi = iirf(&iirI, fi) * ph * ccmodI[xoff] >> 4; | ||
fq = iirf(&iirQ, fq) * ph * ccmodQ[xoff] >> 4; | ||
ire += (fy + fi + fq) * (WHITE_LEVEL * v->white_point / 100) >> 10; | ||
if (ire < 0) ire = 0; | ||
if (ire > 110) ire = 110; | ||
|
||
v->analog[(x + xo) + (y + yo) * CRT_HRES] = ire; | ||
} | ||
} | ||
for (n = 0; n < CRT_CC_VPER; n++) { | ||
for (x = 0; x < CRT_CC_SAMPLES; x++) { | ||
v->ccf[n][x] = 0; | ||
} | ||
} | ||
} | ||
#endif |
Oops, something went wrong.