Skip to content

Commit

Permalink
Added NTSC-VHS system
Browse files Browse the repository at this point in the history
  • Loading branch information
LMP88959 authored May 13, 2023
1 parent 7e0075a commit 408efc5
Show file tree
Hide file tree
Showing 2 changed files with 488 additions and 0 deletions.
338 changes: 338 additions & 0 deletions crt_ntscvhs.c
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
Loading

0 comments on commit 408efc5

Please sign in to comment.