Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vhs #30

Merged
merged 8 commits into from
May 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions crt_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,26 @@ crt_demodulate(struct CRT *v, int noise)
huecs >>= 11;

rn = v->rn;
#if ((CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) && CRT_VHS_NOISE)
line = ((rand() % 8) - 4) + 14;
#endif
for (i = 0; i < CRT_INPUT_SIZE; i++) {
int nn = noise;
#if ((CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) && CRT_VHS_NOISE)
rn = rand();
if (i > (CRT_INPUT_SIZE - CRT_HRES * (16 + ((rand() % 20) - 10))) &&
i < (CRT_INPUT_SIZE - CRT_HRES * (5 + ((rand() % 8) - 4)))) {
int ln, sn, cs;

ln = (i * line) / CRT_HRES;
crt_sincos14(&sn, &cs, ln * 8192 / 180);
nn = cs >> 8;
}
#else
rn = (214019 * rn + 140327895);

#endif
/* signal + noise */
s = v->analog[i] + (((((rn >> 16) & 0xff) - 0x7f) * noise) >> 8);
s = v->analog[i] + (((((rn >> 16) & 0xff) - 0x7f) * nn) >> 8);
if (s > 127) { s = 127; }
if (s < -127) { s = -127; }
v->inp[i] = s;
Expand Down
15 changes: 9 additions & 6 deletions crt_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ extern "C" {
/* library version */
#define CRT_MAJOR 2
#define CRT_MINOR 2
#define CRT_PATCH 0
#define CRT_PATCH 1


#define CRT_SYSTEM_NTSC 0 /* standard NTSC */
#define CRT_SYSTEM_NES 1 /* decode 6 or 9-bit NES pixels */
#define CRT_SYSTEM_PV1K 2 /* Casio PV-1000 */
#define CRT_SYSTEM_SNES 3 /* SNES - uses RGB */
#define CRT_SYSTEM_TEMP 4 /* template implementation */
#define CRT_SYSTEM_NTSC 0 /* standard NTSC */
#define CRT_SYSTEM_NES 1 /* decode 6 or 9-bit NES pixels */
#define CRT_SYSTEM_PV1K 2 /* Casio PV-1000 */
#define CRT_SYSTEM_SNES 3 /* SNES - uses RGB */
#define CRT_SYSTEM_TEMP 4 /* template implementation */
#define CRT_SYSTEM_NTSCVHS 5 /* standard NTSC VHS */

/* the system to be compiled */
#define CRT_SYSTEM CRT_SYSTEM_NTSC
Expand All @@ -46,6 +47,8 @@ extern "C" {
#include "crt_pv1k.h"
#elif (CRT_SYSTEM == CRT_SYSTEM_TEMP)
#include "crt_template.h"
#elif (CRT_SYSTEM == CRT_SYSTEM_NTSCVHS)
#include "crt_ntscvhs.h"
#else
#error No system defined
#endif
Expand Down
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